обновлено: читайте ниже в этом сообщении для минимального решения
У меня есть кое-какие вопросы о MVC4 с плагинами. Я немного погубил и нашел некоторые хорошие вещи, но это точно не соответствует моим требованиям, поэтому я прошу здесь дать некоторые советы.
Похоже, что лучшим решением для виджет-подобных плагинов в MVC является портативная область (в пакете MvcContrib). Я нашел здесь основные рекомендации:
http://lostechies.com/erichexter/2009/11/01/asp-net-mvc-portable-areas-via-mvccontrib/
и некоторые полезные советы здесь:
Больше сообщений в этом посте:
Как создать область ASP.NET MVC в качестве DLL плагина?
Что все круто, но, к сожалению, мои требования немного разные:
-
К сожалению, мне нужна система, в которой плагины добавляются и обнаруживаются динамически, и это не относится к портативным областям, на которые должен ссылаться основной проект сайта MVC. Я хотел бы просто загрузить что-то на сайт и открыть его и использовать новые компоненты, поэтому я буду использовать MEF для этого.
-
К счастью, мои плагины не будут похожи на виджеты, которые могут быть очень сложными и неоднородными; скорее, они являются компонентами, которые должны следовать общей общей схеме. Думайте о них как о специализированных редакторах: для каждого типа данных я предлагаю компонент с функциями редактирования: новый, редактировать, удалять. Поэтому я думал о плагинах-контроллерах, которые реализуют общий интерфейс и предоставляют такие действия, как New, Edit, Delete и т.п.
-
Я должен использовать MVC4, и в будущем мне придется добавлять настройки локализации и мобильных устройств.
-
Я должен избегать зависимостей от сложных фреймворков и держать код как можно более простым.
Итак, всякий раз, когда я хочу добавить новый тип данных для редактирования на этом веб-сайте, я бы просто хотел удалить DLL в свою папку плагинов для логического элемента (контроллер и т.д.) и некоторые представления в правильных местах, чтобы получить сайт и использовать новый редактор.
В конце концов я мог бы включить представления в самой DLL (я нашел это: http://razorgenerator.codeplex.com, и этот учебник: http://www.chrisvandesteeg.nl/2010/11/22/embedding-pre-compiled-razor-views-in-your-dll/, который, я полагаю, я мог бы использовать с разборгенератором codeplex, поскольку код, к которому он относится, несовместим с VS2012), но, вероятно, мне будет лучше держать их отделенными (также из-за требований к локализации и мобильности); Я думал о добавлении механизма загрузки в область администрирования сайта, где вы можете загрузить один zip с помощью DLL с контроллерами и папками с представлениями, а затем позволить серверу разархивировать и хранить файлы там, где это необходимо. Это позволило бы мне легко изменять виды без необходимости развертывания всей надстройки.
Итак, я начал искать MEF и MVC, но большинство сообщений относятся к MVC2 и несовместимы. Мне повезло с этим, что в основном сосредоточено на веб-API, но выглядит многообещающим и достаточно простым:
http://kennytordeur.blogspot.it/2012/08/mef-in-aspnet-mvc-4-and-webapi.html
Это существенно добавляет преобразователь зависимостей на основе MEF и контроллер factory к стандартным приложениям MVC. В любом случае образец в сообщении ссылается на односегментное решение, в то время как мне нужно развернуть несколько разных плагинов. Поэтому я немного изменил код, чтобы использовать каталог MEF DirectoryCatalog (а не AssemblyCatalog), указывающий на мою папку с плагинами, а затем создал тестовое MVC-решение с одним плагином в библиотеке классов.
В любом случае, когда я пытаюсь загрузить контроллер плагина, фреймворк вызывает мой factory GetControllerInstance с нулевым типом, так что, конечно, MEF не может перейти к составу. Вероятно, я пропускаю что-то очевидное, но я новичок в MVC 4, и любое предложение или полезная (MVC4-совместимая) ссылка приветствуется. Спасибо!
Вот важный код:
public static class MefConfig { public static void RegisterMef() { CompositionContainer container = ConfigureContainer(); ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container)); System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = new MefDependencyResolver(container); } private static CompositionContainer ConfigureContainer() { //AssemblyCatalog assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); DirectoryCatalog catalog = new DirectoryCatalog( HostingEnvironment.MapPath("~/Plugins")); CompositionContainer container = new CompositionContainer(catalog); return container; } } public class MefDependencyResolver : IDependencyResolver { private readonly CompositionContainer _container; public MefDependencyResolver(CompositionContainer container) { _container = container; } public IDependencyScope BeginScope() { return this; } public object GetService(Type serviceType) { var export = _container.GetExports(serviceType, null, null).SingleOrDefault(); return (export != null ? export.Value : null); } public IEnumerable GetServices(Type serviceType) { var exports = _container.GetExports(serviceType, null, null); List createdObjects = new List(); if (exports.Any()) createdObjects.AddRange(exports.Select(export => export.Value)); return createdObjects; } public void Dispose() { } } public class MefControllerFactory : DefaultControllerFactory { private readonly CompositionContainer _compositionContainer; public MefControllerFactory(CompositionContainer compositionContainer) { _compositionContainer = compositionContainer; } protected override IController GetControllerInstance( System.Web.Routing.RequestContext requestContext, Type controllerType) { if (controllerType == null) throw new ArgumentNullException("controllerType"); var export = _compositionContainer.GetExports(controllerType, null, null).SingleOrDefault(); IController result; if (null != export) result = export.Value as IController; else { result = base.GetControllerInstance(requestContext, controllerType); _compositionContainer.ComposeParts(result); } //eelse return result; } }
Вы можете скачать полное тестовое решение отсюда:
http://www.filedropper.com/mvcplugins
Изменить: первое рабочее минимальное решение
Вот мои выводы, надеюсь, что они могут быть полезны для некоторых других новичков, начиная с этого материала: мне не удалось успешно запустить фреймворк, указанный в приведенном выше ответе, я полагаю, что должно быть что-то, что нужно обновить для VS2012 и MVC4, Во всяком случае, я посмотрел на код и искал немного больше:
1) во-первых, источником путаницы для меня были два разных интерфейса с тем же именем: IDependencyResolver. Если я хорошо понимаю, один (System.Web.Http.Dependencies.IDependencyResolver) используется для webapi, а другой (System.Web.Mvc.IDependencyResolver) для общего DI. Этот пост помог мне здесь: http://lucid-nonsense.co.uk/dependency-injection-web-api-and-mvc-4-rc/.
2), третий компонент - это контроллер, основанный на стандарте DefaultControllerFactory factory, который имеет решающее значение для этого сообщения, потому что это factory, используемый для контроллеров, размещенных в плагинах.
Вот мои реализации для всех этих, слегка измененные из нескольких образцов: сначала HTTP-резольвер:
public sealed class MefHttpDependencyResolver : IDependencyResolver { private readonly CompositionContainer _container; public MefHttpDependencyResolver(CompositionContainer container) { if (container == null) throw new ArgumentNullException("container"); _container = container; } public object GetService(Type serviceType) { if (serviceType == null) throw new ArgumentNullException("serviceType"); string name = AttributedModelServices.GetContractName(serviceType); try { return _container.GetExportedValue(name); } catch { return null; } } public IEnumerable GetServices(Type serviceType) { if (serviceType == null) throw new ArgumentNullException("serviceType"); string name = AttributedModelServices.GetContractName(serviceType); try { return _container.GetExportedValues(name); } catch { return null; } } public IDependencyScope BeginScope() { return this; } public void Dispose() { } }
Тогда MVC-резольвер, который очень схож, даже если он строго не нужен для фиктивной выборки в этом сценарии:
public class MefDependencyResolver : IDependencyResolver { private readonly CompositionContainer _container; public MefDependencyResolver(CompositionContainer container) { if (container == null) throw new ArgumentNullException("container"); _container = container; } public object GetService(Type type) { if (type == null) throw new ArgumentNullException("type"); string name = AttributedModelServices.GetContractName(type); try { return _container.GetExportedValue(name); } catch { return null; } } public IEnumerable GetServices(Type type) { if (type == null) throw new ArgumentNullException("type"); string name = AttributedModelServices.GetContractName(type); try { return _container.GetExportedValues(name); } catch { return null; } } }
И, наконец, контроллер factory:
[Export(typeof(IControllerFactory))] public class MefControllerFactory : DefaultControllerFactory { private readonly CompositionContainer _container; [ImportingConstructor] public MefControllerFactory(CompositionContainer container) { if (container == null) throw new ArgumentNullException("container"); _container = container; } public override IController CreateController(RequestContext requestContext, string controllerName) { var controller = _container .GetExports() .Where(c => c.Metadata.Name.Equals(controllerName, StringComparison.OrdinalIgnoreCase)) .Select(c => c.Value) .FirstOrDefault(); return controller ?? base.CreateController(requestContext, controllerName); } }
Что касается контроллера образца, я создал его в проекте библиотеки классов:
[Export(typeof(IController))] [PartCreationPolicy(CreationPolicy.NonShared)] [ExportMetadata("Name", "Alpha")] public sealed class AlphaController : Controller { public ActionResult Index() { ViewBag.Message = "Hello, this is the PLUGIN controller!"; return View(); } }
В основном проекте (на сайте MVC) у меня есть папка Plugins, в которой я копирую эту DLL, а также "стандартный" набор представлений в своих папках для этих представлений контроллера.
Это самый простой возможный сценарий, и, вероятно, есть намного больше, чтобы узнать и усовершенствовать, но мне нужно было просто начать с самого начала. В любом случае, любое предложение приветствуется.