Đặt các tệp javascript dành riêng cho chế độ xem trong ứng dụng ASP.NET MVC ở đâu?


96

Đâu là nơi tốt nhất (thư mục nào, v.v.) để đặt các tệp javascript dành riêng cho chế độ xem trong ứng dụng ASP.NET MVC?

Để giữ cho dự án của mình có tổ chức, tôi thực sự muốn có thể đặt chúng cạnh nhau với các tệp .aspx của chế độ xem, nhưng tôi không tìm ra cách hay để tham chiếu chúng khi thực hiện điều đó mà không để lộ ~ / Views / Hành động / cấu trúc thư mục. Có thực sự là một điều tồi tệ khi để thông tin chi tiết về cấu trúc thư mục đó bị rò rỉ?

Cách thay thế là đặt chúng trong thư mục ~ / Scripts hoặc ~ / Content, nhưng hơi khó chịu vì bây giờ tôi phải lo lắng về xung đột tên tệp. Tuy nhiên, đó là một sự khó chịu mà tôi có thể vượt qua, nếu đó là "điều đúng đắn".


2
Tôi thấy các phần hữu ích cho việc này. Xem: stackoverflow.com/questions/4311783/…
Frison Alexander

1
Điều này nghe có vẻ là một câu hỏi điên rồ, nhưng một kịch bản cực kỳ hữu ích là khi bạn lồng tệp javascript của một trang dưới tệp .cshtml. (Ví dụ: với NestIn ). Nó giúp không phải trả lại xung quanh trình khám phá giải pháp.
David Sherret

Câu trả lời:


126

Câu hỏi cũ, nhưng tôi muốn đưa ra câu trả lời của mình trong trường hợp bất kỳ ai khác đến tìm nó.

Tôi cũng muốn xem các tệp js / css cụ thể của mình trong thư mục chế độ xem và đây là cách tôi đã làm điều đó:

Trong thư mục web.config trong thư mục gốc của / Views, bạn cần sửa đổi hai phần để cho phép máy chủ web phục vụ các tệp:

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

Sau đó, từ tệp xem của bạn, bạn có thể tham chiếu các url như bạn mong đợi:

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

Điều này sẽ cho phép cung cấp các tệp .js và .css và sẽ cấm cung cấp bất kỳ thứ gì khác.


Cảm ơn, davesw. Chính xác những gì tôi đang tìm kiếm
Ông Chuông

1
Khi thực hiện việc này, tôi gặp lỗi không thể sử dụng httpHandlers ở chế độ đường ống. Nó muốn tôi chuyển sang chế độ cổ điển trên máy chủ. Cách chính xác để thực hiện việc này là gì khi không muốn máy chủ sử dụng chế độ cổ điển?
Bjørn

1
@ BjørnØyvindHalvorsen Bạn có thể xóa một hoặc phần trình xử lý khác hoặc tắt xác thực cấu hình trong web.config của mình. Xem ở đây
davesw

2
Chỉ cần có các mod cho phần <system.webServer> để nó hoạt động, KHÔNG yêu cầu mod <system.web>.
joedotnot

@joedotnot Đúng, chỉ cần một phần, nhưng phần nào phụ thuộc vào cấu hình máy chủ web của bạn. Hiện tại, hầu hết mọi người sẽ cần phần system.webServer, không phải phần system.web cũ hơn.
davesw

5

Một cách để đạt được điều này là cung cấp hàng của riêng bạn ActionInvoker. Sử dụng mã được bao gồm bên dưới, bạn có thể thêm vào hàm tạo của bộ điều khiển:

ActionInvoker = new JavaScriptActionInvoker();

Bây giờ, bất cứ khi nào bạn đặt .jstệp bên cạnh chế độ xem của mình:

nhập mô tả hình ảnh ở đây

Bạn có thể truy cập trực tiếp:

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

Dưới đây là nguồn:

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);
        }
    }
}

Tuy nhiên, đây có vẻ là một giải pháp tốt, nhưng nó có ảnh hưởng đến thời gian gọi hành động không?
Leandro Soares

Nó có thể hoạt động tốt, nhưng những gì? tôi muốn viết ít mã hơn, không nhiều hơn.
joedotnot

1
@joed không phải bạn viết nhiều mã một lần và ít mã hơn mãi mãi. Câu thần chú của một lập trình viên, không? :)
Kirk Woll

@KirkWoll. không có bất đồng ở đó. Chỉ thất vọng rằng đối với những gì phải là một "tính năng đơn giản", nó đã không ra khỏi hộp. Vì vậy, tôi muốn chọn câu trả lời của davesw (câu trả lời được chấp nhận). Nhưng cảm ơn vì đã chia sẻ mã của bạn, nó có thể hữu ích cho những người khác.
joedotnot

@KirkWoll Tôi là người mới sử dụng MVC và tôi đang cố gắng triển khai giải pháp của bạn trên trang web MVC5. Tôi không chắc nơi đặt hoặc "sử dụng" "ActionInvoker = new JavaScriptActionInvoker ()" ??
Drew

3

Bạn có thể đảo ngược đề xuất của davesw và chỉ chặn .cshtml

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

Hoàn hảo! :) Một giải pháp ngắn gọn tốt đẹp! Và nó hoạt động! :) Tôi không biết tại sao đây không phải là cài đặt mặc định, bởi vì có thể giữ các tập lệnh liên quan đến các chế độ xem cùng với các chế độ xem thực tế sẽ tốt hơn nhiều. Cảm ơn, Vadym.
BruceHill 20/12/12

