Gdzie umieścić pliki javascript specyficzne dla widoku w aplikacji ASP.NET MVC?

96

Jakie jest najlepsze miejsce (w którym folderze itp.) Do umieszczania plików javascript specyficznych dla widoku w aplikacji ASP.NET MVC?

Aby mój projekt był zorganizowany, naprawdę chciałbym móc umieścić je obok plików .aspx widoku, ale nie znalazłem dobrego sposobu na odwołanie się do nich, robiąc to bez ujawniania ~ / Views / Działanie / struktura folderów. Czy naprawdę źle jest pozwolić, aby szczegóły tej struktury folderów wyciekły?

Alternatywą jest umieszczenie ich w folderach ~ / Scripts lub ~ / Content, ale jest to niewielka irytacja, ponieważ teraz muszę się martwić o konflikty nazw plików. Jest to jednak irytacja, z którą mogę sobie poradzić, jeśli to „właściwa rzecz”.

Erv Walter
źródło
2
Znalazłem sekcje przydatne do tego. Zobacz: stackoverflow.com/questions/4311783/…
Frison Alexander
1
Brzmi to jak szalone pytanie, ale niezwykle przydatnym scenariuszem jest zagnieżdżenie pliku javascript strony w pliku .cshtml. (Na przykład z NestIn ). Pomaga to uniknąć odbijania się od eksploratora rozwiązań.
David Sherret,

Odpowiedzi:

126

Stare pytanie, ale chciałem odpowiedzieć na to pytanie, na wypadek gdyby ktoś przyszedł go szukać.

Też chciałem, aby moje pliki js / css specyficzne dla widoku w folderze views, a oto jak to zrobiłem:

W folderze web.config w katalogu głównym / Views musisz zmodyfikować dwie sekcje, aby umożliwić serwerowi WWW obsługę plików:

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

Następnie z pliku widoku możesz odwoływać się do adresów URL zgodnie z oczekiwaniami:

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

Umożliwi to udostępnianie plików .js i .css oraz zabroni udostępniania czegokolwiek innego.

davesw
źródło
Dzięki, Davesw. Dokładnie to, czego szukałem
Mr Bell
1
Kiedy to robię, pojawia się błąd, że httpHandlers nie może być używany w trybie potoku. Chce, żebym przełączył się na tryb klasyczny na serwerze. Jaki jest właściwy sposób zrobienia tego, gdy nie chce się, aby serwer używał trybu klasycznego?
Bjørn
1
@ BjørnØyvindHalvorsen Możesz usunąć jedną lub drugą sekcję modułu obsługi lub wyłączyć sprawdzanie konfiguracji w swoim pliku web.config. Zobacz tutaj
davesw
2
Tylko mody do sekcji <system.webServer> były wymagane, aby działał, mody <system.web> NIE były wymagane.
joedotnot
@joedotnot Poprawnie, potrzebna jest tylko jedna sekcja, ale która zależy od konfiguracji twojego serwera WWW. Obecnie większość ludzi będzie potrzebować sekcji system.webServer, a nie starszej sekcji system.web.
davesw
5

Jednym ze sposobów osiągnięcia tego jest dostarczenie własnego ActionInvoker. Korzystając z poniższego kodu, możesz dodać do konstruktora kontrolera:

ActionInvoker = new JavaScriptActionInvoker();

Teraz za każdym razem, gdy umieścisz .jsplik obok widoku:

wprowadź opis obrazu tutaj

Możesz uzyskać do niego bezpośredni dostęp:

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

Poniżej źródło:

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
źródło
Wydaje się to jednak dobrym rozwiązaniem, ale czy wpływa na czas wezwania do działania?
Leandro Soares
Może dobrze działać, ale co? chcę pisać mniej kodu, nie więcej.
joedotnot
1
@joedotNie piszesz więcej kodu raz i mniej kodu na zawsze. Mantra programisty, prawda? :)
Kirk Woll,
@KirkWoll. nie ma tam sporu. Po prostu rozczarowany, że to, co powinno być „prostą funkcją”, nie wyszło z pudełka. Więc wolałem wybrać odpowiedź davesw (zaakceptowana odpowiedź). Ale dzięki za udostępnienie kodu, może być przydatny dla innych.
joedotnot
@KirkWoll Jestem nowy w MVC i próbuję zaimplementować Twoje rozwiązanie w witrynie MVC5. Nie jestem pewien, gdzie umieścić lub „użyć” „ActionInvoker = new JavaScriptActionInvoker ()” ??
Drew
3

Możesz odwrócić sugestię Davesw i zablokować tylko plik .cshtml

<httpHandlers>
    <add path="*.cshtml" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
