Где разместить файлы javascript для конкретного просмотра в приложении ASP.NET MVC?

96

В каком месте (в какой папке и т. Д.) Помещать файлы javascript, относящиеся к представлению, в приложении ASP.NET MVC?

Чтобы мой проект был организован, мне бы очень хотелось иметь возможность размещать их рядом с файлами .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 и запретит обслуживание чего-либо еще.

Davesw
источник
Спасибо, davesw. Именно то, что я искал
мистер Белл
1
Когда я это делаю, я получаю сообщение об ошибке, что httpHandlers нельзя использовать в конвейерном режиме. Он хочет, чтобы я переключился в классический режим на сервере. Как правильно это сделать, если не нужно, чтобы сервер использовал классический режим?
Bjørn
1
@ BjørnØyvindHalvorsen Вы можете удалить тот или иной раздел обработчика или отключить проверку конфигурации в файле web.config. Смотрите здесь
davesw
2
Для работы требовались только моды в раздел <system.webServer>, моды <system.web> НЕ требовались.
joedotnot 06
@joedotnot Верно, нужен только один раздел, но какой зависит от конфигурации вашего веб-сервера. В настоящее время большинству людей понадобится раздел system.webServer, а не старый раздел system.web.
Davesw 07
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не ты напишешь больше кода один раз и меньше кода навсегда. Мантра программиста, не так ли? :)
Кирк Уолл
@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, что белый список кажется безопаснее; в то же время я не чувствую возможности изменения движка просмотра корпоративного приложения с одного на другой. Для этого не существует идеального средства автоматизации. Преобразование нужно производить вручную. Однажды я сделал это для большого веб-приложения; преобразовал WebForm viewengine в Razor, и я помню дни кошмара, пару месяцев кое-что не работало ... Не могу думать о том, чтобы сделать это снова :). Если мне все равно придется сделать такое гигантское изменение, я считаю, что такое изменение настроек web.config не будет забыто.
Эмран Хуссейн
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

dh6984
источник
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

Используя шаги настройки, приведенные ниже, в сочетании с приведенным выше примером структуры, URL-адрес тестового представления будет доступен через:, /Example/Testа ссылка на файл javascript будет:/Example/Scripts/test.js

Шаг 1 - Включите маршрутизацию атрибутов:

Отредактируйте файл /App_start/RouteConfig.vb и добавьте routes.MapMvcAttributeRoutes()чуть выше существующих маршрутов .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

Шаг 2. Настройте свой сайт для обработки и обработки /{controller}/Scripts/*.js как пути MVC, а не статического ресурса.

Отредактируйте файл /Web.config, добавив следующее в раздел system.webServer -> handlers файла:

<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 аналогичным образом.

Нарисовался
источник