Why doesn't MVC 5 call the GetFile method of my VirtualPathProvider class?

I am trying to download Razor View from a database.

I am following ASP.NET MVC and Virtual Views and VirtualPathProvider in MVC 5 for this.

my code is:

VirtualPathProvider:

public class DbPathProvider : VirtualPathProvider { public override bool FileExists(string virtualPath) { var page = FindPage(virtualPath); if (page == null) { return base.FileExists(virtualPath); } else { return true; } } public override VirtualFile GetFile(string virtualPath) { var page = FindPage(virtualPath); if (page == null) { return base.GetFile(virtualPath); } else { return new DbVirtualFile(virtualPath, page.PageData.ToArray()); } } private SiteModel FindPage(string virtualPath) { var db = new DatabaseContext(); var page = db.SiteModels.FirstOrDefault(x => x.SiteName == virtualPath); return page; } } 

Virtualfile

 public class DbVirtualFile : VirtualFile { private byte[] data; public DbVirtualFile(string virtualPath, byte[] data) : base(virtualPath) { this.data = data; } public override System.IO.Stream Open() { return new MemoryStream(data); } } 

Global.asax:

 protected void Application_Start() { HostingEnvironment.RegisterVirtualPathProvider(new DbPathProvider()); AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } 

Act:

 public ActionResult Display(string id) { var db = new DatabaseContext(); var site = db.SiteModels.FirstOrDefault(x => x.PageName == id); if (site == null) { return RedirectToAction("Index", "Home"); } ViewBag.Body = site.PageContent; return View(System.IO.Path.GetFileNameWithoutExtension(site.SiteName)); } 

Data:

enter image description here

Case 1:

When the virtualPath value is "/Views/Home/Contact.cshtml" , then the method of the FileExists method returns true and the GetFile method .

Case 2:

When the virtualPath value is "~ / Home / Display / ce28bbb6-03cb-4bf4-8820-373890396a90" , then the FileExists method returns true and the GetFile method and the Display action are never called. and result

HTTP Error 404.0 - Not Found The resource you are looking for has been deleted, its name has changed or is temporarily unavailable.

I have no idea about dynamic representation. I just read this article and try to implement it.

Please tell me where I am wrong.

I am using MVC 5 and .NET 4.5

+8
c # asp.net-mvc razor
source share
3 answers

what can I help with. There is something you donโ€™t think is good, because I had problems with that too.

