3

I have a three layered architecture.

I can't use constructor injection and I need to get access to a service in my business code, in which I don't have access to HttpContext.

For example, in action methods, or in filters or middleware I can get a service using:

 HttpContext.RequestServices.GetRequiredService<ITranslator>();

But in my business code, I don't have access to HttpContext.

How can I get an instance of my service?

Update:

Here's my business code:

public class InvoiceBusiness
{
   // for some reasons, I can't use constructor injection here

   public void CalculateTranslationsInvoice(long customerId) 
   {
       // I need to get an instance of ITranslator here, and a couple of other services. 
       // If this method was in a controller, I could use HttpContext.RequestServices. 
       // But here what should I do?
   }
}
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
Ali EXE
  • 853
  • 1
  • 7
  • 21
  • 4
    Sounds like you're doing something wrong here. Can you post where exactly you'd need this, and the code that doesn't allow you to use normal injection? – Camilo Terevinto Aug 01 '21 at 08:34
  • 3
    "for some reasons, I can't use constructor injection here" -> that's not good enough for us. Which are those reasons? Why do you need `CalculateTranslationsInvoice` in that class as well? – Camilo Terevinto Aug 01 '21 at 09:00
  • https://stackoverflow.com/a/44520835/1273882 – Ankush Jain Aug 01 '21 at 09:09

1 Answers1

3

If you're needing to access HTTP concerns in the inner layers, you should abstract it to an interface.

Assume you need to access the current user. Normally, you'd use HttpContext.User. But you can't access it in the domain layer.

The solution is to define an interface in your domain layer that encapsulates what your ITranslator implementation actually needs from the HTTP context.

public interface IUserAccessor {
    ClaimsPrincipal CurrentUser { get; }
}

public class Translator: ITranslator {
    // inject the interface
    private readonly IUserAccessor _userAccessor;
    public Translator(IUserAccessor userAccessor) {
        _userAccessor = userAccessor;   
    }
    // ...
}

Keep this interface as focused as possible. Here, I'm OK with using ClaimsPrincipal and having a dependency on the standard library, but if you're not, you can just extract the user id claim if that makes sense in your application.

Then implement this interface in the application/HTTP layer.

internal class HttpUserAccessor: IUserAccessor {
    IHttpContextAccessor _httpAccessor;

    public HttpUserAccessor(IHttpContextAccessor httpAccessor) {
        _httpAccessor = httpAccessor;
    }

    public ClaimsPrincipal CurrentUser => _httpAccessor.HttpContext?.User;
}

Then register this implementation:

services.AddHttpContextAccessor();
services.AddScoped<IUserAccessor, HttpUserAccessor>();

Now you can access HTTP concerns in any layer without that layer knowing where the data actually comes from.

The bottom line is: you don't have to forego dependency injection. You can define & implement interfaces in different layers.

abdusco
  • 9,700
  • 2
  • 27
  • 44
  • IHttpContextAccessor - "This interface should be used with caution. It relies on AsyncLocal which can have a negative performance impact on async calls. It also creates a dependency on "ambient state" which can make testing more difficult." – Olof84 Apr 25 '22 at 16:20
  • @Olof84 The concerns about testability is not applicable here. We're injecting our own interface `IUserAccessor`. Here it is satisfied by an implementation that uses HttpContext. During testing you'd supply your own mock, which is trivial to implement, as the interface is minimal. As for the AsyncLocal usage, correct me if I'm wrong, we're only using a `ClaimsPrincipal` and not the whole HttpContext. I could have built and returned a fresh instance of ClaimsPrincipal, and it wouldn't be any different than this. – abdusco Apr 30 '22 at 01:53