Usar VirtualPathProvider personalizado para cargar vistas parciales de recursos incrustados

Escribí implementaciones personalizadas de VirtualFile y VirtualPathProvider que obtienen con éxito recursos incrustados que son vistas parciales.

Sin embargo, cuando bash renderizarlos produce este error:

The view at '~/Succeed.Web/Succeed.Web.Controls.SImporter._SImporter.cshtml' must derive from WebViewPage, or WebViewPage.

Al renderizar la vista parcial, dentro de una vista normal, se ve como sigue:

 Html.RenderPartial("~/Succeed.Web/Succeed.Web.Controls.SImporter._SImporter.cshtml"); 

¿Qué lo está haciendo creer que esta no es una vista parcial?

EDITAR: Publiqué mi código para las implementaciones de los proveedores de archivos virtuales y archivos virtuales para cualquier persona que tropiece con esta solución en busca de que ese componente funcione. Esta pregunta también servirá para aquellos basados ​​en el título de la pregunta.

Existe la implementación de VirtualFile para referencia:

 public class SVirtualFile : VirtualFile { private string m_path; public SVirtualFile(string virtualPath) : base(virtualPath) { m_path = VirtualPathUtility.ToAppRelative(virtualPath); } public override System.IO.Stream Open() { var parts = m_path.Split('/'); var assemblyName = parts[1]; var resourceName = parts[2]; assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName); var assembly = System.Reflection.Assembly.LoadFile(assemblyName + ".dll"); if (assembly != null) { return assembly.GetManifestResourceStream(resourceName); } return null; } } 

VirtualPathProvider:

 public class SVirtualPathProvider : VirtualPathProvider { public SVirtualPathProvider() { } private bool IsEmbeddedResourcePath(string virtualPath) { var checkPath = VirtualPathUtility.ToAppRelative(virtualPath); return checkPath.StartsWith("~/Succeed.Web/", StringComparison.InvariantCultureIgnoreCase); } public override bool FileExists(string virtualPath) { return IsEmbeddedResourcePath(virtualPath) || base.FileExists(virtualPath); } public override VirtualFile GetFile(string virtualPath) { if (IsEmbeddedResourcePath(virtualPath)) { return new SVirtualFile(virtualPath); } else { return base.GetFile(virtualPath); } } public override CacheDependency GetCacheDependency( string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { if (IsEmbeddedResourcePath(virtualPath)) { return null; } else { return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart); } } } 

Y, por supuesto, no olvide registrar este nuevo proveedor en el archivo Global.asax de su proyecto en el evento Application_Start ()

 System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new SVirtualPathProvider()); 

Debido a que ahora está publicando sus vistas desde una ubicación desconocida, ya no existe el archivo ~/Views/web.config que se aplica e indica la clase base para sus vistas de razor ( ) De modo que podría agregar una directiva @inherits en la parte superior de cada vista incrustada para indicar la clase base.

 @inherits System.Web.Mvc.WebViewPage @model ... 

Utilicé la respuesta de OP como base, pero la amplié un poco e incorporé la respuesta a la pregunta en mi solución.

Esto parece una pregunta algo común aquí en SO y no he visto una respuesta completa, así que pensé que podría ser útil compartir mi solución de trabajo.

Cargué mis recursos de una base de datos y los guardo en la caché predeterminada (System.Web.Caching.Cache).

Lo que terminé haciendo fue crear una CacheDependency personalizada en la KEY que estoy usando para buscar el recurso. De esta forma, cada vez que mi otro código invalide ese caché (en una edición, etc.) se elimina la dependencia del caché de esa clave y el VirtualPathProvider a su vez invalida su caché y VirtualFile se vuelve a cargar.

También cambié el código para que automáticamente preceda hereda statement para que no tenga que ser almacenado en mi recurso de base de datos y también antepongo algunas declaraciones de uso predeterminadas automáticamente ya que esta “vista” no se carga a través de los canales normales, por lo cualquier cosa predeterminada incluye en su web.config o viewstart no son utilizables.

CustomVirtualFile:

 public class CustomVirtualFile : VirtualFile { private readonly string virtualPath; public CustomVirtualFile(string virtualPath) : base(virtualPath) { this.virtualPath = VirtualPathUtility.ToAppRelative(virtualPath); } private static string LoadResource(string resourceKey) { // Load from your database respository or whatever here... // Note that the caching is disabled for this content in the virtual path // provider, so you must cache this yourself in your repository. // My implementation using my custom service locator that sits on top of // Ninject var contentRepository = FrameworkHelper.Resolve(); var resource = contentRepository.GetContent(resourceKey); if (String.IsNullOrWhiteSpace(resource)) { resource = String.Empty; } return resource; } public override Stream Open() { // Always in format: "~/CMS/{0}.cshtml" var key = virtualPath.Replace("~/CMS/", "").Replace(".cshtml", ""); var resource = LoadResource(key); // this automatically appends the inherit and default using statements // ... add any others here you like or append them to your resource. resource = String.Format("{0}{1}", "@inherits System.Web.Mvc.WebViewPage\r\n" + "@using System.Web.Mvc\r\n" + "@using System.Web.Mvc.Html\r\n", resource); return resource.ToStream(); } } 

CustomVirtualPathProvider:

 public class CustomVirtualPathProvider : VirtualPathProvider { private static bool IsCustomContentPath(string virtualPath) { var checkPath = VirtualPathUtility.ToAppRelative(virtualPath); return checkPath.StartsWith("~/CMS/", StringComparison.InvariantCultureIgnoreCase); } public override bool FileExists(string virtualPath) { return IsCustomContentPath(virtualPath) || base.FileExists(virtualPath); } public override VirtualFile GetFile(string virtualPath) { return IsCustomContentPath(virtualPath) ? new CustomVirtualFile(virtualPath) : base.GetFile(virtualPath); } public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { if (IsCustomContentPath(virtualPath)) { var key = VirtualPathUtility.ToAppRelative(virtualPath); key = key.Replace("~/CMS/", "").Replace(".cshtml", ""); var cacheKey = String.Format(ContentRepository.ContentCacheKeyFormat, key); var dependencyKey = new String[1]; dependencyKey[0] = string.Format(cacheKey); return new CacheDependency(null, dependencyKey); } return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart); } public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) { if (IsCustomContentPath(virtualPath)) { return virtualPath; } return base.GetFileHash(virtualPath, virtualPathDependencies); } } 

¡Espero que esto ayude!

Me apoyé mucho en la información del OP así como en la respuesta de Darin Dimitrov para crear un prototipo simple para compartir componentes de MVC en todos los proyectos. Si bien estos fueron muy útiles, todavía encontré algunas barreras adicionales que se abordan en el prototipo, como el uso de vistas compartidas con @modelos.