2008-12-04 28 views
70

He estado pasando un tiempo mirando el artículo de Phil Haack en Grouping Controllers cosas muy interesantes.Arquitectura de complemento para ASP.NET MVC

Por el momento estoy tratando de averiguar si sería posible usar las mismas ideas para crear una arquitectura modular/plug-in para un proyecto en el que estoy trabajando.

Entonces mi pregunta es: ¿es posible tener las Áreas en el artículo de Phil divididas entre múltiples proyectos?

Veo que los espacios de nombres funcionarán por sí solos, pero me preocupan las vistas que terminan en el lugar correcto. ¿Es algo que se puede resolver con reglas de compilación?

Suponiendo que lo anterior es posible con varios proyectos en una sola solución, ¿alguien tiene alguna idea sobre la mejor manera de hacerlo posible con una solución separada y la codificación de un conjunto predefinido de interfaces? Pasar de un Área a un complemento.

Tengo algunas experiencias con la arquitectura de plug-in, pero no las masas, por lo que cualquier orientación en esta área sería útil.

Respuesta

51

Hice una prueba de concepto hace unas semanas donde puse una pila completa de componentes: una clase de modelo, una clase de controlador y sus vistas asociadas en una DLL, agregué/ajusté one of the examples de las clases VirtualPathProvider que recuperan las vistas por lo que abordarían los que están en la DLL apropiadamente.

Al final, simplemente dejé caer la DLL en una aplicación MVC configurada apropiadamente y funcionó como si hubiera sido parte de la aplicación MVC desde el principio. Lo empujé un poco más y funcionó bien con 5 de estos pequeños complementos mini-MVC. Obviamente, tienes que mirar tus referencias y las dependencias de configuración cuando barajas todo, pero funcionó.

El ejercicio estaba destinado a la funcionalidad de complemento para una plataforma basada en MVC que estoy creando para un cliente. Hay un conjunto básico de controladores y vistas que se complementan con más opciones opcionales en cada instancia del sitio. Vamos a hacer esos bits opcionales en estos complementos DLL modulares. Hasta aquí todo bien.

Escribí una descripción general de mi prototipo y un sample solution for ASP.NET MVC plugins en mi sitio.

EDITAR: 4 años después, he estado haciendo bastantes aplicaciones ASP.NET MVC con complementos y ya no uso el método que describo arriba. En este punto, ejecuto todos mis complementos a través de MEF y no coloco los controladores en complementos. Por el contrario, creo controladores genéricos que usan la información de enrutamiento para seleccionar complementos MEF y entregar el trabajo al plugin, etc. Solo pensé que agregaría ya que esta respuesta recibe bastante.

+1

sus enlaces no funcionan que estaba esperando para ver lo que construir, no tengo problemas similares con mi proyecto en el que necesito para hacer proyectos enchufable para que pueda añadir/eliminar la funcionalidad bajo demanda. lo mismo que la gente hace en wordpress. – Alok

3

Supongo que es posible dejar sus puntos de vista en los proyectos de plug-in.

Esa es mi idea: necesitas un ViewEngine que llame al plugin (probablemente a través de una interfaz) y solicite la vista (IView). El complemento instanciaría la vista no a través de su url (como hace un ViewEngine ordinario - /Views/Shared/View.asp) sino a través de su nombre de la vista) por ejemplo a través de un contenedor de reflexión/DI/IoC.

La devolución de la vista en el plugin podría incluso me hardcoded (ejemplo sencillo):

public IView GetView(string viewName) 
{ 
    switch (viewName) 
    { 
     case "Namespace.View1": 
      return new View1(); 
     case "Namespace.View2": 
      return new View2(); 
     ... 
    } 
} 

... esto era sólo una idea, pero espero que podría trabajar o simplemente ser una buena fuente de inspiración.

4

Así que tuve un pequeño juego con el ejemplo de J Wynia anterior. Muchas gracias por eso, por cierto.

Cambié las cosas para que la extensión de VirtualPathProvider utilizara un constructor estático para crear una lista de todos los recursos disponibles que terminan en .aspx en las distintas dll del sistema. Es laborioso, pero solo lo hacemos una vez.

Es probablemente un abuso total de la forma en que se supone VirtualFiles para ser utilizado, así ;-)

se termina con una:

