0

My team is pretty big on DependencyInjection. Personally I'm a bit too far out of the loop lately to really judge the correct usage of this. But I do see more and more code like this:

public AuthenticationApi(ILogger<AuthenticationApi> logger,
                         HttpClient httpClient,
                         IJsonConverter jsonConverter,
                         IDtoConverter dtoConverter) : base(logger, httpClient, jsonConverter)
{
    _dtoConverter = dtoConverter;
}

And then this multiplies across the code, where half of our code is just calling constructors with endless DependencyInjection related stuff. My team told me, that's the way of .NET Core. And yes, answers like this confirm it:

ILogger and DependencyInjection in ASP.NET Core 2+

And discussions like that would be more along my gut feeling that things like logging, etc. should just be transparent and not handled in endless DependencyInjection constructor chains:

https://softwareengineering.stackexchange.com/questions/371722/criticism-and-disadvantages-of-dependency-injection

In another place (unfortunately I can't find the article anymore), I read that this constructor issues are mainly a result of badly implemented Service Factories.

Thoughts on the topic are appreciated.

Based on the discussion below, this is the baseclass and uses both the Logger and the HttpClient:

internal class ApiBase
{
    private readonly ILogger _logger;
    private readonly IJsonConverter _jsonConverter;
    private readonly HttpClient _httpClient;

    public ApiBase(ILogger logger, HttpClient httpClient, IJsonConverter jsonConverter)
    {
        _logger = logger;
        _jsonConverter = jsonConverter;
        _httpClient = httpClient;
    }

    protected async Task<T> GetAsync<T>(string path, HttpContent content = null)
    {
        _logger.LogDebug($"Sending GET request to {path}");

        using (var request = new HttpRequestMessage(HttpMethod.Get, path))
        {
            request.Content = content;

            using (var response = await _httpClient.SendAsync(request).ConfigureAwait(false))
            {
                if (response.IsSuccessStatusCode)
                {
                    _logger.LogDebug($"GET request to {path} was successful.");

                    var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                    var deserializeResponseContent = _jsonConverter.Deserialize<T>(responseContent);

                    return deserializeResponseContent;
                }

                var message = GetErrorMessage("GET", path, response);
                _logger.LogError(message);
                throw new HttpRequestException(message);
            }
        }
    }
Remy
  • 12,555
  • 14
  • 64
  • 104
  • No, that's not the way of .NET Core at all. There are no service factories involved either - those constructors know nothing about how their parameters are created. Whether you use constructor or parameter injection, or no injection at all, you'll always reach a point where a method has too many parameters. The typical solution is to create a parameter object that combines common dependencies. – Panagiotis Kanavos Feb 15 '21 at 15:52
  • 1
    As for `HttpClient` you'll probably never use just `HttpClient`, unless you write an SPA in Blazor WASM. In any other case you'll have more than one remote service to call, so instead of a single HttpClient you'll have typed classes with their own cookies, authentication, retry strategies. Some dependencies are so common it may make sense to create a `context` class that combines them and pass it everywhere. – Panagiotis Kanavos Feb 15 '21 at 15:54
  • 1
    As for the other two converters ... huh? What are these things for? Do you plan to switch from System.Text.Json to Json.NET on the fly? What's the point then? If you want to use AutoMapper, you can inject an IMapper instance instead of wrapping it in yet another interface just in case you decide to switch mapping libraries – Panagiotis Kanavos Feb 15 '21 at 15:55
  • IJsonConverter is apprently so you can inject any JSON convert you want. E.g. Newtonsoft or something else. Just that I would not inject this into every class, but define that somewhere global? – Remy Feb 15 '21 at 15:59
  • JSON conversions in ASP.NET Core happen in middleware outside the controller itself, so that `IJsonConverter` has very limited use. Never mind that when you actually switch JSON libraries you'll have to rewrite the interface because you'll find it's too different from the new library. BTW the fact so many things happen in middleware chains and pipelines shows there's not just one way to design an applicatiotn. – Panagiotis Kanavos Feb 15 '21 at 16:07
  • In ASP.NET Core controllers the `Context`, an ambient property that provides access to common functions, is neither injected nor a static property. It's populated by a middleware executing before the controller action is called. It's also available through DI if needed. – Panagiotis Kanavos Feb 15 '21 at 16:10
  • What's the question? – TylerH Feb 15 '21 at 18:43
  • Related: https://stackoverflow.com/questions/9892137/windsor-pulling-transient-objects-from-the-container – Steven Feb 16 '21 at 09:45
  • "Personally I'm a bit too far out of the loop lately to really judge the correct usage of this." Tip: Read a [good book](https://mng.bz/BYNl) (discount code "fccseemann") or start with [the introduction](https://livebook.manning.com/book/dependency-injection-principles-practices-patterns/chapter-1). – Steven Feb 16 '21 at 09:48
  • 1
    So, I guess you are one of the authors, @Steven? – Fildor Feb 16 '21 at 09:55
  • 1
    @Fildor: Yes, thought that was clear ;-) – Steven Feb 16 '21 at 10:00
  • Kind of, yes. I guess, _I_'ll have a look, too. Looks very interesting. – Fildor Feb 16 '21 at 10:02
  • Thanks for the interesting discussion so far. I've added the base class to illustrate the situation. Thanks for the book tip. Will have a go at it tonight. – Remy Feb 16 '21 at 13:57

1 Answers1

3

And then this multiplies across the code, where half of our code is just calling constructors with endless DependencyInjection related stuff. My team told me, that's the way of .NET Core.

Yes and no. Constructor injection of dependencies is a standard method in .NET Core to organize your dependencies. And it works great.

What is non-standard is your base class and the fact that you have those constructor chains where half your parameters aren't actually needed, but just go into the base class constructor. I will bet that this base class does not actually do anything worthwhile.

Remove the base class. See what you still need for every controller. Only inject that. These base classes and their own constructors are a great way to obfuscate what the actual dependencies are. Because now suddenly every class needs an IJsonConverter, must be pretty important. But you will have a hard time figuring out who actually makes use of the base class functionality that uses it. So of your 20 classes derived of the base, who really needs it, and who only requires it to make the compiler happy?

My advice is to remove the base class. Inject into each controller what they need, not more and not less. So you can actually see the dependencies. If you have common functionality in that base class, it can probably be a static method somewhere that gets those fields as parameters. Or maybe it can be a service of it's own that is injected where needed. But only where needed.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • I've added the base class. The API we have to call from this code is very unorthodox and does not work like the usual REST APIs. That might be a reason for all of this. – Remy Feb 16 '21 at 13:59
  • 1
    I don't see a need for a base class here. Make it another service. Inject the service where needed. Done. No more constructor chains. – nvoigt Feb 16 '21 at 14:19