asp.netasp.net-mvcasp.net-mvc-2virtualpathprovider

Asp.net MVC VirtualPathProvider views parse error


I am working on a plugin system for Asp.net MVC 2. I have a dll containing controllers and views as embedded resources.

I scan the plugin dlls for controller using StructureMap and I then can pull them out and instantiate them when requested. This works fine. I then have a VirtualPathProvider which I adapted from this post

public class AssemblyResourceProvider : VirtualPathProvider
{
    protected virtual string WidgetDirectory
    {
        get 
        { 
            return "~/bin";
        }
    }

    private bool IsAppResourcePath(string virtualPath)
    {
        var checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
        return checkPath.StartsWith(WidgetDirectory, StringComparison.InvariantCultureIgnoreCase);
    }

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

    public override VirtualFile GetFile(string virtualPath)
    {
        return IsAppResourcePath(virtualPath) ? new AssemblyResourceVirtualFile(virtualPath) : base.GetFile(virtualPath);
    }

    public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies,
                                                       DateTime utcStart)
    {
        return IsAppResourcePath(virtualPath) ? null : base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
    }
}

internal class AssemblyResourceVirtualFile : VirtualFile
{
    private readonly string path;

    public AssemblyResourceVirtualFile(string virtualPath)
        : base(virtualPath)
    {
        path = VirtualPathUtility.ToAppRelative(virtualPath);
    }

    public override Stream Open()
    {
        var parts = path.Split('/');
        var resourceName = Path.GetFileName(path);

        var apath = HttpContext.Current.Server.MapPath(Path.GetDirectoryName(path));
        var assembly = Assembly.LoadFile(apath);
        return assembly != null ? assembly.GetManifestResourceStream(assembly.GetManifestResourceNames().SingleOrDefault(s => string.Compare(s, resourceName, true) == 0)) : null;
    }
}

The VPP seems to be working fine also. The view is found and is pulled out into a stream. I then receive a parse error Could not load type 'System.Web.Mvc.ViewUserControl<dynamic>'. which I can't find mentioned in any previous example of pluggable views. Why would my view not compile at this stage?

Thanks for any help,

Ian

EDIT:

Getting closer to an answer but not quite clear why things aren't compiling. Based on the comments I checked the versions and everything is in V2, I believe dynamic was brought in at V2 so this is fine. I don't even have V3 installed so it can't be that. I have however got the view to render, if I remove the <dynamic> altogether.

So a VPP works but only if the view is not strongly typed or dynamic

This makes sense for the strongly typed scenario as the type is in the dynamically loaded dll so the viewengine will not be aware of it, even though the dll is in the bin. Is there a way to load types at app start? Considering having a go with MEF instead of my bespoke Structuremap solution. What do you think?


Solution

  • The settings that allow parsing of strongly typed views are in ~/Views/Web.Config. When the view engine is using a virtual path provider, it is not in the views folder so doesn't load those settings.

    If you copy everything in the pages node of Views/Web.Config to the root Web.Config, strongly typed views will be parsed correctly.