Onde colocar arquivos javascript específicos da visualização em um aplicativo ASP.NET MVC?

96

Qual é o melhor lugar (qual pasta, etc) para colocar arquivos javascript específicos da visualização em um aplicativo ASP.NET MVC?

Para manter meu projeto organizado, eu realmente adoraria colocá-los lado a lado com os arquivos .aspx da visualização, mas não encontrei uma boa maneira de referenciá-los ao fazer isso sem expor o ~ / Views / Ação / estrutura de pastas. É realmente ruim permitir que detalhes dessa estrutura de pastas vazem?

A alternativa é colocá-los nas pastas ~ / Scripts ou ~ / Content, mas é um pouco irritante porque agora tenho que me preocupar com conflitos de nome de arquivo. É uma irritação que posso superar, no entanto, se for "a coisa certa".

Erv Walter
fonte
2
Achei seções úteis para isso. Consulte: stackoverflow.com/questions/4311783/…
Frison Alexander
1
Isso parece uma pergunta maluca, mas um cenário extremamente útil é quando você aninha o arquivo javascript de uma página no .cshtml. (Por exemplo, com NestIn ). Isso ajuda a não ter que se preocupar com o explorador de soluções.
David Sherret

Respostas:

126

Pergunta antiga, mas gostaria de responder no caso de alguém vir procurá-la.

Eu também queria meus arquivos js / css específicos de visualização na pasta de visualizações, e foi assim que fiz:

Na pasta web.config na raiz de / Views, você precisa modificar duas seções para permitir que o servidor da web exiba os arquivos:

    <system.web>
        <httpHandlers>
            <add path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
            <add path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
            <add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
        </httpHandlers>
        <!-- other content here -->
    </system.web>

    <system.webServer>
        <handlers>
            <remove name="BlockViewHandler"/>
            <add name="JavaScript" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
            <add name="CSS" path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
            <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
        </handlers>
        <!-- other content here -->
    </system.webServer>

Então, a partir do seu arquivo de visualização, você pode referenciar os urls conforme o esperado:

@Url.Content("~/Views/<ControllerName>/somefile.css")

Isso permitirá a exibição de arquivos .js e .css e proibirá a exibição de qualquer outra coisa.

Davesw
fonte
Obrigado, Davesw. Exatamente o que eu estava procurando
Sr. Bell
1
Quando faço isso, recebo a mensagem de erro informando que httpHandlers não pode ser usado no modo pipeline. Ele quer que eu mude para o modo clássico no servidor. Qual é a maneira correta de fazer isso quando não se deseja que o servidor use o modo clássico?
Bjørn
1
@ BjørnØyvindHalvorsen Você pode excluir uma ou outra seção do manipulador ou desligar a validação da configuração em seu web.config. Veja aqui
davesw
2
Apenas os mods para a seção <system.webServer> eram necessários para que funcionasse, os mods <system.web> NÃO eram necessários.
joedotnot
@joedotnot Correto, apenas uma seção é necessária, mas qual depende da configuração do seu servidor web. Atualmente, a maioria das pessoas precisará da seção system.webServer, não da seção system.web mais antiga.
davesw
5

Uma maneira de conseguir isso é fornecer o seu próprio produto ActionInvoker. Usando o código incluído abaixo, você pode adicionar ao construtor do seu controlador:

ActionInvoker = new JavaScriptActionInvoker();

Agora, sempre que você colocar um .jsarquivo próximo à sua visualização:

insira a descrição da imagem aqui

Você pode acessá-lo diretamente:

http://yourdomain.com/YourController/Index.js

Abaixo está a fonte:

namespace JavaScriptViews {
    public class JavaScriptActionDescriptor : ActionDescriptor
    {
        private string actionName;
        private ControllerDescriptor controllerDescriptor;

        public JavaScriptActionDescriptor(string actionName, ControllerDescriptor controllerDescriptor)
        {
            this.actionName = actionName;
            this.controllerDescriptor = controllerDescriptor;
        }

