У меня есть составное приложение ASP .NET MVC 3 Razor, использующее MEF. Все будет хорошо, если я разверну плагины в виде DLL-файлов и представлений (CSHTML) в обычной папке Views из приложения. Но это не очень чисто, и это не будет настоящим плагином, если я не буду размещать представления как встроенные ресурсы в файлах DLL (вместе с контроллерами и моделями).

Я следил за многими статьями (большинство из них устарели). На самом деле здесь, в Stack Overflow, есть один неплохой пример: Контроллеры и представления внутри библиотеки классов.

Я также проверил документы для VirtualPathProvider, и мне удалось создать собственный, который находит файл в сборке и загружает его идеально (или, по крайней мере, получает к нему поток). Для этого я следовал VirtualPathProvider документация в MSDN.

Также существует реализация для VirtualFile, но пока нет для VirtualDirectory.

Вот в чем проблема. Я работаю с представлениями Razor. Я знаю, что им нужны спецификации конфигурации из файла web.config для Razor, чтобы построить их. Но если я встрою их в DLL, эта конфигурация просто потеряется.

Интересно, поэтому я продолжаю получать ошибку:

Представление в «~/Plugins/CRM.Web.Views.CRM.Index.cshtml» должно быть производным от WebViewPage или WebViewPage.

Может быть, мне просто нужно добавить код, чтобы он работал? Любые идеи?

28
Anderson Matos 15 Фев 2011 в 20:56
1
Есть над чем подумать: если я добавлю "@inherits System.Web.Mvc.WebViewPage" в каждый файл cshtml, все пойдет нормально. Однако я не могу просто сделать это (это было бы огромным усилием для чего-то, что уже автоматически использует обычные файлы просмотра). Итак, есть идеи?
 – 
Anderson Matos
15 Фев 2011 в 21:21
Этот пост может быть чем-то полезен, но я просто не могу проверить это прямо сейчас. Я попробую как можно скорее и опубликую результаты/ответ здесь. stackoverflow.com/questions/6465855/…
 – 
Anderson Matos
8 Ноя 2011 в 17:02
1
Странно, что это не работает. Виртуальный путь является абстракцией файловой системы, используемой ВСЕМИ классами asp.net, поэтому Razor нарушил бы этот шаблон, обратившись напрямую к фактической файловой системе без использования провайдера виртуального пути... трудно поверить, что люди Microsoft сделали такой большой Ошибка дизайна. Может быть. ваши проблемы связаны с ошибкой в ​​относительных путях. Взгляните на этот старый пост: thecodinghumanist.com/Content/VirtualPathProviderExample.aspx. также некоторое программное обеспечение, которое вы можете скачать... может быть, в вашей реализации чего-то не хватает, сравните с ним
 – 
Francesco Abbruzzese
11 Апр 2012 в 00:44
1
Ссылка старая, и да, она отлично работает с aspx и ascx. Не работает с CSHTML.
 – 
Sean Chase
12 Апр 2012 в 21:43
1
Вы можете использовать мой EmbeddedResourceVirtualPathProvider, который можно установить через Nuget. Он загружает ресурсы из сборок, на которые ссылаются, а также может быть настроен на получение зависимостей от исходных файлов во время разработки, чтобы вы могли обновлять представления без необходимости повторной компиляции.
 – 
mcintyre321
27 Фев 2013 в 17:12

4 ответа

Мой предпочтительный способ встраивания Razor Views в библиотеку классов — скопировать их в папки Views/Areas веб-сайта MVC с событием после сборки. Расположение пользовательских представлений можно указать, если вы переопределяете ViewEngine или VirtualPathProvider.

Сложная часть для меня заключалась в том, чтобы заставить intellisense работать в этих библиотеках View Class. Во-первых, вы должны добавить Web.Config в свою сборку View. Обратите внимание, что вам не обязательно включать его в свою сборку. Он должен находиться только в корневом каталоге сборки (или в папке представлений). Вот пример. Обратите внимание на важный раздел Сборки/Компиляция.

<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

  <appSettings>
    <add key="webpages:Enabled" value="false" />
  </appSettings>

  <system.web>
    <compilation targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        <add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </assemblies>
    </compilation>

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

    <!--
        Enabling request validation in view pages would cause validation to occur
        after the input has already been processed by the controller. By default
        MVC performs request validation before a controller processes the input.
        To change this behavior apply the ValidateInputAttribute to a
        controller or action.
    -->
    <pages
        validateRequest="false"
        pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
        pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
        userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <controls>
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
      </controls>
    </pages>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <remove name="BlockViewHandler"/>
      <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
    </handlers>
  </system.webServer>
</configuration>

Затем вам нужно изменить файл vbproj вашей библиотеки классов, чтобы все элементы OutputPath указывали на «bin\», а не на «Debug\bin\» или «Release\bin\». Это основное различие, которое я обнаружил между библиотеками классов и типами веб-проектов ASP.Net, которые могут вызывать ошибки IntelliSense.

Если вы по-прежнему получаете сообщение об ошибке обязательного наследования, рассмотрите возможность использования @Inherits System.Web.Mvc.WebViewPage в своих представлениях. Если вы не копируете свои представления в проект своего веб-сайта, возможно, вы загружаете их из встроенных ресурсов с помощью пользовательского ViewEngine/VirtualPathProvider. Если это так, вам определенно нужны наследования, чтобы Razor, к сожалению, знал, какой у вас базовый класс представления.

Удачи.

7
sky-dev 23 Ноя 2011 в 21:41
Интересный. Я попробую это и опубликую результаты.
 – 
