I have the following action method:
public class HomeController : Controller
{
[ProfileAction]
[CustomAction]
public string FilterTest()
{
//Does not get executed
return "This is the FilterTest action";
}
}
Here's the code for the filters:
public class CustomActionAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
//This is executed first
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
//This is executed third
}
}
public class ProfileActionAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
//This is executed second
filterContext.Result = new HttpNotFoundResult();
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
//this does not execute
}
}
According to Microsoft the OnActionExecuted() is executed after a controller action is executed.
The action method FilterTest() is never called because I set the Result property in ProfileActionAttribute.OnActionExecuting().
Why does CustomActionAttribute.OnActionExecuted() get called if the action method never even gets called?
Related
Assume this ActionFilter:
public class MemberAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (filterContext.HttpContext.Session["someValue"] == null)
{
filterContext.HttpContext.Response.Redirect("/Error");
}
}
}
And assume this base controller:
[Member]
public class BaseController : Controller
{
protected int _memberID;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
_memberID = int.Parse(requestContext.HttpContext.Session["someValue"].ToString());
}
}
In a controller that extends BaseController I have this action:
[HttpPost]
public ActionResult Create(PlayerCreateVM vm)
{
if(!ModelState.IsValid)
{
return View(vm);
}
vm.Player.MemberID = _memberID;
_playerManager.Create(vm.Player.ToModel());
return RedirectToAction("Index");
}
As you can see in the action the creation of a 'player' in this case relies on _memberID not being null. Hence the ActionFilter that is applied to the BaseController. None of the actions in the sub classed Controllers will fire unless this value is not null.
But, the BaseControllers Initialize() and the BaseController's constructor still fire. Is there an equivelant type of Filter that actually prevents the constructor and its base from even being constructed.
I created an attribute class, here is the code
namespace ZDemo.Validate1
{
public class ValidateRequest : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/*further code*/
}
base.OnActionExecuting(filterContext);
}
}
}
And added it as an attribute to some actions in different controllers
[Validate1.ValidateRequest]
public ActionResult Add()
{
return View();
}
But when I run this code with a debugger it is not being called at all.
What am I doing wrong?
Call base.OnActionExecuting(filterContext); at the beginning of the method:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
//Your code here
}
Here is a sample action filter. We know that when we write an action filter then we need to decorate the controller with an attribute like this, to use it for any controller.
I like to know whether there is any way to write an action filter which will work for all controllers in way that I do not need to decorate all the controllers with an action filter attribute. Any ideas?
[LogActionFilter]
public class HomeController : Controller
{}
public class LogActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Log("OnActionExecuting", filterContext.RouteData);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
Log("OnActionExecuted", filterContext.RouteData);
}
private void Log(string methodName, RouteData routeData)
{
var controllerName = routeData.Values["controller"];
var actionName = routeData.Values["action"];
var message = String.Format("{0} controller:{1} action:{2}", methodName, controllerName, actionName);
Debug.WriteLine(message, "Action Filter Log");
}
}
public class LogActionFilterAttribute : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
Log("OnActionExecuted", filterContext.RouteData);
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
Log("OnActionExecuting", filterContext.RouteData);
}
private void Log(string methodName, RouteData routeData)
{
var controllerName = routeData.Values["controller"];
var actionName = routeData.Values["action"];
var message = String.Format("{0} controller:{1} action:{2}", methodName, controllerName, actionName);
Debug.WriteLine(message, "Action Filter Log");
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalFilters.Filters.Add(new LogActionFilterAttribute());
}
}
For your scenario you can just make a Custom BaseController and put your [LogActionFilter] attribute on Custom Basecontroller and inherit all your Controllers from Custom Basecontroller as shown below :
[LogActionFilter]
public class MyBaseController : Controller
{
}
public class MyOtherController : MyBaseController //<----instead of using Controller as base use MyBaseController as base class
{
public ActionResult Index()
{
// ...
}
}
The advantage of this approach is that you have to put your custom [LogActionFilter] attribute only at one place i.e. only on Custom BaseController.
If you're already subclassing from a base controller, you don't need a filter attribute or to register anything. You can just override the desired methods, e.g.,
public class BaseController : Controller {
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
ViewBag.SomeValue = "glorp frob blizz nunk";
// Access the controller, parameters, querystring, etc. from the filterContext
base.OnActionExecuting(filterContext);
}
}
Then, every controller that subclasses from it will run that method:
public sealed class GlorpController : BaseController {
public ActionResult Index() => View();
}
The view will quite happily see the ViewBag.SomeValue value.
I have a BaseController in which I put in some data in the ViewData collection by overriding OnActionExecuting.
Now i have an Action in a ChildController that doesn't need that view data.
For that purpose I created an DontPopulateViewData ActionFilterAttribute that sets a bool on the BaseController that prevents the BaseController from populating the viewdata.
Problem: the ActionFilters OnActionExecuting method is called after the one in BaseController and not before.
Will ActionFilters always be called before overridden OnActionExecuting in base controllers and is there a way to get around this?
In addition to what Marwan Aouida posted and suggested (using an ActionFilter on the base class), I don't think you're going to be able to create an ActionFilter that executes before the OnActionExecuting() overload on the base class. The following code:
[MyActionFilter(Name = "Base", Order = 2)]
public class MyBaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
Response.Write("MyBaseController::OnActionExecuting()<br>");
base.OnActionExecuting(filterContext);
}
protected override void Execute(System.Web.Routing.RequestContext requestContext)
{
requestContext.HttpContext.Response.Write("MyBaseController::Execute()<br>");
base.Execute(requestContext);
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
Response.Write("MyBaseController::OnActionExecuted()<br>");
base.OnActionExecuted(filterContext);
}
}
public class MyActionFilter : ActionFilterAttribute
{
public string Name;
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Response.Write("MyActionFilter_" + Name + "::OnActionExecuted()<br>");
base.OnActionExecuted(filterContext);
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Response.Write("MyActionFilter_" + Name + "::OnActionExecuting()<br>");
base.OnActionExecuting(filterContext);
}
}
public class MyTestController : MyBaseController
{
[MyActionFilter(Name = "Derived", Order = 1)]
public void Index()
{
Response.Write("MyTestController::Index()<br>");
}
}
produces this output:
MyBaseController::Execute()
MyBaseController::OnActionExecuting()
MyActionFilter_Derived::OnActionExecuting()
MyActionFilter_Base::OnActionExecuting()
MyTestController::Index()
MyActionFilter_Base::OnActionExecuted()
MyActionFilter_Derived::OnActionExecuted()
MyBaseController::OnActionExecuted()
The ActionFilterAttribute class has a property called "Order" which you can use to set the order in which the Action Filters are executed.
In your case you have to set the order of the Filter Attribute in the BaseController to 2 and the Filter Attribute in the DerivedController to 1:
[MyFilter(Order=2)]
public class BaseController:Controller
{
public ActionResult MyAction() {
}
}
[MySecondFilter(Order=1)]
public class DerivedController:BaseController
{
public ActionResult AnotherAction() {
}
}
Read this for more infos: http://msdn.microsoft.com/en-us/library/dd381609.aspx
Note: I didn't test this.
If you have a model-bound parameter in an action method, how can you get to that parameter in an action filter?
[MyActionFilter]
public ActionResult Edit(Car myCar)
{
...
}
public class MyActionFilterAttribute : ActionFilterAttribute
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
//I want to access myCar here
}
}
Is there anyway to get myCar without going through the Form variables?
Not sure about OnActionExecuted but you can do it in OnActionExecuting:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// I want to access myCar here
if(filterContext.ActionParameters.ContainsKey("myCar"))
{
var myCar = filterContext.ActionParameters["myCar"] as Car;
if(myCar != null)
{
// You can access myCar here
}
}
}
}