Подтвердить что ты не робот

ASP.NET MVC 3 RC AreaRegistration.RegisterAllAreas() и динамически загруженные сборки

В настоящее время я экспериментирую с динамически загружаемыми областями с помощью ASP.NET MVC 3 RC. Я видел, что во многих местах он писал, что это не то, для чего предназначены области, и (по крайней мере, до MVC 2) невозможно, скажем здесь например.

Но все же! Должно быть возможно заставить его работать, не так ли? Я создал решение, добавил проект MVC 3, добавил область и некоторый контент. Все работает хорошо. Теперь я создал новый проект библиотеки классов (в том же решении), добавил ссылку на него из MVC-проекта и начал перемещаться по части, связанной с областью, в библиотеку. Изменен выходной каталог проекта библиотеки в папку области MVC-проекта и убедитесь, что Views и их web.config скопированы в папку вывода.

Прочитав так много о том, как вы не могли иметь внешние области, было немного удивительно, что это сработало. На самом деле проблем нет! Проблема начинается, когда я удаляю ссылку между проектами и вместо этого загружаю библиотеку в код. (Перед вызовом AreaRegistration.RegisterAllAreas().) Теперь это не сработает. На всех.

Я немного зациклился на источнике MVC 3, и проблема, похоже, связана с BuildManager.GetReferencedAssemblies(), который используется чтобы собрать сборки для реализации AreaRegistration.

Теперь я не уверен на 100%, но похоже, что этот метод рассматривает только сборки, которые присутствовали/упоминались во время компиляции, может кто-то подтвердить, действительно ли это так?

Я отлаживал это, и этот метод-вызов действительно не нашел сборку, которую я загрузил непосредственно перед вызовом. Возможно, из-за чего-то еще, что я пропустил, возможно. Любые идеи?

4b9b3361

Ответ 1

То, как все работает, немного сложнее.

GetReferencedAssemblies содержит ссылочные сборки, а не загруженные сборки. Это включает в себя:

  • все сборки, на которые ссылается ваше приложение web.config(например, System.Web.Mvc)
  • все унаследовано от root web.config, которое включает в себя такие вещи, как System, System.Web и другие, которые вам не нужно добавлять сами. (Вы можете посмотреть здесь: C:\Windows\Microsoft.Net\Framework\v4.0.30319\web.config).
    Он также содержит специальный элемент *, который:
  • содержит все в вашей папке bin

Итак, теперь возьмите приложение v1 (все в одном приложении). Все работает, потому что код приложения скомпилирован в папку bin, которая автоматически включается. Кроме того, все представления областей и т.д. Находятся в самом приложении, поэтому они доступны.

Теперь в приложении v2 (в другом проекте с proj-to-proj ссылкой и) пользовательская задача сборки, которая копирует представления в нужное место в вашем основном приложении), все еще работает, потому что по умолчанию ссылки proj-to-proj означают, что двоичный файл библиотеки классов копируется в папку вашего bin приложения. Таким образом, по вышеуказанным правилам код области по-прежнему загружается правильно. Тот факт, что вы установили путь вывода библиотеки в какое-то место в своей основной папке "Области приложений", на самом деле не имеет никакого значения - вы просто получаете две копии двоичного файла.

Теперь в приложении v3 (без сборки proj-proj ref, сборка библиотеки области загружается вручную), сборка вашей библиотеки загружается слишком поздно. К моменту запуска вашего кода набор ссылочных сборок уже заблокирован и больше не может быть изменен.

Существует способ запускать код и добавлять элементы в список зарегистрированных сборок: вы можете сделать это с помощью метода AddReferencedAssembly, который должен быть вызван методом PreApplicationStartMethodAttribute.

Конечно, вам все равно придется иметь дело с тем, как вы управляете своими файлами просмотра. Способ, которым вы в настоящее время настроены, в значительной степени аналогичен представлению в основном приложении (поскольку они эффективно копируются в нужное место).

Ответ 2

1 - Отделите вас от Mvc Области в разные проекты Mvc, которые будут скомпилированы в свои собственные отдельные сборки

2 - Добавьте это в свой класс AssemblyInfo.cs, чтобы вызвать метод при загрузке приложения

[assembly: PreApplicationStartMethod(typeof(PluginAreaBootstrapper), "Init")]

3 - Здесь, как выглядит метод Init при его вызове во время загрузки

public class PluginAreaBootstrapper
{
    public static readonly List<Assembly> PluginAssemblies = new List<Assembly>();

    public static List<string> PluginNames()
    {
        return PluginAssemblies.Select(
            pluginAssembly => pluginAssembly.GetName().Name)
            .ToList();
    }

    public static void Init()
    {
        var fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Areas");

        foreach (var file in Directory.EnumerateFiles(fullPluginPath, "*Plugin*.dll"))
            PluginAssemblies.Add(Assembly.LoadFile(file));

        PluginAssemblies.ForEach(BuildManager.AddReferencedAssembly);
    }
}

4 - добавьте пользовательский RazorViewEngine

public class PluginRazorViewEngine : RazorViewEngine
{
    public PluginRazorViewEngine()
    {
        AreaMasterLocationFormats = new[]
        {
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/{1}/{0}.vbhtml",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.vbhtml"
        };

        AreaPartialViewLocationFormats = new[]
        {
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/{1}/{0}.vbhtml",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.vbhtml"
        };

        var areaViewAndPartialViewLocationFormats = new List<string>
        {
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/{1}/{0}.vbhtml",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.vbhtml"
        };

        var partialViewLocationFormats = new List<string>
        {
            "~/Views/{1}/{0}.cshtml",
            "~/Views/{1}/{0}.vbhtml",
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Shared/{0}.vbhtml"
        };

        var masterLocationFormats = new List<string>
        {
            "~/Views/{1}/{0}.cshtml",
            "~/Views/{1}/{0}.vbhtml",
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Shared/{0}.vbhtml"
        };

        foreach (var plugin in PluginAreaBootstrapper.PluginNames())
        {
            masterLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.cshtml");
            masterLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.vbhtml");
            masterLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/Shared/{1}/{0}.cshtml");
            masterLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/Shared/{1}/{0}.vbhtml");

            partialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.cshtml");
            partialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.vbhtml");
            partialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/Shared/{0}.cshtml");
            partialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/Shared/{0}.vbhtml");

            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.cshtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Views/{1}/{0}.vbhtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Areas/{2}/Views/{1}/{0}.cshtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Areas/{2}/Views/{1}/{0}.vbhtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Areas/{2}/Views/Shared/{0}.cshtml");
            areaViewAndPartialViewLocationFormats.Add(
                "~/Areas/" + plugin + "/Areas/{2}/Views/Shared/{0}.vbhtml");
        }

        ViewLocationFormats = partialViewLocationFormats.ToArray();
        MasterLocationFormats = masterLocationFormats.ToArray();
        PartialViewLocationFormats = partialViewLocationFormats.ToArray();
        AreaPartialViewLocationFormats = areaViewAndPartialViewLocationFormats.ToArray();
        AreaViewLocationFormats = areaViewAndPartialViewLocationFormats.ToArray();
    }
}

5 - Зарегистрируйте свои области из разных проектов Mvc (Area)

namespace MvcApplication8.Web.MyPlugin1
{
    public class MyPlugin1AreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get { return "MyPlugin1"; }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "MyPlugin1_default",
                "MyPlugin1/{controller}/{action}/{id}",
                new {action = "Index", id = UrlParameter.Optional}
                );
        }
    }
}

Исходный код и дополнительные ссылки можно найти здесь: http://blog.longle.io/2012/03/29/building-a-composite-mvc3-application-with-pluggable-areas