OnActionExecuted being called twice in Web API - asp.net-mvc

I am trying to do some stuff after my controller is done with the action at OnActionExecuted.
However the method is called twice.
My filter method
public class TestFilter: ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
//do stuff here
}
}
and my controller
[TestFilter]
public class BaseController : ApiController
{
public LoginResponseDTO Login(LoginRequestDTO loginRequestDTO)
{
//do login stuff
}
}
when i try this filter, the onActionExecuted Method gets called twice which causes my action in the method to be applied twice to the response. I have searched for a reason but cannot find a solution.
Any Ideas?

The answer is from #Martijn comments above:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class TestFilter: ActionFilterAttribute
All credits goes to him.
(Note: I'll remove the post, if he decide to add the comment as answer)

For me the issue was I was calling /myApi/action which was redirecting to /myApi/action/ and this caused OnActionExecuted() to run twice.
I filtered out where filterContext.Result is RedirectResult within OnActionExecuted since I wasn't interested in running my code then. The HTTP status code showed as 200 on both the calls so filtering by that won't work.

You can override the AllowMultiple inside your ActionFilterAttribute, like so:
public override bool AllowMultiple { get { return false; } }
public override void OnActionExecuting(HttpActionContext actionContext)
{
//Your logic
}
That will stop your ActionFilter being called twice. Also check that it is not registered twice. Check out this stackoverflow answer to see more about that.
Do be aware that AttributeUsage attribute is a single-use attribute--it can't be applied more than once to the same class, as you will find in the remarks section of this.

If you have registered the custom filter in Global.asax.cs, like this:
GlobalConfiguration.Configuration.Filters.Add(new TestFilterAttribute());
Please revoke the attribute above your custom controller.

Make sure [BasicAuthenticateFilter] is not in "Controller" level as well as "Method" level. It will run twice.

Related

How to intercept all the ASP.NET WebApi controller action methods calls with Ninject interception for logging?

Our company has the need to log certain things each time one of our action methods of our ASP.NET WebApi controllers gets called. Since we use Ninject for the DI right now, we'd like to use it also for this purpose. This is what I have tried so far.
I have Ninject, Ninject.Extensions.Interception and Ninject.Extensions.Interception.DynamicProxy installed through NuGet and I have the following module
public class InterceptAllModule : InterceptionModule
{
public override void Load()
{
Kernel.Intercept(p => p.Request.Service.Name.EndsWith("Controller")).With(new TimingInterceptor());
}
}
Where TimingInterceptor is
public class TimingInterceptor : SimpleInterceptor
{
readonly Stopwatch _stopwatch = new Stopwatch();
protected override void BeforeInvoke(IInvocation invocation)
{
_stopwatch.Start();
}
protected override void AfterInvoke(IInvocation invocation)
{
_stopwatch.Stop();
string message = string.Format("[Execution of {0} took {1}.]",invocation.Request.Method,_stopwatch.Elapsed);
Log.Info(message + "\n");
_stopwatch.Reset();
}
}
Now, when I try to hook the module up with ninject kernel, and run my site
var kernel = new StandardKernel(new InterceptAllModule());
However, whenever there is a call coming in to one of the action method, it throws an error saying
Cannot instantiate proxy of class: MyApiController.
Could someone with experience point out what I'm doing wrong please? Thanks.
Update
So using your Code and Remo's excellent point about needing the action methods to be virtual and putting in an empty default constructor (just to placate dynamic proxy, keep your other constructor still) I have got both the action filter and the interception approach working.
I would say that as it stands your code will intercept potentially unwanted methods on the ApiController so you will probably also need to put some code in place to filter these out e.g. ExecuteAsync and Dispose.
My only other point is performance. Huge disclaimer these are just very basic tests (using the action filter approach each time to log the stats), I invite you to do your own(!)... but using the DynamicProxy interceptor I was getting a time of around 4 milliseconds per get request
[Execution of Get took 00:00:00.0046615.]
[Execution of Get took 00:00:00.0041988.]
[Execution of Get took 00:00:00.0039383.]
Commenting out the Interception code and using an Action filter I was getting sub millisecond performance:
[Execution of Get took 00:00:00.0001146.]
[Execution of Get took 00:00:00.0001116.]
[Execution of Get took 00:00:00.0001364.]
It is up to you whether this is actually an issue or concern but I thought I would point this out.
Previous Response
Have you rulled out using ActionFilters? This is the natural extension point for AOP on an MVC action.
If you were interested in methods other than the actual action on the controller then I would understand but I thought I would post a suggestion anyway.
Inspired by Are ActionFilterAttributes reused across threads? How does that work? and Measure Time Invoking ASP.NET MVC Controller Actions.
Updated to show the exclusion of the timer when method tagged. Inspiration from core WebApi framework specifically AllowAnonymousAttribute and AuthorizeAttribute
Register this globally so that all actions are monitored by this:
GlobalConfiguration.Configuration.Filters.Add(new TimingActionFilter());
Then:
public class TimingActionFilter : ActionFilterAttribute
{
private const string Key = "__action_duration__";
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (SkipLogging(actionContext))
{
return;
}
var stopWatch = new Stopwatch();
actionContext.Request.Properties[Key] = stopWatch;
stopWatch.Start();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (!actionExecutedContext.Request.Properties.ContainsKey(Key))
{
return;
}
var stopWatch = actionExecutedContext.Request.Properties[Key] as Stopwatch;
if(stopWatch != null)
{
stopWatch.Stop();
var actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;
Debug.Print(string.Format("[Execution of {0} took {1}.]", actionName, stopWatch.Elapsed));
}
}
private static bool SkipLogging(HttpActionContext actionContext)
{
return actionContext.ActionDescriptor.GetCustomAttributes<NoLogAttribute>().Any() ||
actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<NoLogAttribute>().Any();
}
}
And
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]
public class NoLogAttribute : Attribute
{
}
Now you can exclude the global filter using:
public class ExampleController : ApiController
{
// GET api/example
[NoLog]
public Example Get()
{
//
}
}
For anyone still lurking, the reason I wanted to use Ninject was so I could inject a logger (or anything else) into the interceptor, but I wanted to intercept all actions.
Mark's answer is perfect, but instead of registering globally using
GlobalConfiguration.Configuration.Filters.Add(new TimingActionFilter());
bind your filter with Ninject using
Kernal.BindHttpFilter<TimingActionFilter>(FilterScope.Action).
You'll need to create an appropriate contructor in the TimingActionFilter class.

