c# - MEF con MVC 4 o 5-Arquitectura enchufable(2014)
asp.net .net (3)
Hay proyectos que implementan una arquitectura de complemento. Es posible que desee utilizar uno de estos o echar un vistazo a su código fuente para ver cómo logran estas cosas:
- Framework ASP.NET MVC Plugin (usando MVC 4)
- .NET 4.0 ASP.NET MVC 3 plug-in arquitectura con vistas incrustadas (obviamente usando MVC 3 pero los principios fundamentales aún pueden aplicarse)
Además, 404 en Controladores en ensambles externos está tomando un enfoque interesante. Aprendí mucho con solo leer la pregunta.
Estoy intentando construir una aplicación MVC4 / MVC5 con una arquitectura conectable como Orchard CMS. Así que tengo una aplicación MVC que será el proyecto de inicio y me ocuparé de la autenticación, navegación, etc. Luego habrá múltiples módulos construidos por separado como bibliotecas de clase asp.net o proyectos de mvc simplificados y tendrán controladores, vistas, repositorios de datos, etc.
He pasado 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 (archivos DLL separados) si agrego referencia a esos archivos DLL. Pero la razón detrás de usar 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 Controllers y MEF, ¿cómo juntar estos dos? que es lo siguiente que voy a intentar. Pero me sorprende que MEF no trabaje de la caja con MVC.
¿Alguien ha conseguido una arquitectura similar funcionando (con o sin MefContrib)? Inicialmente, incluso pensé en despojar a Orchard CMS y usarlo como marco, pero es demasiado complejo. También sería bueno desarrollar la aplicación en MVC5 para aprovechar WebAPI2.
Solo tenga en cuenta que el contenedor de MEF tiene una "buena característica" que mantiene referencias a cualquier objeto IDisposable que crea, y dará lugar a una gran pérdida de memoria. Supuestamente, la fuga de memoria se puede abordar con este nuget - http://nuget.org/packages/NCode.Composition.DisposableParts.Signed
He trabajado en un proyecto que tenía una arquitectura similar conectable como la que describiste y utilizaba las mismas tecnologías ASP.NET MVC
y MEF
. Tuvimos una aplicación Host ASP.NET MVC que manejó la autenticación, autorización y todas las solicitudes. Nuestros complementos (módulos) se copiaron a una subcarpeta de este. Los complementos también eran ASP.NET MVC
que tenían sus propios modelos, controladores, vistas, archivos css y js. Estos son los pasos que seguimos para que funcione:
Configurando MEF
Creamos un motor basado en MEF
que descubre todas las partes compostables al inicio de la aplicación y crea un catálogo de las partes compostables. Esta es una tarea que se realiza una sola vez al inicio de la aplicación. El motor debe descubrir todas las partes enchufables, que en nuestro caso estaban ubicadas en la carpeta bin
de la aplicación host o en la carpeta Modules(Plugins)
.
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 método Compose
de la clase se llama desde el método Application_Start
en el archivo Global.asax.cs
. El código se reduce por 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 supone que todos los complementos se copian en una subcarpeta separada de la carpeta Modules
que se encuentra en la raíz de la aplicación host. Cada subcarpeta de plugins contiene la subcarpeta Views
y el dll
de cada plugin. En el método Application_Start
anterior, también se inicializan la fábrica de controlador personalizado y el motor de vista personalizado que definiré a continuación.
Creando una fábrica de controladores que lea desde el MEF
Aquí está el código para definir fábrica de controlador personalizado 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 Export
atributo Export
:
[Export("Plugin1", typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Plugin1Controller : Controller
{
//
// GET: /Plugin1/
public ActionResult Index()
{
return View();
}
}
El primer parámetro del constructor del atributo Export
debe ser único porque especifica el nombre del contrato e identifica de forma única a cada controlador. PartCreationPolicy
debe establecerse en NonShared porque los controladores no pueden reutilizarse para múltiples solicitudes.
Crear View Engine que sepa encontrar las vistas desde los complementos
Se necesita la creación de motor de vista personalizada porque el motor de vista por convención busca solo vistas en la carpeta Views
de la aplicación de host. Dado que los complementos se encuentran en una carpeta de Modules
separada, debemos indicar al motor de visualización que también lo haga.
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 carpeta bin
. Para resolver este problema, siga el siguiente link .