0

I am working on a asp.net MVC project. We want to log all our action method calls in this style:

namespace.controller.method(parameter1name: parameter1value, ... parameternname: parameternvalue)

Is this possible in C# or asp.net MVC with an attribute? My idea would be an ActionFilterAttribute but as I understand them, they are called before the actual method is called.

A few more details, why an ActionFilterAttribute doesn't work in this case. We want to log the method call with the full .net namespace, the classname, the original method name and all parameters.

Here is an example

namespace MyTestNamespace {

    public class HomeController {

        [ActionName("Index")]
        [HttpGet]
        public ActionResult MyCall(string username) {
            ViewBag.Great = "Hello " + username;
            return View();
        }
    }
}

The logging output should look like this:

MyTestNamespace.HomeController.MyCall(username: Knerd)

But the logging output I can get with an ActionFilterAttribute would like this:

MyTestNamespace.HomeController.Index(username: Knerd)
Knerd
  • 1,892
  • 3
  • 28
  • 55
  • how does a filter *not* fit the bill here? – DLeh Apr 07 '15 at 13:27
  • HttpModule would work also. There are a number of ways you can get the current route parameters out of the request. Would need to be mindful of performance in this case however. – timothyclifford Apr 07 '15 at 13:30
  • @DLeh, they don't give us the namespace and class name. – Knerd Apr 07 '15 at 13:39
  • 1
    your question is not clear. what does the namespace and class name have to do with anything? you'll need to expand on your question with what you need and why filters don't work in your case. – DLeh Apr 07 '15 at 13:40
  • @DLeh I added a few more details, I hope they help :) – Knerd Apr 07 '15 at 13:52

1 Answers1

3

You can recover most if not all of this information in an action filter. The only thing missing here would probably be namespace, but you should be able to find that by accessing the filterContext.Controller object and doing a bit of reflection on that.

public class MyFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var controllerName = routeData.Values["controller"];
        var actionName = routeData.Values["action"];

        foreach(var kvp in filterContext.ActionParameters)
            //log your params

        //thanks @Andy Nichols
        var fullControllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
    }
}

In most cases, the name of the route should match the name of the method. When it doesn't, you could set up some sort of global registry for these route mappings.

Below is an example of how you could do this. You're losing some route configurability here, but you could expand this more to allow that.

public static class RouteMappingConfig
{
    static RouteMappingConfig () { RouteMappings = new List<RouteMapping>(); }
    public static List<RouteMapping> RouteMappings { get; set; }
}
public class RouteMapping
{
    public class RouteMapping(string ctrl, string action, string method) { /*...*/ }
    public string Controller { get; set; }
    public string Action { get; set; }
    public string Method { get; set; }
}

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        //set up mappings
        RouteMappingConfig.RouteMappings.Add(new RouteMapping("MyController", "MyAction", "MyMethod"));

        foreach(var mapping in RouteMappingConfig.RouteMappings)
        {
            routes.MapRoute(
                name: mapping.Controller + "_" + mapping.Action,
                url: "{controller}/{action}/",
                defaults: new { controller = mapping.Controller, action = mapping.Action }
            );
        }
    }
}

public class MyFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var controllerName = routeData.Values["controller"];
        var actionName = routeData.Values["action"];

        //thanks @Andy Nichols
        var fullControllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
        var mapping = RouteMappingConfig.RouteMappings.SingleOrDefault(x => x.Controller == controllerName && x.Action == actionName);
        string methodName = actionName;
        if(mapping != null)
        {
            methodName = mapping.Method;
        }

        foreach(var kvp in filterContext.ActionParameters)
            //log your params

    }
}
DLeh
  • 23,806
  • 16
  • 84
  • 128
  • Nearly, by that method we get the name of the action. Which can be overriden by the `ActionNameAttribute`. That is what I try to work around so I get the classname. – Knerd Apr 07 '15 at 14:00
  • 1
    you can get the controller name including namespace using `filterContext.ActionDescriptor.ControllerDescriptor.ControllerType.FullName` – Andy Nichols Apr 07 '15 at 14:04
  • @knerd well this gets you 90% of the way there. you'll need to provide some more specific info with what you've tried in your filter and the info that you're unable to get – DLeh Apr 07 '15 at 14:26
  • @DLeh, like I said, what I tried is nearly your code. And it gives me the messages, that I posted in my question. The requirement just wants it a little different, not the name of the action should be logged. We want the method name and that is, as far as I can see it, not possible to get with an `ActionFilterAttribute`. – Knerd Apr 07 '15 at 14:29
  • if your filter is applied at the method level, you could do something like this to get the name of the method: http://stackoverflow.com/q/2168942/526704 – DLeh Apr 07 '15 at 14:37
  • @Dleh, that might be a way. But we would like to use it application wide, any idea about that? – Knerd Apr 07 '15 at 14:39
  • im guessing that you're right that this information isn't known in the attribute. in most cases, a method name should align with the name of the action. for the cases where it doesn't, perhaps you should set up some other structure that maps action -> method. Then use that in your `RouteConfig` and access it in your attribute to get the result of that mapping. – DLeh Apr 07 '15 at 14:44
  • thanks, hope this helps! imo, the method name shouldn't really matter that much. the action name is what really counts anyway. – DLeh Apr 07 '15 at 14:54