¿Dónde colocar archivos javascript específicos de la vista en una aplicación ASP.NET MVC?

96

¿Cuál es el mejor lugar (qué carpeta, etc.) para colocar archivos javascript específicos de vista en una aplicación ASP.NET MVC?

Para mantener mi proyecto organizado, realmente me encantaría poder ponerlos uno al lado del otro con los archivos .aspx de la vista, pero no he encontrado una buena manera de hacer referencia a ellos al hacerlo sin exponer las vistas ~ / / Acción / estructura de carpetas. ¿Es realmente malo dejar que se filtren los detalles de esa estructura de carpetas?

La alternativa es ponerlos en las carpetas ~ / Scripts o ~ / Content, pero es una pequeña irritación porque ahora tengo que preocuparme por los conflictos de nombres de archivos. Sin embargo, es una irritación que puedo superar si es "lo correcto".

Erv Walter
fuente
2
Encontré secciones útiles para esto. Ver: stackoverflow.com/questions/4311783/…
Frison Alexander
1
Esto suena como una pregunta loca, pero un escenario extremadamente útil es cuando anida el archivo javascript de una página en .cshtml. (Por ejemplo, con NestIn ). Ayuda a no tener que rebotar en el explorador de soluciones.
David Sherret

Respuestas:

126

Pregunta antigua, pero quería dar mi respuesta en caso de que alguien más venga a buscarla.

Yo también quería mis archivos js / css específicos de vista en la carpeta de vistas, y así es como lo hice:

En la carpeta web.config en la raíz de / Views, necesita modificar dos secciones para permitir que el servidor web sirva los archivos:

    <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>

Luego, desde su archivo de vista, puede hacer referencia a las URL como espera:

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

Esto permitirá el servicio de archivos .js y .css, y prohibirá el servicio de cualquier otra cosa.

Davew
fuente
Gracias, davesw. Exactamente lo que estaba buscando
Mr Bell
1
Cuando hago esto, aparece el error de que httpHandlers no se puede usar en el modo de canalización. Quiere que cambie al modo clásico en el servidor. ¿Cuál es la forma correcta de hacer esto cuando uno no quiere que el servidor use el modo clásico?
Bjørn
1
@ BjørnØyvindHalvorsen Puede eliminar una u otra sección del controlador o desactivar la validación de la configuración en su web.config. Vea aquí
davesw
2
Solo se requerían las modificaciones de la sección <system.webServer> para que funcionara, las modificaciones de <system.web> NO eran necesarias.
joedotnot
@joedotnot Correcto, solo se necesita una sección, pero cuál depende de la configuración de su servidor web. Actualmente, la mayoría de la gente necesitará la sección system.webServer, no la sección system.web anterior.
davesw
5

Una forma de lograrlo es proporcionar la suya propia ActionInvoker. Usando el código incluido a continuación, puede agregar al constructor de su controlador:

ActionInvoker = new JavaScriptActionInvoker();

Ahora, siempre que coloque un .jsarchivo junto a su vista:

ingrese la descripción de la imagen aquí

Puedes acceder a él directamente:

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

A continuación se muestra la fuente:

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
fuente
Sin embargo, esta parece una buena solución, pero ¿afecta el tiempo de llamada a las acciones?
Leandro Soares
Bien puede funcionar, pero ¿qué? quiero escribir menos código, no más.
joedotnot
1
@joedotno escribes más código una vez y menos código para siempre. El mantra de un programador, ¿no? :)
Kirk Woll
@KirkWoll. no hay desacuerdo allí. Simplemente decepcionado de que, por lo que debería ser una "característica simple", no salió de la caja. Así que preferí optar por la respuesta de davesw (la respuesta aceptada). Pero gracias por compartir su código, puede ser útil para otros.
joedotnot
@KirkWoll Soy nuevo en MVC y estoy tratando de implementar su solución en un sitio MVC5. No estoy seguro de dónde colocar o "usar" el "ActionInvoker = new JavaScriptActionInvoker ()" ??
Drew
3

Puede invertir la sugerencia de davesw y bloquear solo .cshtml

<httpHandlers>
    <add path="*.cshtml" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
