Dove inserire i file javascript specifici per la visualizzazione in un'applicazione ASP.NET MVC?


96

Qual è il posto migliore (quale cartella e così via) per inserire file javascript specifici per la visualizzazione in un'applicazione ASP.NET MVC?

Per mantenere il mio progetto organizzato, mi piacerebbe davvero poterli mettere fianco a fianco con i file .aspx della vista, ma non ho trovato un buon modo per fare riferimento a loro quando lo faccio senza esporre le ~ / Views / Azione / struttura delle cartelle. È davvero una brutta cosa lasciare trapelare i dettagli di quella struttura di cartelle?

L'alternativa è metterli nelle cartelle ~ / Scripts o ~ / Content, ma è una piccola irritazione perché ora devo preoccuparmi dei conflitti di nome dei file. È un'irritazione che posso superare, però, se è "la cosa giusta".


2
Ho trovato le sezioni utili per questo. Vedi: stackoverflow.com/questions/4311783/…
Frison Alexander

1
Sembra una domanda folle, ma uno scenario estremamente utile è quando annidi il file javascript di una pagina sotto .cshtml. (Ad esempio, con NestIn ). Aiuta a non dover rimbalzare in Esplora soluzioni.
David Sherret

Risposte:


126

Vecchia domanda, ma volevo mettere la mia risposta nel caso in cui qualcun altro venisse a cercarla.

Anch'io volevo i miei file js / css specifici per la visualizzazione nella cartella delle viste, ed ecco come l'ho fatto:

Nella cartella web.config nella radice di / Views è necessario modificare due sezioni per consentire al webserver di servire i file:

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

Quindi dal tuo file di visualizzazione puoi fare riferimento agli URL come ti aspetti:

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

Ciò consentirà la pubblicazione di file .js e .css e impedirà la pubblicazione di qualsiasi altra cosa.


Grazie, Davesw. Esattamente quello che stavo cercando
Mr Bell,

1
Quando lo faccio ottengo l'errore che httpHandlers non può essere utilizzato in modalità pipeline. Vuole che passi alla modalità classica sul server. Qual è il modo corretto di farlo quando non si desidera che il server utilizzi la modalità classica?
Bjørn

1
@ BjørnØyvindHalvorsen Puoi eliminare l'una o l'altra sezione del gestore o disattivare la convalida della configurazione nel tuo web.config. Vedi qui
davesw

2
Solo le mod per la sezione <system.webServer> erano necessarie per funzionare, le mod di <system.web> NON erano richieste.
gioedotnon il

@joedotnot Corretto, è necessaria solo una sezione, ma quale dipende dalla configurazione del server web. Attualmente la maggior parte delle persone avrà bisogno della sezione system.webServer, non della vecchia sezione system.web.
davesw

5

Un modo per raggiungere questo obiettivo è fornire il tuo ActionInvoker. Utilizzando il codice incluso di seguito, puoi aggiungere al costruttore del controller:

ActionInvoker = new JavaScriptActionInvoker();

Ora, ogni volta che posizioni un .jsfile accanto alla tua vista:

inserisci qui la descrizione dell'immagine

Puoi accedervi direttamente:

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

Di seguito la 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);
        }
    }
}

Questa sembra una buona soluzione, tuttavia, ma influisce sul tempo di chiamata alle azioni?
Leandro Soares

Potrebbe funzionare, ma cosa? voglio scrivere meno codice, non di più.
joedotnot

1
@joedot non scrivi più codice una volta e meno codice per sempre. Il mantra di un programmatore, no? :)
Kirk Woll

@KirkWoll. nessun disaccordo lì. Sono solo deluso dal fatto che per quella che dovrebbe essere una "caratteristica semplice", non è uscito dalla scatola. Quindi ho preferito optare per la risposta di Davesw (la risposta accettata). Ma grazie per aver condiviso il tuo codice, potrebbe essere utile per gli altri.
gioedotnon l'

@KirkWoll Sono nuovo in MVC e sto cercando di implementare la tua soluzione in un sito MVC5. Non sono sicuro di dove posizionare o "utilizzare" "ActionInvoker = new JavaScriptActionInvoker ()" ??
Drew il

3

Puoi invertire il suggerimento di davesw e bloccare solo .cshtml

<httpHandlers>
    <add path="*.cshtml" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>

