ASP.NET ActionFilters and inheritance - asp.net

All my controllers inherit from a BaseController that has an ActionFilter attribute:
[AnalyticsData]
public class BaseController : Controller {}
public class AccountController : BaseController {}
Some of my Actions in my controllers reuse the AnalyticsData ActionFilter:
public class AccountController : BaseController
{
[AnalyticsData(Page="AccountProfile")]
public ActionResult Profile()
{
// return View
}
}
I notice that the AnalyticsData ActionFilter only runs once. This is a good thing and I only want it to run once, but I'm wondering how that happens. If I set my breakpoint inside the OnActionExecuting:
public class AnalyticsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// break point set here
}
}
...it only gets hit once when AccountController serves it Profile view.
How do ActionFilters and/or Attributes work that [AnalyticsData(Page="AccountProfile")] on the Action overrides/replaces [AnalyticsData] on BaseController?

The short answer is that the ASP.NET MVC framework code that retrievs the list of filters for each action removes duplicates (action filters of the same type) in such a way that it prefers actionfilters defined on the action method over ones defined on the controller (or its base class). In MVC 2 this logic is performed in a few internal methods in the ActionDescriptor class

Related

How can I make every controller except the account controller in my WebAPI application require a user to be authenticated

I realize that I can decorate each controller with [Authorize].
However is there a way that I can do this globally so that it's the default and then have the Account controller set as anonymous only ?
Create a BaseController which all other controllers inherit from. Have this class then inherit from Controller, like so
SomeController : BaseController
Then in BaseController
BaseController : Controller
Add an authorize attribute to the base controller. All controllers inheriting from BaseController will now require authorization. Controllers which don't, wont. So, your account controller will only inherit from Controller, not BaseController as you don't want this authorized.
There are other advantages of having a base controller. You can override OnAction executed to log application usage for instance.
I would create a second base controller called BaseUnsecuredController which your account controller can inherit from which won't have an authorize attrubute. Then have an abstract base controller class which contains the implementations of common actions you wish to share between the base controllers, like logging and error handling.
Hope this helps.
Use a basecontroller, from which each controller inherits. Then set the [Authorize] attribute on the base controller.
Apply the filter globally like this.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Existing code
config.Filters.Add(new System.Web.Http.AuthorizeAttribute());
}
}
Then, apply [AllowAnonymous] on the AccountController or specific action methods.
[AllowAnonymous]
public class AccountController : WebApiController {}
You can add the AuthorizeAttribute globally by changing your FilterConfig to add it to all requests:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//Other filters
filters.Add(new AuthorizeAttribute());
}
After that you can add the [OverrideAuthorization] attribute to your controller.
If you have any AuthenticationFilter set globally it won't be reseted. If you want to reset both you also need to use the [OverrideAuthentication] attribute.

ASP.NET MVC: How to override parent Controller method

Let's say I have many Controllers, and a lot of this Controllers share common Action. What is the best way to share these common Actions to eliminate duplicating the code?
One way I know is to refactor these common Actions into parent abstract Controller, like so..
public abstract class BaseController : Controller {
//handles common help page for all controllers
public ActionResult Help(string helpTopic) {
..open help page..
return View(page);
}
}
//now Controller1 and Controller2 has the help page for free!
public class Controller1: BaseController {
}
public class Controller2: BaseController {
}
But if I attempt to override the Help action within any of the sub controllers like so..
//customized help page for Controller1
public class Controller1: BaseController {
public new ActionResult Help(string helpTopic) {
.. my own customized help page..
return View(page);
}
}
I will get error The current request for action 'Help' on controller type 'Controller1' is ambiguous between....
So how do I override parent controller method?
What I did to successfully override a parent controller's method is to mark the method as virtual and use the override keyword in the child controller method, like so...
public abstract class BaseController : Controller {
//handles common help page for all controllers
public virtual ActionResult Help(string helpTopic) {
..open help page..
return View(page);
}
}
//Controller1 has a customized help page
public class Controller1: BaseController {
public override ActionResult Help(string helpTopic) {
.. my own customized help page..
return View(page);
}
}
//Controller2 has help page for free!
public class Controller2: BaseController {
}

Parent Controller Class in ASP.NET MVC 2

