MEF con MVC 4 o 5 - Arquitectura conectable (2014)


80

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.


1
¿Tiene esta configuración para trabajar con MVC5? Estoy tratando de configurar lo mismo con MVC 5. Se agradece su ayuda
Junior

1
Aquí hay un ejemplo completo que tiene versiones que implementan tanto EF como el estrecho ASP.net Parece completo. codeproject.com/Articles/1109475/…
BrownPony

¿Por qué no utilizan MEF más aplicaciones? Todo el mundo parece hacer lo suyo en este.
johnny

Respuestas:


105

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 bincarpeta 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 Composemétodo de la clase se llama desde el Application_Startmétodo del Global.asax.csarchivo. 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 Modulescarpeta que se encuentra en la raíz de la aplicación host. Cada subcarpeta de complemento contiene Viewsuna subcarpeta y la DLL de cada complemento. En el Application_Startmé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 Exportatributo:

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

El primer parámetro del Exportconstructor de atributos debe ser único porque especifica el nombre del contrato e identifica de forma única a cada controlador. El PartCreationPolicydebe 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 Viewscarpeta de la aplicación host. Dado que los complementos están ubicados en una Modulescarpeta 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 bincarpeta. Para solucionar este problema siga el siguiente enlace .


1
¿Qué tal una ruta personalizada para cada módulo individual? Creo que cada módulo necesita obtener una referencia de enrutamiento y asax global debe tener una interfaz de ruta en la que la interfaz de ruta se verá tanto en la carpeta del módulo como en el núcleo.
Sharif y

3
Resolvimos eso definiendo un área separada para cada complemento. En cada complemento, creamos una clase que hereda de AreaRegistration y, al anular el método RegisterArea, pudimos definir las rutas que queríamos usar en los complementos.
Ilija Dimov

10
¿Tiene algún proyecto de muestra para esta solución?
cpoDesign

2
Estoy de acuerdo con cpoDesign. un proyecto de muestra sería bueno
chris vietor

2
También estoy de acuerdo en que el proyecto de muestra en GitHub sería genial para descargar :)
Kbdavis07


3

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.