Vadym Nikolaiev
fuente
¡Perfecto! :) ¡Una bonita solución corta! ¡Y funciona! :) No tengo idea de por qué esta no es la configuración predeterminada, porque es mucho mejor poder mantener los scripts relacionados con las vistas junto con las vistas reales. Gracias, Vadym.
BruceHill
24
Sería cauteloso con este enfoque, aunque parece agradable y limpio. Si en el futuro, esta aplicación incluye motores de visualización distintos a Razor (por ejemplo, WebForms, Spark, etc.), serán públicos silenciosamente. También afecta a archivos como Site.Master. La lista blanca parece el enfoque más seguro
arserbin3
Estoy de acuerdo con @ arserbin3 en que las listas blancas parecen más seguras; Al mismo tiempo, no siento la posibilidad de que el motor de visualización de una aplicación empresarial cambie de una a otra. No existe una herramienta de automatización perfecta para hacerlo. La conversión debe realizarse a mano. Una vez lo hice para una gran aplicación web; Convertí el motor de visualización de WebForm a Razor, y recuerdo los días de pesadilla, durante un par de meses, las cosas no funcionaban aquí y allá ... No puedo pensar en hacer tal cosa de nuevo :). Si tengo que hacer un cambio tan grande de todos modos, creo que ese cambio de configuración de web.config no se olvidará.
Emran Hussain
1

Sé que este es un tema bastante antiguo, pero tengo algunas cosas que me gustaría agregar. Intenté la respuesta de davesw pero arrojaba un error 500 al intentar cargar los archivos de script, así que tuve que agregar esto al web.config:

<validation validateIntegratedModeConfiguration="false" />

a system.webServer. Esto es lo que tengo, y pude hacerlo 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>

Aquí hay más información sobre la validación: https://www.iis.net/configreference/system.webserver/validation

dh6984
fuente
0

agregue este código en el archivo web.config dentro de la etiqueta 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
fuente
0

También quería colocar archivos js relacionados con una vista en la misma carpeta que la vista.

No pude hacer que las otras soluciones en este hilo funcionen, no es que estén rotas, pero soy demasiado nuevo en MVC para que funcionen.

Usando la información proporcionada aquí y varias otras pilas, se me ocurrió una solución que:

  • Permite que el archivo javascript se coloque en el mismo directorio que la vista a la que está asociado.
  • Las URL del script no revelan la estructura física subyacente del sitio
  • Las URL del script no tienen que terminar con una barra inclinada (/)
  • No interfiere con los recursos estáticos, por ejemplo: /Scripts/someFile.js todavía funciona
  • No requiere que se habilite runAllManagedModulesForAllRequests.

Nota: también estoy usando el enrutamiento de atributos HTTP. Es posible que la ruta que se usa en mi alma pueda modificarse para que funcione sin habilitar esto.

Dado el siguiente ejemplo de estructura de directorio / archivo:

Controllers
-- Example
   -- ExampleController.vb

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

Usando los pasos de configuración que se indican a continuación, combinados con la estructura de ejemplo anterior, se accedería a la URL de la vista de prueba a través de: /Example/Testy se haría referencia al archivo javascript a través de:/Example/Scripts/test.js

Paso 1: habilitar el enrutamiento de atributos:

Edite su archivo /App_start/RouteConfig.vb y agréguelo routes.MapMvcAttributeRoutes()justo encima de las rutas existentes.

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

Paso 2: configure su sitio para tratar y procesar /{controller}/Scripts/*.js como una ruta MVC y no como un recurso estático

Edite su archivo /Web.config, agregando lo siguiente a la sección system.webServer -> handlers del archivo:

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

Aquí está de nuevo con 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>  

Paso 3: agregue el siguiente resultado de la acción de scripts a su archivo de controlador

  • Asegúrese de editar la ruta de la ruta para que coincida con el nombre de {controlador} para el controlador, para este ejemplo es: <Ruta (" Ejemplo / Scripts / {nombre de archivo}")>
  • Deberá copiar esto en cada uno de sus archivos de Controller. Si lo desea, probablemente haya una manera de hacer esto como una configuración de ruta única y única de alguna manera.

        ' /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 el contexto, este es mi archivo 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 finales No hay nada especial sobre los archivos javascript test.vbhtml view / test.js y no se muestran aquí.

Guardo mi CSS en el archivo de vista, pero podría agregarlo fácilmente a esta solución para que pueda hacer referencia a sus archivos CSS de una manera similar.

Dibujó
fuente