MEF com MVC 4 ou 5 – Arquitetura conectável (2014)

Eu estou tentando construir um aplicativo MVC4 / MVC5 com uma arquitetura conectável como Orchard CMS. Então eu tenho um aplicativo MVC que será o projeto de boot e cuido de auth, navegação etc. Então haverá vários módulos construídos separadamente como bibliotecas de class asp.net ou projetos mvc despojados e ter controladores, views, repos de dados etc.

Passei o dia todo passando por tutoriais na Web e baixando amostras, etc. e descobri que Kenny tem o melhor exemplo – http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and -webapi.html

Eu sou capaz de importar os controladores dos módulos (DLLs separados) se eu adicionar referência a essas DLLs. Mas a razão por trás do uso do MEF é poder adicionar módulos em tempo de execução. Eu quero que as DLLs junto com as views sejam copiadas para um diretório ~ / Modules // no projeto de boot (eu consegui fazer isso) e o MEF iria simplesmente pegá-las. Lutando para fazer o MEF carregar essas bibliotecas.

Há também MefContrib como explicado nesta resposta ASP.NET MVC 4.0 Controllers e MEF, como trazer esses dois juntos? qual é a próxima coisa que estou prestes a tentar. Mas estou surpreso que o MEF não funcione com o MVC.

Alguém tem uma arquitetura semelhante trabalhando (com ou sem MefContrib)? Inicialmente, pensei em descascar o Orchard CMS e usá-lo como uma estrutura, mas é muito complexo. Também seria bom desenvolver o aplicativo no MVC5 para aproveitar o WebAPI2.

Eu trabalhei em um projeto que possuía arquitetura conectável similar a que você descreveu e que usou as mesmas tecnologias ASP.NET MVC e MEF . Nós tivemos um aplicativo host ASP.NET MVC que lidou com a autenticação, autorização e todas as solicitações. Nossos plugins (módulos) foram copiados para uma sub-pasta dele. Os plugins também eram ASP.NET MVC que possuíam seus próprios modelos, controllers, views, css e js. Estes são os passos que seguimos para que funcione:

Configurando o MEF

Criamos um mecanismo baseado no MEF que descobre todas as partes componíveis no início do aplicativo e cria um catálogo das partes compostas. Essa é uma tarefa que é executada apenas uma vez no início do aplicativo. O mecanismo precisa descobrir todas as partes plugáveis, que no nosso caso estavam localizadas na pasta bin do aplicativo host ou na pasta Modules(Plugins) .

 public class Bootstrapper { private static CompositionContainer CompositionContainer; private static bool IsLoaded = false; public static void Compose(List pluginFolders) { if (IsLoaded) return; var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"))); foreach (var plugin in pluginFolders) { var directoryCatalog = new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules", plugin)); catalog.Catalogs.Add(directoryCatalog); } CompositionContainer = new CompositionContainer(catalog); CompositionContainer.ComposeParts(); IsLoaded = true; } public static T GetInstance(string contractName = null) { var type = default(T); if (CompositionContainer == null) return type; if (!string.IsNullOrWhiteSpace(contractName)) type = CompositionContainer.GetExportedValue(contractName); else type = CompositionContainer.GetExportedValue(); return type; } } 

