Où placer les fichiers JavaScript spécifiques à la vue dans une application ASP.NET MVC?

96

Quel est le meilleur endroit (quel dossier, etc.) pour placer des fichiers JavaScript spécifiques à la vue dans une application ASP.NET MVC?

Pour garder mon projet organisé, j'aimerais vraiment pouvoir les mettre côte à côte avec les fichiers .aspx de la vue, mais je n'ai pas trouvé un bon moyen de les référencer en faisant cela sans exposer les ~ / Views / Action / structure des dossiers. Est-ce vraiment une mauvaise chose de laisser fuir les détails de cette structure de dossiers?

L'alternative est de les mettre dans les dossiers ~ / Scripts ou ~ / Content, mais c'est une irritation mineure car maintenant je dois m'inquiéter des conflits de noms de fichiers. C'est une irritation que je peux surmonter, cependant, si c'est «la bonne chose».

Erv Walter
la source
2
J'ai trouvé des sections utiles pour cela. Voir: stackoverflow.com/questions/4311783/…
Frison Alexander
1
Cela ressemble à une question folle, mais un scénario extrêmement utile est lorsque vous imbriquez le fichier javascript d'une page sous le .cshtml. (Par exemple, avec NestIn ). Cela permet de ne pas avoir à rebondir autour de l'explorateur de solutions.
David Sherret

Réponses:

126

Ancienne question, mais je voulais mettre ma réponse au cas où quelqu'un d'autre viendrait la chercher.

Je voulais aussi mes fichiers js / css spécifiques à la vue dans le dossier views, et voici comment je l'ai fait:

Dans le dossier web.config à la racine de / Views, vous devez modifier deux sections pour permettre au serveur Web de servir les fichiers:

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

Ensuite, à partir de votre fichier de vue, vous pouvez référencer les URL comme vous le souhaitez:

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

Cela permettra de servir des fichiers .js et .css, et interdira de servir quoi que ce soit d'autre.

Davesw
la source
Merci, Davesw. Exactement ce que je cherchais
Mr Bell
1
Quand je fais cela, j'obtiens l'erreur que httpHandlers ne peut pas être utilisé en mode pipeline. Il veut que je passe en mode classique sur le serveur. Quelle est la bonne façon de procéder quand on ne veut pas que le serveur utilise le mode classique?
Bjørn
1
@ BjørnØyvindHalvorsen Vous pouvez supprimer l'une ou l'autre section de gestionnaire ou désactiver la validation de la configuration dans votre web.config. Voir ici
davesw
2
Seuls les mods de la section <system.webServer> étaient nécessaires pour que cela fonctionne, les mods <system.web> n'étaient PAS requis.
joedotnot
@joedotnot Correct, une seule section est nécessaire, mais laquelle dépend de la configuration de votre serveur Web. Actuellement, la plupart des gens auront besoin de la section system.webServer, pas de l'ancienne section system.web.
davesw
5

Une façon d'y parvenir est de fournir le vôtre ActionInvoker. En utilisant le code inclus ci-dessous, vous pouvez ajouter au constructeur de votre contrôleur:

ActionInvoker = new JavaScriptActionInvoker();

Désormais, chaque fois que vous placez un .jsfichier à côté de votre vue:

entrez la description de l'image ici

Vous pouvez y accéder directement:

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

Voici la source:

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
la source
Cela semble cependant une bonne solution, mais cela affecte-t-il le temps d'appel aux actions?
Leandro Soares
Cela peut bien fonctionner, mais qu'est-ce que c'est? je veux écrire moins de code, pas plus.
joedotnot
1
@joedotnot vous écrivez plus de code une fois, et moins de code pour toujours. Le mantra d'un programmeur, non? :)
Kirk Woll
@KirkWoll. pas de désaccord là-bas. Juste déçu que pour ce qui devrait être une "fonctionnalité simple", il ne soit pas sorti de la boîte. J'ai donc préféré opter pour la réponse de davesw (la réponse acceptée). Mais merci de partager votre code, cela peut être utile pour les autres.
joedotnot
@KirkWoll Je suis nouveau sur MVC et j'essaye d'implémenter votre solution dans un site MVC5. Je ne sais pas où placer ou "utiliser" le "ActionInvoker = new JavaScriptActionInvoker ()" ??
Tiré le
3

Vous pouvez inverser la suggestion de davesw et bloquer uniquement .cshtml

<httpHandlers>
    <add path="*.cshtml" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