privada estática IDictionary resourceVirtualFile;

con la cadena como rutas de acceso virtuales.

el código siguiente hace algunas suposiciones sobre el espacio de nombres de los archivos .aspx pero funcionará en casos simples. Lo bueno es que no tiene que crear rutas de vista complicadas que se crean a partir del nombre del recurso.

class ResourceVirtualFile : VirtualFile 
{ 
    string path; 
    string assemblyName; 
    string resourceName; 

    public ResourceVirtualFile(
     string virtualPath, 
     string AssemblyName, 
     string ResourceName) 
     : base(virtualPath) 
    { 
     path = VirtualPathUtility.ToAppRelative(virtualPath); 
     assemblyName = AssemblyName; 
     resourceName = ResourceName; 
    } 

    public override Stream Open() 
    { 
     assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName + ".dll"); 

     Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyName); 
     if (assembly != null) 
     { 
      Stream resourceStream = assembly.GetManifestResourceStream(resourceName); 
      if (resourceStream == null) 
       throw new ArgumentException("Cannot find resource: " + resourceName); 
      return resourceStream; 
     } 
     throw new ArgumentException("Cannot find assembly: " + assemblyName); 
    } 

    //todo: Neaten this up 
    private static string CreateVirtualPath(string AssemblyName, string ResourceName) 
    { 
     string path = ResourceName.Substring(AssemblyName.Length); 
     path = path.Replace(".aspx", "").Replace(".", "/"); 
     return string.Format("~{0}.aspx", path); 
    } 

    public static IDictionary<string, VirtualFile> FindAllResources() 
    { 
     Dictionary<string, VirtualFile> files = new Dictionary<string, VirtualFile>(); 

     //list all of the bin files 
     string[] assemblyFilePaths = Directory.GetFiles(HttpRuntime.BinDirectory, "*.dll"); 
     foreach (string assemblyFilePath in assemblyFilePaths) 
     { 
      string assemblyName = Path.GetFileNameWithoutExtension(assemblyFilePath); 
      Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFilePath); 

      //go through each one and get all of the resources that end in aspx 
      string[] resourceNames = assembly.GetManifestResourceNames(); 

      foreach (string resourceName in resourceNames) 
      { 
       if (resourceName.EndsWith(".aspx")) 
       { 
        string virtualPath = CreateVirtualPath(assemblyName, resourceName); 
        files.Add(virtualPath, new ResourceVirtualFile(virtualPath, assemblyName, resourceName)); 
       } 
      } 
     } 

     return files; 
    } 
} 

entonces usted puede hacer algo como esto en el VirtualPathProvider extendida:

private bool IsExtended(string virtualPath) 
    { 
     String checkPath = VirtualPathUtility.ToAppRelative(virtualPath); 
     return resourceVirtualFile.ContainsKey(checkPath); 
    } 

    public override bool FileExists(string virtualPath) 
    { 
     return (IsExtended(virtualPath) || base.FileExists(virtualPath)); 
    } 

    public override VirtualFile GetFile(string virtualPath) 
    { 
     string withTilda = string.Format("~{0}", virtualPath); 

     if (resourceVirtualFile.ContainsKey(withTilda)) 
      return resourceVirtualFile[withTilda]; 

     return base.GetFile(virtualPath); 
    } 
14

realidad estoy trabajando en un marco de extensibilidad para usar en la parte superior de ASP.NET MVC. Mi marco de extensibilidad se basa en el famoso contenedor Ioc: Structuremap.

El caso de uso que intento cumplir es simple: crear una aplicación que debe tener alguna funcionalidad básica que se pueda extender para cada cliente (= multi-tenancy). Solo debe haber una instancia de la aplicación alojada, pero esta instancia se puede adaptar para cada cliente sin realizar ningún cambio en el sitio web central.

Me inspiré en el artículo sobre la tenacidad múltiple escrito por Ayende Rahien: http://ayende.com/Blog/archive/2008/08/16/Multi-Tenancy--Approaches-and-Applicability.aspx Otra fuente de inspiración fue el libro de Eric Evans sobre Domain Driven Design. Mi marco de extensibilidad se basa en el patrón de repositorio y el concepto de agregados de raíz. Para poder utilizar el marco, la aplicación de alojamiento debe construirse alrededor de repositorios y objetos de dominio. Los controladores, repositorios u objetos de dominio están vinculados en tiempo de ejecución por ExtensionFactory.

