Dependency Injection with a Static Logger, Static Helper Class

Dependency injection with a static logger, static helper class

You can't inject a static logger. You have to either change it to an instance logger (if you can), or wrap it in an instance logger (that will call the static). Also it is fairly hard to inject anything to a static class (because you don't control the static constructor in any way) - that's why I tend to pass all the objects I want to inject as parameters.

How to inject dependency to static class

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.

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.
  • 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. Static constructors can't have any parameters and static properties lead to the Ambient Context anti-pattern (see section 5.3), and Temporal Coupling. 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.

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

In that case, 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.

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.

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. 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.

Use dependency injection in static class

You basically have two options:

  1. Change the class from static to an instance class and supply the dependency through Constructor Injection.
  2. Supply the dependency to the class's public method through Method Injection.

Here are examples for each option.

Option 1. Change the class from static to an instance class

Change the class from static to an instance class and supply IConfiguration through Constructor Injection. XHelper should in that case be injected into the constructor of its consumers. Example:

public class XHelper
{
private readonly IConfiguration config;

public XHelper(IConfiguration config)
{
this.config = config ?? throw new ArgumentNullException("config");
}

public TResponse Execute(string metodo, TRequest request)
{
string y = this.config.apiUrl;

return xxx;
}
}

2. Supply the IConfiguration to the Execute method through Method Injection.

Example:

public static class XHelper
{
public static TResponse Execute(
string metodo, TRequest request, IConfiguration config)
{
if (config is null) throw new ArgumentNullException("config");

string y = config.apiUrl;

return xxx;
}
}

Less favorable options

There are of course more options to consider, but I consider them all to be less favorable, because they would either cause code smells or anti-patterns.

For instance:

  • You might be inclined to make the DI Container accessible through a static method and call it from inside your static class, but this is an anti-pattern called Service Locator.
  • You could allow setting the static class's dependencies through static properties on that class, but this leads to the Ambient Context anti-pattern.
  • You could change the class to an instance class, but instead of using Constructor Injection make use of Property Injection, but this causes Temporal Coupling, which is a code smell.

ASP.NET Core Web API Logging from a Static Class

Solution is to have a static reference to the LoggerFactory in a utility static class initialized on startup:

/// <summary>
/// Shared logger
/// </summary>
internal static class ApplicationLogging
{
internal static ILoggerFactory LoggerFactory { get; set; }// = new LoggerFactory();
internal static ILogger CreateLogger<T>() => LoggerFactory.CreateLogger<T>();
internal static ILogger CreateLogger(string categoryName) => LoggerFactory.CreateLogger(categoryName);
}

Which you initialize on Startup.cs:

public Startup(ILogger<Startup> logger, ILoggerFactory logFactory, IHostingEnvironment hostingEnvironment)
{
_log = logger;
_hostingEnvironment = hostingEnvironment;
Util.ApplicationLogging.LoggerFactory = logFactory;//<===HERE
}

Then you can build a logger to use from your static class like so:

internal static class CoreJobSweeper
{
private static ILogger log = Util.ApplicationLogging.CreateLogger("CoreJobSweeper");
}

How to convert this static class method into a dependency injection? (Specific code included)

What should do this class? Sending email, right? So interface:

public interface IEmailSender
{
Task<bool> Send(Email email);
}

How we can implement it? Like this:

public class MyEmailSenderOne : IEmailSender
{
public static readonly string tokenUrl = ConfigurationManager.AppSettings["tokenUrl"];
public static readonly string tokenKey = ConfigurationManager.AppSettings["tokenKey"];

public async Task<bool> Send(Email email)
{
var http = new HttpClient();
http.DefaultRequestHeaders.Add("subscription-key", tokenKey);

try
{
await http.PostAsync(tokenUrl + "email", new StringContent(JsonConvert.SerializeObject(email), Encoding.UTF8, "application/json"));
}
catch (Exception e)
{
return false;
}

return true;
}
}

or

public class MyAnotherAwesomeEmailSender : IEmailSender
{
public async Task<bool> Send(Email email)
{
// send with different way
return true;
}
}

How we can inject this?

public class SomeClass
{
private IEmailSender _sender;
public SomeClass(IEmailSender sender)
{
_sender = sender;
}

public void Foo()
{
// do smth useful
_sender.Send(new Email());
}
}

UPD.

Because your email settings persistant (will not change during lifetime), and because this settings related ONLY to your implementation of IEMailSender, you should to inject them in your implementation. Just think about = why caller code (Controller) should know about how your implementation works?
So

public class MyEmailSenderOne : IEmailSender
{
private FormSettings _settings;

public MyEmailSenderOne(IOptions<FormSettings> settings)
{
_settings = settings.Value;
}

public async Task<bool> Send(Email email)
{
var http = new HttpClient();
http.DefaultRequestHeaders.Add("subscription-key", _settings.tokenApiKey);

try
{
await http.PostAsync(_settings.tokenApiUrl + "email", new StringContent(JsonConvert.SerializeObject(email), Encoding.UTF8, "application/json"));
}
catch (Exception e)
{
return false;
}

return true;
}
}

And, controller now dint know about any settings for your implementation, and it looks like

public class CommunicationsController : Controller
{
private IEmailSender _sender;

public CommunicationsController(IEmailSender sender)
{
_sender = sender;
}

public async Task<ActionResult> ContactUsFormSubmit(ContactUs request)
{
...
request.EmailSent = await _sender.SendEmail(new Email() { TemplateId = 3, Body = JsonConvert.SerializeObject(request) });
...
}
}

As you can see, controller is very clean now and you can easily change your implementation of IEmailSender to any other without changing Controller code. This is one of advantages of using DI.

.net core Dependency Injection vs rarely used static class

Does the instantiation of all those classes like services.AddSingleton<... slow down the startup process of the Website?

NO.

Registering them with the DI container does not affect performance, provided that they are simply registered and not initialized before adding them to the container.

Or are they instantiated only at the point where they are used for the first time?

Those singletons will only be initialized when or if the page/class that needs them are initialize


The fact that they are not used frequently is of no consequence. The fact that they are used at all in the system for it to perform its function is the reason why tried and practiced design principles should be applied.



Related Topics



Leave a reply



Submit