24

I want my ApplicationContext constructor to have the UserManager as a parameter, but I am having trouble with dependency injection.

Code:

public class ApplicationContext : IdentityDbContext<ApplicationUser>
{
    private IHttpContextAccessor _contextAccessor { get; set; }
    public ApplicationUser ApplicationUser { get; set; }
    private UserManager<ApplicationUser> _userManager;

    public ApplicationContext(DbContextOptions<ApplicationContext> options, IHttpContextAccessor contextAccessor, UserManager<ApplicationUser> userManager)
        : base(options)
    {
        _contextAccessor = contextAccessor;
        var user = _contextAccessor.HttpContext.User;
        _userManager = userManager;
        ApplicationUser = _userManager.Users.FirstOrDefault(u => u.Id == _userManager.GetUserId(user));
    }
}

And in startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("RCI.App")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationContext>()
        .AddDefaultTokenProviders();

    services.AddAuthentication();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
    services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();

    services.AddOptions();

}

Error message:

A circular dependency was detected for the service of type 'Microsoft.AspNetCore.Identity.UserManager`1[RCI.App.Models.ApplicationUser]'.

Could anyone point out what I'm doing wrong?

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
rory
  • 1,490
  • 3
  • 22
  • 50
  • Just for clarification, do you understand what a circular dependency is? – Nkosi Sep 21 '17 at 22:27
  • Also, this appears to be an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What is the ultimate goal you are trying to achieve? Why do you need access to the user manager within the application context? – Nkosi Sep 21 '17 at 22:29
  • @Nkosi I want to update each entity with created/modified by on every create/update with the current user. I know I could pass in the user to my `Save()` method but it would be much easier to just have it in the context in one place instead – rory Sep 21 '17 at 22:32
  • 3
    `UserManager` and `ApplicationContext` in your example have explicit dependencies on each other, resulting in a circular dependency. when resolving `ApplicationContext` it has to create an `UserManager` which needs a `ApplicationContext`. You see where that is going? – Nkosi Sep 21 '17 at 22:37
  • 2
    Having a dependency on a user manager inside a *database* context doesn’t sound like a good idea. You probably should have a service instead that depends on both the database context and the user manager. – poke Sep 21 '17 at 22:39
  • 1
    @rory You already have access to `Users` within the application context. make your query directly on that with the the current request's user id. no need to reference the user manager at all. – Nkosi Sep 21 '17 at 22:40
  • @Nkosi where does it say that `UserManager` is dependant on `ApplicationContext` ? – rory Sep 21 '17 at 22:41
  • 1
    @rory check it's constructor – Nkosi Sep 21 '17 at 22:41
  • @Nkosi thanks a mill, didn't realise I had access to Users in the context already! Just for clarification though, I still don't see where `UserManager` is dependant on the context. Here is the ctor: `public UserManager(IUserStore store, IOptions optionsAccessor, IPasswordHasher passwordHasher, IEnumerable> userValidators, IEnumerable> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger> logger);` – rory Sep 21 '17 at 22:45
  • 2
    `UserManager` depends on `UserStore` which depends on the database context that’s registered with ASP.NET Core Identity, which happens to be your database context that depends on the user manager. – poke Sep 21 '17 at 22:45
  • 2
    @rory you should also keep the `ApplicationContext` constructor lean and not try to access the user or make queries within there. extract the user and make your queries within the target methods as the request would have been fully realized by then. It should basically only have `_contextAccessor = contextAccessor;` the rest should be done on one of the crud operations. – Nkosi Sep 21 '17 at 22:49

3 Answers3

29

If you don't actually need the UserManager in the constructor, you can store a reference to the IServiceProvider instead:

private IHttpContextAccessor _contextAccessor { get; set; }
public ApplicationUser ApplicationUser { get; set; }
private IServiceProvider _services;

public ApplicationContext(DbContextOptions<ApplicationContext> options,
    IHttpContextAccessor contextAccessor, IServiceProvider services)
    : base(options)
{
    _contextAccessor = contextAccessor;
    var user = _contextAccessor.HttpContext.User;
    _services = services;
}

Then later, when you actually need the ApplicationUser, call e.g. GetRequiredService<ApplicationUser>() (defined in Microsoft.Extensions.DependencyInjection):

var manager = _services.GetRequiredService<UserManager<ApplicationUser>>();
var user = manager.Users.FirstOrDefault(u => u.Id == _userManager.GetUserId(user));

Of course, you can use a Lazy<T> to lazy-load the manager or user the first time and then store a reference to it.

In general, @poke is correct about re-architecting to avoid such circular dependencies, but leaving this answer here in case someone else has a similar problem and refactoring isn't an option.

Tobias J
  • 19,813
  • 8
  • 81
  • 66
24

Circular dependencies are usually a sign for an improper application design, which should be revised. As I already mentioned in the comments, having a database context that depends on the user manager does not seem to be a good idea. This makes me assume that your database context does too much and likely violates the single-responsibility principle.

Just looking at the dependencies of your database context, you are already adding too much application specific state in there: You not only have a dependency on the user manager, but also on the HTTP context accessor; and you are resolving the HTTP context also immediately in the constructor (which is generally not the best idea).

From your code excerpt it seems that you want to retrieve the current user for later use. If you want to use this for example to filter queries for the user, then you should think about whether it’s really a good idea to statically bake this into the database context instance. Consider accepting an ApplicationUser inside methods instead. That way, you get rid of all those dependencies, you make your database context better testable (since the user is no longer a state of the context), and you also make the single responsibility of the context clearer:

public IList<Thing> GetThings (ApplicationUser user)
{
    // just an example…
    return Things.Where(t => t.UserId == user.Id).ToList();
}

Note that this is also inversion of control. Instead of having the database context actively retrieve the user it should query for (which would add another responsibility, violating the SRP), it expects to be passed the user it should query for, moving the control to the calling code.

Now, if you query stuff for the current user very often, it might become somewhat annoying to resolve the current user in a controller and then pass it to the database context. In that case, create a service to no longer repeat yourself. That service can then depend on the database context and other things to figure out the current user.

But just clearing up your database context from stuff it shouldn’t do is enough to fix this circular dependency.

poke
  • 369,085
  • 72
  • 557
  • 602
9

Many Thanks to Toby, for solution. You can also use Lazy<IMyService> to prevent calling _services.GetRequiredService<UserManager<ApplicationUser>>() each time u wanna use it.

private IHttpContextAccessor _contextAccessor { get; set; }
public ApplicationUser ApplicationUser { get; set; }
private Lazy<UserManager<ApplicationUser>> _userManager;

public ApplicationContext(DbContextOptions<ApplicationContext> options,
    IHttpContextAccessor contextAccessor, IServiceProvider services)
    : base(options)
{
    _contextAccessor = contextAccessor;
    var user = _contextAccessor.HttpContext.User;
    _userManager = new Lazy<UserManager<ApplicationUser>>(() =>
                services.GetRequiredService<UserManager<ApplicationUser>>());
}

and when u wanna use it just say :

_userManager.value.doSomeThing();
Atzi
  • 457
  • 1
  • 6
  • 16