On my controller I have it inherit a MainController and there I override the Initialize and the OnActionExecuting.
Here I see what is the URL and by that I can check what Client is it, but I learned that for every Method called, this is fired up again and again, even a simple redirectToAction will fire the Initialization of the same controller.
Is there a better technique to avoid this repetition of database call? I'm using Entity Framework, so it will take no time to call the DB as it has the result in cache already, but ... just to know if there is a better technique now in MVC3 rather that host the variables in a Session Variable
sample code
public class MyController : MainController
{
public ActionResult Index()
{
return View();
}
}
public class MainController : Controller
{
public OS_Clients currentClient { get; set; }
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// get URL Info
string url = requestContext.HttpContext.Request.Url.AbsoluteUri;
string action = requestContext.RouteData.GetRequiredString("action");
string controller = requestContext.RouteData.GetRequiredString("controller");
object _clientUrl = requestContext.RouteData.Values["cliurl"];
if (_clientUrl != null && _clientUrl.ToString() != "none")
{
// Fill up variables
this.currrentClient = db.FindClientById(_clientUrl.ToString());
}
base.Initialize(requestContext);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// based on client and other variables, redirect to Disable or Login Actions
// ... more code here like:
// filterContext.Result = RedirectToAction("Login", "My");
base.OnActionExecuting(filterContext);
}
}
is it still best to do as:
public OS_Clients currentClient {
get {
OS_Clients _currentClient = null;
if (Session["CurrentClient"] != null)
_currentClient = (OS_Clients)Session["CurrentClient"];
return _currentClient;
}
set {
Session["CurrentClient"] = value;
}
}
It seems that you dealing with application security in that case I would suggest to create Authorization filter, which comes much early into the action. You can put your permission checking code over there and the framework will automatically redirect the user to login page if the permission does not meet AuthorizeCore.
Next, if the user has permission you can use the HttpContext.Items as a request level cache. And then you can create another ActionFilter and in action executing or you can use the base controller to get the user from the Httpcontext.items and assign it to controller property.
If you are using asp.net mvc 3 then you can use the GlobalFilters to register the above mentioned filters instead of decorating each controller.
Hope that helps.
In your base controller, you need to cache the result of the first call in a Session variable.
This makes sure the back-end (DB) is not called unnecessarily, and that the data is bound to the user's Session instead of shared across users, as would be the case with the Application Cache.
Related
I am working on MVC5 applications where I have a base controller which is inherited by every controller in my application.
I am using OnActionExecuting function of base controller to load and maintain menus from database.
As this function will be called every-time any controller inherit base controller so some times it has been called more than once.
Can I use/create any other function from base controller which will be called once when view is about to render.
Is there any better way to maintain menus for the same user as in such case no need to hit database on every page, TIA.
Good idea will be to use caching. For instance Cache Class where you can store objects and retrieve them by key.
public abstract class ControllerBase
{
private readonly Cache _cache;
public ControllerBase(Cache cache)
{
// null check
_cache = cache;
}
protected virtual void OnActionExecuting(ActionExecutingContext filterContext)
{
string username = filterContext.HttpContext.User.Identity.Name;
// authenticate duser has always username
if(!String.IsNullOrEmpty(username))
{
var cacheItem = _cache.Get("menu_" + username);
if(cacheItem is {Type})
{
}
else
{
// Load from db
// Add to cache with sliding expiration
}
}
else
{
var cacheItem = _cache.Get("menu_anonymous_user");
if(cacheItem is {Type})
{
}
else
{
// Load from db
// Add to cache with sliding expiration
}
}
}
}
It is an example. It can be done much better, but for an idea it is enough, I guess.
What is the correct way to restrict access to a controller?
For instance, I might have "ProductReviewController", I want to be able to check that this controller is accessible in the current store and is enabled. I'm not after the code to do that but am interested in the approach to stopping the user getting to the controller should this criteria not be met. I would like the request to just carry on as if the controller was never there (so perhaps throwing a 404).
My thoughts so far:
A data annotation i.e [IsValidController]. Which Attribute class would I derive from - Authorize doesn't really seem to fit and I would associate this with user authentication. Also, I'm not sure what the correct response would be if the criteria wasn't met (but I guess this would depend on the Attribute it's deriving from). I could put this data annotation against my base controller.
Find somewhere lower down in the page life cycle and stop the user hitting the controller at all if the controller doesn't meet my criteria. i.e Create my own controller factory as depicted in point 7 here: http://blogs.msdn.com/b/varunm/archive/2013/10/03/understanding-of-mvc-page-life-cycle.aspx
What is the best approach for this?
Note: At the moment, I am leaning towards option 1 and using AuthorizeAttribute with something like the code below. I feel like I am misusing the AuthorizeAttribute though.
public class IsControllerAccessible : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!CriteriaMet())
return false;
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "Generic",
action = "404"
})
);
}
}
I think you are confused about AuthorizeAttribute. It is an Action Filter, not a Data Annotation. Data Annotations decorate model properties for validatioj, Action Filter's decorate controller actions to examine the controller's context and doing something before the action executes.
So, restricting access to a controller action is the raison d'etre of the AuthorizeAttribute, so let's use it!
With the help of the good folks of SO, I created a customer Action Filter that restricted access to actions (and even controllers) based on being part of an Access Directory group:
public class AuthorizeADAttribute : AuthorizeAttribute
{
public string Groups { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (base.AuthorizeCore(httpContext))
{
/* Return true immediately if the authorization is not
locked down to any particular AD group */
if (String.IsNullOrEmpty(Groups))
return true;
// Get the AD groups
var groups = Groups.Split(',').ToList<string>();
// Verify that the user is in the given AD group (if any)
var context = new PrincipalContext(ContextType.Domain, "YOURADCONTROLLER");
var userPrincipal = UserPrincipal.FindByIdentity(context,
IdentityType.SamAccountName,
httpContext.User.Identity.Name);
foreach (var group in groups)
{
try
{
if (userPrincipal.IsMemberOf(context, IdentityType.Name, group))
return true;
}
catch (NoMatchingPrincipalException exc)
{
var msg = String.Format("While authenticating a user, the operation failed due to the group {0} could not be found in Active Directory.", group);
System.ApplicationException e = new System.ApplicationException(msg, exc);
ErrorSignal.FromCurrentContext().Raise(e);
return false;
}
catch (Exception exc)
{
var msg = "While authenticating a user, the operation failed.";
System.ApplicationException e = new System.ApplicationException(msg, exc);
ErrorSignal.FromCurrentContext().Raise(e);
return false;
}
}
}
return false;
}
}
Note this will return a 401 Unauthorized, which makes sense, and not the 404 Not Found you indicated above.
Now, the magic in this is you can restrict access by applying it at the action level:
[AuthorizeAD(Groups = "Editor,Contributer")]
public ActionResult Create()
Or at the controller level:
[AuthorizeAD(Groups = "Admin")]
public class AdminController : Controller
Or even globally by editing FilterConfig.cs in `/App_Start':
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new Code.Filters.MVC.AuthorizeADAttribute() { Groups = "User, Editor, Contributor, Admin" });
}
Complete awesome sauce!
P.S. You mention page lifecycle in your second point. There is no such thing in MVC, at least not in the Web Forms sense you might be thinking. That's a good thing to my mind, as things are greatly simplified, and I don't have to remember a dozen or so different lifecycle events and what the heck each one of them is raised for!
We have an MVC app which has a custom forms authentication view/controller. The controller will verify things and then do a FormsAuthentication.RedirectFromLoginPage call.
At this point in the Global.asax we'll receive a Application_OnAuthenticateRequest call from where we'll get their Context.User information and make another call to gather information relevant to this account which we then store in their Context.User & System.Threading.Thread.CurrentPrincipal. We also do a little caching of this information since in our system retrieving what we need is expensive which leads to cache invalidation & re-retrieval of this information.
It seems a bit odd at this point that we've got these separated into separate calls. I'm almost wondering if the Login controller shouldn't be gathering the details as part of its authentication check and storing them. Then the Application_OnAuthenticateRequest can only worry about if the cache needs to be invalidated and the users details re-retrieved.
Or maybe there is some other way of handling this I don't even know about..?
You can do what you want in MVC by leveraging RedirectToRouteResult and a custom cache updating ActionFilter. This is called the PRG (Post-Redirect-Get) pattern. You are actually already doing this, but it gets a little confused, because what you are doing is a cross between the classic ASP.NET way of doing things and the MVC way of doing things. There's nothing wrong with your initial approach (provided it is working correctly), but to do the same sort of thing and have more control and understanding of how it works in the scheme of things you could do something like:
public class AuthenticationController :Controller
{
[HttpPost]
public RedirectToRouteResult Login(string username, string password)
{
//authenticate user
//store authentication info in TempData like
bool authenticated = true|false; // do your testing
if(authenticated)
{
TempData["MustUpdateCache"] = true | false;
return RedirectToAction("LoginSuccess", new{userId = membershipUser.UserId});
}
else
{
TempData["MustUpdateCache"] = true | false;
return RedirectToAction("Login");
}
}
[HttpGet, UpdateCache]
public ActionResult LoginSuccess(Guid userId, string url)
{
HttpContext.User = LoadUser(userId);
return View();
}
[HttpGet, UpdateCache]
public ViewResult Login()
{
return View();
}
}
public class UpdateCacheAttribute:ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var tempData = filterContext.Controller.TempData;
if (tempData.ContainsKey("MustUpdateCache") && (bool)tempData["MustUpdateCache"])
{
UpdateCache(filterContext);
}
}
void UpdateCache(ControllerContext controllerContext)
{
//update your cache here
}
}
I am implementing a CustomAuthorizeAttribute. I need to get the name of the action being executed. How can i get the name of current action name getting executed in the AuthorizeCore function which i am overriding ?
If you're using cache (or have plans to), then overriding AuthorizeCore, like Darin Dimitrov shows in this answer is a much safer bet:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var routeData = httpContext.Request.RequestContext.RouteData;
var controller = routeData.GetRequiredString("controller");
var action = routeData.GetRequiredString("action");
...
}
The reason for this is documented in the MVC source code itself:
AuthorizeAttribute.cs (lines 72-101)
public virtual void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (OutputCacheAttribute.IsChildActionCacheActive(filterContext)) {
// If a child action cache block is active, we need to fail immediately, even if authorization
// would have succeeded. The reason is that there's no way to hook a callback to rerun
// authorization before the fragment is served from the cache, so we can't guarantee that this
// filter will be re-run on subsequent requests.
throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
}
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 {
HandleUnauthorizedRequest(filterContext);
}
}
Even if you didn't plan on using cache, those two magic strings seem a small price to pay for the peace of mind you get in return (and the potential headaches you save yourself.) If you still want to override OnAuthorization instead, you should at least make sure the request isn't cached. See this post by Levi for more context.
You can get the Action Name like this:
public class CustomAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
filterContext.ActionDescriptor.ActionName;
}
}
EDIT:
If you want to inherit from the AuthorizationAttribute you'll need to override the OnAuthorization method.
public class CustomAuthAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}
Title said it all.
Some context:
I got a search mechanism - search view, search results view and a details view (which represents one item of results, like a formview in webforms). I want a link in details view, which would return user to search results view.
Ideas:
Just read about TempData, but i guess that wouldn't help, cause user might call some actions before he wants to return.
Session might work, but I'm not sure how exactly i should handle it.
I don't want to use javascript to accomplish this.
Edit:
Seems that i'll stick with eu-ge-ne`s solution. Here's result:
#region usages
using System.Web.Mvc;
using CompanyName.UI.UIApp.Infrastructure.Enums;
#endregion
namespace CompanyName.UI.UIApp.Infrastructure.Filters
{
/// <summary>
/// Apply on action method to store URL of request in session
/// </summary>
public class RememberUrlAttribute : ActionFilterAttribute
{
public override void OnActionExecuting
(ActionExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
if (httpContext.Request.RequestType == "GET"
&& !httpContext.Request.IsAjaxRequest())
{
SessionManager
.Save(SessionKey.PreviousUrl,
SessionManager.Get(SessionKey.CurrentUrl) ??
httpContext.Request.Url);
SessionManager
.Save(SessionKey.CurrentUrl,
httpContext.Request.Url);
}
}
}
}
Btw, how does .IsAjaxRequest() method works? It understands only MS AJAX or it's smarter than that?
I think you need something like this custom filter (not tested - have no VS at the moment):
public class PrevUrlAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
var session = filterContext.HttpContext.Session;
if (httpContext.Request.RequestType == "GET"
&& !httpContext.Request.IsAjaxRequest())
{
session["PrevUrl"] = session["CurUrl"] ?? httpContext.Request.Url;
session["CurUrl"] = httpContext.Request.Url;
}
}
}
You can examine the HTTP Referrer header to retrieve the previous URL.
Of course, you'll have to handle gracefully just in case the user does not pass in this value.
Yo
:)