Estoy tratando de construir una aplicación MVC4 / MVC5 con una arquitectura conectable como Orchard CMS. Entonces tengo una aplicación MVC que será el proyecto de inicio y se encargará de la autenticación, la navegación, etc. Luego, habrá múltiples módulos construidos por separado como bibliotecas de clases asp.net o proyectos mvc reducidos y tendrán controladores, vistas, repositorios de datos, etc.
Pasé todo el día revisando tutoriales en la web y descargando muestras, etc., y descubrí que Kenny tiene el mejor ejemplo: http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and -webapi.html
Puedo importar los controladores de los módulos (DLL separados) si agrego una referencia a esos DLL. Pero la razón detrás del uso de MEF es poder agregar módulos en tiempo de ejecución. Quiero que las DLL junto con las vistas se copien en un directorio ~ / Modules // en el proyecto de inicio (he logrado hacer esto) y MEF simplemente las recogería. Luchando para que MEF cargue estas bibliotecas.
También hay MefContrib como se explica en esta respuesta ASP.NET MVC 4.0 Controladores y MEF, ¿cómo unir estos dos? que es lo siguiente que voy a intentar. Pero me sorprende que MEF no funcione de fábrica con MVC.
¿Alguien tiene una arquitectura similar funcionando (con o sin MefContrib)? Inicialmente, incluso pensé en eliminar Orchard CMS y usarlo como marco, pero es demasiado complejo. También sería bueno desarrollar la aplicación en MVC5 para aprovechar WebAPI2.
Respuestas:
Trabajé en un proyecto que tenía una arquitectura conectable similar a la que describiste y usaba las mismas tecnologías ASP.NET MVC y MEF. Teníamos una aplicación ASP.NET MVC que manejaba la autenticación, autorización y todas las solicitudes. Nuestros complementos (módulos) se copiaron en una subcarpeta del mismo. Los complementos también eran aplicaciones ASP.NET MVC que tenían sus propios modelos, controladores, vistas, archivos css y js. Estos son los pasos que seguimos para que funcione:
Configuración de MEF
Creamos un motor basado en MEF que descubre todas las partes componibles al inicio de la aplicación y crea un catálogo de las partes componibles. Esta es una tarea que se realiza solo una vez al inicio de la aplicación. El motor necesita descubrir todas las partes conectables, que en nuestro caso estaban ubicadas en la
bin
carpeta de la aplicación host o en laModules(Plugins)
carpeta.public class Bootstrapper { private static CompositionContainer CompositionContainer; private static bool IsLoaded = false; public static void Compose(List<string> 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<T>(string contractName = null) { var type = default(T); if (CompositionContainer == null) return type; if (!string.IsNullOrWhiteSpace(contractName)) type = CompositionContainer.GetExportedValue<T>(contractName); else type = CompositionContainer.GetExportedValue<T>(); return type; } }
Este es el código de muestra de la clase que realiza el descubrimiento de todas las partes de MEF. El
Compose
método de la clase se llama desde elApplication_Start
método delGlobal.asax.cs
archivo. El código se reduce en aras de la simplicidad.public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { var pluginFolders = new List<string>(); 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)); } }
Se asume que todos los complementos se copian en una subcarpeta separada de la
Modules
carpeta que se encuentra en la raíz de la aplicación host. Cada subcarpeta de complemento contieneViews
una subcarpeta y la DLL de cada complemento. En elApplication_Start
método anterior también se inicializan la fábrica de controladores personalizados y el motor de vista personalizada que definiré a continuación.Creando una fábrica de controladores que lee de MEF
Aquí está el código para definir la fábrica de controladores personalizados que descubrirá el controlador que necesita manejar la solicitud:
public class CustomControllerFactory : IControllerFactory { private readonly DefaultControllerFactory _defaultControllerFactory; public CustomControllerFactory() { _defaultControllerFactory = new DefaultControllerFactory(); } public IController CreateController(RequestContext requestContext, string controllerName) { var controller = Bootstrapper.GetInstance<IController>(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(); } } }
Además, cada controlador debe estar marcado con el
Export
atributo:[Export("Plugin1", typeof(IController))] [PartCreationPolicy(CreationPolicy.NonShared)] public class Plugin1Controller : Controller { // // GET: /Plugin1/ public ActionResult Index() { return View(); } }
El primer parámetro del
Export
constructor de atributos debe ser único porque especifica el nombre del contrato e identifica de forma única a cada controlador. ElPartCreationPolicy
debe establecerse en NonShared porque los controladores no pueden ser reutilizados para múltiples peticiones.Creando View Engine que sabe encontrar las vistas de los complementos
Es necesaria la creación de un motor de vista personalizado porque el motor de vista por convención busca vistas solo en la
Views
carpeta de la aplicación host. Dado que los complementos están ubicados en unaModules
carpeta separada , debemos decirle al motor de visualización que busque allí también.public class CustomViewEngine : RazorViewEngine { private List<string> _plugins = new List<string>(); public CustomViewEngine(List<string> pluginFolders) { _plugins = pluginFolders; ViewLocationFormats = GetViewLocations(); MasterLocationFormats = GetMasterLocations(); PartialViewLocationFormats = GetViewLocations(); } public string[] GetViewLocations() { var views = new List<string>(); 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<string>(); masterPages.Add("~/Views/Shared/{0}.cshtml"); _plugins.ForEach(plugin => masterPages.Add("~/Modules/" + plugin + "/Views/Shared/{0}.cshtml") ); return masterPages.ToArray(); } }
Resuelva el problema con vistas fuertemente tipadas en los complementos
Al usar solo el código anterior, no pudimos usar vistas fuertemente tipadas en nuestros complementos (módulos), porque los modelos existían fuera de la
bin
carpeta. Para solucionar este problema siga el siguiente enlace .fuente
Solo tenga en cuenta que el contenedor de MEF tiene una "característica agradable" que mantiene referencias a cualquier objeto IDisposable que crea y dará lugar a una gran pérdida de memoria. Supuestamente, la pérdida de memoria se puede solucionar con este nuget: http://nuget.org/packages/NCode.Composition.DisposableParts.Signed
fuente
Hay proyectos que implementan una arquitectura de complementos. Es posible que desee utilizar uno de estos o echar un vistazo a su código fuente para ver cómo logran estas cosas:
Además, 404 sobre controladores en ensamblajes externos está adoptando un enfoque interesante. Aprendí mucho con solo leer la pregunta.
fuente