Un complemento es simplemente un asistente que contiene controladores o repositorios u objetos de dominio que respeta una convención de nomenclatura específica. La convención de nomenclatura es simple, cada clase debe ser prefijada por el ID del cliente, por ejemplo: AdventureworksHomeController.

Para extender una aplicación, copie un ensamblaje de complemento en la carpeta de extensión de la aplicación. Cuando un usuario solicita una página en la carpeta raíz del cliente, por ejemplo: http://multitenant-site.com/[customerID]/[controller]/[action] la verificación de marco si hay un complemento para ese cliente en particular y crear instancias de las clases de complemento personalizadas, de lo contrario, carga el valor predeterminado una vez. Las clases personalizadas pueden ser Controladores: repositorios u objetos de dominio. Este enfoque permite extender una aplicación en todos los niveles, desde la base de datos a la UI, a través del modelo de dominio, repositorios.

Cuando desee extender algunas funciones existentes, cree un complemento que contenga subclases de la aplicación principal. Cuando tiene que crear funcionalidades totalmente nuevas, agrega nuevos controladores dentro del complemento. Estos controladores serán cargados por el framework MVC cuando se solicite la url correspondiente.Si desea extender la interfaz de usuario, puede crear una nueva vista dentro de la carpeta de extensiones y hacer referencia a la vista mediante un controlador nuevo o subclasificado. Para modificar el comportamiento existente, puede crear nuevos repositorios u objetos de dominio o subclasificar los existentes. La responsabilidad del marco es determinar qué controlador/repositorio/objeto de dominio debe cargarse para un cliente específico.
Aconsejo echar un vistazo a structuremap (http://structuremap.sourceforge.net/Default.htm) y especialmente en las características de Registry DSL http://structuremap.sourceforge.net/RegistryDSL.htm.

Este es el código que uso en la puesta en marcha de la solicitud de registro de todos los controladores/repositorios enchufables o objetos de dominio:

protected void ScanControllersAndRepositoriesFromPath(string path) 
     { 
      this.Scan(o => 
      { 
       o.AssembliesFromPath(path); 
       o.AddAllTypesOf<SaasController>().NameBy(type => type.Name.Replace("Controller", "")); 
       o.AddAllTypesOf<IRepository>().NameBy(type => type.Name.Replace("Repository", "")); 
       o.AddAllTypesOf<IDomainFactory>().NameBy(type => type.Name.Replace("DomainFactory", "")); 
      }); 
     } 

También uso un ExtensionFactory heredando de la System.Web.Mvc. DefaultControllerFactory. Esta fábrica es responsable de cargar los objetos de extensión (controladores/registros u objetos de dominio). Puede plugin de sus propias fábricas registrándolos en el arranque en el archivo Global.asax:

protected void Application_Start() 
     { 
      ControllerBuilder.Current.SetControllerFactory(
       new ExtensionControllerFactory() 
       ); 
     } 

este marco como un sitio de ejemplo en pleno funcionamiento se puede encontrar en: http://code.google.com/p/multimvc/

+2

Esto es realmente interesante, me gusta la idea de la funcionalidad de sobrecarga para diferentes inquilinos. El artículo de Ayende fue interesante. –

0

[envíos como respuesta porque no puedo comentar]

Gran solución: utilicé el enfoque de J Wynia y lo obtuve para obtener una vista desde un ensamblaje por separado. Sin embargo, este enfoque parece solo representa la vista. Los controladores dentro del complemento no parecen ser compatibles, ¿correcto? Por ejemplo, si una vista desde un complemento hizo una publicación atrás, el controlador de esas vistas dentro del complemento será no se llamará. En su lugar, se enrutará a un controlador dentro de la aplicación raíz MVC. ¿Lo estoy entendiendo correctamente o hay una solución para este problema?

+0

Puede registrar las rutas localmente al complemento, solo necesita alguna forma de configurarlas. Probablemente puedas usar StructureMap para evitar jugar mucho con la reflexión. –

+0

Ignorar mi comentario/respuesta. Lo estaba haciendo mal, pero lo hice funcionar como estaba previsto. Funciona increíble! Perdón por el ruido! – tbehunin

Cuestiones relacionadas