58

In my application I regularly want to write log messages to disk. I created a simple logger class and it's constructed using Dependency Injection, as follows:

var logger = new LogService(new FileLogger());
logger.WriteLine("message");

But this now means that every class in my system that needs to log, needs to get this LogService injected into it, which seems redundant. I, therefore, like to make the LogService static instead. This way it doesn't need to be injected into consuming classes.

But here comes the problem. If I make a logger class static, there is no way to inject dependency by constructor into that static class.

So, I changed my LogService to this.

public static class LogService()
{
    private static readonly ILoggable _logger;
    static LogService()
    {
         _logger = new FileLogger();
    }
    
    public static void WriteLine(string message) ...
}

This feels weird to me. I think this is not DI anymore.

What's the best way for me to inject dependencies into a static class?

Steven
  • 166,672
  • 24
  • 332
  • 435
Masuri
  • 894
  • 2
  • 9
  • 19
  • 8
    the fact you want to inject a dependency into a static class suggests you might have the wrong approach. – Mitch Wheat Sep 14 '18 at 05:27
  • 3
    DI systems are not only more than capable of injecting constructor parameters, but more importantly make certain objects appear as if they are _singletons_ - the net result of static classes even if they are not actually objects –  Sep 14 '18 at 05:54
  • 3
    By making a class static, you're stating it's life-cycle. Moving lifecycle management away from the class you adhere single responsibility principle. So avoid using static classes, especially if they retain some internal state. – stop-cran Sep 14 '18 at 06:24

5 Answers5

95

Dependency Injection, as a practice, is meant to introduce abstractions (or seams) to decouple volatile dependencies. A volatile dependency is a class or module that, among other things, can contain nondeterministic behavior or in general is something you which to be able to replace or intercept.

For a more detailed discussion about volatile dependencies, see section 1.3.2 of this freely readable introduction of my book.

Because your FileLogger writes to disk, it contains nondeterministic behavior. For this reason you introduced the ILoggable abstraction. This allows consumers to be decoupled from the FileLogger implementation, and allows you—later on—when such requirement comes in, to easily swap this FileLogger implementation with a SqlLogger implementation that logs to a SQL database, or even have an implementation that forwards the call to both the FileLogger or the SqlLogger.

To be able to successfully decouple a consumer from its volatile dependency, however, you need to inject that dependency into the consumer. There are three common patterns to choose from:

  • Constructor Injection—Dependencies are statically defined as list of parameters to the class's instance constructor.
  • Property Injection—Dependencies are injected into the consumer via writable instance properties. This pattern is sometime also called 'setter injection', especially in the Java world.
  • Method Injection—Dependencies are injected into the consumer as method parameters.

Both Constructor Injection and Property Injection are applied inside the startup path of the application (a.k.a. the Composition Root) and require the consumer to store the dependency in a private field for later reuse. This requires the constructor and property to be instance members, i.e. non-static. Constructor Injection is typically preferred over Property Injection, because Property Injection leads to Temporal Coupling. Static constructors can't have any parameters and static properties lead to the Ambient Context anti-pattern (see section 5.3)—this hinders testability and maintainability.

Method injection, on the other hand, is applied outside the Composition Root and it does not store any supplied dependency, but instead merely uses it. Here's an example from the earlier reference:

// This method calculates the discount based on the logged in user.
// The IUserContext dependency is injected using Method Injection.
public static decimal CalculateDiscountPrice(decimal price, IUserContext context)
{
    // Note that IUserContext is never stored - only used.
    if (context == null) throw new ArgumentNullException("context");
    decimal discount = context.IsInRole(Role.PreferredCustomer) ? .95m : 1;
    return price * discount;
}

Method injection is, therefore, the only of the three patterns that can be applied to both instance and static classes.

When applying Method Injection, the method's consumer must supply the dependency. This does mean, however, that the consumer itself must have been supplied with that dependency either through Constructor, Property, or Method Injection. For example:

public class ProductServices : IProductServices
{
    private readonly IProductRepository repository;
    private readonly IUserContext userContext;

    public ProductServices(
        IProductRepository repository,
        IUserContext userContext) // <-- Dependency applied using Ctor Injection
    {
        this.repository = repository;
        this.userContext = userContext;
    }

    public decimal CalculateCustomerProductPrice(Guid productId)
    {
        var product = this.repository.GetById(productId);

        return CalculationHelpers.CalculateDiscountPrice(
            product.Price,
            this.userContext); // <-- Dep forwarded using Method Injection
    }
}

Your example of the static LogService that created FileLogger inside its constructor is a great example of tightly coupled code. This is known as the Control Freak anti-pattern (section 5.1) or in general can be seen as a DIP violation—this is the opposite of DI.

To prevent tight coupling of volatile dependencies, the best is to make LogService non-static and inject its volatile dependencies into its sole public constructor:

public class LogService
{
    private readonly ILoggable _logger;

    public LogService(ILoggable logger)
    {
         _logger = logger;
    }
    
    public void WriteLine(string message) ...
}

This likely defeats the purpose of your LogService class, because now consumers would be better of by injecting ILoggable directly instead of injecting LogService. But this brings you back to the reason why you probably wanted to make that class static in the first place, which is that you have many classes that need to log and it feels cumbersome to inject ILoggable into all those constructors.

