I'm not positive, but I think this is an MVC routing problem. I still don't fully understand how routing works in MVC.
Lets say I have several action methods that I've applied a custom filter attribute to:
[MyAttribute]
public ActionResult Method1()
{
...
}
[MyAttribute]
public ActionResult Method2()
{
...
}
These methods normally don't accept any parameters. Unfortunately the requirements have changed and now all of these methods might be receiving an optional parameter called "MyParameter". If the parameter is passed in then a property in the ViewBag will be set.
In the actual project, there are ~70 of these methods. I was hoping to do it via the "MyAttribute" filter, because it already exists. So I would add something like this to the "MyAttribute" filter:
public class MyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
... // other existing code
// If a certain parameter was passed, I want to set a ViewBag property.
if( filterContext.ActionParamenters.ContainsKey("MyParameter") )
filterContext.Controller.ViewBag.MyProperty = filterContext.ActionParamenters["MyParameter"];
}
}
But unless the method's definition has "MyParameter" in it as a parameter, it doesn't see it in the ActionParameters collection. This is what makes me think it is a routing issue.
I would really prefer to not need to add the argument to all of the methods, but if there's no other way to then I can. Any suggestions or ideas would be appreciated.
I found and went with this:
ViewBag.MyProperty = filterContext.HttpContext.Request.Prams["MyParameter"];
Will this won't work in action filter?
ViewBag.MyProperty =
filterContext.Controller.ValueProvider.GetValue("MyParameter").AttemptedValue;
Related
Some very basic question.
When to initialize or assign in OnActionExecuting?
Very simple scenario:
public partial class OrderController : DefaultController
{
private int customerId = 0;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
customerId = WebSecurity.CurrentUserId;
base.OnActionExecuting(filterContext);
}
}
Should I assign customerId in OnActionExecuting so I can reuse it in code in each action?
Readability
Eliminating repeating
or should I just assign it in separate actions each time?
We do not need this assignment or in case of initialization of some object in each action)?
the answer is, of course, it depends on your team's coding style and other circumstances.
Another option you haven't considered is creating a custom action filter to contain this logic, and reuse it with only one line of code above the action-method signature.
Then if you decide to apply it to all/any action-methods, you can apply it to the controller, or to a base-controller even.
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 ...
}
I have bunch of action-methods that need to verify the ownership of the orderId passed to the action something like:
public ActionResult CancelOrder(int orderId) {
If (!MyDatabase.VerifyOwnership(orderId, User.Identity.Name) return View("You are an imposter!");
// ...
}
What's an easy way to verify orderId belongs to User.IdentityName without having to copy/paste same lines over and over?
I have tried ActionFilterAttribute but it doesn't have access to the context (MyDatabase object for example). What's a good way to handle this?
" but it doesn't have an access to the context"
Sure it does:
public class VerifyOwner : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var myController = (MyControllerType)filterContext.Controller;
if (!myController.MyDatabase.VerifyOwnership(orderId, User.Identity.Name)
//do what you do
base.OnActionExecuting(filterContext);
}
}
All you have to do is cast the Controller property to your controller type. This get really easy is you have a custom base Controller all your Controllers inherit from. Then set that base controller to have the MyDatabase property and you have an easy time using this attribute across multiple controllers.
Your controller seems to have access to your context. Therefore if you use an action filter attribute that implements IAuthorizationFilter you can cast the filterContext.Controller in the OnAuthorization method to your controller type and be able to do what you set out to in the first place. (Which I reckon is the way to go!)
Kindness,
Dan
I asked a question earlier today about ActionFilters in ASP.Net MVC. It turned out my problem was really that my ActionFilter is not even running. Among other things I read this article, and I can't find anything he does that I don't.
This is my code:
// The ActionFilter itself
public class TestingIfItWorksAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.TempData["filter"] = "it worked!";
base.OnActionExecuting(filterContext);
}
}
// The Controller Action with the filter applied
[TestingIfItWorks]
public ActionResult Test()
{
var didit = TempData["filter"];
return View();
}
A breakpoint in the filter method is never hit when I debug, and TempData["filter"] holds a null value when the view is rendered.
Why is this not working?
In case it's helpful to anyone using MVC 4/5:
ActionFilters don't run if you get the namespace of your ActionFilterAttribute or IActionFilter wrong: https://stackoverflow.com/a/13710468/188926
Use System.Web.Http.Filters for Web API, System.Web.Mvc for standard MVC actions.
As in the question, the filter attribute will simply be ignored (no error) if you get it wrong, which makes it difficult to diagnose.
Based on your comments to another answer
When testing via unit tests, the filter is not invoked. If you want to invoke the filter then you'll need mimic the ControllerActionInvoker. It's probably better, to test the filter itself in isolation, then use reflection to ensure that the filter is applied to your action with the correct attributes. I prefer this mechanism over testing the filter and action in combination.
Original
Surely you need an override on your method otherwise you aren't actually replacing the method on the base class. I would have expected the compiler to complain that you needed either a new or override on it. If you don't include the override keyword, it will behave as if you used new. Since the framework invokes it as an ActionFilterAttribute, this means that your method will never get called.
Quoting from MSDN:
If the method in the derived class is
not preceded by new or override
keywords, the compiler will issue a
warning and the method will behave as
if the new keyword were present.
In addition to what tvanofosson said, your action method isn't actually rendering anything to the view. Does your view have a <%=TempData["Filter"].ToString()%> statement or something similar?
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()