Di mana harus meletakkan file javascript khusus tampilan di aplikasi ASP.NET MVC?

96

Di manakah tempat terbaik (folder mana, dll.) Untuk meletakkan file javascript khusus tampilan dalam aplikasi ASP.NET MVC?

Untuk menjaga agar proyek saya tetap teratur, saya benar-benar ingin dapat menempatkannya berdampingan dengan file .aspx tampilan, tetapi saya belum menemukan cara yang baik untuk mereferensikannya ketika melakukan itu tanpa mengekspos ~ / Views / Tindakan / struktur folder. Apakah benar-benar buruk untuk membiarkan detail struktur folder tersebut bocor?

Alternatifnya adalah dengan meletakkannya di folder ~ / Scripts atau ~ / Content, tetapi sedikit mengganggu karena sekarang saya harus khawatir tentang bentrokan nama file. Namun, iritasi yang bisa saya atasi, jika itu adalah "hal yang benar".

Erv Walter
sumber
2
Saya menemukan bagian berguna untuk ini. Lihat: stackoverflow.com/questions/4311783/…
Frison Alexander
1
Ini terdengar seperti pertanyaan gila, tetapi skenario yang sangat berguna adalah ketika Anda menumpuk file javascript halaman di bawah .cshtml. (Misalnya, dengan NestIn ). Ini membantu tidak harus terpental di sekitar penjelajah solusi.
David Sherret

Jawaban:

126

Pertanyaan lama, tetapi saya ingin memberikan jawaban saya jika ada orang lain yang datang mencarinya.

Saya juga ingin melihat file js / css tertentu di bawah folder views, dan inilah cara saya melakukannya:

Di folder web.config di root / Views Anda perlu memodifikasi dua bagian agar server web dapat melayani 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>

Kemudian dari file view Anda, Anda dapat mereferensikan url seperti yang Anda harapkan:

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

Ini akan mengizinkan penyajian file .js dan .css, dan akan melarang penyajian file lainnya.

davesw
sumber
Terima kasih, davesw. Persis apa yang saya cari
Tuan Bell
1
Ketika saya melakukan ini saya mendapatkan kesalahan bahwa httpHandlers tidak dapat digunakan dalam mode pipa. Ia ingin saya beralih ke mode klasik di server. Apa cara yang benar untuk melakukan ini ketika seseorang tidak ingin server menggunakan mode klasik?
Bjørn
1
@ BjørnØyvindHalvorsen Anda dapat menghapus satu atau bagian penangan lainnya atau mematikan validasi konfigurasi di web.config Anda. Lihat di sini
davesw
2
Hanya mod ke bagian <system.webServer> yang diperlukan agar dapat berfungsi, mod <system.web> TIDAK diperlukan.
joedotnot
@joedotnot Benar, hanya diperlukan satu bagian, tetapi bagian mana yang bergantung pada konfigurasi server web Anda. Saat ini kebanyakan orang akan membutuhkan bagian system.webServer, bukan bagian system.Web yang lebih tua.
davesw
5

Salah satu cara untuk mencapai ini adalah dengan menyediakan milik Anda sendiri ActionInvoker. Menggunakan kode yang disertakan di bawah ini, Anda dapat menambahkan ke konstruktor pengontrol Anda:

ActionInvoker = new JavaScriptActionInvoker();

Sekarang, setiap kali Anda menempatkan .jsfile di samping tampilan Anda:

masukkan deskripsi gambar di sini

Anda dapat mengaksesnya secara langsung:

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

Di bawah ini adalah sumbernya:

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
sumber
Namun, ini tampaknya solusi yang baik, tetapi apakah itu memengaruhi waktu panggilan untuk bertindak?
Leandro Soares
Ini mungkin berhasil, tapi apa? saya ingin menulis lebih sedikit kode, tidak lebih.
joedotnot
1
@joedotnot Anda tidak menulis lebih banyak kode sekali, dan lebih sedikit kode selamanya. Mantra seorang programmer, bukan? :)
Kirk Woll
@Tokopedia tidak ada perselisihan di sana. Hanya kecewa bahwa untuk apa yang seharusnya menjadi "fitur sederhana", itu tidak keluar dari kotak. Jadi saya lebih suka memilih jawaban davesw (jawaban yang diterima). Tapi terima kasih telah membagikan kode Anda, semoga bermanfaat bagi orang lain.
joedotnot
@KirkWoll Saya baru mengenal MVC dan saya mencoba menerapkan solusi Anda di situs MVC5. Saya tidak yakin di mana harus menempatkan atau "menggunakan" "ActionInvoker = new JavaScriptActionInvoker ()" ??
Drew
3

Anda dapat membalik saran davesw dan hanya memblokir .cshtml

<httpHandlers>
    <add path="*.cshtml" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
