I have created a customized role base authorization attribute.My idea is that when a user with role name "employee" Log In should not be allowed to access the "admin" page through URL. But when I implement the [MyRoleAuthorization] in Employee controller and Log In the error says "This webpage has a redirect loop".
This is code for [MyRoleAuthorization]
public class MyRoleAuthorization : AuthorizeAttribute
{
string isAuthorized;
private string AuthorizeUser(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext != null)
{
var context = filterContext.RequestContext.HttpContext;
if (Convert.ToString(context.Session["RoleName"]) == "Admin")
{
isAuthorized = "Admin";
}
else if (Convert.ToString(context.Session["RoleName"]) == "Employee")
{
isAuthorized = "Employee";
}
else if (Convert.ToString((context.Session["RoleName"])) == "Customer")
{
isAuthorized = "Customer";
}
else
{
throw new ArgumentException("filterContext");
}
}
return isAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
throw new ArgumentException("filterContext");
if (AuthorizeUser(filterContext) == "Admin")
{
filterContext.Result = new RedirectToRouteResult
(new RouteValueDictionary(new { controller = "Admin" }));
}
else if (AuthorizeUser(filterContext) == "Employee")
{
filterContext.Result = new RedirectToRouteResult
(new RouteValueDictionary(new { controller = "Employee" }));
}
else if (AuthorizeUser(filterContext) == "Customer")
{
filterContext.Result = new RedirectToRouteResult
(new RouteValueDictionary(new { controller = "Customer" }));
}
}
}
}
My Employee controller looks like this
[MyRoleAuthorization]
public ActionResult Index()
{
var employee = db.Employee.Include(e => e.User);
return View(employee.ToList());
}
Can you please help me.
Your redirection code is always going to redirect the user to the Employee Index Action, even when the action your are redirecting to is authenticated for the employee. You will need to provide another set of rules in your authorization and change your OnAuthorize method.
Such as
public class MyRoleAuthorization : AuthorizeAttribute
{
/// <summary>
/// the allowed types
/// </summary>
readonly string[] allowedTypes;
/// <summary>
/// Default constructor with the allowed user types
/// </summary>
/// <param name="allowedTypes"></param>
public MyRoleAuthorization(params string[] allowedTypes)
{
this.allowedTypes = allowedTypes;
}
/// <summary>
/// Gets the allowed types
/// </summary>
public string[] AllowedTypes
{
get { return this.allowedTypes; }
}
/// <summary>
/// Gets the authorize user
/// </summary>
/// <param name="filterContext">the context</param>
/// <returns></returns>
private string AuthorizeUser(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext != null)
{
var context = filterContext.RequestContext.HttpContext;
string roleName = Convert.ToString(context.Session["RoleName"]);
switch (roleName)
{
case "Admin":
case "Employee":
case "Customer":
return roleName;
default:
throw new ArgumentException("filterContext");
}
}
throw new ArgumentException("filterContext");
}
/// <summary>
/// The authorization override
/// </summary>
/// <param name="filterContext"></param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
throw new ArgumentException("filterContext");
string authUser = AuthorizeUser(filterContext);
if (!this.AllowedTypes.Any(x => x.Equals(authUser, StringComparison.CurrentCultureIgnoreCase)))
{
filterContext.Result = new HttpUnauthorizedResult();
return;
}
}
}
This can then be decorated as
public class EmployeeController : Controller
{
[MyRoleAuthorization("Employee")]
public ActionResult Index()
{
return View();
}
}
Now your login code should be modified to send the user to the correct controller.
Your biggest problem is when you go to the employee controller as an employee, you're redirected to the employee controller, where your attribute redirects you to the employee controller and so on. Try to avoid redirecting within the attribute as it makes your code brittle & when you come back in a years time, you won't remember why your routes don't work as you intend
Try this:
public class MyRoleAuthorization : AuthorizeAttribute
{
public string Role{get;set;}
private string AuthorizeUser(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext != null)
{
var context = filterContext.RequestContext.HttpContext;
return (string)context.Session["RoleName"];
}
throw new ArgumentException("filterContext");
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
throw new ArgumentException("filterContext");
var role = AuthorizeUser(filterContext);
if (role.Equals(Role))
{
// insert positive outcome from role check, ie let the action continue
}
else
{
// denied! redirect to login page or show denied page (403)
}
}
}
[MyRoleAuthorization("Employee")]
public ActionResult Index()
{
var employee = db.Employee.Include(e => e.User);
return View(employee.ToList());
}
It seems that when authorized, you redirect to the Customer controller, for example. This controller likely has your attribute on it, and so it authorizes the user, who is seen as a customer, and redirects to the Customer controller... Which has your attribute on it, and so it authorizes the user...
Infinite loop.
Related
So i've hosted my site on Smarter ASP . The application is made in ASP.NET Core MVC with MSSQL database . The problem is that when a user logs into the account from a PC for example and then a user from a mobile phone opens the site he get logged directly into the other persons account and both devices act like one .
Ive tried to add session but it doesnt work.
This is my Authentication class :
/// <summary>
/// This static class is the web applications authentication that is going
/// to be used all across the application
/// </summary>
/// <param name="LoggedUser">Container holding the data for the logged user</param>
/// <param name="Authenticate">Checks for existing user with that parameters and fills the LoggedUser container</param>
/// <param name="getFullName">returns the full name of the logged user</param>
public class Authentication
{
public static User LoggedUser { get; set; }
public static void Authenticate(string email , string password)
{
UserRepository userRepository = new UserRepository();
var user = userRepository.FindByEmailAndPassword(email, password);
LoggedUser = user;
}
public static string getFullName()
{
if (LoggedUser == null)
{
return "";
}
else
{
return LoggedUser.firstName + " " + LoggedUser.lastName;
}
}
public static void UpdateUser()
{
if (LoggedUser != null)
{
UserRepository userRepository =new UserRepository();
LoggedUser = userRepository.FindById(LoggedUser.ID);
}
}
}
This is the login where after i check whether the user wants to stay loged
i am trying to create a session but im not sure if thats the correct way :
[HttpPost]
public IActionResult Login(LoginVM model)
{
if (!ModelState.IsValid)
return View(model);
Authentication.Authenticate(model.email, model.password);
if (Authentication.LoggedUser == null)
return View(model);
if (model.keepMeLoged == 1)
{
CookieOptions cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTimeOffset.Now.AddDays(30);
Response.Cookies.Append("userID" , Authentication.LoggedUser.ID.ToString(),cookieOptions);
}
if (Authentication.LoggedUser != null)
{
HttpContext.Session.SetInt32("UserID" , Authentication.LoggedUser.ID);
HttpContext.Session.SetString("Email", Authentication.LoggedUser.email);
}
return RedirectToAction("Index", "Home");
}
And here i delete the session :
public IActionResult Logout()
{
Authentication.LoggedUser = null;
Response.Cookies.Delete("userID");
HttpContext.Session.Clear();
return RedirectToAction("Index","Home");
}
I have the follow Custom AuthorizeAttribute:
public class SystemAuthorizeAttribute : AuthorizeAttribute
{
public Form PermissionForm { get; set; } //Enum
public PermissionValue Permissions { get; set; }//Enum
public override void OnAuthorization(AuthorizationContext filterContext)
{
//Request is an Authenticated Method?
if (filterContext.HttpContext.Request.IsAuthenticated)
{
Debug.WriteLine("Test 1 " + PermissionForm);
if (!CurrentUserHasPermissionForm(PermissionForm))
{
//Deny access code
}
}
}
//...
}
After Login method it redirects to Index page from HomeController. The problem is when use SystemAuthorize Attribute in my HomeController the Form value always come as 0 when it should be 4 (Content).
HomeController method:
[SystemAuthorize(PermissionForm = Form.CONTENT, Permissions = PermissionValue.VIEW)]
public ActionResult Index()
{
return this.View();
}
Login method:
[AllowAnonymous]
[Route("Account/Login")]
public ActionResult Login(LoginViewModel model, string url = "")
{
var user= GetUserAccount(model);
if (user == null)
{
ModelState.AddModelError("", "User not found!");
return View(model);
}
else
{
FormsAuthentication.SetAuthCookie(user.Sign, false);
var authTicket = new FormsAuthenticationTicket(1, user.Sign, DateTime.Now, DateTime.Now.AddMinutes(20), false, JsonConvert.SerializeObject(user));
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
HttpContext.Response.Cookies.Add(authCookie);
return RedirectToAction("Index", "Home");
}
}
Form enum:
public enum Form : short
{
PATIENT = 1,
USERS = 2,
MEDICE = 3,
CONTENT = 4,
}
What I'm doing wrong or missing?
Unfortunately Microsoft made this a bit confusing by combining IAuthorizationFilter with Attribute in the same class. The fact of the matter is that attributes cannot do anything except store meta-data.
The part of MVC that reads the attribute is the IAuthorizationFilter which through some MVC magic is registered with MVC automatically when you place AuthorizeAttribute (or a subclass) on a controller or action.
But the only way to actually read the meta-data from the attribute is to use Reflection. The meta-data is in the same class, but not the same instance of the class. The meta-data is in the Attribute, but the code that executes when the filter runs is in the IAuthorizationFilter, which is a separate instance of the same class.
public class SystemAuthorizeAttribute : AuthorizeAttribute
{
public Form PermissionForm { get; set; } //Enum
public PermissionValue Permissions { get; set; }//Enum
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var actionDescriptor = httpContext.Items["ActionDescriptor"] as ActionDescriptor;
if (actionDescriptor != null)
{
var authorizeAttribute = this.GetSystemAuthorizeAttribute(actionDescriptor);
// If the authorization attribute exists
if (authorizeAttribute != null)
{
// Run the authorization based on the attribute
var form = authorizeAttribute.PermissionForm;
var permissions = authorizeAttribute.Permissions;
// Return true if access is allowed, false if not...
if (!CurrentUserHasPermissionForm(form))
{
//Deny access code
}
}
}
return true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Pass the current action descriptor to the AuthorizeCore
// method on the same thread by using HttpContext.Items
filterContext.HttpContext.Items["ActionDescriptor"] = filterContext.ActionDescriptor;
base.OnAuthorization(filterContext);
}
private SystemAuthorizeAttribute GetSystemAuthorizeAttribute(ActionDescriptor actionDescriptor)
{
SystemAuthorizeAttribute result = null;
// Check if the attribute exists on the action method
result = (SystemAuthorizeAttribute)actionDescriptor
.GetCustomAttributes(attributeType: typeof(SystemAuthorizeAttribute), inherit: true)
.SingleOrDefault();
if (result != null)
{
return result;
}
// Check if the attribute exists on the controller
result = (SystemAuthorizeAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(attributeType: typeof(SystemAuthorizeAttribute), inherit: true)
.SingleOrDefault();
return result;
}
}
Note that OnAuthorization has some logic in it that you will need to support output caching and the part of the code that checks for [AllowAnonymous], so you should not put your authorization check there, but in AuthorizeCore. But unfortunately, AuthorizeCore isn't passed the ActionDescriptor you need to check whether the attribute exists, so you need the above httpContext.Items hack to ensure it is passed into that method on the same thread.
The Reflection part becomes much more clear if you separate your Attribute into a different class from the IAuthorizationFilter, as in this example.
In previous MVC version i use authentication service like this
public class OvAuthorizeAttribute : FilterAttribute
{
public async Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
..........
var user = await ContainerFactory.Container.GetInstance<IMembershipService>().GetUser(token);
if (user == null)
........
actionContext.Request.Properties["User"] = user;
}
}
[OvAuthorize]
public class CommonController : Controller
{
public User CurrentUser
{
get
{
return Request.Properties["User"] as User; //ERROR
}
}
}
But now, i can't access Request.Properties in new Controller definition
You can get User directly from the Controller instance. The following property is exposed on Controller.
/// <summary>
/// Gets or sets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User
{
get
{
return Context?.User;
}
}
I have created the filter below to restrict access to sections of my application to only users that are logged in. However, my app still complains that the user objects have not been instantiated before the filter fires the redirect. How can I make the redirect kick the user out before the action method has a chance to notice the objects are null?
Context
For completeness, it is worth mentioning:
UserSession.CurrentOrDefault();
Returns an object if it finds values stored in the current session, or null if the session doesn't exist.
The filter
public class RestrictAccess : ActionFilterAttribute
{
public UserRole RequiredRole { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var userSession = UserSession.CurrentOrDefault();
if(userSession != null)
{
int userRole = Convert.ToInt32(userSession.User.Role);
int requiredRole = Convert.ToInt32(this.RequiredRole);
if(userRole >= requiredRole)
{
base.OnActionExecuting(filterContext);
return;
}
else
{
HttpContext.Current.Response.Redirect("/");
return;
}
}
HttpContext.Current.Response.Redirect("/Session/Create");
}
}
An example action method that complains:
[RestrictAccess]
public ActionResult Index()
{
var userSession = UserSession.CurrentOrDefault();
// This is the part that throws the exception. userSession.User is null here.
// My expectation was for this to be unreachable if user is null because of the filter.
var model = new IndexViewModel { User = userSession.User };
return View(model);
}
You should implement your own AuthorizeAttribute for that
public class Authorization : AuthorizeAttribute
{
public UserRole RequiredRole { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var userSession = UserSession.CurrentOrDefault();
if(userSession != null)
{
int userRole = Convert.ToInt32(userSession.User.Role);
int requiredRole = Convert.ToInt32(this.RequiredRole);
if(userRole >= requiredRole)
{
return true;
}
else
{
return false;
}
}
return false;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
try
{
if (AuthorizeCore(filterContext.HttpContext))
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else
{
filterContext.Result = new RedirectResult("/Session/Create");
}
}
catch (Exception)
{
filterContext.Result = new RedirectResult("/Session/Create");
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
When you call HttpContext.Current.Response.Redirect, you are going outside of the MVC architecture, so it does not know not to run the action method and so continues and errors.
Instead you should set the Result of your ActionExecutingContext filterContext to a RedirectResult like so:
public class RestrictAccess : ActionFilterAttribute
{
public UserRole RequiredRole { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var userSession = UserSession.CurrentOrDefault();
if(userSession != null)
{
int userRole = Convert.ToInt32(userSession.User.Role);
int requiredRole = Convert.ToInt32(this.RequiredRole);
if(userRole >= requiredRole)
{
base.OnActionExecuting(filterContext);
return;
}
else
{
filterContext.Result = new RedirectResult("/");
return;
}
}
filterContext.Result = new RedirectResult("/Session/Create");
}
}
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());
}
Then we are taken back to our VirtualPathProvider and the process begins 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 an extension match which I store in the database too:
Then GetFile will be called and as long as you have followed my path, you should return the VirtualFile
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