How to use HandleError with model state errors

I want to use a custom action filter to handle specific exceptions from my service classes to populate the model state and then return the view.
For example, take my previous code:
public ActionResult SomeAction(SomeViewModel model)
{
try
{
_someService.SomeMethod(model);
}
catch (ServiceException ex)
{
ModelState.AddModelError(ex.Key, ex.ErrorMessage);
}
return View();
}
Basically, it would call a service, and if a ServiceException was thrown, it would know that there was an issue w/ the model data, and add the error to the ModelState, then just return the view. But I noticed some very un-DRY-like patterns, because I had this same try/catch code in every action method.
So, to DRY it up a bit, I basically created a new HandleServiceError action filter:
public class HandleServiceErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
((Controller)context.Controller)
.ModelState
.AddModelError(
((ServiceException)context.Exception).Key,
((ServiceException)context.Exception).ErrorMessage
);
context.ExceptionHandled = true;
}
}
Then simplified my action methods like so:
public ActionResult SomeAction(SomeViewModel model)
{
_someService.SomeMethod(model);
return View();
}
Problem is, once the action filter handles the error, it doesn't return to my action method. I sort of understand, under the hood, why this is happening. But I would still like to figure out a way to do what I'm trying to do.
Is this possible?
Thanks in advance.
UPDATE:
I tried the suggestions from the article Darin provided in his answer, but ran into issues trying to use constructor injection with the controller's model state.
For example, if you look at their Controllers\ProductController.cs code, they have the controller's empty constructor using a service locator to create the service, passing in the controller's ModelState at that point:
public ProductController()
{
_service = new ProductService(new ModelStateWrapper(this.ModelState),
new ProductRepository());
}
But if you look at the injected constructor, it assumes the ModelState will be injected into the constructor for the service:
public ProductController(IProductService service)
{
_service = service;
}
I don't know how to get CI to work with the current controller's ModelState. If I could figure this out, then this approach may work.
You could still return the corresponding view:
context.Result = new ViewResult
{
ViewName = context.RouteData.GetRequiredString("action")
};
You may also take a look at the following article for an alternative about how to perform validation at the service layer.

Get instance of ActionFilterAttribute in the method

I am newbie in ASP.NET MVC platform and I faced with the following problem.
I am using ActionFilterAttribute to do some routine work before and after action method run. The problems is that I need to get instance of the attribute in action method to read some properties which was set in OnActionExecuting method. For example
public class SomeController : Controller{
public SomeController(){ }
[Some]
public ActionResult Index(){
SomeModel = someRepository.GetSomeModel();
//get instance of some attribute and read SomeProperty
return View(SomeModel);
}
}
public class SomeAttribute : ActionFilterAttribute{
public int SomeProperty { get; set; }
public SomeAttribute(){ }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var parameters = filterContext.ActionParameters;
//Here to set SomeProperty depends on parameters
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//do some work
}
}
Any ideas?
Filter attributes must be designed to be thread-safe. The framework makes no guarantees that a single instance of your filter attribute will only service one request at a time. Given this, you cannot mutate attribute instance state from within the OnActionExecuting / OnActionExecuted methods.
Consider one of these as alternatives:
Use HttpContext.Items to store the value in OnActionExecuting, then read it from the action method. You can access HttpContext via the filterContext parameter passed to OnActionExecuting.
Put the property on the controller instead of the attribute, then have the OnActionExecuting method cast the controller to SomeController and set the property directly from within that method. This will work since the framework does by default guarantee that controller instances are transient; a single controller instance will never service more than one request.
Option 1: Your ActionFilter can add information to the ViewModel, e.g.
filterContext.Controller.ViewData["YourKey"] = "Value to add";
Option 2: You can put code in your base Controller class that finds all the attributes that have been applied to the method that is executing, and you can put them in a member variable that the Action method can then use.
e.g.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var attrs = filterContext.ActionDescriptor.GetCustomAttributes(true).OfType<Some>();
...
}
Edit: And as others have noted, trying to mutate the attribute isn't going to work.
Sorry, I do not believe this is possible. Since the value of SomeProperty must be based on parameters sent into the constructor of the attribute, it must be easy to calculate. I would suggest adding some static methods to get the value from within the action.

