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 la Modules(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 el Application_Start
método del Global.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 contiene Views
una subcarpeta y la DLL de cada complemento. En el Application_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
{
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. El PartCreationPolicy
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 una Modules
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 .