Perfetto! :) Una bella soluzione breve! E funziona! :) Non ho idea del motivo per cui questa non sia l'impostazione predefinita, perché è molto meglio essere in grado di mantenere gli script relativi alle visualizzazioni insieme alle visualizzazioni effettive. Grazie, Vadym.
BruceHill

24
Sarei cauto con questo approccio, anche se sembra carino e pulito. Se in futuro questa app include motori di visualizzazione diversi da Razor (ex WebForms, Spark, ecc.), Saranno silenziosamente pubblici. Influisce anche su file come Site.Master. Il whitelisting sembra l'approccio più sicuro
arserbin3

Sono d'accordo con @ arserbin3 sul fatto che la lista bianca sembra più sicura; allo stesso tempo, non sento la possibilità che il motore di visualizzazione di un'applicazione aziendale cambi da uno all'altro. Non esiste uno strumento di automazione perfetto per farlo. La conversione deve essere eseguita a mano. Una volta l'ho fatto per una grande applicazione web; ha convertito il motore di visualizzazione WebForm in Razor, e ricordo i giorni da incubo, per un paio di mesi, le cose non funzionavano qua e là ... Non riesco a pensare di fare di nuovo una cosa del genere :). Se devo comunque fare un cambiamento così gigantesco, credo che tale modifica dell'impostazione di web.config non verrà dimenticata.
Emran Hussain

1

So che questo è un argomento piuttosto vecchio, ma vorrei aggiungere alcune cose. Ho provato la risposta di davesw ma generava un errore 500 durante il tentativo di caricare i file di script, quindi ho dovuto aggiungerlo a web.config:

<validation validateIntegratedModeConfiguration="false" />

a system.webServer. Ecco cosa ho e sono riuscito a farlo funzionare:

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

Ecco ulteriori informazioni sulla convalida: https://www.iis.net/configreference/system.webserver/validation


0

aggiungi questo codice nel file web.config all'interno del 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>

0

Volevo anche posizionare i file js relativi a una vista nella stessa cartella della vista.

Non sono riuscito a far funzionare le altre soluzioni in questo thread, non che siano interrotte ma sono troppo nuovo per MVC per farle funzionare.

Utilizzando le informazioni fornite qui e molti altri stack, ho trovato una soluzione che:

  • Consente al file javascript di essere posizionato nella stessa directory della vista a cui è associato.
  • Gli URL degli script non rivelano la struttura fisica del sito sottostante
  • Gli URL dello script non devono terminare con una barra finale (/)
  • Non interferisce con le risorse statiche, ad esempio: /Scripts/someFile.js funziona ancora
  • Non richiede l'abilitazione di runAllManagedModulesForAllRequests.

Nota: utilizzo anche il routing degli attributi HTTP. È possibile che il percorso utilizzato nella mia anima possa essere modificato per funzionare senza abilitarlo.

Dato il seguente esempio di struttura di directory / file:

Controllers
-- Example
   -- ExampleController.vb

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

Utilizzando i passaggi di configurazione indicati di seguito, combinati con la struttura di esempio sopra, si accederà all'URL della vista di prova tramite: /Example/Teste si farebbe riferimento al file javascript tramite:/Example/Scripts/test.js

Passaggio 1: abilita il routing degli attributi:

Modifica il tuo file /App_start/RouteConfig.vb e aggiungilo routes.MapMvcAttributeRoutes()appena sopra i percorsi esistenti.

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

Passaggio 2: configura il tuo sito per trattare ed elaborare /{controller}/Scripts/*.js come un percorso MVC e non come una risorsa statica

Modifica il tuo file /Web.config, aggiungendo quanto segue alla sezione system.webServer -> handlers del file:

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

Eccolo di nuovo con il contesto:

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

Passaggio 3: aggiungere il seguente risultato dell'azione degli script al file del controller

  • Assicurati di modificare il percorso della rotta in modo che corrisponda al nome {controller} per il controller, per questo esempio è: <Route (" Example / Scripts / {filename}")>
  • Dovrai copiarlo in ciascuno dei tuoi file Controller. Se lo desideri, probabilmente c'è un modo per farlo come una singola configurazione di rotta in qualche modo.

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

Per contesto, questo è il mio file 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

Note finali Non c'è niente di speciale nei file javascript test.vbhtml view / test.js e non sono mostrati qui.

Conservo il mio CSS nel file di visualizzazione, ma potresti facilmente aggiungere a questa soluzione in modo da poter fare riferimento ai tuoi file CSS in modo simile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.