        public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
        {
            return new ViewResult();
        }

        public override ParameterDescriptor[] GetParameters()
        {
            return new ParameterDescriptor[0];
        }

        public override string ActionName
        {
            get { return actionName; }
        }

        public override ControllerDescriptor ControllerDescriptor
        {
            get { return controllerDescriptor; }
        }
    }

    public class JavaScriptActionInvoker : ControllerActionInvoker
    {
        protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
        {
            var action = base.FindAction(controllerContext, controllerDescriptor, actionName);
            if (action != null)
            {
                return action;
            } 

            if (actionName.EndsWith(".js"))
            {
                return new JavaScriptActionDescriptor(actionName, controllerDescriptor);
            }

            else 
                return null;
        }
    }

    public class JavaScriptView : IView
    {
        private string fileName;

        public JavaScriptView(string fileName)
        {
            this.fileName = fileName;
        }

        public void Render(ViewContext viewContext, TextWriter writer)
        {
            var file = File.ReadAllText(viewContext.HttpContext.Server.MapPath(fileName));
            writer.Write(file);
        }
    }


    public class JavaScriptViewEngine : VirtualPathProviderViewEngine
    {
        public JavaScriptViewEngine()
            : this(null)
        {
        }

        public JavaScriptViewEngine(IViewPageActivator viewPageActivator)
            : base()
        {
            AreaViewLocationFormats = new[]
            {
                "~/Areas/{2}/Views/{1}/{0}.js",
                "~/Areas/{2}/Views/Shared/{0}.js"
            };
            AreaMasterLocationFormats = new[]
            {
                "~/Areas/{2}/Views/{1}/{0}.js",
                "~/Areas/{2}/Views/Shared/{0}.js"
            };
            AreaPartialViewLocationFormats = new []
            {
                "~/Areas/{2}/Views/{1}/{0}.js",
                "~/Areas/{2}/Views/Shared/{0}.js"
            };
            ViewLocationFormats = new[]
            {
                "~/Views/{1}/{0}.js",
                "~/Views/Shared/{0}.js"
            };
            MasterLocationFormats = new[]
            {
                "~/Views/{1}/{0}.js",
                "~/Views/Shared/{0}.js"
            };
            PartialViewLocationFormats = new[]
            {
                "~/Views/{1}/{0}.js",
                "~/Views/Shared/{0}.js"
            };
            FileExtensions = new[]
            {
                "js"
            };
        }

        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            if (viewName.EndsWith(".js"))
                viewName = viewName.ChopEnd(".js");
            return base.FindView(controllerContext, viewName, masterName, useCache);
        }


        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            return new JavaScriptView(partialPath);
        }

        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            return new JavaScriptView(viewPath);
        }
    }
}
Kirk Woll
fonte
Esta parece uma boa solução, no entanto, mas afeta o tempo de chamada para ações?
Leandro Soares
Pode funcionar, mas e daí? eu quero escrever menos código, não mais.
joedotnot
1
@joedotnot você escreve mais código uma vez, e menos código para sempre. O mantra de um programador, não? :)
Kirk Woll
@KirkWoll. nenhuma discordância aí. Apenas desapontado que para o que deveria ser um "recurso simples", ele não saiu da caixa. Por isso, preferi optar pela resposta de davesw (resposta aceite). Mas obrigado por compartilhar seu código, pode ser útil para outras pessoas.
joedotnot
@KirkWoll Sou novo no MVC e estou tentando implementar sua solução em um site MVC5. Não tenho certeza de onde colocar ou "usar" o "ActionInvoker = new JavaScriptActionInvoker ()" ??
Desenhou
3

Você pode inverter a sugestão de davesw e bloquear apenas .cshtml

<httpHandlers>
    <add path="*.cshtml" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