Vadym Nikolaiev
la source
Parfait! :) Une belle solution courte! Et il fonctionne! :) Je ne sais pas pourquoi ce n'est pas le paramètre par défaut, car il est bien préférable de pouvoir conserver les scripts relatifs aux vues avec les vues réelles. Merci, Vadym.
BruceHill
24
Je serais prudent avec cette approche, même si elle semble belle et propre. Si à l'avenir, cette application inclut des moteurs de vue autres que Razor (ex WebForms, Spark, etc.), ils seront publics en silence. Affectant également des fichiers comme Site.Master. La liste blanche semble l'approche la plus sûre
arserbin3
Je suis d'accord avec @ arserbin3 que la liste blanche semble plus sûre; en même temps, je ne peux pas ressentir la possibilité d'un changement de View Engine d'une application d'entreprise de l'un à l'autre. Il n'y a pas d'outil d'automatisation parfait pour ce faire. La conversion doit être effectuée à la main. Une fois, je l'ai fait pour une grande application Web; converti le moteur de visualisation WebForm en Razor, et je me souviens des jours de cauchemar, pendant quelques mois, les choses ne fonctionnaient pas ici et là ... Je ne peux pas penser à refaire une telle chose :). Si je dois faire un tel changement de toute façon, alors, je crois que ce changement de paramètre web.config ne sera pas oublié.
Emran Hussain le
1

Je sais que c'est un sujet assez ancien, mais j'aimerais ajouter quelques éléments. J'ai essayé la réponse de davesw mais cela lançait une erreur 500 en essayant de charger les fichiers de script, donc j'ai dû ajouter ceci au web.config:

<validation validateIntegratedModeConfiguration="false" />

à system.webServer. Voici ce que j'ai, et j'ai pu le faire fonctionner:

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

Voici plus d'informations sur la validation: https://www.iis.net/configreference/system.webserver/validation

dh6984
la source
0

ajoutez ce code dans le fichier web.config à l'intérieur de la balise 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
la source
0

Je voulais également placer les fichiers js liés à une vue dans le même dossier que la vue.

Je n'ai pas pu faire fonctionner les autres solutions de ce fil, non pas qu'elles soient cassées, mais je suis trop novice dans MVC pour les faire fonctionner.

En utilisant les informations données ici et plusieurs autres piles, j'ai trouvé une solution qui:

  • Permet au fichier javascript d'être placé dans le même répertoire que la vue à laquelle il est associé.
  • Les URL de script ne révèlent pas la structure physique sous-jacente du site
  • Les URL de script ne doivent pas nécessairement se terminer par une barre oblique (/)
  • N'interfère pas avec les ressources statiques, par exemple: /Scripts/someFile.js fonctionne toujours
  • Ne nécessite pas l'activation de runAllManagedModulesForAllRequests.

Remarque: j'utilise également le routage d'attributs HTTP. Il est possible que l'itinéraire utilisé dans mon âme puisse être modifié pour fonctionner sans l'activer.

Compte tenu de l'exemple de structure de répertoire / fichier suivant:

Controllers
-- Example
   -- ExampleController.vb

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

En utilisant les étapes de configuration ci-dessous, combinées à l'exemple de structure ci-dessus, l'URL de la vue de test serait accessible via: /Example/Testet le fichier javascript serait référencé via:/Example/Scripts/test.js

Étape 1 - Activer le routage d'attributs:

Modifiez votre fichier /App_start/RouteConfig.vb et ajoutez routes.MapMvcAttributeRoutes()juste au-dessus des routes existantes.

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

Étape 2 -Configurez votre site pour traiter et traiter /{controller}/Scripts/*.js comme un chemin MVC et non comme une ressource statique

Modifiez votre fichier /Web.config, en ajoutant ce qui suit à la section system.webServer -> handlers du fichier:

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

Le voici encore avec le contexte:

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

Étape 3 - Ajoutez le résultat de l'action de scripts suivant à votre fichier Controller

  • Assurez-vous de modifier le chemin de la route pour qu'il corresponde au nom du {contrôleur} du contrôleur, pour cet exemple, il s'agit de: <Route (" Example / Scripts / {filename}")>
  • Vous devrez le copier dans chacun de vos fichiers Controller. Si vous le souhaitez, il existe probablement un moyen de le faire en tant que configuration de route unique et unique.

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

Pour le contexte, voici mon fichier 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

Notes finales Il n'y a rien de spécial à propos des fichiers javascript test.vbhtml view / test.js et ne sont pas affichés ici.

Je garde mon CSS dans le fichier de vue mais vous pouvez facilement ajouter à cette solution afin que vous puissiez référencer vos fichiers CSS de la même manière.

A dessiné
la source