Подключаемая архитектура для ASP.NET MVC
Я потратил некоторое время, глядя на статью Фила Хаака о Контроллеры Группировка очень интересные вещи.
на данный момент я пытаюсь выяснить, можно ли использовать те же идеи для создания плагина/модульной архитектуры для проекта, над которым я работаю.
Итак, мой вопрос: возможно ли разделить области в статье Фила на несколько проектов?
Я вижу, что пространства имен будут работать сами но я беспокоюсь о том, что взгляды окажутся в нужном месте. Это то, что можно разобраться с правилами построения?
предполагая, что вышеизложенное возможно с несколькими проектами в одном решении, есть ли у кого-нибудь идеи о лучшем способе сделать это возможным с помощью отдельного решения и кодирования для предопределенного набора интерфейсов? Перемещение из области в плагин.
У меня есть некоторый опыт работы с архитектурой плагинов, но не с массами, поэтому никаких указаний эта область была бы полезна.
6 ответов:
Я сделал доказательство концепции несколько недель назад, где я поместил полный стек компонентов: класс модели, класс контроллера и связанные с ними представления в DLL, добавил / настроил один из примеров классов VirtualPathProvider, которые извлекают представления, чтобы они соответствующим образом обращались к ним в DLL.
в конце концов, я просто бросил DLL в соответствующим образом настроенное приложение MVC, и он работал так же, как если бы он был частью приложения MVC с самого начала. Я толкнул его немного дальше, и он работал с 5 из этих маленьких мини-MVC плагинов просто отлично. Очевидно, что вы должны следить за своими ссылками и зависимостями конфигурации при перетасовке всего этого, но это сработало.
упражнение было направлено на функциональность плагина для платформы на основе MVC, которую я создаю для клиента. Существует основной набор контроллеров и представлений, которые дополняются более необязательными в каждом экземпляре сайта. Мы собираемся сделать эти дополнительные биты в эти модульные DLL Плагины. Пока все хорошо.
Я написал обзор моего прототипа и решение образца для ASP.NET MVC Плагины на моем сайте.
EDIT: 4 года, я делал довольно много ASP.NET MVC-приложения с плагинами и больше не используют метод, описанный выше. На этом этапе я запускаю все свои плагины через MEF и вообще не помещаю контроллеры в плагины. Скорее, я делаю универсальные контроллеры, которые используют информацию о маршрутизации для выбора плагинов MEF и передайте работу плагину и т. д. Просто подумал, что я добавлю, так как этот ответ получает справедливый бит.
Я на самом деле работаю над платформой расширяемости для использования поверх ASP.NET MVC. Моя структура расширяемости основана на известном контейнере Ioc: Structuremap .
вариант использования, который я пытаюсь выполнить, прост: создайте приложение, которое должно иметь некоторую базовую функциональность, которая может быть расширена для каждого клиента (=multi-tenancy). Там должен быть только один экземпляр приложения, но этот экземпляр может быть адаптирована для каждого клиента без внесения каких-либо изменений к основному сайту.
меня вдохновила статья о мульти-упорстве, написанная Айенде Рахиеном: http://ayende.com/Blog/archive/2008/08/16/Multi-Tenancy--Approaches-and-Applicability.aspx Еще одним источником вдохновения стала книга Эрика Эванса о доменном дизайне. Моя структура расширяемости основана на шаблоне репозитория и концепции корневых агрегатов. Чтобы иметь возможность использовать фреймворк, хостинг-приложение должно быть построено вокруг репозиториев и объект домена. Контроллеры, репозитории или объекты домена связываются во время выполнения с помощью ExtensionFactory.
плагин-это просто asselmbly, который содержит контроллеры или репозитории или объекты домена, которые соблюдают определенное соглашение об именах. Соглашение об именовании просто, каждый класс должен иметь префикс customerID например: AdventureworksHomeController.
для расширения приложения вы копируете сборку плагина в папку расширения приложения. приложение. Когда пользователь запрашивает страницу в корневой папке клиента, например: http://multitenant-site.com/[customerID] / [controller] / [action] платформа проверяет, есть ли плагин для этого конкретного клиента и создает экземпляр пользовательских классов плагинов, иначе он загружает значение по умолчанию один раз. Пользовательские классы могут быть контроллерами-репозиториями или объектами домена. Этот подход позволяет расширить приложение на всех уровнях, от базы данных до пользовательского интерфейса, через модель домена, хранилища.
когда вы хотите расширить некоторые существующие функции, вы создаете плагин сборки, которая содержит подклассы основного приложения. Когда вам нужно создать совершенно новые функциональные возможности, вы добавляете новые контроллеры внутри плагина. Эти контроллеры будут загружены платформой MVC при запросе соответствующего url-адреса. Если вы хотите продлить пользовательского интерфейса, вы можете создать новый вид внутри папки расширение и ссылку на новый или контроллер .Для изменение существующего поведения вы можете создавать новые репозитории или объекты домена или подклассы, выходящие из них. Ответственность за фреймворк заключается в определении того, какой объект контроллера/ репозитория / домена должен быть загружен для конкретного клиента.
Я советую взглянуть на structuremap (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, наследующий от системы.Сеть.MVC. DefaultControllerFactory. Эта фабрика отвечает за загрузку объектов расширения (контроллеров/реестров или объектов домена). Вы можете подключить свои собственные фабрики, зарегистрировав их при запуске в глобальном масштабе.файл asax:
protected void Application_Start() { ControllerBuilder.Current.SetControllerFactory( new ExtensionControllerFactory() ); }
эта структура как полностью рабочее место образца можно найти дальше:http://code.google.com/p/multimvc/
Так что я немного поиграл с примером из J Wynia выше. Большое спасибо за это кстати.
Я изменил вещи так ,чтобы расширение VirtualPathProvider использовало статический конструктор для создания списка всех доступных ресурсов, заканчивающихся.aspx в различных dll-х в системе. Это трудоемко, но только мы делаем это только один раз.
вероятно, это полное злоупотребление тем, как предполагается использовать VirtualFiles ; -)
вы в конечном итоге с:
private static 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); }
Я думаю, что можно оставить свои взгляды в плагинах проектов.
Это моя идея: вам нужен 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(); ... } }
...это была просто идея, но я надеюсь, что это может работать или просто быть хорошим вдохновением.
этот пост может быть немного поздно, но я играл с ASP.NET MVC2 и придумали прототип, используя функцию "области".
вот ссылка для всех, кто заинтересован:http://www.veebsbraindump.com/2010/06/asp-net-mvc2-plugins-using-areas/
[публикация в качестве ответа, потому что я не могу комментировать]
отличное решение-я использовал подход J Wynia и получил его для визуализации вида из отдельной сборки. Однако, этот подход представляется только визуализация представления. Контроллеры в плагине не поддерживаются, правильно? Например, если представление из плагина сделало сообщение назад, этот контроллер представлений внутри плагина будет не можно назвать. Вместо этого он будет направлен на контроллер в корневом приложении MVC. Я правильно понимаю это или есть обходной путь для этой проблемы?