Плагин для архитектуры ASP.NET MVC - PullRequest
70 голосов
/ 04 декабря 2008

Я потратил некоторое время на просмотр статьи Фила Хаака о Группировка контроллеров очень интересные вещи.

В данный момент я пытаюсь выяснить, возможно ли использовать те же идеи для создания подключаемой / модульной архитектуры для проекта, над которым я работаю.

Итак, мой вопрос: возможно ли разделить статью Области в Филе на несколько проектов?

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

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

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

Ответы [ 6 ]

51 голосов
/ 04 декабря 2008

Несколько недель назад я выполнил проверку концепции, где поместил полный набор компонентов: класс модели, класс контроллера и связанные с ними представления в DLL, добавил / отрегулировал один из примеров классов VirtualPathProvider, которые извлекают представления, чтобы они соответствующим образом обращались к представлениям в DLL.

В конце я просто поместил DLL в надлежащим образом настроенное приложение MVC, и оно работало так же, как если бы оно было частью приложения MVC с самого начала. Я продвинул его немного дальше, и он отлично работал с 5 из этих маленьких плагинов mini-MVC. Очевидно, вы должны следить за ссылками и конфигурационными зависимостями, когда все перемешиваете, но это сработало.

Это упражнение было направлено на функциональность плагина для платформы на основе MVC, которую я создаю для клиента. Существует основной набор контроллеров и представлений, которые дополняются дополнительными в каждом экземпляре сайта. Мы собираемся сделать эти дополнительные биты в эти модульные плагины DLL. Пока все хорошо.

Я написал обзор своего прототипа и пример решения для плагинов ASP.NET MVC на моем сайте.

РЕДАКТИРОВАТЬ: 4 года спустя я делал довольно много приложений ASP.NET MVC с плагинами и больше не использую метод, который я описал выше. На этом этапе я запускаю все свои плагины через MEF и вообще не помещаю контроллеры в плагины. Скорее, я создаю общие контроллеры, которые используют информацию о маршрутизации для выбора плагинов MEF и перекладывают работу на плагин и т. Д. Просто подумал, что добавлю, так как ответ на этот вопрос получил немало.

14 голосов
/ 24 марта 2009

Я на самом деле работаю над платформой расширяемости для использования поверх ASP.NET MVC. Моя структура расширяемости основана на известном контейнере Ioc: Structuremap.

Вариант использования, который я пытаюсь выполнить, прост: создайте приложение, которое должно иметь некоторые базовые функции, которые могут быть расширены для каждого клиента (= мультитенантность). Должен быть только один экземпляр размещенного приложения, но этот экземпляр можно адаптировать для каждого клиента без внесения каких-либо изменений в основной веб-сайт.

Меня вдохновила статья о многопользовательском стиле, написанная Ayende Rahien: http://ayende.com/Blog/archive/2008/08/16/Multi-Tenancy--Approaches-and-Applicability.aspx Еще одним источником вдохновения стала книга Эрика Эванса о доменно-управляемом дизайне. Моя структура расширяемости основана на шаблоне хранилища и концепции корневых агрегатов. Чтобы иметь возможность использовать структуру, хост-приложение должно быть построено на основе репозиториев и доменных объектов. Контроллеры, репозитории или доменные объекты связываются во время выполнения с помощью ExtensionFactory.

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

Чтобы расширить приложение, скопируйте сборку плагина в папку расширения приложения. Когда пользователь запрашивает страницу в корневой папке клиента, например: http://multitenant -site.com / [CustomerID] / [контроллер] / [действие] фреймворк проверяет, есть ли плагин для этого конкретного клиента, и создает экземпляры пользовательских классов плагинов, в противном случае он загружает значение по умолчанию один раз. Пользовательские классы могут быть Контроллеры - Хранилища или Доменные объекты. Такой подход позволяет расширить приложение на всех уровнях, от базы данных до пользовательского интерфейса, через модель домена, репозитории.

Когда вы хотите расширить некоторые существующие функции, вы создаете подключаемый модуль сборки, который содержит подклассы основного приложения. Когда вы создаете совершенно новые функции, вы добавляете новые контроллеры внутри плагина. Эти контроллеры будут загружены платформой MVC при запросе соответствующего URL. Если вы хотите расширить пользовательский интерфейс, вы можете создать новое представление внутри папки расширений и ссылаться на представление новым или подклассовым контроллером. Чтобы изменить существующее поведение, вы можете создавать новые репозитории или объекты домена или подклассы выходящих из них. Обязанность платформы - определить, какой контроллер / хранилище / объект домена должен быть загружен для конкретного клиента.
Советую взглянуть на структурную карту (http://structuremap.sourceforge.net/Default.htm) и особенно на возможности DSL реестра http://structuremap.sourceforge.net/RegistryDSL.htm.

Это код, который я использую при запуске приложения для регистрации всех подключаемых контроллеров / репозиториев или объектов домена:

protected void ScanControllersAndRepositoriesFromPath(string path)
        {
            this.Scan(o =>
            {
                o.AssembliesFromPath(path);
                o.AddAllTypesOf<SaasController>().NameBy(type => type.Name.Replace("Controller", ""));
                o.AddAllTypesOf<IRepository>().NameBy(type => type.Name.Replace("Repository", ""));
                o.AddAllTypesOf<IDomainFactory>().NameBy(type => type.Name.Replace("DomainFactory", ""));
            });
        }

Я также использую ExtensionFactory, унаследованный от System.Web.MVC. DefaultControllerFactory. Эта фабрика отвечает за загрузку объектов расширения (контроллеров / реестров или объектов домена). Вы можете подключить свои собственные фабрики, зарегистрировав их при запуске в файле Global.asax:

protected void Application_Start()
        {
            ControllerBuilder.Current.SetControllerFactory(
                new ExtensionControllerFactory()
                );
        }

Этот фреймворк как полнофункциональный образец сайта можно найти по адресу: http://code.google.com/p/multimvc/

4 голосов
/ 09 декабря 2008

Итак, я немного поигрался с примером из J Wynia выше. Большое спасибо за это.

Я изменил вещи так, чтобы расширение VirtualPathProvider использовало статический конструктор для создания списка всех доступных ресурсов, заканчивающихся на .aspx в различных DLL в системе. Это трудоемко, но только мы делаем это только один раз.

Вероятно, это полное злоупотребление тем, как виртуальные файлы должны использоваться; -)

