Resolve FilterAttributes On Controller And Action - asp.net-mvc

I would like to do this:
[RequiresAuthentication(CompanyType.Client)]
public class FooController
{
public ActionResult OnlyClientUsersCanDoThis()
public ActionResult OnlyClientUsersCanDoThisToo()
[RequiresAuthentication]
public ActionResult AnyTypeOfUserCanDoThis()
You can see why this won't work. On the third action the controller-level filter will block non-clients. I would like instead to "resolve" conflicting filters. I would like for the more specific filter (action filter) to always win. This seems natural and intuitive.
Once upon a time filterContext exposed MethodInfo for the executing action. That would have made this pretty easy. I considered doing some reflection myself using route info. That won't work because the action it might be overloaded and I cannot tell which one is the current executing one.
The alternative is to scope filters either at the controller level or the action level, but no mix, which will create a lot of extra attribute noise.

We're looking into a way to expose other filters, but no promises.
Applying a filter to the controller isn't really a "scope", it's merely a short-hand for applying it to all filters. Unfortunately, that means you can't include all but one action. One simple way you could do this is to put that one method in another controller. You could even add a custom route just for that one case so the URL doesn't have to change.

you can put authorisation logic into the OnActionExecuting(..) method of the Controller, i.e.
public override void OnActionExecuting(ActionExecutingContext filterContext) {
base.OnActionExecuting(filterContext);
new RequiresAuthentication()
{ /* initialization */ }.OnActionExecuting(filterContext);
}
Hope this helps,
Thomas

You could change filter order with the general auth filter on the controller and the specific auth filters on the actions. Somehow like this:
[RequiresAuthentication]
public class FooController
{
[RequiresAuthentication(CompanyType.Client)]
public ActionResult OnlyClientUsersCanDoThis()
[RequiresAuthentication(CompanyType.Client)]
public ActionResult OnlyClientUsersCanDoThisToo()
public ActionResult AnyTypeOfUserCanDoThis()

Related

How I can block the execution of an action in MVC?

Situation:
I have a controller (with its associated views), that after it was developed, it was decided that it will be excluded from the current phase, and instead, it will be included in some phase in the future.
I cannot simply exclude or delete the controller and the views from the solution because of what I mentioned that will be using the controller in the future. Something that came to my mind was to force each action on the controller to redirect to the main page if they are accessed, but I think this is not too "elegant".
Question:
What other method can I use to block the execution of an action in a controller when they are accessed through the URL?
Keeping unused code in your codebase like that is generally a bad idea. Assuming you have some sort of version control in place, keep a branch around with that controller, but delete it from your master branch. Merge that branch back in when you're bringing the feature back.
If, however, removing the code really isn't possible, I'd make a custom filter to redirect any requests to that controller to some other URL.
i.e.
[RedirectTo("Index", "Home")]
public class MyFutureController : Controller {
...
where you have
public class RedirectToFilter : ActionFilterAttribute {
public string RedirectAction { get; set;}
public string RedirectController { get; set; }
public RedirectToFilter(string redirectAction, string redirectController) {
RedirectAction = redirectAction;
RedirectController = redirectController;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = RedirectToAction(RedirectAction, RedirectController);
}
}
Any requests to FutureController will just get redirected to your Home/Index route. In the future, you just have to remove the attribute from the controller and you're good to go.
You can add a custom route, so that when request comes for this controller it actually redirects to some other controller, e.g. HomeController
Use an OnActionExecuting action filter on the controller.
public class FooController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Detail()
{
return View();
}
protected override void OnActionExecuting( ActionExecutingContext filterContext )
{
filterContext.Result = RedirectToAction( "Index", "Home" );
}
When you're ready to start using the controller just delete the OnActionExecuting method and you're all set.
Just decorate the action method with the [NonAction] attribute. More Here
You could simply write a view to display to the user who directs themselves there that it is still under construction or something of the like. Excluding or commenting out the controller is certainly an option, or you could rewrite all associated methods to reroute to another page.
I think the best solution is probably to just exclude it altogether, having it in your application doesn't benefit you or the user if you're not using it currently. The code should remain so you're free to tweak and refine it before you actually get a release version going.

How to tell if an attribute call is originated from Controller vs. Action

When an action filter is called, is there any way to determine whether this call is originated from an attribute applied at controller level or action level?
I need myAttribute to be run for all of my action methods. The Delete action method, however, is specifically annotated with the filter so myAttribute is called twice. I need the call originated from Controller to do nothing or potentially do different things in that case.
Is there anyway to do this without actually removing the [myAttribute] from controller?
[myAttribute]
public class HomeController
{
public ViewResult Index()
{
}
public ViewResult View()
{
}
public ViewResult Edit()
{
}
[myAttribute]
public ViewResult Delete()
{
}
}
I am using these action filters to authorize a user. A user could have access to a controller but if a method specifically demands permission (by having [myAttribute] above it), then controller level access is not enough and that action should be explicitly mentioned in user permissions.
Not a solution,
What you need to understand is Attributes is designed to use for Annotation only not to define Behavior.
Moreover, When you apply Filter attribute on a Controller, It actually meant to apply for all the ActionMethods within the controller.
What I suggest is, Create one more FilterAttribute that have specific work related to your Delete method and decorate your method with it.

How to handle site-wide querystring parameters in MVC?

I need to be able to process a querystring parameter throughout the site (like ?promo=38 for example). I was trying the procedure specified here Passing a {sitename} parameter to MVC controller actions but it wasn't working. My guess it's because according to http://blog.stevensanderson.com/2007/11/20/aspnet-mvc-pipeline-lifecycle/ querystring processing happens after the controller is instantiated.
So what would be a simple way to accomplish what I want? namely, being able to do something like setting a base controller property, or setting a session variable, from a querystring parameter anywhere in my site, without having to manually specify something in all the controller actions?
Override OnActionExecuting() your base controller or in an Action Filter as suggested by #jrummell. An Action Filter might be the way to go, but you would still need to decorate all of your controllers or create a base controller and decorate that with it.
public class PromoActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewBag.Promo = filterContext.HttpContext.Request.QueryString("Promo");
base.OnActionExecuting(filterContext);
}
}
[PromoActionFilter]
public class BaseController : Controller
{
... Some Actions ...
}

How can I overload ASP.NET MVC Actions based on the accepted HTTP verbs?

Wanted to use the same URL for a GET/PUT/DELETE/POST for a REST based API, but when the only thing different about the Actions is which HTTP verbs it accepts, it considers them to be duplicate!
"Type already defines a member called 'Index' with the same parameter types."
To which I said, so what? This one only accepts GET, this one only accepts POST... should be able to be co-exist right?
How?
That's not ASP.NET MVC limitation or whatever. It's .NET and how classes work: no matter how hard you try, you cannot have two methods with the same name on the same class which take the same parameters. You could cheat using the [ActionName] attribute:
[HttpGet]
[ActionName("Foo")]
public ActionResult GetMe()
{
...
}
[HttpPut]
[ActionName("Foo")]
public ActionResult PutMe()
{
...
}
[HttpDelete]
[ActionName("Foo")]
public ActionResult DeleteMe()
{
...
}
[HttpPost]
[ActionName("Foo")]
public ActionResult PostMe()
{
...
}
Of course in a real RESTFul application the different verbs would take different parameters as well, so you will seldom have such situations.
You may take a look at SimplyRestful for some ideas about how your routes could be organized.
While ASP.NET MVC will allow you to have two actions with the same name, .NET won't allow you to have two methods with the same signature - i.e. the same name and parameters.
You will need to name the methods differently use the ActionName attribute to tell ASP.NET MVC that they're actually the same action.
That said, if you're talking about a GET and a POST, this problem will likely go away, as the POST action will take more parameters than the GET and therefore be distinguishable.
So, you need either:
[HttpGet]
public ActionResult ActionName() {...}
[HttpPost, ActionName("ActionName")]
public ActionResult ActionNamePost() {...}
Or:
[HttpGet]
public ActionResult ActionName() {...}
[HttpPost]
public ActionResult ActionName(string aParameter) {...}
Another option is to have a single method that accepts all and distinguishes between HttpMethod and calls the appropriate code from there. E.g.
string httpMethod = Request.HttpMethod.ToUpperInvariant();
switch (httpMethod)
{
case "GET":
return GetResponse();
case "POST":
return PostResponse();
default:
throw new ApplicationException(string.Format("Unsupported HttpMethod {0}.", httpMethod));
}
As a workaround you can add to one of the methods an extra argument with a default value, just to bypass the limitation and be able to build.
Of course take in mind that this is not the most recommended way of doing things, and also you will have to make clear in your code (by the parameter name or via comments) that this is an extra argument just to allow it to build, and of course make sure that you have decorated your attributes correctly.

ASP.NET MVC: Can I say [Authorize Roles="Administrators"] on the Controller class, but have one public action?

I started off using the default project's AccountController, but I've extended/changed it beyond recognition. However, in common with the original I have a LogOn and LogOff action.
Clearly, the LogOn action must be accessible to everyone. However, since I've added lots of other actions to this controller (to create & edit users), I want 99% of the actions to require administrator role membership.
I could decorate all my actions with [Authorize Roles="Administrators"] but there's a risk I'll forget one. I'd rather make it secure by default, by decorating the controller class itself with that attribute, and then relax the requirement on my LogOn method. Can I do that?
(As in, can I do that out-of-the-box without creating custom classes, etc. I don't want to complicate things more than necessary.)
To override an controller Attribute at the Action level you have to create a custom Attribute and then set the Order property of your custom attribute to a higher value than the controller AuthorizeAttribute. I believe both attributes are then still executed unless your custom attribute generates a result with immediate effect such as redirecting.
See Overriding controller AuthorizeAttribute for just one action for more information.
So I believe in your case you will just have to add the AuthorizeAttribute on the Actions and not at the controller level. You could however create a unit test to ensure that all Actions (apart from LogOn) have an AuthorizeAttribute
You can use AuthorizeAttribute on your class
http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute.aspx
For relaxing you can implement for example a custom action filter attribute like this (I didn' test if it works).
public class GetRidOfAutorizationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// you can for example do nothing
filterContext.Result = new EmptyResult();
}
}
After way too much time, I came up with a solution.
public class OverridableAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var action = filterContext.ActionDescriptor;
if(action.IsDefined(typeof(IgnoreAuthorization), true)) return;
var controller = action.ControllerDescriptor;
if(controller.IsDefined(typeof(IgnoreAuthorization), true)) return;
base.OnAuthorization(filterContext);
}
}
Which can be paired with IgnoreAuthorization on an Action
public class IgnoreAuthorization : Attribute
{
}

Resources