Куди покласти файли JavaScript для перегляду в програмі ASP.NET MVC?


96

Яке найкраще місце (яку папку тощо) розміщувати у програмі ASP.NET MVC файли javascript для перегляду?

Щоб підтримувати свій проект організованим, я б дуже хотів мати можливість поставити їх поруч із файлами .aspx подання, але я не знайшов хорошого способу посилатися на них, роблячи це, не виставляючи ~ / Views / Дія / структура папки. Невже погано дозволяти деталям цієї структури папок витікати?

Альтернатива полягає в тому, щоб помістити їх у папки ~ / Scripts або ~ / Content, але це незначне роздратування, тому що зараз мені доводиться турбуватися про зіткнення імен файлів. Це роздратування, яке я можу пережити, однак, якщо це "правильна річ".


2
Я знайшов для цього корисні розділи. Див .: stackoverflow.com/questions/4311783/…
Фрісон Олександр

1
Це звучить як божевільне запитання, але надзвичайно корисним сценарієм є вкладання файлу javascript сторінки в .cshtml. (Наприклад, з NestIn ). Це допомагає не відбиватися навколо дослідника рішень.
Девід Шеррет,

Відповіді:


126

Старе запитання, але я хотів дати свою відповідь на випадок, якщо хтось прийде шукати його.

Я теж хотів, щоб мої файли перегляду js / css переглядались у папці views, і ось як я це зробив:

У папці web.config у кореневій папці / Views потрібно змінити два розділи, щоб веб-сервер міг обслуговувати файли:

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

Тоді з вашого файлу перегляду ви можете посилатися на URL-адреси, як ви очікували:

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

Це дозволить обслуговувати файли .js та .css, а також заборонятиме обслуговування інших речей.


Дякую, Дейве. Саме те, що я шукав
містер Белл

1
Коли я це роблю, я отримую помилку, що httpHandlers не можна використовувати в конвеєрному режимі. Він хоче, щоб я перейшов на класичний режим на сервері. Який правильний спосіб зробити це, коли хтось не хоче, щоб сервер використовував класичний режим?
Bjørn

1
@ BjørnØyvindHalvorsen Ви можете видалити той чи інший розділ обробника або вимкнути перевірку конфігурації у своєму web.config. Дивіться тут
davesw

2
Для його роботи потрібні були лише моди розділу <system.webServer>, а <system.web> моди НЕ потрібні.
joedotnot

@joedotnot Правильно, потрібен лише один розділ, але який залежить від конфігурації веб-сервера. В даний час більшості людей знадобиться розділ system.webServer, а не старіший розділ system.web.
davesw

5

Одним із способів досягти цього є постачання власного ActionInvoker. Використовуючи код, включений нижче, ви можете додати до конструктора вашого контролера:

ActionInvoker = new JavaScriptActionInvoker();

Тепер, щоразу, коли ви розміщуєте .jsфайл поруч зі своїм видом:

введіть тут опис зображення

Ви можете отримати до нього доступ безпосередньо:

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

Нижче наведено джерело:

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

Однак це здається хорошим рішенням, але чи впливає воно на час заклику до дій?
Леандро Соарес

Це цілком може спрацювати, але що? я хочу писати менше коду, не більше.
joedotnot

1
@joedotне ви пишете більше коду один раз, і менше коду назавжди. Мантра програміста, ні? :)
Kirk Woll,

@KirkWoll. ніяких розбіжностей там немає. Просто розчарований тим, що для того, що повинно бути "простою особливістю", він не вийшов з коробки. Тому я віддав перевагу вибору відповіді Davesw (прийнятої відповіді). Але дякую, що поділилися своїм кодом, це може бути корисно для інших.
joedotnot

@KirkWoll Я новачок у MVC, і я намагаюся впровадити ваше рішення на сайті MVC5. Я не впевнений, де розмістити або "використовувати" "ActionInvoker = new JavaScriptActionInvoker ()" ??
Дрю

3

Ви можете інвертувати пропозицію davesw і заблокувати лише .cshtml

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

Ідеально! :) Приємне коротке рішення! І це працює! :) Я не уявляю, чому це не налаштування за замовчуванням, тому що набагато краще мати можливість зберігати сценарії, що стосуються подань, разом із фактичними переглядами. Дякую, Вадиме.
BruceHill