Este é o código de amostra da class que realiza a descoberta de todas as partes do MEF. O método Compose da class é chamado a partir do método Application_Start no arquivo Global.asax.cs . O código é reduzido por uma questão de simplicidade.

 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { var pluginFolders = new List(); var plugins = Directory.GetDirectories(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules")).ToList(); plugins.ForEach(s => { var di = new DirectoryInfo(s); pluginFolders.Add(di.Name); }); AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); Bootstrapper.Compose(pluginFolders); ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory()); ViewEngines.Engines.Add(new CustomViewEngine(pluginFolders)); } } 

Supõe-se que todos os plug-ins sejam copiados em uma subpasta separada da pasta Modules localizada na raiz do aplicativo host. Cada subpasta de plug-in contém a subpasta Views e a dll de cada plug-in. No método Application_Start acima, também são inicializados o factory do controlador customizado e o mecanismo de visualização customizada que definirei abaixo.

Criando fábrica do controlador que lê do MEF

Aqui está o código para definir a fábrica do controlador personalizado, que descobrirá o controlador que precisa lidar com a solicitação:

 public class CustomControllerFactory : IControllerFactory { private readonly DefaultControllerFactory _defaultControllerFactory; public CustomControllerFactory() { _defaultControllerFactory = new DefaultControllerFactory(); } public IController CreateController(RequestContext requestContext, string controllerName) { var controller = Bootstrapper.GetInstance(controllerName); if (controller == null) throw new Exception("Controller not found!"); return controller; } public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) { return SessionStateBehavior.Default; } public void ReleaseController(IController controller) { var disposableController = controller as IDisposable; if (disposableController != null) { disposableController.Dispose(); } } } 

Além disso, cada controlador deve ser marcado com o atributo Export :

 [Export("Plugin1", typeof(IController))] [PartCreationPolicy(CreationPolicy.NonShared)] public class Plugin1Controller : Controller { // // GET: /Plugin1/ public ActionResult Index() { return View(); } } 

O primeiro parâmetro do construtor do atributo Export deve ser exclusivo porque especifica o nome do contrato e identifica exclusivamente cada controlador. O PartCreationPolicy deve ser definido como NonShared porque os controladores não podem ser reutilizados para várias solicitações.

Criando o View Engine que sabe encontrar os pontos de vista dos plugins

A criação do mecanismo de visualização customizada é necessária porque o mecanismo de visualização por convenção procura por visualizações apenas na pasta Views do aplicativo host. Como os plugins estão localizados na pasta separada de Modules , precisamos informar ao mecanismo de visualização para procurar lá também.

 public class CustomViewEngine : RazorViewEngine { private List _plugins = new List(); public CustomViewEngine(List pluginFolders) { _plugins = pluginFolders; ViewLocationFormats = GetViewLocations(); MasterLocationFormats = GetMasterLocations(); PartialViewLocationFormats = GetViewLocations(); } public string[] GetViewLocations() { var views = new List(); views.Add("~/Views/{1}/{0}.cshtml"); _plugins.ForEach(plugin => views.Add("~/Modules/" + plugin + "/Views/{1}/{0}.cshtml") ); return views.ToArray(); } public string[] GetMasterLocations() { var masterPages = new List(); masterPages.Add("~/Views/Shared/{0}.cshtml"); _plugins.ForEach(plugin => masterPages.Add("~/Modules/" + plugin + "/Views/Shared/{0}.cshtml") ); return masterPages.ToArray(); } } 

Resolva o problema com visualizações fortemente tipadas nos plugins

Usando apenas o código acima, não poderíamos usar views fortemente tipadas em nossos plugins (módulos), porque os modelos existiam fora da pasta bin . Para resolver este problema, siga o seguinte link .

Esteja ciente de que o contêiner do MEF tem um “bom recurso” que mantém referências a qualquer object IDisposable que ele cria, e levará a um enorme memory leaks. Alegadamente o memory leaks pode ser resolvido com este nuget – http://nuget.org/packages/NCode.Composition.DisposableParts.Signed

Existem projetos que implementam uma arquitetura de plugins. Você pode querer usar um desses ou dar uma olhada no código-fonte deles para ver como eles realizam essas coisas:

  • Framework de Plugins ASP.NET MVC (usando MVC 4)
  • .NET 4.0 ASP.NET MVC 3 arquitetura de plug-in com views incorporadas (obviamente usando MVC 3, mas princípios fundamentais ainda podem ser aplicados)

Além disso, 404 em Controllers in External Assemblies está adotando uma abordagem interessante. Eu aprendi muito apenas lendo a pergunta.