Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VirtualPathProvider in MVC 5

I can't seem to get a custom VirtualPathProvider working in asp.net MVC 5.

The FileExists method returns true but then the GetFile method isn't called. I believe this is because IIS takes over the request and does not let .NET handle it.

I have tried setting RAMMFAR and creating a custom handler, as in this solution https://stackoverflow.com/a/12151501/801189 but still no luck. I get a error 404.

My Custom Provider:

public class DbPathProvider : VirtualPathProvider
{
    public DbPathProvider() : base()
    {

    }

    private static bool IsContentPath(string virtualPath)
    {
        var checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
        return checkPath.StartsWith("~/CMS/", StringComparison.InvariantCultureIgnoreCase);
    }

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

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

    public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        return null;

    }

    public override String GetFileHash(String virtualPath, IEnumerable virtualPathDependencies)
    {
        return Guid.NewGuid().ToString();
    }
}

My Custom Virtual File:

public class DbVirtualFile : VirtualFile
{
    public DbVirtualFile(string path): base(path)
    {

    }

    public override System.IO.Stream Open()
    {
        string testPage = "This is a test!";
        return new System.IO.MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(testPage));
    }
}

web.config handler I have tried to use, without success. It currently gives error 500 :

<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
  <remove name="FormsAuthenticationModule" />
</modules>

<handlers>
  <add name="ApiURIs-ISAPI-Integrated-4.0"
 path="/CMS/*"
 verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS"
 type="System.Web.Handlers.TransferRequestHandler"
 preCondition="runtimeVersionv4.0" />
</handlers>

If I try to navigate to site.com/CMS/Home/Index, the FileExists method is called but strangely, the virtualPath parameter recieves only ~/CMS/Home.

Adding breakpoints, it seems that for the url site.com/CMS/Home/Index, the FileExists method keeps getting repeatedly called. This may be causing an infinite recursion, giving the internal server error.

like image 502
David James Ball Avatar asked Jun 16 '14 05:06

David James Ball


1 Answers

It was actually nothing to do with IIS, and in fact confusion on the order of events. It seems I didn't understand that a routed action method must return a view, that the VirtualPathProvider will try to resolve, rather than going to the VirtualPathProvider directly.

I create a simple controller called ContentPagesController with a single GetPage action:

public class ContentPagesController : Controller
    {
        [HttpGet]
        public ActionResult GetPage(string pageName)
        {
            return View(pageName);
        }
    }

I then set up my route to serve virtual pages:

routes.MapRoute(
 name: "ContentPageRoute",
 url: "CMS/{*pageName}",
 defaults: new { controller = "ContentPages", action = "GetPage" },
 constraints: new { controller = "ContentPages", action = "GetPage" }
);

I register my custom VirtualPathProvider before I register my routes, in globals.asax.cs.

Now suppose I have a page in my database with the relative url /CMS/Home/AboutUs. The pageName parameter will have value Home/AboutUs and the return View() call will instruct the VirtualPathProvider to look for variations of the file ~/Views/ContentPages/Home/AboutUs.cshtml.

A few of the variations it will be look for include:

~/Views/ContentPages/Home/AboutUs.aspx
~/Views/ContentPages/Home/AboutUs.ascx
~/Views/ContentPages/Home/AboutUs.vbhtml

All you now need to do is check the virtualPath that is passed to the GetFiles method, using a database lookup or similar. Here is a simple way:

private bool IsCMSPath(string virtualPath)
        {
           return virtualPath == "/Views/ContentPages/Home/AboutUs.cshtml" || 
                virtualPath == "~/Views/ContentPages/Home/AboutUs.cshtml"; 
        }

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

        public override VirtualFile GetFile(string virtualPath)
        {
            if (IsCMSPath(virtualPath))
            {
                return new DbVirtualFile(virtualPath);
            }

            return base.GetFile(virtualPath);
        }

The custom virtual file will be made and returned to the browser in the GetFile method.

Finally, a custom view engine can be created to give different virtual view paths that are sent to VirtualPathProvider.

Hope this helps.

like image 179
David James Ball Avatar answered Oct 17 '22 06:10

David James Ball