Wo werden ansichtsspezifische Javascript-Dateien in einer ASP.NET MVC-Anwendung abgelegt?

96

Was ist der beste Ort (welcher Ordner usw.), um ansichtsspezifische Javascript-Dateien in einer ASP.NET MVC-Anwendung abzulegen?

Um mein Projekt organisiert zu halten, würde ich sie gerne neben die ASPX-Dateien der Ansicht stellen, aber ich habe keine gute Möglichkeit gefunden, sie zu referenzieren, ohne die ~ / Views anzuzeigen / Aktion / Ordnerstruktur. Ist es wirklich eine schlechte Sache, Details dieser Ordnerstruktur auslaufen zu lassen?

Die Alternative besteht darin, sie in den Ordnern ~ / Scripts oder ~ / Content abzulegen, ist aber eine kleine Irritation, da ich mich jetzt um Dateinamenkonflikte kümmern muss. Es ist jedoch eine Irritation, die ich überwinden kann, wenn es "das Richtige" ist.

Erv Walter
quelle
2
Ich fand Abschnitte nützlich dafür. Siehe: stackoverflow.com/questions/4311783/…
Frison Alexander
1
Das klingt nach einer verrückten Frage, aber ein äußerst nützliches Szenario ist, wenn Sie die Javascript-Datei einer Seite unter der CSC-Datei verschachteln. (Zum Beispiel mit NestIn ). Es hilft, nicht im Solution Explorer herumhüpfen zu müssen.
David Sherret

Antworten:

126

Alte Frage, aber ich wollte meine Antwort geben, falls jemand anderes danach sucht.

Ich wollte auch meine ansichtsspezifischen js / css-Dateien unter dem Ordner views anzeigen, und so habe ich es gemacht:

Im Ordner web.config im Stammverzeichnis von / Views müssen Sie zwei Abschnitte ändern, damit der Webserver die Dateien bereitstellen kann:

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

Anschließend können Sie in Ihrer Ansichtsdatei auf die erwarteten URLs verweisen:

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

Dies ermöglicht das Bereitstellen von .js- und .css-Dateien und verbietet das Bereitstellen von anderen Daten.

davesw
quelle
Danke, Davew. Genau das, wonach ich gesucht habe
Mr Bell
1
Wenn ich dies tue, erhalte ich die Fehlermeldung, dass httpHandlers nicht im Pipeline-Modus verwendet werden können. Ich soll auf dem Server in den klassischen Modus wechseln. Was ist der richtige Weg, wenn der Server nicht den klassischen Modus verwenden soll?
Bjørn
1
@ BjørnØyvindHalvorsen Sie können den einen oder anderen Handlerabschnitt löschen oder die Konfigurationsüberprüfung in Ihrer web.config deaktivieren. Siehe hier
Davesw
2
Nur die Mods für den Abschnitt <system.webServer> waren erforderlich, damit es funktioniert. <System.web> Mods waren NICHT erforderlich.
Joedotnot
@joedotnot Richtig, es wird nur ein Abschnitt benötigt, aber welcher hängt von Ihrer Webserverkonfiguration ab. Derzeit benötigen die meisten Benutzer den Abschnitt system.webServer, nicht den älteren Abschnitt system.web.
Davesw
5

Eine Möglichkeit, dies zu erreichen, besteht darin, Ihre eigenen zu liefern ActionInvoker. Mit dem unten enthaltenen Code können Sie dem Konstruktor Ihres Controllers Folgendes hinzufügen:

ActionInvoker = new JavaScriptActionInvoker();

Wann immer Sie eine .jsDatei neben Ihrer Ansicht platzieren:

Geben Sie hier die Bildbeschreibung ein

Sie können direkt darauf zugreifen:

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

Unten ist die Quelle:

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
quelle
Dies scheint jedoch eine gute Lösung zu sein, wirkt sich dies jedoch auf die Aufrufzeit für Aktionen aus?
Leandro Soares
Es mag gut funktionieren, aber was zum? Ich möchte weniger Code schreiben, nicht mehr.
Joedotnot
1
@joedotnot Sie schreiben mehr Code einmal und weniger Code für immer. Das Mantra eines Programmierers, nein? :)
Kirk Woll
@ KirkWoll. Keine Meinungsverschiedenheit da. Nur enttäuscht, dass es für eine "einfache Funktion" nicht sofort einsatzbereit war. Also habe ich mich lieber für die Antwort von davesw entschieden (die akzeptierte Antwort). Aber danke, dass Sie Ihren Code geteilt haben, er kann für andere nützlich sein.
Joedotnot
@KirkWoll Ich bin neu bei MVC und versuche, Ihre Lösung auf einer MVC5-Site zu implementieren. Ich bin nicht sicher, wo ich den "ActionInvoker = new JavaScriptActionInvoker ()" platzieren oder "verwenden" soll?
Drew
3

Sie können den Vorschlag von davesw umkehren und nur .cshtml blockieren

<httpHandlers>
    <add path="*.cshtml" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