make sure each controller method has a ValidateAntiForgeryToken attribute?

Is there any way to centralize enforcement that every action method must have a "ValidateAntiForgeryToken" attribute? I'm thinking it would have to be done by extending one the "routing" classes.
Edit: Or maybe do some reflection at application startup?
Yes. You can do this by creating your own BaseController that inherits the Mvc Controller, and overloads the OnAuthorization(). You want to make sure it is a POST event before enforcing it:
public abstract class MyBaseController : Controller
{
protected override void OnAuthorization(AuthorizationContext filterContext)
{
//enforce anti-forgery stuff for HttpVerbs.Post
if (String.Compare(filterContext.HttpContext.Request.HttpMethod,
System.Net.WebRequestMethods.Http.Post, true) == 0)
{
var forgery = new ValidateAntiForgeryTokenAttribute();
forgery.OnAuthorization(filterContext);
}
base.OnAuthorization(filterContext);
}
}
Once you have that, make sure all of your controllers inherit from this MyBaseController (or whatever you call it). Or you can do it on each Controller if you like with the same code.
Sounds like you're trying to prevent "oops I forgot to set that" bugs. If so I think the best place to do this is with a custom ControllerActionInvoker.
Essentially what you want to do is stop MVC from even finding an action without a AntiForgery token:
public class MustHaveAntiForgeryActionInvoker : ControllerActionInvoker
{
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
var foundAction = base.FindAction(controllerContext, controllerDescriptor, actionName);
if( foundAction.GetCustomAttributes(typeof(ValidateAntiForgeryTokenAttribute), true ).Length == 0 )
throw new InvalidOperationException("Can't find a secure action method to execute");
return foundAction;
}
}
Then in your controller, preferably your base controller:
ActionInvoker = new MustHaveAntiForgeryActionInvoker();
Just wanted to add that custom Controller base classes tend to get "thick" and imo its always best practice to use MVC's brilliant extensibility points to hook in the features you need where they belong.
Here is a good guide of most of MVC's extensibility points:
http://codeclimber.net.nz/archive/2009/04/08/13-asp.net-mvc-extensibility-points-you-have-to-know.aspx
Ok, I just upgraded a project to MVC v2.0 here, and eduncan911's solution doesn't work anymore if you use the AuthorizeAttribute on your controller actions. It was somewhat hard to figure out why.
So, the culprit in the story is that the MVC team added the use of the ViewContext.HttpContext.User.Identity.Name property in the value for the RequestVerificationToken.
The overridden OnAuthorization in the base controller is executed before any filters on the controller action. So, the problem is that the Authorize attribute has not yet been invoked and therefore is the ViewContext.HttpContext.User not set. So the UserName is String.Empty whereas the AntiForgeryToken used for validation includes the real user name = fail.
We solved it now with this code:
public abstract class MyBaseController : Controller
{
protected override void OnAuthorization(AuthorizationContext filterContext)
{
//enforce anti-forgery stuff for HttpVerbs.Post
if (String.Compare(filterContext.HttpContext.Request.HttpMethod, "post", true) == 0)
{
var authorize = new AuthorizeAttribute();
authorize.OnAuthorization(filterContext);
if (filterContext.Result != null) // Short circuit validation
return;
var forgery = new ValidateAntiForgeryTokenAttribute();
forgery.OnAuthorization(filterContext);
}
base.OnAuthorization(filterContext);
}
}
Some references to the MVC code base:
ControllerActionInvoker#InvokeAuthorizationFilters() line 283. Same short circuiting.
AntiForgeryData#GetUsername() line 98. New functionality.
How about this?
[ValidateAntiForgeryToken]
public class MyBaseController : Controller
{
}

Returning an MVC ActionResult before the specific controller method is called

I have a base controller class from which my other controllers are inherited
public abstract class BaseController : Controller
{
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
...
}
}
During initialization I'm doing some setup, and there are a few cases where I'd want to short circuit the execution, jumping directly to the return of the ActionResult, skipping the execution of the actual Action method entirely. Something along these lines
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
if(specialCase)
{
ViewData[...] = specialCaseInformation;
return View("~/Shared/SpecialCase.aspx");
}
}
The intention would be to skip whatever ActionResult method was going to be called and replace it with my global special case page. But I don't think Initialize was meant for this.
What I think I need to do is create a seperate ActionFilterAttribute class, override the OnActionExecuting method, and if the specialCase comes up, construct a ViewResult object and assign it to the filterContext.Result property.
Am I going in the right direction with this, or should I be doing this differently?
Yes, an ActionFilterAttribute is exactly the right way. Look at HandleErrorAttribute.cs for an example.
Initialize is not the right way, as you say.

Resources