0

I have a lot of services that require access to the current user. I set up a PrincipalProvider which bound to the current HttpContext, I also registered it per request so that once instantiated, it would never lose the context. It looks like this:

public class PrincipalProvider : IPrincipalProvider
{

    // Readonly fields
    private readonly HttpContext _current;

    /// <summary>
    /// Default constructor
    /// </summary>
    public PrincipalProvider()
    {
        _current = HttpContext.Current;
    }

    /// <summary>
    /// Gets the current user
    /// </summary>
    public IPrincipal User => _current?.User;
}

And I have bound it like this:

builder.RegisterType<PrincipalProvider>().As<IPrincipalProvider>().InstancePerRequest();

Now, any service that has this injected into it should have the user exposed. The issue I have is simple. If I have a controller set up like this:

public OrdersController(IOrderProvider provider)
{
    _provider = provider;
}

That IOrderProvider has the IPrincipleProvider lazily injected into it, so in this case the user is available because the parent service is created on the Controller. The issue is if it isn't injected into the service, but instead used on another service that is injected into it. For example, I have this controller:

public UsersController(IUserProvider provider, IAdvancedEncryptionStandardProvider encyptionProvider, IPhotoManager photoManager)
{
    _userProvider = provider;
    _encryptionProvider = encyptionProvider;
    _photoManager = photoManager;
}

The IUserProvider doesn't have the IPrincipalProvider injected into it, but it does have another service that does:

public TroposSessionRequest(CormarConfig config, IAdvancedEncryptionStandardProvider encryptionProvider, Lazy<IPrincipalProvider> principalProvider)
{
    _config = config;
    _encryptionProvider = encryptionProvider;
    _principalProvider = principalProvider;
}

The problem here, is that when the TroposSessionRequest is created, the HttpContext is not available and therefor is null. I was hoping there was a way to instantiate the IPrincipleProvider when the context becomes available and then keep it for the entire request. We can do this for every request as the PrincipalProvider is almost always needed.

Does anyone know how to do this?

r3plica
  • 11,412
  • 17
  • 75
  • 203

1 Answers1

1

The simplest way to workaround the problem is to simply access HttpContext.Current in your User accessor:

public IPrincipal User => HttpContext.Current?.User;

If you're concerned about testability, you can mock HttpContext.Current.User relatively easily.

If you're set on solving it using DI you could abstract HttpContext behind a IHttpContextAccessor dependency (but since you're still depending on the HttpContext type you'd still be in the same boat in terms of testing).

public class PrincipalProvider : IPrincipalProvider
{

    // Readonly fields
    private readonly IHttpContextAccessor _httpContextAccessor;

    /// <summary>
    /// Default constructor
    /// </summary>
    public PrincipalProvider(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    /// <summary>
    /// Gets the current user
    /// </summary>
    public IPrincipal User => _httpContextAccessor.HttpContext?.User;
}
Richard Szalay
  • 78,647
  • 19
  • 169
  • 223
  • 1
    As I see it, your "workaround" isn't a workaround _at all_. It is actually _the_ solution, since you should [prevent having runtime data in the object graph during construction](https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=99). – Steven Jul 28 '17 at 10:36
  • Where can I get access to this IHttpContextAccessor? Is this something only available in core? I am not using .net core :( – r3plica Jul 28 '17 at 13:38
  • I meant that you'd define it yourself. – Richard Szalay Jul 28 '17 at 20:10