24
Tôi sẽ thận trọng với cách tiếp cận này, mặc dù nó có vẻ đẹp và sạch sẽ. Nếu trong tương lai, ứng dụng này bao gồm các công cụ xem khác với Razor (ví dụ như WebForms, Spark, v.v.), chúng sẽ âm thầm được công khai. Cũng ảnh hưởng đến các tệp như Site.Master. Danh sách trắng dường như là cách tiếp cận an toàn hơn
arserbin3

Tôi đồng ý với @ arserbin3 rằng danh sách trắng có vẻ an toàn hơn; đồng thời, tôi không cảm thấy có khả năng thay đổi View Engine của ứng dụng doanh nghiệp từ cái này sang cái khác. Không có công cụ tự động hóa hoàn hảo để làm như vậy. Việc chuyển đổi cần được thực hiện bằng tay. Một khi tôi đã làm như vậy cho một Ứng dụng Web lớn; đã chuyển đổi viewengine WebForm thành Razor, và tôi nhớ những ngày ác mộng, trong vài tháng, mọi thứ không hoạt động ở đây và ở đó ... Tôi không thể nghĩ về việc làm điều đó một lần nữa :). Nếu tôi phải thực hiện thay đổi lớn như vậy, thì tôi tin rằng thay đổi cài đặt web.config như vậy sẽ không bị lãng quên.
Emran Hussain

1

Tôi biết đây là một chủ đề khá cũ, nhưng tôi có một vài điều tôi muốn bổ sung. Tôi đã thử câu trả lời của davesw nhưng nó gặp lỗi 500 khi cố gắng tải các tệp tập lệnh, vì vậy tôi phải thêm nó vào web.config:

<validation validateIntegratedModeConfiguration="false" />

sang system.webServer. Đây là những gì tôi có và tôi đã có thể làm cho nó hoạt động:

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

Đây là thông tin thêm về xác thực: https://www.iis.net/configreference/system.webserver/validation


0

thêm mã này vào tệp web.config bên trong thẻ 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

Tôi cũng muốn đặt các tệp js liên quan đến một chế độ xem trong cùng một thư mục với chế độ xem.

Tôi không thể làm cho các giải pháp khác trong chuỗi này hoạt động, không phải là chúng bị hỏng nhưng tôi quá mới đối với MVC để làm cho chúng hoạt động.

Sử dụng thông tin được cung cấp ở đây và một số ngăn xếp khác, tôi đã đưa ra một giải pháp:

  • Cho phép đặt tệp javascript trong cùng thư mục với chế độ xem mà nó được liên kết.
  • URL tập lệnh không cung cấp cấu trúc trang web vật lý cơ bản
  • URL tập lệnh không cần phải kết thúc bằng dấu gạch chéo (/)
  • Không can thiệp vào tài nguyên tĩnh, ví dụ: /Scripts/someFile.js vẫn hoạt động
  • Không yêu cầu runAllManagedModulesForAllRequests được bật.

Lưu ý: Tôi cũng đang sử dụng Định tuyến thuộc tính HTTP. Có thể tuyến đường được sử dụng trong linh hồn của tôi có thể được sửa đổi để hoạt động mà không cần kích hoạt điều này.

Đưa ra cấu trúc thư mục / tệp ví dụ sau:

Controllers
-- Example
   -- ExampleController.vb

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

Sử dụng các bước cấu hình được cung cấp bên dưới, kết hợp với cấu trúc ví dụ ở trên, URL chế độ xem thử nghiệm sẽ được truy cập qua: /Example/Testvà tệp javascript sẽ được tham chiếu qua:/Example/Scripts/test.js

Bước 1 - Bật định tuyến thuộc tính:

Chỉnh sửa tệp /App_start/RouteConfig.vb của bạn và thêm routes.MapMvcAttributeRoutes()ngay phía trên các tuyến đường hiện có.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

Bước 2 -Cấu hình trang web của bạn để xử lý và xử lý /{controller}/Scripts/*.js dưới dạng đường dẫn MVC chứ không phải tài nguyên tĩnh

Chỉnh sửa tệp /Web.config của bạn, thêm phần sau vào phần system.webServer -> trình xử lý của tệp:

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

Đây là một lần nữa với ngữ cảnh:

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

Bước 3 - Thêm kết quả hành động tập lệnh sau vào tệp Bộ điều khiển của bạn

  • Đảm bảo chỉnh sửa đường dẫn tuyến để khớp với tên {controller} của bộ điều khiển, ví dụ này là: <Route (" Ví dụ / Scripts / {filename}")>
  • Bạn sẽ cần sao chép tệp này vào từng tệp Bộ điều khiển của mình. Nếu bạn muốn, có lẽ có một cách để thực hiện việc này dưới dạng cấu hình tuyến đường đơn lẻ, một lần, bằng cách nào đó.

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

Đối với ngữ cảnh, đây là tệp ExampleController.vb của tôi:

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

Ghi chú cuối cùng Không có gì đặc biệt về các tệp javascript test.vbhtml view / test.js và không được hiển thị ở đây.

Tôi giữ CSS của mình trong tệp xem nhưng bạn có thể dễ dàng thêm vào giải pháp này để bạn có thể tham chiếu các tệp CSS của mình theo cách tương tự.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.