I am trying to load Razor View from database.
I follow ASP.NET MVC and virtual views and VirtualPathProvider in MVC 5 to do that.
my Code :
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);
}
Action :
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:
Case 1:
When virtualPath value is "/Views/Home/Contact.cshtml" then FileExists method return true and GetFile method is called.
Case 2:
When virtualPath value is "~/Home/Display/ce28bbb6-03cb-4bf4-8820-373890396a90" then FileExists method return true and GetFile method and Display Action is never called. and result is
HTTP Error 404.0 - Not Found The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
I have no idea about dynamic view. I just read that two article and try to implement it.
Please tell me where I am doing wrong.
I am using MVC 5 and .NET 4.5
something I should be able to help you with. There is something you are not thinking about which is ok, because I had loads of trouble with this too.
I think the issue you have here is knowing in what order everything fires. This is what happens:
First the VirtualPathProvider FileExists method is called and this is where you are doing your magic to find the page. My methods are slightly different to 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 the 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 must be registered in RouteConfig like this:
// 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 move to a 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 your sake 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());
}
I really hope that helps :)
I have same issue. Razor view engine not call GetFile if you virtualPath is not *.cshtml format. You need generate {Guid}.cshtml path format
WARNING: ASP.NET 4 (MVC5) relative!
Despite the number of answers and a selected one, I will share my experience.
FileExists(string virtualPath)
gets called twice, once with a app relative path ("~/bla-bla") and once more with absolute one ("/bla-bla"). In my case the second one (the abs. path) fails. Seems like in ASP.NET 3 this was OK, however ASP.NET 4 (MVC5) checks it too. And requires it to return true
. As a result
GetFile(string virtualPath)
doesn't get called.
For those who need to implement such behaviour VirtualPathUtility
offers ToAbsolute
and ToAppRelative
convenience methods.
HTH
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With