I've been working on a rather large web application that requires a specific id param in the url for every page visited (for example, /controller/action/id?AccountId=23235325). This id has to be verified every time someone visits a page.
I want to reduce code replication as much as possible, and was wondering if there is a way to use an init method or constructor in a controller that inherits the MVC controller, and then have that controller extended by the others.
I'm using ASP.NET MVC 2.
Yes this is possible using either a base controller class that all your controllers inherit or by creating a custom attribute that you decorate your controller with.
Base controller:
public class BaseController : Controller
{
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// verify logic here
}
}
Your controllers:
public class AccountController : BaseController
{
// the Initialize() function will get called for every request
// thus running the verify logic
}
Custom Authorization Attribute:
public class AuthorizeAccountNumberAttribute : AuthorizationAttribute
{
protected override AuthorizationResult IsAuthorized(System.Security.Principal.IPrincipal principal, AuthorizationContext authorizationContext)
{
// verify logic here
}
}
On your controller(s):
[AuthorizeAccountNumber]
public class AccountController : Controller
{
// the IsAuthorized() function in the AuthorizeAccountNumber will
// get called for every request and thus the verify logic
}
You can combine both approaches to have another custom base controller class which is decorated with the [AuthorizeAccountNumber] which your controllers that require verification inherit from.

How to set navigation variables in MVC Masterpages

I need to have some navigation options, that require keys that are specific to the current user, that reside in a masterpage. I need some advice on best practise.
In have the following links in a left nav in a masterpage
http://www.example.com/manageShop/123
http://www.example.com/addProductToShop/123
http://www.example.com/addStaffToShop/123
Where '123' is the shop id that the current user is the manager of. I need some way of passing this to the masterpage
Currently I'm going something to this effect:
<li><%= Html.ActionLink<ShopController>(x => x.ManageShop((int)Session["ShopKey"]), "Manage")%></li>
I thought this was a good idea as I only have to set the ShopKey once in the session and its done, the down side is that iv noticed that the session gets mixed if you have the site open is two tabs.
Alternatively I tried this:
<li><%= Html.ActionLink<ShopController>(x => x.ManageShop((int)ViewData["ShopKey"]), "Manage")%></li>
But this means you have to keep setting the ViewData in every action in every controller. Which is awful.
EDIT: I have had alook at filters like eu-ge-ne suggested below, but I dont this really solves my problem as I still have the issue of setting the ShopKey everywhere?
What is the solution?
You can create custom filter for this:
public class UserKeyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData["UserKey"] = UserKey;
}
}
and use it on your controller or controller actions
[UserKey]
public class YourController : Controller
{
// or
public class YourController : Controller
{
[UserKey]
public ActionResult Index()
{
or use Controller.OnActionExecuting() (or even create base controller for this as Arnis L. said):
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData["UserKey"] = UserKey;
}
}
// and then derive your controllers from BaseController
public class YourController : BaseController
{

ASP MVC Authorize all actions except a few

I have a controller and I would like to require Authorization for all actions by default except a couple. So in the example below all actions should require authentication except the Index. I don't want to decorate every action with the Authorize, I just want to override the default authorization in certain circumstances probably with a custom filter such as NotAuthorize.
[Authorize]
public class HomeController : BaseController
{
[NotAuthorize]
public ActionResult Index()
{
// This one wont
return View();
}
public ActionResult About()
{
// This action will require authorization
return View();
}
}
Ok, this is what I did. If there is a better way let me know.
public class NotAuthorizeAttribute : FilterAttribute
{
// Does nothing, just used for decoration
}
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Check if this action has NotAuthorizeAttribute
object[] attributes = filterContext.ActionDescriptor.GetCustomAttributes(true);
if (attributes.Any(a => a is NotAuthorizeAttribute)) return;
// Must login
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
What about [AllowAnonymous] ??
MVC4 has a new attribute exactly meant for this [AllowAnonymous] (as pointed out by Enrico)
[AllowAnonymous]
public ActionResult Register()
Read all about it here:
http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx
Here's what I would do, similar to Craig's answer with a couple of changes:
1) Create an ordinary attribute deriving from System.Attribute (no need to derive from FilterAttribute since you aren't going to be using anything FilterAttribute provides).
Maybe create a class hierarchy of attributes so you can test based on the hierarchy, e.g.
Attribute
AuthorizationAttribute
AuthorizationNotRequiredAttribute
AuthorizationAdminUserRequiredAttribute
AuthorizationSuperUserRequiredAttribute
2) In your BaseController override the OnAuthorization method rather than the OnActionExecuting method:
protected override void OnAuthorization(AuthorizationContext filterContext)
{
var authorizationAttributes = filterContext.ActionDescriptor.GetCustomAttributes(true).OfType<AuthorizationAttribute>();
bool accountRequired = !authorizationAttributes.Any(aa => aa is AuthorizationNotRequiredAttribute);
I like the approach of being secure by default: even if you forget to put an attribute on the Action it will at least require a user to be logged in.
Use a custom filter as described in Securing your ASP.NET MVC 3 Application.
Mark the controller with [Authorize]
[Authorize]
public class YourController : ApiController
Mark actions you want public with :
[AllowAnonymous]
Little late to the party, but I ended up creating a Controller-level auth attribute and an Action-level auth attribute and just skipping over the Controller auth if the Action had its own Auth attribute. See code here:
https://gist.github.com/948822

Resources