Pass Objects to ActionFilter Constructor in MVC C#

1.2k views Asked by At

I am trying to create custom log filter in my MVC application. Following is my code

public class LoggerAttribute: ActionFilterAttribute
    {

        private readonly IHttpLogService _httpLogService;
        private readonly ILogService _logService;
        public LoggerAttribute(IHttpLogService httpLogService, ILogService logService)
        {
            _httpLogService = httpLogService;
            _logService = logService;
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            LogDetails(filterContext);
        }

        private void LogDetails(ActionExecutingContext filterContext)
        {
            try
            {
                HttpLogService httpService = new HttpLogService();
                var httplogger = new LogMetaData()
                {
                    RequestParams = filterContext,
                    ResponseParams  = filterContext
                };
                _httpLogService.Emit("source", "", "Name", httplogger);
            }
            catch (Exception ex)
            {
                _logService.Emit("Error", "token", "Error encountered while trying to execute the request.", ex);
                throw new Exception("An error occurred. Please try again later.");
            }
        }
    }

In the above code, I was trying to pass the service instance to my filter attribute. How can I achieve passing an instance to my custom filter attribute?

2

There are 2 answers

3
Andrew On

Add public properties and set them in the attribute like Name property here:

[DeserializeAs(Name = "MAIL")]

Like that:

public class LoggerAttribute: ActionFilterAttribute
{

    private readonly IHttpLogService _httpLogService;
    private readonly ILogService _logService;
    public LoggerAttribute(IHttpLogService httpLogService, ILogService logService)
    {
        _httpLogService = httpLogService;
        _logService = logService;
    }

    public string CustomProperty { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        LogDetails(filterContext);
    }

    private void LogDetails(ActionExecutingContext filterContext)
    {
        try
        {
            HttpLogService httpService = new HttpLogService();
            var httplogger = new LogMetaData()
            {
                RequestParams = filterContext,
                ResponseParams  = filterContext
            };
            _httpLogService.Emit("source", "", "Name", httplogger);
        }
        catch (Exception ex)
        {
            _logService.Emit("Error", "token", "Error encountered while trying to execute the request.", ex);
            throw new Exception("An error occurred. Please try again later.");
        }
    }
}

and set it:

[Logger(CustomProperty="YourValue")]
0
trademark On

I did this a while back on an ASP.NET MVC project with Ninject for DI. Andrew's April 10th comment on his own answer is the right direction, but here's what that might look like for you (example uses Ninject, but you can adapt to whatever DI you're using).

  1. Technically your attribute is ok, but as a matter of practice, you should define an attribute that has no behavior that simply ties to a filter where the behavior lives. I've adapted yours to fit this best-practice as follows:
public class LoggerAttribute : ActionFilterAttribute {}

public class LoggerFilter : IActionFilter
{

    private readonly IHttpLogService _httpLogService;
    private readonly ILogService _logService;
    public LoggerAttribute(IHttpLogService httpLogService, ILogService logService)
    {
        _httpLogService = httpLogService;
        _logService = logService;
    }

    // Code removed for brevity
}
  1. In Global.asax.cs, you need to instantiate your service (or get one from the factory if you have one).
namespace MyApp.Web
{
    public class MvcApplication : NinjectHttpApplication
    {
        private static readonly IHttpLogService _httpLogService = someFactory.GetLogService();
        // Or if you don't use a factory
        // private static readonly IHttpLogService _httpLogService = new MyLogServiceImplementation();
        private static readonly ILogService _logService = new MyLogService();

        // Other stuff like OnApplicationStarted() here

        protected override IKernel CreateKernel()
        {
            var kernel = new StandardKernel();
            kernel.BindFilter<LoggerFilter>(FilterScope.Action, 0)
                .WhenActionMethodHas<LoggerAttribute>()
                .WithConstructorArgument("httpLogService", _httpLogService)
                .WithConstructorArgument("logService", _logService);
        }
    }
}

The key part is where we .WithConstructorArgument() and that syntax will vary by DI package.

Also see my more detailed answer here concerning a similar question/structure.