I think the problem you have here is to know in which order everything works. Here's what happens:

  • First, the VirtualPathProvider FileExists method is called and this is where you do your magic to find the page. My methods are slightly different from yours, here is an example:

     public IList<Page> Pages { get { using (var uow = new UnitOfWork<SkipstoneContext>()) { var companyService = new CompanyService(uow); var company = companyService.GetTenant(); if (company != null) { var pageService = new PageService(uow, company.Id); return pageService.GetPublished(); } } return null; } } public override bool FileExists(string virtualPath) { if (IsVirtualPath(virtualPath)) { if (FindPage(virtualPath) != null) { var file = (PageVirtualFile)GetFile(virtualPath); return file.Exists; } } return Previous.FileExists(virtualPath); } public override VirtualFile GetFile(string virtualPath) { if (IsVirtualPath(virtualPath)) { var page = FindPage(virtualPath); if (page != null) { var decodedString = Uri.UnescapeDataString(page.ViewData); var bytes = Encoding.ASCII.GetBytes(decodedString); return new PageVirtualFile(virtualPath, bytes); } } return Previous.GetFile(virtualPath); } public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart) { if (IsVirtualPath(virtualPath)) return null; return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart); } public override string GetFileHash(string virtualPath, System.Collections.IEnumerable virtualPathDependencies) { if (IsVirtualPath(virtualPath)) return Guid.NewGuid().ToString(); return Previous.GetFileHash(virtualPath, virtualPathDependencies); } private Page FindPage(string virtualPath) { var virtualName = VirtualPathUtility.GetFileName(virtualPath); var virtualExtension = VirtualPathUtility.GetExtension(virtualPath); try { if (Pages != null) { var id = Convert.ToInt32(virtualName.Replace(virtualExtension, "")); var page = Pages.Where(model => model.Id == id && model.Extension.Equals(virtualExtension, StringComparison.OrdinalIgnoreCase)).SingleOrDefault(); return page; } } catch(Exception ex) { // Do nothing } return null; } private bool IsVirtualPath(string virtualPath) { var path = (VirtualPathUtility.GetDirectory(virtualPath) != "~/") ? VirtualPathUtility.RemoveTrailingSlash(VirtualPathUtility.GetDirectory(virtualPath)) : VirtualPathUtility.GetDirectory(virtualPath); if (path.Equals("~/Views/Routing", StringComparison.OrdinalIgnoreCase) || path.Equals("/Views/Routing", StringComparison.OrdinalIgnoreCase)) return true; else return false; } 
  • Now, if this page is served from a database, it is a virtual page, so the next thing that happens is that it calls your MvcHandler (in my handler I have these functions)

     protected override IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state) { var vpp = new SkipstoneVirtualPathProvider(); // Create an instance of our VirtualPathProvider class var requestContext = ((MvcHandler)httpContext.Handler).RequestContext; // Get our request context var path = requestContext.HttpContext.Request.Url.AbsolutePath; // Get our requested path var pages = vpp.Pages; // Get all the published pages for this company if (pages != null && !string.IsNullOrEmpty(path)) // If we have any pages and we have a path { var page = this.MatchPage(pages, path); if (page != null) // If we find the page { requestContext.RouteData.Values["controller"] = "Routing"; // Set the controller requestContext.RouteData.Values["action"] = "Index"; // And the action } } return base.BeginProcessRequest(httpContext, callback, state); } private Page MatchPage(IList<Page> pages, string path) { if (path == "/" || !path.EndsWith("/")) { var page = pages.Where(model => model.Path.Equals(path, StringComparison.OrdinalIgnoreCase)).SingleOrDefault(); // Try to match the path first if (page == null) // If we have no page, then get the directory and see if that matches any page { path = VirtualPathUtility.RemoveTrailingSlash(VirtualPathUtility.GetDirectory(path)); page = pages.Where(model => model.Path.Equals(path, StringComparison.OrdinalIgnoreCase)).SingleOrDefault(); } return page; // return our page or null if no page exists } return null; // return null if anything fails } 
  • Just in case, when you create your MvcHandler, if you have not already done so, it should be registered in RouteConfig as follows:

      // default MVC route routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ).RouteHandler = new SkipstoneRouteHandler(); 
  • In my MvcHandler, I get my page and, if it matches any virtual page, I go to RoutingController

     public ActionResult Index() { using (var uow = new UnitOfWork<SkipstoneContext>()) { var userId = User.Identity.GetUserId(); var service = new PageService(uow, this.CompanyId); var userService = new UserService(uow, this.CompanyId); var user = userService.Get(userId); var fileName = service.View(Request.Url, user); if (!fileName.StartsWith(@"/") && !fileName.StartsWith("~/")) return View(fileName); // Show the page else return Redirect(fileName); // Redirect to our page } } 

just for you. I will show the service.View method

  /// <summary> /// Views the CMS page /// </summary> /// <param name="uri">The current Request.Url</param> /// <param name="user">The current User</param> /// <returns>The filename of the requested page</returns> public string View(Uri uri, User user) { var path = uri.AbsolutePath; // Get our Requested Url var queryString = uri.Query; var pages = this.GetPublished(); var page = pages.Where(model => model.Path.Equals(path, StringComparison.OrdinalIgnoreCase)).SingleOrDefault(); // Try to get the page if (page == null) // If our page is null page = pages.Where(model => model.Path.Equals(VirtualPathUtility.RemoveTrailingSlash(VirtualPathUtility.GetDirectory(path)))).SingleOrDefault(); // try to get the page based off the directory if (page == null) // If the page is still null, then it doesn't exist throw new HttpException(404, "This page has been deleted or removed."); // Throw the 404 error if (page.Restricted && user == null) // If our page is restricted and we are not logged in return "~/Account/LogOn?ReturnUrl=" + page.Path + queryString; // Redirect to the login page if (user != null && page.Restricted) { if (PageService.IsForbidden(page, user)) throw new HttpException(401, "You do not have permission to view this page."); // Throw 401 error } return Path.GetFileNameWithoutExtension(page.Id.ToString()); } 
  • Then we return to our VirtualPathProvider , and the process starts again, but this time the virtual path will look something like this: "~ / Views / Routing / 1331.aspx"
  • If you notice, my FindPage method also looks for a match with the extension, which I also store in the database: enter image description here
  • Then GetFile will be called , and as long as you follow my path, you must return VirtualFile

I really hope this helps :)

+5
source share

I have the same problem. The Razor viewer does not call GetFile unless you have VirtualPath in * .cshtml format. You need to create the {Guid} .cshtml path format

+3
source share

WARNING: ASP.NET 4 Relationship (MVC5)!

Despite the number of answers and the one chosen, I will share my experience.

 FileExists(string virtualPath) 

receives a call twice , once with the relative path app ("~ / bla-bla") and again with an absolute ("/ bla-bla"). In my case, the second (abs. Path) fails. This seemed to be fine in ASP.NET 3, however ASP.NET 4 (MVC5) also checks. And requires it to return true . As a result

 GetFile(string virtualPath) 

not called.

For those who need to implement this behavior, VirtualPathUtility offers the convenience methods ToAbsolute and ToAppRelative .

NTN

0
source share

All Articles