Vadym Nikolaiev
quelle
Perfekt! :) Eine schöne kurze Lösung! Und es funktioniert! :) Ich habe keine Ahnung, warum dies nicht die Standardeinstellung ist, da es viel besser ist, Skripte, die sich auf Ansichten beziehen, zusammen mit den tatsächlichen Ansichten zu behalten. Danke, Vadym.
BruceHill
24
Ich wäre mit diesem Ansatz vorsichtig, auch wenn er schön und sauber erscheint. Wenn diese App in Zukunft andere View-Engines als Razor enthält (z. B. WebForms, Spark usw.), werden sie stillschweigend öffentlich sein. Betrifft auch Dateien wie Site.Master. Whitelisting scheint der sicherere Ansatz zu sein
arserbin3
Ich stimme @ arserbin3 zu, dass White-Listing sicherer erscheint. Gleichzeitig kann ich nicht spüren, dass die View Engine einer Unternehmensanwendung von einer zur anderen wechselt. Dafür gibt es kein perfektes Automatisierungswerkzeug. Die Konvertierung muss von Hand erfolgen. Einmal tat ich dies für eine große Webanwendung; konvertierte WebForm Viewengine zu Razor, und ich erinnere mich an die Tage des Albtraums, für ein paar Monate funktionierten die Dinge hier und da nicht ... Ich kann nicht daran denken, so etwas noch einmal zu tun :). Wenn ich sowieso solch eine riesige Änderung vornehmen muss, dann glaube ich, dass eine solche Änderung der web.config-Einstellungen nicht vergessen wird.
Emran Hussain
1

Ich weiß, dass dies ein ziemlich altes Thema ist, aber ich möchte ein paar Dinge hinzufügen. Ich habe die Antwort von davesw ausprobiert, aber beim Laden der Skriptdateien wurde ein Fehler von 500 ausgegeben. Daher musste ich diesen zur Datei web.config hinzufügen:

<validation validateIntegratedModeConfiguration="false" />

zu system.webServer. Folgendes habe ich und konnte es zum Laufen bringen:

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

Weitere Informationen zur Validierung finden Sie hier: https://www.iis.net/configreference/system.webserver/validation

dh6984
quelle
0

Fügen Sie diesen Code in die Datei web.config im Tag system.web ein

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

Ich wollte auch js-Dateien, die sich auf eine Ansicht beziehen, im selben Ordner wie die Ansicht ablegen.

Ich konnte die anderen Lösungen in diesem Thread nicht zum Laufen bringen, nicht dass sie kaputt sind, aber ich bin zu neu in MVC, um sie zum Laufen zu bringen.

Unter Verwendung der hier gegebenen Informationen und mehrerer anderer Stapel habe ich eine Lösung gefunden, die:

  • Ermöglicht das Ablegen der Javascript-Datei in demselben Verzeichnis wie die Ansicht, der sie zugeordnet ist.
  • Skript-URLs geben die zugrunde liegende physische Site-Struktur nicht preis
  • Skript-URLs müssen nicht mit einem abschließenden Schrägstrich (/) enden.
  • Beeinträchtigt keine statischen Ressourcen, z. B.: /Scripts/someFile.js funktioniert weiterhin
  • Es ist nicht erforderlich, dass runAllManagedModulesForAllRequests aktiviert ist.

Hinweis: Ich verwende auch HTTP-Attribut-Routing. Es ist möglich, dass die in meiner Lösung verwendete Route so geändert wird, dass sie funktioniert, ohne dies zu aktivieren.

Angesichts des folgenden Beispiels Verzeichnis / Dateistruktur:

Controllers
-- Example
   -- ExampleController.vb

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

Unter Verwendung der unten angegebenen Konfigurationsschritte in Kombination mit der obigen Beispielstruktur würde auf die URL /Example/Testder Testansicht zugegriffen über: und auf die Javascript-Datei würde verwiesen über:/Example/Scripts/test.js

Schritt 1 - Attribut-Routing aktivieren:

Bearbeiten Sie Ihre Datei /App_start/RouteConfig.vb und fügen Sie sie routes.MapMvcAttributeRoutes()direkt über den vorhandenen Routen hinzu. 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

Schritt 2 - Konfigurieren Sie Ihre Site so, dass /{controller}/Scripts/*.js als MVC-Pfad und nicht als statische Ressource behandelt und verarbeitet wird

Bearbeiten Sie Ihre Datei /Web.config und fügen Sie dem Abschnitt system.webServer -> Handler der Datei Folgendes hinzu:

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

Hier ist es wieder mit Kontext:

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

Schritt 3 - Fügen Sie Ihrer Controller-Datei das folgende Ergebnis der Skriptaktion hinzu

  • Stellen Sie sicher, dass Sie den Routenpfad so bearbeiten, dass er mit dem Namen {controller} für den Controller übereinstimmt. In diesem Beispiel lautet er: <Route (" Beispiel / Skripte / {Dateiname}")>
  • Sie müssen dies in jede Ihrer Controller-Dateien kopieren. Wenn Sie möchten, gibt es wahrscheinlich eine Möglichkeit, dies als einzelne, einmalige Routenkonfiguration zu tun.

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

Für den Kontext ist dies meine ExampleController.vb-Datei:

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

Schlussbemerkungen Die Javascript-Dateien von test.vbhtml view / test.js enthalten nichts Besonderes und werden hier nicht angezeigt.

Ich behalte mein CSS in der Ansichtsdatei, aber Sie können diese Lösung problemlos ergänzen, damit Sie auf ähnliche Weise auf Ihre CSS-Dateien verweisen können.

Drew
quelle