Vistas en ensamblajes separados en ASP.NET MVC

Intento crear una aplicación web en la que desee poder instalar ensamblajes por separado. Estoy usando MVC preview 4 combinado con Unity para la dependency injections, que utilizo para crear los controladores de mis ensamblajes de complementos. Estoy usando WebForms (aspx por defecto) como mi motor de visualización.

Si quiero usar una vista, estoy atascado en los que están definidos en el proyecto central, debido a la comstackción dinámica de la parte ASPX. Estoy buscando una forma adecuada de adjuntar archivos ASPX en un ensamblaje diferente, sin tener que pasar por todo el paso de implementación. ¿Me estoy perdiendo algo obvio? ¿O debería recurrir a la creación de mis puntos de vista mediante progtwigción?


Actualización: cambié la respuesta aceptada. Aunque la respuesta de Dale es muy completa, busqué la solución con un proveedor de ruta virtual diferente. Funciona como un amuleto, y toma solo unas 20 líneas en código, creo.

Básicamente, este es el mismo problema que las personas tenían con WebForms y trataban de comstackr sus archivos UserControl ASCX en una DLL. Encontré este http://www.codeproject.com/KB/aspnet/ASP2UserControlLibrary.aspx que podría funcionar para usted también.

Me tomó demasiado tiempo lograr que esto funcionara correctamente desde varias muestras parciales, así que aquí está el código completo necesario para obtener vistas desde una carpeta Vistas en una biblioteca compartida estructurada de la misma manera que una carpeta Vistas normal pero con todo configurado para comstackr como incrustado recursos. Solo usará el archivo incrustado si el archivo habitual no existe.

La primera línea de Application_Start:

HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedViewPathProvider()); 

VirtualPathProvider

  public class EmbeddedVirtualFile : VirtualFile { public EmbeddedVirtualFile(string virtualPath) : base(virtualPath) { } internal static string GetResourceName(string virtualPath) { if (!virtualPath.Contains("/Views/")) { return null; } var resourcename = virtualPath .Substring(virtualPath.IndexOf("Views/")) .Replace("Views/", "OrangeGuava.Common.Views.") .Replace("/", "."); return resourcename; } public override Stream Open() { Assembly assembly = Assembly.GetExecutingAssembly(); var resourcename = GetResourceName(this.VirtualPath); return assembly.GetManifestResourceStream(resourcename); } } public class EmbeddedViewPathProvider : VirtualPathProvider { private bool ResourceFileExists(string virtualPath) { Assembly assembly = Assembly.GetExecutingAssembly(); var resourcename = EmbeddedVirtualFile.GetResourceName(virtualPath); var result = resourcename != null && assembly.GetManifestResourceNames().Contains(resourcename); return result; } public override bool FileExists(string virtualPath) { return base.FileExists(virtualPath) || ResourceFileExists(virtualPath); } public override VirtualFile GetFile(string virtualPath) { if (!base.FileExists(virtualPath)) { return new EmbeddedVirtualFile(virtualPath); } else { return base.GetFile(virtualPath); } } } 

El último paso para que funcione es que la raíz Web.Config debe contener las configuraciones correctas para analizar las vistas MVC fuertemente tipadas, ya que no se usará la de la carpeta de vistas:

      

Se requieren algunos pasos adicionales para que funcione con Mono. En primer lugar, debe implementar GetDirectory, ya que todos los archivos en la carpeta de vistas se cargan cuando se inicia la aplicación en lugar de según sea necesario:

 public override VirtualDirectory GetDirectory(string virtualDir) { Log.LogInfo("GetDirectory - " + virtualDir); var b = base.GetDirectory(virtualDir); return new EmbeddedVirtualDirectory(virtualDir, b); } public class EmbeddedVirtualDirectory : VirtualDirectory { private VirtualDirectory FileDir { get; set; } public EmbeddedVirtualDirectory(string virtualPath, VirtualDirectory filedir) : base(virtualPath) { FileDir = filedir; } public override System.Collections.IEnumerable Children { get { return FileDir.Children; } } public override System.Collections.IEnumerable Directories { get { return FileDir.Directories; } } public override System.Collections.IEnumerable Files { get { if (!VirtualPath.Contains("/Views/") || VirtualPath.EndsWith("/Views/")) { return FileDir.Files; } var fl = new List(); foreach (VirtualFile f in FileDir.Files) { fl.Add(f); } var resourcename = VirtualPath.Substring(VirtualPath.IndexOf("Views/")) .Replace("Views/", "OrangeGuava.Common.Views.") .Replace("/", "."); Assembly assembly = Assembly.GetExecutingAssembly(); var rfl = assembly.GetManifestResourceNames() .Where(s => s.StartsWith(resourcename)) .Select(s => VirtualPath + s.Replace(resourcename, "")) .Select(s => new EmbeddedVirtualFile(s)); fl.AddRange(rfl); return fl; } } } 

Finalmente, las vistas fuertemente tipadas casi funcionarán perfectamente. El modelo se tratará como un objeto sin tipo, por lo que para obtener una escritura fuerte, debe comenzar sus vistas compartidas con algo como

 <% var Model2 = Model as IEnumerable; %> 
 protected void Application_Start() { WebFormViewEngine engine = new WebFormViewEngine(); engine.ViewLocationFormats = new[] { "~/bin/Views/{1}/{0}.aspx", "~/Views/Shared/{0}.aspx" }; engine.PartialViewLocationFormats = engine.ViewLocationFormats; ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(engine); RegisterRoutes(RouteTable.Routes); } 

Establezca la propiedad ‘Copiar en la salida’ de su vista en ‘Copiar siempre’

Una adición a todos ustedes que todavía están buscando el santo grial: me he acercado un poco más a encontrarlo, si no están demasiado apegados al motor de visualización de formularios web.

Recientemente probé Spark viewengine. Además de ser totalmente increíble y no volvería a los formularios web, incluso si me amenazaban, también proporciona algunos ganchos muy buenos para la modularidad de una aplicación. El ejemplo en sus documentos está usando Windsor como un contenedor IoC, pero no puedo imaginar que sea mucho más difícil si quieres tomar otro enfoque.