tag page net mvc asp asp.net-mvc plugins

page - Arquitectura de plug-in para ASP.NET MVC



forms asp net core (6)

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

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

Probablemente sea un abuso total de la forma en que se supone que los archivos virtuales también se usarán ;-)

terminas con un:

private static IDictionary resourceVirtualFile;

con la cadena como caminos virtuales.

el siguiente código 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; } }

A continuación, puede hacer algo como esto en el VirtualPathProvider extendido:

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); }

He pasado un tiempo mirando el artículo de Phil Haack sobre Grouping Controllers cosas muy interesantes.

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

Entonces mi pregunta es: ¿es posible dividir las Áreas en el artículo de Phil en varios proyectos?

Puedo ver 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?

Asumiendo que lo anterior es posible con múltiples 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.


De hecho, estoy trabajando en un marco de extensibilidad para usar sobre ASP.NET MVC. Mi marco de extensibilidad se basa en el famoso contenedor Ioc: Structuremap.

El caso de uso que trato de cumplir es simple: crear una aplicación que debería tener alguna funcionalidad básica que pueda extenderse 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 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 la libro de Eric Evans sobre el Diseño Dirigido por el Dominio. 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 crea una instancia 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 características 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 al inicio de la aplicación para registrar todos los controladores / repositorios de plugins u 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 una ExtensionFactory que hereda de System.Web.MVC. DefaultControllerFactory. Esta fábrica es responsable de cargar los objetos de extensión (controladores / registros u objetos de dominio). Puede agregar sus propias fábricas registrándolas al inicio en el archivo Global.asax:

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

Este marco como un sitio de muestra completamente operativo se puede encontrar en: http://code.google.com/p/multimvc



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é / modifiqué uno de los ejemplos de las clases VirtualPathProvider que recuperan las vistas para abordarían los que están en la DLL apropiadamente.

Al final, simplemente dejé caer la DLL en una aplicación MVC configurada adecuadamente 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 estuvo dirigido a la funcionalidad de complementos 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 una solución de muestra para los plugins ASP.NET MVC 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.


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.

El retorno de la vista en el complemento podría incluso estar codificado (sigue un ejemplo simple):

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

... esto fue solo una idea, pero espero que pueda funcionar o simplemente ser una buena inspiración.


[publicar como respuesta porque no puedo comentar]

Gran solución: utilicé el enfoque de J Wynia y obtuve una vista desde un ensamblaje por separado. Sin embargo, este enfoque parece representar solo 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, no se llamará al controlador de esas vistas dentro del complemento. En cambio, 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?