Vadym Nikolaiev
źródło
Idealny! :) Niezłe krótkie rozwiązanie! I to działa! :) Nie mam pojęcia, dlaczego nie jest to ustawienie domyślne, ponieważ znacznie lepiej jest mieć możliwość przechowywania skryptów odnoszących się do widoków razem z rzeczywistymi widokami. Dzięki, Vadym.
BruceHill,
24
Byłbym ostrożny przy takim podejściu, mimo że wydaje się ładne i czyste. Jeśli w przyszłości ta aplikacja będzie zawierała silniki widoku inne niż Razor (ex WebForms, Spark itp.), Będą one dyskretnie publiczne. Dotyczy również plików, takich jak Site.Master. Biała
lista
Zgadzam się z @ arserbin3, że biała lista wydaje się bezpieczniejsza; jednocześnie nie czuję możliwości zmiany View Engine aplikacji korporacyjnej z jednego na inny. Nie ma do tego idealnego narzędzia do automatyzacji. Konwersja musi być wykonana ręcznie. Kiedyś zrobiłem to dla dużej aplikacji internetowej; przekonwertowany silnik widoku WebForm na Razor i pamiętam koszmarne dni, przez kilka miesięcy coś nie działało tu i tam ... Nie mogę myśleć o zrobieniu czegoś takiego ponownie :). Jeśli i tak będę musiał dokonać tak gigantycznej zmiany, to uważam, że taka zmiana ustawień web.config nie zostanie zapomniana.
Emran Hussain
1

Wiem, że to dość stary temat, ale chciałbym dodać kilka rzeczy. Wypróbowałem odpowiedź davesw, ale podczas próby załadowania plików skryptów wyrzucał błąd 500, więc musiałem dodać to do web.config:

<validation validateIntegratedModeConfiguration="false" />

do system.webServer. Oto, co mam i udało mi się to uruchomić:

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

Oto więcej informacji na temat walidacji: https://www.iis.net/configreference/system.webserver/validation

dh6984
źródło
0

dodaj ten kod w pliku web.config wewnątrz tagu 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
źródło
0

Chciałem również umieścić pliki js związane z widokiem w tym samym folderze co widok.

Nie byłem w stanie uruchomić innych rozwiązań w tym wątku, nie dlatego, że są zepsute, ale jestem zbyt nowy w MVC, aby je uruchomić.

Korzystając z informacji podanych tutaj i kilku innych stosów, opracowałem rozwiązanie, które:

  • Umożliwia umieszczenie pliku javascript w tym samym katalogu, z którym jest powiązany widok.
  • Adresy URL skryptów nie ujawniają podstawowej struktury fizycznej witryny
  • Adresy URL skryptu nie muszą kończyć się końcowym ukośnikiem (/)
  • Nie koliduje ze statycznymi zasobami, np .: /Scripts/someFile.js nadal działa
  • Nie wymaga włączenia runAllManagedModulesForAllRequests.

Uwaga: używam również routingu atrybutów HTTP. Możliwe, że trasa używana w mojej opinii może zostać zmodyfikowana, aby działała bez włączania tego.

Biorąc pod uwagę następującą przykładową strukturę katalogów / plików:

Controllers
-- Example
   -- ExampleController.vb

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

Korzystając z kroków konfiguracyjnych podanych poniżej, w połączeniu z powyższą przykładową strukturą, dostęp do adresu URL widoku testowego można uzyskać za pośrednictwem:, /Example/Testa odwołanie do pliku javascript byłoby dostępne za pośrednictwem:/Example/Scripts/test.js

Krok 1 - Włącz routing atrybutów:

Edytuj swój plik /App_start/RouteConfig.vb i dodaj routes.MapMvcAttributeRoutes()tuż nad istniejącymi trasami.

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

Krok 2 - Skonfiguruj witrynę tak, aby traktowała i przetwarzała /{controller}/Scripts/*.js jako ścieżkę MVC, a nie zasób statyczny

Edytuj swój plik /Web.config, dodając następujące elementy do sekcji system.webServer -> handlers pliku:

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

Oto znowu z kontekstem:

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

Krok 3 - Dodaj wynik działania następującego skryptu do pliku kontrolera

  • Pamiętaj, aby edytować ścieżkę trasy, aby pasowała do nazwy {kontrolera} kontrolera, w tym przykładzie jest to: <Route (" Example / Scripts / {filename}")>
  • Będziesz musiał skopiować to do każdego z plików kontrolera. Jeśli chcesz, prawdopodobnie jest sposób na zrobienie tego jako pojedynczej, jednorazowej konfiguracji trasy.

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

W kontekście jest to mój plik 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

Uwagi końcowe Nie ma nic specjalnego w plikach javascript test.vbhtml view / test.js i nie są one tutaj pokazane.

Trzymam mój CSS w pliku widoku, ale możesz łatwo dodać to rozwiązanie, abyś mógł odwoływać się do swoich plików CSS w podobny sposób.

Rysował
źródło