Vadym Nikolaiev
sumber
Sempurna! :) Solusi singkat yang bagus! Dan berhasil! :) Saya tidak tahu mengapa ini bukan pengaturan default, karena jauh lebih baik untuk menyimpan skrip yang berkaitan dengan view bersama dengan view yang sebenarnya. Terima kasih, Vadym.
BruceHill
24
Saya akan berhati-hati dengan pendekatan ini, meskipun tampaknya bagus dan bersih. Jika di masa mendatang, aplikasi ini menyertakan mesin tampilan selain Razor (mis. WebForms, Spark, dll), mereka akan menjadi publik secara diam-diam. Juga mempengaruhi file seperti Site.Master. Memasukkan ke daftar putih tampaknya merupakan pendekatan yang lebih aman
arserbin3
Saya setuju dengan @ arserbin3 bahwa daftar putih tampaknya lebih aman; pada saat yang sama, saya tidak dapat merasakan kemungkinan perubahan Mesin Tampilan aplikasi perusahaan dari satu ke yang lain. Tidak ada alat otomatisasi yang sempurna untuk melakukannya. Konversi perlu dilakukan dengan tangan. Setelah saya melakukannya untuk Aplikasi Web yang besar; mengubah mesin tampilan WebForm menjadi Razor, dan saya ingat hari-hari mimpi buruk, selama beberapa bulan, hal-hal tidak berfungsi di sana-sini ... Tidak dapat berpikir untuk melakukan hal seperti itu lagi :). Jika saya harus melakukan perubahan besar, maka, saya yakin perubahan pengaturan web.config seperti itu tidak akan dilupakan.
Emran Hussain
1

Saya tahu ini adalah topik yang agak lama, tetapi saya memiliki beberapa hal yang ingin saya tambahkan. Saya mencoba jawaban davesw tetapi menghasilkan kesalahan 500 ketika mencoba memuat file skrip, jadi saya harus menambahkan ini ke web.config:

<validation validateIntegratedModeConfiguration="false" />

ke system.webServer. Inilah yang saya miliki, dan saya bisa membuatnya berfungsi:

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

Berikut adalah informasi lebih lanjut tentang validasi: https://www.iis.net/configreference/system.webserver/validation

dh6984.dll
sumber
0

tambahkan kode ini di file web.config di dalam 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>
Peter Isaac
sumber
0

Saya juga ingin menempatkan file js yang terkait dengan tampilan di folder yang sama dengan tampilan.

Saya tidak bisa mendapatkan solusi lain di utas ini untuk berfungsi, bukan karena mereka rusak tetapi saya terlalu baru di MVC untuk membuatnya berfungsi.

Menggunakan informasi yang diberikan di sini dan beberapa tumpukan lainnya, saya menemukan solusi yang:

  • Mengizinkan file javascript untuk ditempatkan di direktori yang sama dengan tampilan yang terkait dengannya.
  • URL skrip tidak memberikan struktur situs fisik yang mendasarinya
  • URL skrip tidak harus diakhiri dengan garis miring (/)
  • Tidak mengganggu sumber daya statis, misalnya: /Scripts/someFile.js masih berfungsi
  • Tidak membutuhkan runAllManagedModulesForAllRequests untuk diaktifkan.

Catatan: Saya juga menggunakan HTTP Attribute Routing. Ada kemungkinan bahwa rute yang digunakan di soultion saya dapat dimodifikasi untuk bekerja tanpa mengaktifkan ini.

Diberikan contoh struktur direktori / file berikut:

Controllers
-- Example
   -- ExampleController.vb

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

Dengan menggunakan langkah-langkah konfigurasi yang diberikan di bawah ini, dikombinasikan dengan contoh struktur di atas, URL tampilan pengujian akan diakses melalui: /Example/Testdan file javascript akan direferensikan melalui:/Example/Scripts/test.js

Langkah 1 - Aktifkan Perutean Atribut:

Edit file /App_start/RouteConfig.vb Anda dan tambahkan routes.MapMvcAttributeRoutes()tepat di atas rute yang ada.

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

Langkah 2 -Konfigurasi situs Anda untuk memperlakukan, dan memproses, /{controller}/Scripts/*.js sebagai jalur MVC dan bukan sumber daya statis

Edit file /Web.config Anda, tambahkan yang berikut ini ke bagian system.webServer -> handlers dari file:

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

Ini dia lagi dengan konteks:

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

Langkah 3 - Tambahkan hasil tindakan skrip berikut ke file Controller Anda

  • Pastikan untuk mengedit jalur rute agar cocok dengan nama {controller} untuk controller, untuk contoh ini: <Route (" Example / Scripts / {filename}")>
  • Anda perlu menyalin ini ke setiap file Controller Anda. Jika Anda ingin, mungkin ada cara untuk melakukan ini sebagai konfigurasi rute tunggal, satu kali, entah bagaimana.

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

Untuk konteksnya, ini adalah file ExampleController.vb saya:

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

Catatan Akhir Tidak ada yang istimewa tentang file javascript test.vbhtml view / test.js dan tidak ditampilkan di sini.

Saya menyimpan CSS saya dalam file tampilan tetapi Anda dapat dengan mudah menambahkan solusi ini sehingga Anda dapat mereferensikan file CSS Anda dengan cara yang sama.

Drew
sumber