Представления в отдельных сборках в ASP.NET MVC

Я пытаюсь создать веб-приложение, где я хочу иметь возможность подключать отдельные сборки. Я использую MVC Preview 4 в сочетании с Unity для внедрения зависимостей, которые я использую для создания контроллеров из моих сборок плагинов. Я использую WebForms (по умолчанию aspx) в качестве движка просмотра.

Если я хочу использовать представление, я застреваю на тех, которые определены в основном проекте, из-за динамической компиляции части ASPX. Я ищу правильный способ вложить файлы ASPX в другую сборку, не проходя весь этап развертывания. Я что-то упускаю из виду? Или я должен прибегнуть к созданию своих взглядов программно?


Обновление: я изменил принятый ответ. Хотя ответ Дейла очень тщательный, я выбрал решение с другим провайдером виртуальных путей. Я думаю, что это работает как шарм и занимает всего около 20 строк кода.

4 ответа

Решение

По сути, это та же проблема, что и у людей, использующих WebForms и пытающихся скомпилировать свои файлы UserControl ASCX в DLL. Я нашел этот http://www.codeproject.com/KB/aspnet/ASP2UserControlLibrary.aspx который может работать и для вас.

Мне потребовалось слишком много времени, чтобы заставить это работать должным образом из различных частичных примеров, так что вот полный код, необходимый для получения представлений из папки Views в общей библиотеке, структурированной так же, как обычная папка Views, но со всем настроенным для сборки как встроенной Ресурсы. Он будет использовать только встроенный файл, если обычный файл не существует.

Первая строка Application_Start:

HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedViewPathProvider());

VirtualPathProvider

   public class EmbeddedVirtualFile : VirtualFile
{
    public EmbeddedVirtualFile(string virtualPath)
        : base(virtualPath)
    {
    }

    internal static string GetResourceName(string virtualPath)
    {
        if (!virtualPath.Contains("/Views/"))
        {
            return null;
        }



        var resourcename = virtualPath
            .Substring(virtualPath.IndexOf("Views/"))
            .Replace("Views/", "OrangeGuava.Common.Views.")
            .Replace("/", ".");

        return resourcename;

    }


    public override Stream Open()
    {
        Assembly assembly = Assembly.GetExecutingAssembly();


        var resourcename = GetResourceName(this.VirtualPath);
        return assembly.GetManifestResourceStream(resourcename);
    }




}

public class EmbeddedViewPathProvider : VirtualPathProvider
{


    private bool ResourceFileExists(string virtualPath)
    {

        Assembly assembly = Assembly.GetExecutingAssembly();


        var resourcename = EmbeddedVirtualFile.GetResourceName(virtualPath);
        var result = resourcename != null && assembly.GetManifestResourceNames().Contains(resourcename);
        return result;
    }

    public override bool FileExists(string virtualPath)
    {
        return base.FileExists(virtualPath) || ResourceFileExists(virtualPath);
    }


    public override VirtualFile GetFile(string virtualPath)
    {

        if (!base.FileExists(virtualPath))
        {
            return new EmbeddedVirtualFile(virtualPath);
        }
        else
        {
            return base.GetFile(virtualPath);
        }

    }

}

Последний шаг, чтобы заставить это работать, состоит в том, что корневой Web.Config должен содержать правильные параметры настройки для анализа строго типизированных представлений MVC, поскольку тот в папке представлений не будет использоваться:

<pages
    validateRequest="false"
    pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
    pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
    userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
  <controls>
    <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
  </controls>
</pages>

Пара дополнительных шагов требуется, чтобы заставить это работать с Моно. Во-первых, вам нужно реализовать GetDirectory, так как все файлы в папке представлений загружаются при запуске приложения, а не по мере необходимости:

public override VirtualDirectory GetDirectory(string virtualDir)
    {
        Log.LogInfo("GetDirectory - " + virtualDir);
        var b = base.GetDirectory(virtualDir);
        return new EmbeddedVirtualDirectory(virtualDir, b);
    }

public class EmbeddedVirtualDirectory : VirtualDirectory
{
    private VirtualDirectory FileDir { get; set; } 

    public EmbeddedVirtualDirectory(string virtualPath, VirtualDirectory filedir)
        : base(virtualPath)
    {
        FileDir = filedir;
    }

    public override System.Collections.IEnumerable Children
    {
        get { return FileDir.Children; }
    }

    public override System.Collections.IEnumerable Directories
    {
        get { return FileDir.Directories; }
    }

    public override System.Collections.IEnumerable Files
    {
        get {

            if (!VirtualPath.Contains("/Views/") || VirtualPath.EndsWith("/Views/"))
            {
                return FileDir.Files;
            }

            var fl = new List<VirtualFile>();

            foreach (VirtualFile f in FileDir.Files)
            {
                fl.Add(f);
            }


            var resourcename = VirtualPath.Substring(VirtualPath.IndexOf("Views/"))
.Replace("Views/", "OrangeGuava.Common.Views.")
.Replace("/", ".");

            Assembly assembly = Assembly.GetExecutingAssembly();

            var rfl = assembly.GetManifestResourceNames()
                .Where(s => s.StartsWith(resourcename))
                .Select(s => VirtualPath + s.Replace(resourcename, ""))
                .Select(s => new EmbeddedVirtualFile(s));
            fl.AddRange(rfl);

            return fl;
        }
    }
}

Наконец, строго типизированные представления будут работать почти, но не совсем идеально. Модель будет восприниматься как нетипизированный объект, поэтому, чтобы получить надежную типизацию, вам нужно начать свои общие представления с чем-то вроде

<% var Model2 = Model as IEnumerable<AppModel>;  %>
protected void Application_Start()
{
    WebFormViewEngine engine = new WebFormViewEngine();

    engine.ViewLocationFormats = new[] { "~/bin/Views/{1}/{0}.aspx", "~/Views/Shared/{0}.aspx" };
    engine.PartialViewLocationFormats = engine.ViewLocationFormats;

    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(engine);

    RegisterRoutes(RouteTable.Routes);
}

Установите свойство "Копировать в вывод" вашего представления в "Копировать всегда"

Дополнение для всех вас, кто все еще ищет святой Грааль: я подошел немного ближе, чтобы найти его, если вы не слишком привязаны к представлению webforms.

Я недавно опробовал двигатель Spark. Помимо того, что я совершенно потрясающий, и я бы не стал возвращаться к веб-формам, даже если бы мне угрожали, он также предоставляет несколько очень хороших хуков для модульности приложения. Пример в их документах - использование Windsor в качестве контейнера IoC, но я не могу представить, что будет намного сложнее, если вы захотите использовать другой подход.

Другие вопросы по тегам