вы получите:

частный статический IDictionary resourceVirtualFile;

со строкой, являющейся виртуальными путями.

код ниже делает некоторые предположения относительно пространства имен файлов .aspx, но он будет работать в простых случаях. Это хорошо, что вам не нужно создавать сложные пути просмотра, они создаются из имени ресурса.

class ResourceVirtualFile : VirtualFile
{
    string path;
    string assemblyName;
    string resourceName;

    public ResourceVirtualFile(
        string virtualPath,
        string AssemblyName,
        string ResourceName)
        : base(virtualPath)
    {
        path = VirtualPathUtility.ToAppRelative(virtualPath);
        assemblyName = AssemblyName;
        resourceName = ResourceName;
    }

    public override Stream Open()
    {
        assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName + ".dll");

        Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyName);
        if (assembly != null)
        {
            Stream resourceStream = assembly.GetManifestResourceStream(resourceName);
            if (resourceStream == null)
                throw new ArgumentException("Cannot find resource: " + resourceName);
            return resourceStream;
        }
        throw new ArgumentException("Cannot find assembly: " + assemblyName);
    }

    //todo: Neaten this up
    private static string CreateVirtualPath(string AssemblyName, string ResourceName)
    {
        string path = ResourceName.Substring(AssemblyName.Length);
        path = path.Replace(".aspx", "").Replace(".", "/");
        return string.Format("~{0}.aspx", path);
    }

    public static IDictionary<string, VirtualFile> FindAllResources()
    {
        Dictionary<string, VirtualFile> files = new Dictionary<string, VirtualFile>();

        //list all of the bin files
        string[] assemblyFilePaths = Directory.GetFiles(HttpRuntime.BinDirectory, "*.dll");
        foreach (string assemblyFilePath in assemblyFilePaths)
        {
            string assemblyName = Path.GetFileNameWithoutExtension(assemblyFilePath);
            Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFilePath);  

            //go through each one and get all of the resources that end in aspx
            string[] resourceNames = assembly.GetManifestResourceNames();

            foreach (string resourceName in resourceNames)
            {
                if (resourceName.EndsWith(".aspx"))
                {
                    string virtualPath = CreateVirtualPath(assemblyName, resourceName);
                    files.Add(virtualPath, new ResourceVirtualFile(virtualPath, assemblyName, resourceName));
                }
            }
        }

        return files;
    }
}

Затем вы можете сделать что-то подобное в расширенном VirtualPathProvider:

    private bool IsExtended(string virtualPath)
    {
        String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
        return resourceVirtualFile.ContainsKey(checkPath);
    }

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

    public override VirtualFile GetFile(string virtualPath)
    {
        string withTilda = string.Format("~{0}", virtualPath);

        if (resourceVirtualFile.ContainsKey(withTilda))
            return resourceVirtualFile[withTilda];

        return base.GetFile(virtualPath);
    }
3 голосов
/ 15 июня 2010

Этот пост может быть немного запоздалым, но я играю с ASP.NET MVC2 и придумала прототип с использованием функции "Области".

Вот ссылка для всех, кому интересно: http://www.veebsbraindump.com/2010/06/asp-net-mvc2-plugins-using-areas/

3 голосов
/ 04 декабря 2008

Полагаю, можно оставить свои представления в проектах плагинов.

Это моя идея: вам нужен ViewEngine, который будет вызывать плагин (вероятно, через интерфейс) и запрашивать представление (IView). Затем плагин будет создавать экземпляр представления не через его URL (как это делает обычный ViewEngine - /Views/Shared/View.asp), а через имя представления (например, через отражение или контейнер DI / IoC).

Возвращение представления в плагине может быть даже жестко задано (простой пример приведен ниже):

public IView GetView(string viewName)
{
    switch (viewName)
    {
        case "Namespace.View1":
            return new View1();
        case "Namespace.View2":
            return new View2();
        ...
    }
}

... это была просто идея, но я надеюсь, что это сработает или просто послужит хорошим вдохновением.

0 голосов
/ 24 февраля 2010

[публикуется как ответ, потому что я не могу комментировать]

Отличное решение - я использовал подход J Wynia и получил его для визуализации вида из отдельной сборки. Однако этот подход, по-видимому, только отображает представление. Контроллеры в плагине не поддерживаются, верно? Например, если представление из плагина отправило сообщение назад, контроллер этих просмотров в плагине будет , а не , будет называться . Вместо этого он будет направлен на контроллер в корневом приложении MVC . Я правильно понимаю, или есть обходной путь для этой проблемы?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...