Vadym Nikolaiev
fonte
Perfeito! :) Uma boa solução curta! E funciona! :) Não tenho ideia de por que essa não é a configuração padrão, porque é muito melhor poder manter scripts relacionados às visualizações junto com as visualizações reais. Obrigado, Vadym.
BruceHill
24
Eu seria cauteloso com essa abordagem, embora pareça boa e limpa. Se, no futuro, este aplicativo incluir mecanismos de visualização diferentes do Razor (ex WebForms, Spark, etc), eles serão silenciosamente públicos. Também afetando arquivos como Site.Master. A lista de permissões parece a abordagem mais segura
arserbin3
Eu concordo com @ arserbin3 que a lista branca parece mais segura; ao mesmo tempo, não consigo sentir a possibilidade de o View Engine de um aplicativo corporativo mudar de um para outro. Não existe uma ferramenta de automação perfeita para fazer isso. A conversão deve ser feita manualmente. Uma vez fiz isso para um grande aplicativo da Web; converti o viewengine do WebForm para Razor, e me lembro dos dias de pesadelo, por alguns meses, as coisas não funcionavam aqui e ali ... Não consigo pensar em fazer isso de novo :). Se eu tiver que fazer essa mudança gigante de qualquer maneira, acredito que essa mudança na configuração do web.config não será esquecida.
Emran Hussain,
1

Sei que este é um tópico bastante antigo, mas gostaria de acrescentar algumas coisas. Tentei a resposta de davesw, mas ele estava gerando um erro 500 ao tentar carregar os arquivos de script, então tive que adicionar isso ao web.config:

<validation validateIntegratedModeConfiguration="false" />

para system.webServer. Aqui está o que eu tenho e consegui fazer funcionar:

<system.webServer>
  <handlers>
    <remove name="BlockViewHandler"/>
    <add name="JavaScript" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
    <add name="CSS" path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
    <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
  </handlers>
  <validation validateIntegratedModeConfiguration="false" />
</system.webServer>
<system.web>
  <compilation>
    <assemblies>
      <add assembly="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    </assemblies>
  </compilation>
  <httpHandlers>
      <add path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
      <add path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
      <add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
  </httpHandlers>
</system.web>

Aqui estão mais informações sobre validação: https://www.iis.net/configreference/system.webserver/validation

dh6984
fonte
0

adicione este código no arquivo web.config dentro da tag system.web

<handlers>
    <remove name="BlockViewHandler"/>
    <add name="JavaScript" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
    <add name="CSS" path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
     <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>
Peter Isaac
fonte
0

Também queria colocar os arquivos js relacionados a uma visualização na mesma pasta da visualização.

Não fui capaz de fazer as outras soluções neste segmento funcionarem, não que elas estejam quebradas, mas sou muito novo no MVC para fazê-las funcionar.

Usando as informações fornecidas aqui e várias outras pilhas, encontrei uma solução que:

  • Permite que o arquivo javascript seja colocado no mesmo diretório da visualização à qual está associado.
  • Os URLs de script não revelam a estrutura física subjacente do site
  • O URL do script não precisa terminar com uma barra final (/)
  • Não interfere com recursos estáticos, por exemplo: /Scripts/someFile.js ainda funciona
  • Não requer runAllManagedModulesForAllRequests para ser ativado.

Observação: também estou usando o roteamento de atributo HTTP. É possível que a rota usada na minha alma possa ser modificada para funcionar sem habilitar isso.

Dado o seguinte exemplo de estrutura de diretório / arquivo:

Controllers
-- Example
   -- ExampleController.vb

Views
-- Example
   -- Test.vbhtml
   -- Test.js

Usando as etapas de configuração fornecidas abaixo, combinadas com a estrutura do exemplo acima, o URL de visualização de teste seria acessado por meio de: /Example/Teste o arquivo javascript seria referenciado por meio de:/Example/Scripts/test.js

Etapa 1 - Habilitar Roteamento de Atributo:

