1

I'd like to perform some work per web request to a .NET MVC Web Application. Specifically, I'd like to make a log entry into a DB for each page load.

Performing this work in an overridden Controller class Controller.Initialize() method doesn't work, because a new controller is created with every call to @Html.Action(). Thus, if a child action is called from a view, then I end up double logging--which isn't what I want.

How can I insert some work into the MVC Lifecycle, such that it executes once per page request?

Community
  • 1
  • 1
Michael R
  • 1,547
  • 1
  • 19
  • 27

2 Answers2

4

You can use either OnActionExecuting or OnActionExecuted.

For example,

public class HomeController : Controller
{
    [LogActionFilter]
    public ActionResult Index()
    {
        return View();
    }
}

public class LogActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // If you register globally, you need this check.
        if (!filterContext.IsChildAction)
        {
           Log("OnActionExecuting", filterContext.RouteData);
        }
        base.OnActionExecuting(filterContext);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // If you register globally, you need this check.
        if (!filterContext.IsChildAction)
        {
           Log("OnActionExecuted", filterContext.RouteData);
        }
        base.OnActionExecuted(filterContext);
    }

    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");
    }
}
Win
  • 61,100
  • 13
  • 102
  • 181
  • Hi @Win, that may work; I had considered creating a new ActionFilterAttribute. But I believe I'd have to decorate (apply this new attribute) to numerous existing controller actions. Is this right? – Michael R Jan 21 '15 at 23:50
  • Yes. Ideally, you just add LogActionFilter to important Action methods only. In other words, you do not want to log each and every action methods, because logging is expensive. – Win Jan 21 '15 at 23:52
  • @Michael, you don't have to apply the attribute to every controller, you can register it globally. – Craig W. Jan 21 '15 at 23:58
  • @Michael Craig is correct. If you register globally, make sure you check **IsChildAction**. I updated the answer. – Win Jan 22 '15 at 00:06
  • @Win, Creating a new `ActionFilterAttribute` looks like a good solution as well. :) @Craig W. I didn't know that I could register it globally. Thanks for your help! – Michael R Jan 22 '15 at 02:23
2

You can check in the Initialize if the action is a Child Action.

ControllerContext.IsChildAction
Romias
  • 13,783
  • 7
  • 56
  • 85
  • Attempting that `if (!ControllerContext.IsChildAction) { }` snippet in `protected override void Initialize()` doesn't work because ControllerContext is null. – Michael R Jan 21 '15 at 23:45
  • Romias, I explored your suggestion again. I noticed that `ControllerContext.IsChildAction` _is_ available in `protected override void OnActionExecuted()`. – Michael R Jan 22 '15 at 00:12
  • Instead of doing it in Initialize... create a GlobalFilter, and register it in Global.asax – Romias Jan 22 '15 at 00:12
  • OnActionExecuted is triggered just before start rendering the view, and after you passed the Model to the View(model). So in that moment you can log info of the model itself (serialize it for example). – Romias Jan 22 '15 at 00:14
  • Thanks. Using `ControllerContext.IsChildAction` in `protected override void OnActionExecuted()` is working for me. – Michael R Jan 22 '15 at 02:20
  • Of course, it also fires for actions which are not rendering views. – John Saunders Jan 22 '15 at 02:40