Anderson Matos
25 Ноя 2011 в 17:25

Вы можете взглянуть на подписка на запись в блоге.

3
Darin Dimitrov 15 Фев 2011 в 21:16
Интересный. Но это требует от меня предварительной компиляции всех представлений. Я не в этом, я просто хочу добавить файл cshtml в библиотеку классов и все (встроенный ресурс и только это). Другая вещь, которую, я думаю, вы просто не могли видеть, это то, что Я СОЗДАЮ ПЛАГИНЫ! Поправьте меня, если я ошибаюсь, но я никогда не видел, чтобы основное приложение ссылалось на плагин, который просто даже не знает, существует ли этот плагин или нет! знак равно
 – 
Anderson Matos
16 Фев 2011 в 01:49
@LordALMM, почему? Почему предоставленное решение не соответствует вашим требованиям по экстернализации представлений в отдельных сборках?
 – 
Darin Dimitrov
25 Авг 2011 в 20:28
«Интересно. Но это требует от меня предварительной компиляции всех представлений. Я не в этом, я просто хочу добавить файл cshtml в библиотеку классов и все (встроенный ресурс и только это)». (15 февраля). Оно работает? да. Но это не лучший способ. Тот факт, что вы можете убить муравья бомбой, не делает его лучшим подходом для этой задачи. Я ищу способ собрать Razor и какого-то поставщика вместе, чтобы я мог использовать встроенный ресурс (но НЕ предварительно скомпилированные классы, просто файлы CSHTML).
 – 
Anderson Matos
6 Сен 2011 в 03:40
1
Кроме того, если я буду создавать настраиваемые плагины, гораздо проще иметь исходный код, чем сборку или IL. Я пытаюсь создать какое-то приложение MVC MEF, которое позволяет моему клиенту создавать файлы CSHTML и добавлять собственные функции (например, SAP с настроенным кодом).
 – 
Anderson Matos
6 Сен 2011 в 03:42

Хоссам,

Пост, о котором вы говорите, - это то, что Дарин уже предложил. Основным недостатком этого подхода является использование специального компилятора MvcRazorClassGenerator для преобразования файлов представления CSHTML в файлы классов. Для этого вам необходимо установить для каждого представления CSHTML в вашем проекте значение Content, а для пользовательского инструмента — MvcRazorClassGenerator.

Я не могу говорить за LordALMMa, но я скачал исходный код компилятора и попробовал его, и он не совсем работает так, как я надеялся.

Мой другой подход заключался в том, чтобы включить файлы CSHTML в качестве встроенных ресурсов во внешнюю DLL, прочитать необработанное содержимое файла и выполнить представление в виде строки (см. пример RazorEngine в CodeProject: http://razorengine.codeplex.com/)

Я не хотел полностью зависеть от RazorEngine в корпоративном приложении, потому что я не знаю, насколько хорошо он совместим со всем синтаксисом Razor, поэтому я пока отказался от этого.

Я исхожу из прототипа, созданного мной в ASP.NET MVC 2.0, который представляет собой многопользовательское приложение. На ферме серверов у нас есть один экземпляр приложения, где все клиенты используют одну и ту же кодовую базу. В моем прототипе MVC 2.0 я смог определить, для какого «клиента» был сделан запрос, проверить пользовательский контроллер, который переопределяет базовый (для настройки основного кода), а также проверить пользовательские представления (для настройки основной вид). Это позволяет нам развертывать «плагин» для каждого клиента. Программное обеспечение определяет, есть ли у клиента настраиваемый контроллер, который соответствует запросу, а также настраиваемое действие, которое соответствует, и если это так, оно использует вместо этого настраиваемый контроллер/действие.

Когда я начал переносить свой прототип на MVC 3, я столкнулся с той же проблемой, что и у LordALMMa: ошибка «Представление в '... Index.cshtml' должно быть получено из WebViewPage или WebViewPage». Я посмотрю, как разместить "@inherits System.Web.Mvc.WebViewPage" в своих представлениях CSHTML и посмотрю, приблизит ли это меня к тому, чтобы заставить его работать.

Поскольку у меня есть работающий прототип MVC 2.0, использующий MVC 3, Razor не является главным приоритетом, и я не трачу на него кучу времени. Я уверен, что смогу перенести MVC 2.0 в MVC 3.0, используя движок WebForms, если нам нужно использовать 4.0 Framework.

2
Nick Bork 25 Май 2011 в 07:13

Эй, я подозреваю, что у вас есть веские причины для просмотра внутри DLL. Однако также учтите, что это необычный способ упаковки всего в одну сущность.

Если вы разрабатываете плагин, в наши дни люди выбирают упаковку в формате NUGET, который, среди прочего, также решает ваши проблемы. Он имеет структуру .nupkg, которая также является одним из способов распространения плагинов в виде пакетов и библиотек.

Другое решение, которому обычно следуют сообщества, заключается в том, что (если они не хотят чего-то столь же сложного, как nuget), они кодируют DLL-плагины таким образом, что он не использует механизмы просмотра, такие как бритва, вместо этого выводит HTML сам по себе, используя старый примитивный способ ответа. .Write и, таким образом, стать независимыми от файлов cshtml. Если вы все еще хотите использовать cshtml, см. эту запись в блоге для предварительной компиляции их в классы.

0
Zasz 23 Ноя 2011 в 21:55
Хм, я знаю, что ответ - это не то, что ищет вопрос, но моя цель - отговорить спрашивающего от того, чтобы пойти по этому пути решения его более глубокой проблемы упаковки.
 – 
Zasz
26 Ноя 2011 в 18:57