Edite seu arquivo /App_start/RouteConfig.vb e adicione routes.MapMvcAttributeRoutes()logo acima das rotas existentes.MapRoute:

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web
Imports System.Web.Mvc
Imports System.Web.Routing

Public Module RouteConfig
    Public Sub RegisterRoutes(ByVal routes As RouteCollection)
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}")

        ' Enable HTTP atribute routing
        routes.MapMvcAttributeRoutes()

        routes.MapRoute(
            name:="Default",
            url:="{controller}/{action}/{id}",
            defaults:=New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional}
        )
    End Sub
End Module

Etapa 2 - configure seu site para tratar e processar /{controller}/Scripts/*.js como um caminho MVC e não um recurso estático

Edite seu arquivo /Web.config, adicionando o seguinte à seção system.webServer -> handlers do arquivo:

<add name="ApiURIs-ISAPI-Integrated-4.0" path="*/scripts/*.js" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />

Aqui está novamente com contexto:

  <system.webServer>
    <modules>
      <remove name="TelemetryCorrelationHttpModule"/>
      <add name="TelemetryCorrelationHttpModule" type="Microsoft.AspNet.TelemetryCorrelation.TelemetryCorrelationHttpModule, Microsoft.AspNet.TelemetryCorrelation" preCondition="managedHandler"/>
      <remove name="ApplicationInsightsWebTracking"/>
      <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler"/>
    </modules>
    <validation validateIntegratedModeConfiguration="false"/>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0"/>
      <remove name="OPTIONSVerbHandler"/>
      <remove name="TRACEVerbHandler"/>
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0"/>
      <add name="ApiURIs-ISAPI-Integrated-4.0" path="*/scripts/*.js" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>  

Etapa 3 - Adicione o seguinte resultado de ação de scripts ao arquivo do controlador

  • Certifique-se de editar o caminho da rota para corresponder ao nome do {controlador} para o controlador, para este exemplo é: <Route (" Exemplo / Scripts / {nome do arquivo}")>
  • Você precisará copiar isso para cada um dos arquivos do controlador. Se você quiser, provavelmente há uma maneira de fazer isso como uma configuração de rota única e única.

        ' /Example/Scripts/*.js
        <Route("Example/Scripts/{filename}")>
        Function Scripts(filename As String) As ActionResult
            ' ControllerName could be hardcoded but doing it this way allows for copy/pasting this code block into other controllers without having to edit
            Dim ControllerName As String = System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values("controller").ToString()
    
            ' the real file path
            Dim filePath As String = Server.MapPath("~/Views/" & ControllerName & "/" & filename)
    
            ' send the file contents back
            Return Content(System.IO.File.ReadAllText(filePath), "text/javascript")
        End Function

Para contexto, este é meu arquivo ExampleController.vb:

Imports System.Web.Mvc

Namespace myAppName
    Public Class ExampleController
        Inherits Controller

        ' /Example/Test
        Function Test() As ActionResult
            Return View()
        End Function


        ' /Example/Scripts/*.js
        <Route("Example/Scripts/{filename}")>
        Function Scripts(filename As String) As ActionResult
            ' ControllerName could be hardcoded but doing it this way allows for copy/pasting this code block into other controllers without having to edit
            Dim ControllerName As String = System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values("controller").ToString()

            ' the real file path
            Dim filePath As String = Server.MapPath("~/Views/" & ControllerName & "/" & filename)

            ' send the file contents back
            Return Content(System.IO.File.ReadAllText(filePath), "text/javascript")
        End Function


    End Class
End Namespace

Notas finais Não há nada de especial sobre os arquivos javascript test.vbhtml view / test.js e não são mostrados aqui.

Eu mantenho meu CSS no arquivo de visualização, mas você pode facilmente adicionar a esta solução para que possa referenciar seus arquivos CSS de maneira semelhante.

Desenhou
fonte