Lars Thorén
Lars Thorén

Reputation: 553

Injecting a logger with constructor dependencies

I'm trying to refactor some code to use .NET Core dependency injection via mapping services in startup.cs. I would like to inject an IRequestDatabaseLogger here instead of newing it up. However it requires the context in the constructor. How can I achieve this? Is it even possible without an DI framework or even then?

    public class ActionFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var requestDatabaseLogger = new RequestDatabaseLogger(context);
            long logId = requestDatabaseLogger.Log();

            context.HttpContext.AddCurrentLogId(logId);

            base.OnActionExecuting(context);
        }
   }

Upvotes: 6

Views: 1274

Answers (3)

ssmith
ssmith

Reputation: 8962

You should use the TypeFilter to achieve this, and wrap the filter that has the dependency (in this case on a logger or a context) inside of the filter. I show a detailed example of this in my MSDN Article on ASP.NET Core Filters. The related source code is here (look at the ValidateAuthorExists filter).

Here's what it might look like in your scenario:

public class MyFilterAttribute : TypeFilterAttribute
{
    public MyFilterAttribute():base(typeof(MyFilterImpl))
    {
    }

    private class MyFilterImpl : IAsyncActionFilter
    {
        public MyFilterImpl( *inject dependencies here*)
        {}
    }
}

This is how you can use attributes in .NET Core while still injecting dependencies into the underlying action filter. I also cover this in my upcoming ASP.NET Core Quickstart course on DevIQ.com (look for it end of this month).

Upvotes: 2

Maarten
Maarten

Reputation: 22955

Inject a RequestDatabaseLoggerFactory in the constructor, which can be used to create a RequestDatabaseLogger instance.

public interface IRequestDatabaseLoggerFactory {
    IRequestDatabaseLogger Create(ActionExecutingContext context);
}
public class RequestDatabaseLoggerFactory : IRequestDatabaseLoggerFactory {
    public IRequestDatabaseLogger Create(ActionExecutingContext context) {
        return new RequestDatabaseLogger(context);
    }
}

public class ActionFilter : ActionFilterAttribute
{
    public ActionFilter(IRequestDatabaseLoggerFactory factory) {
        _factory = factory;
    }

    private readonly IRequestDatabaseLoggerFactory _factory;

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var requestDatabaseLogger = _factory.Create(context);
        long logId = requestDatabaseLogger.Log();

        context.HttpContext.AddCurrentLogId(logId);

        base.OnActionExecuting(context);
    }

}

Upvotes: 0

Steven
Steven

Reputation: 172865

However it requires the context in the constructor.

Letting the construction of application components depend on runtime data is an anti-pattern, as described here. That article describes how to solve these problems in general.

In your case this probably means that your component should depend on ASP.NET Core's IHttpContextAccessor abstraction instead, which is a pattern described in the referenced article.

Alternatively, as described in the article, you can pass through the required runtime data to the logger using it's Log method.

Upvotes: 8

Related Questions