24
Я був би обережний з таким підходом, хоча він здається приємним і чистим. Якщо в майбутньому ця програма включає механізми перегляду, крім Razor (колишні WebForms, Spark тощо), вони мовчки будуть загальнодоступними. Також впливає на файли, такі як Site.Master. Білий список здається більш безпечним
arserbin3

Я погоджуюсь з @ arserbin3, що білий список здається безпечнішим; в той же час я не відчуваю можливості зміни View Engine корпоративного додатка з одного на інший. Для цього не існує ідеального інструменту автоматизації. Перетворення потрібно робити вручну. Одного разу я зробив це для великого веб-додатку; перетворив перегляд WebForm на Razor, і я пам’ятаю дні кошмарів, пару місяців тут і там не працювало ... Не можу подумати про те, щоб знову зробити таке :). Якщо мені все одно доведеться робити такі гігантські зміни, тоді, я вважаю, такі зміни налаштувань web.config не будуть забуті.
Emran Hussain

1

Я знаю, що це досить стара тема, але я хотів би додати кілька речей. Я спробував відповідь davesw, але він видавав помилку 500 при спробі завантажити файли скриптів, тому мені довелося додати це до web.config:

<validation validateIntegratedModeConfiguration="false" />

до system.webServer. Ось, що я маю, і я зміг змусити його працювати:

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

Ось додаткова інформація про перевірку: https://www.iis.net/configreference/system.webserver/validation


0

додати цей код у файл web.config всередині тегу 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

Я також хотів розмістити файли js, пов’язані з видом, в ту саму папку, що і представлення.

Мені не вдалося змусити інші рішення в цьому потоці працювати, не те, що вони зламані, але я занадто новачок у MVC, щоб змусити їх працювати.

Використовуючи наведену тут інформацію та кілька інших стеків, я придумав рішення, яке:

  • Дозволяє розміщувати файл javascript у тому самому каталозі, що і подання, з яким він пов'язаний.
  • URL-адреси сценарію не видають базової фізичної структури сайту
  • URL-адреси сценарію не повинні закінчуватися косою рискою (/)
  • Не заважає статичним ресурсам, наприклад: /Scripts/someFile.js все ще працює
  • Не вимагає активації runAllManagedModulesForAllRequests.

Примітка: Я також використовую маршрутизацію атрибутів HTTP. Цілком можливо, що маршрут, який використовується в моєму розумінні, може бути модифікований для роботи, не дозволяючи цього.

Враховуючи такий приклад структури каталогу / файлу:

Controllers
-- Example
   -- ExampleController.vb

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

За допомогою кроків конфігурації, наведених нижче, у поєднанні з наведеною вище /Example/Testзразковою структурою, до URL-адреси тестового подання можна буде отримати доступ за допомогою: а файл javascript матиме посилання через:/Example/Scripts/test.js

Крок 1 - Увімкнути маршрутизацію атрибутів:

Відредагуйте файл /App_start/RouteConfig.vb і додайте routes.MapMvcAttributeRoutes()трохи вище існуючих маршрутів.

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

Крок 2 - Налаштуйте свій сайт на обробку та обробку /{controller}/Scripts/*.js як шляху MVC, а не статичного ресурсу

Відредагуйте файл /Web.config, додавши до файлу system.webServer -> обробників файлу наступне:

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

Ось знову з контекстом:

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

Крок 3 - Додайте наступні результати дії сценаріїв до вашого файлу контролера

  • Обов’язково відредагуйте шлях маршруту, щоб він відповідав імені {controller} для контролера, для цього прикладу це: <Route (" Example / Scripts / {filename}")>
  • Вам потрібно буде скопіювати це у кожен з ваших файлів контролера. Якщо ви хочете, можливо, є спосіб зробити це як єдину, одноразову конфігурацію маршруту.

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

Для контексту це мій файл 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

Заключні примітки У файлах javascript test.vbhtml view / test.js немає нічого особливого, і вони тут не відображаються.

Я зберігаю свій CSS у файлі представлення, але ви можете легко додати це рішення, щоб можна було посилатися на ваші файли CSS подібним чином.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.