This, however, might be caused by another design issue in your code. To understand this, you might want to read through this q&a to get some sense of what design changes you can make that allows less classes to depend on your logger class.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • That was a great answer. I want to learn these concepts in a "brain-friendly" way. Can anyone point me to a good resource? Basically I want to understand whatever is said in the answer in a 'explain like I'm five' way. – Debugger Mar 10 '22 at 10:03
  • 3
    I would say the best resource on these concepts is the book [Dependency Injection Principles, Practices, and Patterns](https://mng.bz/BYNl), which I coauthored. I certainly consider it "brain-friendly", but certainly not suited for five year olds. It expects some experience with software development. – Steven Mar 10 '22 at 13:09
  • Why not make the logging service an actual service? Isn't that the point of a disconnected architecture (service architecture)? What if you DONT want the caller of the service to determine what that service does? Wouldn't it be better to keep those details away and let the service only provide an interface (hiding the details of it's operation)? Isn't this the point of decoupling in the first place? – MC9000 Jul 30 '22 at 21:12
  • @MC9000: The design with the concrete `LoggerService` interface isn't the most intuitive to me. Yu typically don't want classes to depend on the concrete `LoggerService`, but rather hide it behind some sort of `ILogger` abstraction. But, unfortunately, the question gives too little details about the `LoggerService` implementation for me to make strong statements about that. From a DI perspective, depending on a concrete class would be fine in case it by itself is a Stable Dependency and is part of the same module as the consuming classes. – Steven Jul 31 '22 at 10:25
20

It doesn't make sense to use dependency injection (DI) with a static class. Instead of DI, simply add an initialization method to your static class and pass in the dependency.

public static class LogService
{
    private static ILoggable _logger;

    public static ILoggable Logger
    {
        get
        {
             return _logger;
        }
    }

    public static void InitLogger(ILoggable logger)
    {
         _logger = logger;
    }
}

To use the logger, just make sure to call InitLogger() first:

LogService.InitLogger(new FileLogger());
LogService.Logger.WriteLine("message");
TimChang
  • 2,249
  • 13
  • 25
  • 1
    Doesn't the InitLogger method need a return type? This is not a constructor... Am I missing something? – Jazimov Dec 29 '20 at 14:42
  • @Jazimov its return type is void. – Ch Usman Jul 06 '22 at 06:30
  • Note that this solution is an implementation of the [Ambient Context anti-pattern](https://freecontent.manning.com/the-ambient-context-anti-pattern/), which I mentioned in my answer. Please familiar yourself with the downsides of this pattern before deciding whether to apply it or not. – Steven Aug 29 '23 at 07:46
4

You could use Lazy initialization for any object you need to inject to a static class.

https://learn.microsoft.com/en-us/dotnet/api/system.lazy-1?view=net-5.0

This would allow you to pass around static objects that can be shared across running instances and other classes/methods that need to use those objects. An example would be an HttpClient that you want to share across your entire application. You can lazy initialize the HttpClient inside of a static class and refer to the static class to get the HttpClient.

Here's another example using a CosmosDB client: https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections?tabs=csharp#azure-cosmos-db-clients

brennfoster
  • 152
  • 1
  • 3
  • How is lazy initialization equivalent to dependency injection? – John C Jun 09 '22 at 23:08
  • @JohnC OP stated that he wanted to use a static class. Lazy initialization would allow him to use a static class that can be used anywhere in the application as if you were injecting a singleton implementation of the class. – brennfoster Jun 14 '22 at 19:24
  • 1
    Please share some sample code that backs your claim, relevant to ops example. I looked through all those links and don't see how this would be equivalent to DI for a static method. – John C Jun 14 '22 at 21:34
0

You can find something similar to singleton by using method injector

Private static Iconfiguration config;
Public static Iconfiguration Configuration
{
get{
    if(config == null){
     var builder = new Configuration builder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("app setting.josn");
     config = builder.Build();
     return = config;
    }
   else return config;
   }
}

Then in target method call Configuration Good luck

sep7696
  • 494
  • 2
  • 16
0

use this code

using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection;

namespace SJVS.Framework.Logs

public class LogService {
    public static ILogger Logger =>
        new InjectScopeService<ILogContainer<LogContainer>>()
        .Inject(ApplicationBuilder).Logger;
    public static IApplicationBuilder ApplicationBuilder { get; set; } } public interface ILogContainer<T> {
    ILogger<T> Logger { get; } } public class LogContainer : ILogContainer<LogContainer> {
    public ILogger<LogContainer> Logger { get; set; }
    public LogContainer(ILogger<LogContainer> logger)
    {
        Logger = logger;
    } 
 }

public interface IInjectScopeService<TService> {
    TService Inject(IApplicationBuilder applicationBuilder); } public class InjectScopeService<TService>: IInjectScopeService<TService> {
    public TService Inject(IApplicationBuilder applicationBuilder)
    {
        using var scope = applicationBuilder.ApplicationServices.CreateScope();
        var service = scope.ServiceProvider.GetRequiredService<TService>();
        return service;
    } 
 }

and set LogService.ApplicationBuilder in Program.cs :

LogService.ApplicationBuilder = app;
Mahyar Mottaghi Zadeh
  • 1,178
  • 6
  • 18
  • 31