Uso de VirtualPathProvider personalizado para cargar vistas parciales de recursos integrados

Escribí implementaciones personalizadas de VirtualFile y VirtualPathProvider que están obteniendo con éxito recursos integrados que son vistas parciales.

Sin embargo, cuando intento representarlos, se produce este error:

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

Al renderizar la vista parcial, dentro de una Vista normal, se ve así:

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

¿Qué hace que crea que esta no es una vista parcial?

EDITAR: Publiqué mi código para las implementaciones del proveedor de archivos virtuales y de archivos virtuales para cualquiera que se encuentre con esta solución en busca de que funcione ese componente. Esta pregunta también será útil para aquellos que se basen en el título de la pregunta.

Aquí está la implementación de VirtualFile como 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;
    }
}

Proveedor de ruta virtual:

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

preguntado Oct 12 '11, 17:10

3 Respuestas

Debido a que ahora está publicando sus vistas desde una ubicación desconocida, ya no existe el ~/Views/web.config archivo que se aplica e indica la clase base para sus vistas de maquinilla de afeitar (<pages pageBaseType="System.Web.Mvc.WebViewPage">). Entonces, 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 ...

Respondido 12 Oct 11, 21:10

Muchísimas gracias. Lo único que lamento es que solo tengo un voto a favor para dar. = P Up Vote esta respuesta - Matthew Cox

@ darin Dimitrov ¿Supongo que no conoces un truco que pueda emplear para animar a intellisense a tratar esto como una Vista cuando esté dentro de mi dll? En este momento, tengo muy poca compatibilidad con intellisense con las vistas que coloqué dentro de mi dll: Matthew Cox

@MatthewCox, no sé mucho sobre Intellisense en vistas. Hace mucho tiempo que dejé de confiar en él. ¿No obtienes Intellisense si el archivo tiene la .cshtml ¿extensión? - darin dimitrov

Sí, pero es muy limitado, que es la parte extraña. Por ejemplo, no admitirá @Html. [Lo que sea] - Matthew Cox

@FeistyMango - El intellisense para el @Html helper se basa en que los espacios de nombres estén en el web.config dentro de la carpeta Vistas. Como sugiere la respuesta, debería poder importar System.Web.Mvc.Html y System.Web.Mvc directamente en las vistas para obtener intellisense para ellos. - Matt Millican

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

Esta 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.

Cargo mis recursos desde una base de datos y los tengo almacenados en caché en la caché predeterminada (System.Web.Caching.Cache).

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

También cambié el código para que anteponga automáticamente la declaración de herencia para que no sea necesario almacenarla en el recurso de mi base de datos y también antepongo automáticamente algunas declaraciones de uso predeterminadas ya que esta "vista" no se carga a través de los canales normales, por lo que todo lo que se incluya de forma predeterminada en su web.config o viewstart no se puede utilizar.

Archivo virtual personalizado:

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<IContentRepository>();

        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<dynamic>\r\n" +
                                           "@using System.Web.Mvc\r\n" +
                                           "@using System.Web.Mvc.Html\r\n", resource);

        return resource.ToStream();
    }
}

Proveedor de rutas virtuales personalizadas:

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!

Respondido 06 ago 13, 17:08

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

respondido 18 mar '15, 16:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.