Dependency injecting an ASP.NET Identity user (and unit testing) - c#

I don't fully understand how to refactor my app to make it more testable.
I am using ASP.NET Core 3.0 (Razor Pages, if that matters).
Current State:
I have razor pages models that inject in my context (inherited from IdentityDbContext) and userManager:
public class DashboardModel : PageModel
{
private readonly ApplicationDbContext _context;
private readonly UserManager<AVUser> _userManager;
public DashboardModel(ApplicationDbContext context, UserManager<AVUser> userManager)
{
_context = context;
_userManager = userManager;
}
public async Task<IActionResult> OnGetAsync()
{
AVUser = await _userManager.GetUserAsync(User);
MyRepository myRepository = new MyRepository(_context, AVUser);
// omitted - just getting data from the repository for use on the page
return Page();
}
I have repositories created who have constructors as the following:
public MyRepository(ApplicationDbContext context, AVUser user)
I never use the context directly in my Model classes and instead create repositories in the Model classes using the context passed in. In this case ApplicationDbContext is my EF Core context, and AVUser is my type of Identity User, defined as:
public class AVUser : IdentityUser
My DI is set up as:
// DB Setup
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
// Identity Setup
services.AddDefaultIdentity<AVUser>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
What I don't understand
I want to make my razor pages model classes unit testable without relying on the database or repository classes, mocking up the repository class, so I expect I have to remove the context from the constructor and instead created interfaces for my repositories and DI them.
However, I don't know how to DI in the user parameter for the Repository - how do I set up my service in Startup.cs so this can get passed in? Today to get the value for this, I run:
AVUser = await _userManager.GetUserAsync(User);

If you inject your repositories in the DI container, IoC will automatically inject their dependencies like controllers and pages. so you just need to have a constructor like this in your repository class:
private readonly ApplicationDbContext _context;
private readonly UserManager<AVUser> _userManager;
public MyRepository(ApplicationDbContext context, UserManager<AVUser> userManager)
{
_context = context;
_userManager = userManager;
}
and then inject it to the container:
services.AddTransient<IMyRepository,MyRepository>();
and that's it.
remember to create an interface for your repository so you can inject different instances for either production or testing. Nonetheless, You shouldn't rely on DI for testing. If you are writing unit tests then create a test implementation for your MyRepository and simply instantiate it. If you are writing integration tests then you have to use a different sturtup class.

Related

Trouble when using ApplicationUser: IdentityUser, UserManager in clean architecture asp.net core

I am working a asp.net core 6.0 WebAPI in a clean Architecture.https://github.com/jasontaylordev/CleanArchitecture
There are 4 projects in this architecture.
WebApi, Infrastructure, Application and Domain.
Domain is the Core.
Application Layer is dependent on Domain Layer.
Infrastructure Layer is dependent on Application Layer.
WebApi Layer is dependent on Application Layer and Infrastructure Layer.
And The Queries and command (CQRS) should be written inside Application Layer
I want to use Asp.Net Core Identity.
ApplicationUser.cs ( in Infrastructure/Identity )
namespace Infrastructure.Identity;
public class ApplicationUser : IdentityUser
{
// removed
}
IIdentityService.cs (in Application/Common/Interfaces)
namespace Application.Common.Interfaces;
public interface IIdentityService
{
Task<string> GetUserNameAsync(string userId);
Task<bool> IsInRoleAsync(string userId, string role);
Task<bool> AuthorizeAsync(string userId, string policyName);
Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password);
Task<Result> DeleteUserAsync(string userId);
// and so on
}
IdentityService.cs ( in Infrastructure/Identity )
namespace Infrastructure.Identity;
public class IdentityService : IIdentityService
{
private readonly UserManager<ApplicationUser> _userManager;
public IdentityService(
UserManager<ApplicationUser> userManager )
{
_userManager = userManager;
}
//implementations of Interface by using userManager
}
above all work fine.
My Issue is, I have to UserManager<ApplicationUser> userManager to write a query (in APllication Layer )
I got error, Unnecessary using directive. [Application] The type or namespace name 'Infrastructure' does not exist in the namespace are you missing an assembly reference?) [Application]
Query.cs ( in Application/SiteCodes/Queries/GetAll )
using Infrastructure.Identity; // error
namespace Application.SiteCodes.Queries.GetAll
{
public class GetAllQueryHandler : IRequest<List<SiteCode>>
{
}
public class GetAllQueryHandlerHandler : IRequestHandler<GetAllQueryHandler,List<SiteCode>>
{
private readonly UserManager<ApplicationUser> _userManager; // error
private readonly IHttpContextAccessor _httpContextAccessor;
// constructor removed
public async Task<List<SiteCode>> Handle(GetAllQueryHandler request,
CancellationToken cancellationToken)
{
var user = await
_userManager.GetUserAsync(_httpContextAccessor.HttpContext.User); // By using usermanger. I have to call more function like this from useManager
// removed rest
}
}
}
How can I do this without any error?. How can I call userManager and ApplicationUser ( : IdentyUser) inside Application Layer
Please help me.
Note : I don't want to write function by myself like useManager package. I want to use userManager.
Eg: Like this
private readonly UserManager<ApplicationUser> _userManager;
_userManager.GetUserAsync(_httpContextAccessor.HttpContext.User);
Anyone have idea to solve this issue?
In your application layer, you're only supposed to use the common interfaces. For example here, you shouldn't use the UserManager class directly, but use the public interface IIdentityService.
The application layer is described as:
This layer defines interfaces that are implemented by outside layers. For example, if the application need to access a notification service, a new interface would be added to application and an implementation would be created within infrastructure.
That means you can extend the IIdentityService interface as needed, with the corresponding implementation in the infrastructure layer.
You also shouldn't be using the IHttpContextAccessor in your application layer, because it's part of the WebUI layer.
Some ideas to fix your issues:
IHttpContextAccessor should only be accessed in the WebUI layer: in your API controller, get the current user from it, then retrieve the ApplicationUser and its ID from the UserManager
pass the user's ID to your application layer
use the IIdentityService with that user ID

Mediatr unable to resolve UserManager in ASP.Net Core

I am building ASP.Net Core App depending on this clean architecture example which is using MediatR to execute commands.
And i want to use ASP.Net Core Identity in my app, so in my CreateUserCommandHandler i want to use UserManager to add new user, but when i add UserManager to Command contractor MediatR unable to create the handler and fail with this exception:
System.InvalidOperationException: Error constructing handler for request of type MediatR.IRequestHandler`2[GoGYM.Application.Identity.Commands.CreateUser.CreateUserCommand,MediatR.Unit]. Register your handlers with the container. See the samples in GitHub for examples. ---> System.InvalidOperationException: Unable to resolve service for type 'GoGYM.Persistence.GoGYMDbContext' while attempting to activate 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9[GoGYM.Domain.Entities.ApplicationUser,GoGYM.Domain.Entities.ApplicationRole,GoGYM.Persistence.GoGYMDbContext,System.String,Microsoft.AspNetCore.Identity.IdentityUserClaim`1[System.String],Microsoft.AspNetCore.Identity.IdentityUserRole`1[System.String],Microsoft.AspNetCore.Identity.IdentityUserLogin`1[System.String],Microsoft.AspNetCore.Identity.IdentityUserToken`1[System.String],Microsoft.AspNetCore.Identity.IdentityRoleClaim`1[System.String]]'.
In Configure services i register my DBContext and MediatR like this:
// Add AutoMapper
services.AddAutoMapper(new Assembly[] { typeof(AutoMapperProfile).GetTypeInfo().Assembly });
// Add MediatR
services.AddMediatR(typeof(GetUsersListQueryHandler).GetTypeInfo().Assembly);
// Add DbContext using SQL Server Provider
services.AddDbContext<IGoGYMDbContext, GoGYMDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("NorthwindDatabase")));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<GoGYMDbContext>();
services.AddMvc();
....
And this my command handler code:
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Unit>
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IGoGYMDbContext _context;
public CreateUserCommandHandler(IGoGYMDbContext context, UserManager<ApplicationUser> userManager)
{
_context = context;
_userManager = userManager;
}
public Task<Unit> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
And my controller
[HttpPost]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesDefaultResponseType]
public async Task<IActionResult> Create(string values)
{
await Mediator.Send(new CreateUserCommand(values));
return NoContent();
}
I have tried a lot of things and nothing work, only if i remove UserManager from command handler it gets executed then.
You register IGoGYMDbContext with DI but pass in GoGYMDbContext to AddEntityFrameworkStores. GoGYMDbContext isn't registered with DI, so it can't be resolved when requested by the ASP.NET Core Identity framework.
The following changes allow you to register both the interface and the implementation, but using the same implementation instance whether requested via the interface or the implementation:
Remove the interface from the call to AddDbContext:
services.AddDbContext<GoGYMDbContext>(...);
Add a passthrough from the interface to the GoGYMDbContext implementation:
services.AddScoped<IGoGYMDbContext>(sp => sp.GetRequiredService<GoGYMDbContext>());

How to access token's data in dbcontext

I have web api application .net framework 4.6.2 and we store user credentials in the token. I override dbcontext.savechanges to track database changes. I want to access userId which is stored in token and in api I can access that by httpContext. But the HTTP context is not available in dbcontext.
Does System.Web.HttpContext.Current get the current Request Context?
I should point out that adding stuff that references this directly into a DbContext is poor design though... You should probably use Dependency Injection and manage this via some service with a lifetime scoped to the request - or at least that's how I'd do it...
As #GPW said that's a poor design decision.
To solve that situation you need to use IoC to do registrations such as:
// Autofac
builder.Register(c => new HttpContextWrapper(HttpContext.Current))
.As<HttpContextBase>()
.InstancePerRequest();
// Again Autofac
builder.RegisterModule(new AutofacWebTypesModule());
// Castle Windsor
container.Register(Component.For<HttpContextBase()
.LifeStyle.PerWebRequest
.UsingFactoryMethod(() => new HttpContextWrapper(HttpContext.Current)));
With controllers using contructor injection:
public class HomeController : Controller
{
private readonly HttpContextBase _httpContext;
public HomeController(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
}
So you inject HttpContextBase in order to access the context
public class EntitiesContext : DbContext
{
private readonly HttpContextBase _httpContext;
public EntitiesContext(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
}

Access DBContext using EF7 and VNext

In my MVC 6 project I have my ApplicationDBContext class
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
protected override void OnModelCreating(ModelBuilder builder)
{
}
}
This is added to my services in the Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
//Other configurations removed for brevity
}
Now when I create a new Controller, it asks me if I want to use the Entity Framework, and I can choose my data context. When that controller is created the context is passed in the constructor using what I assume is dependency injection.
public class CompanyController : Controller
{
private ApplicationDbContext _context;
public CompanyController(ApplicationDbContext context)
{
_context = context;
}
}
Now, I don't want to do all database interactions in the controllers, but rather in my other classes. What I can't figure out, is how to get the ApplicationDbContext from my other classes. Passing it from the controller obviously won't work because classes could be called from other places than the controller.
If I just try new ApplicationDbContext(); I get the following error:
No database providers are configured. Configure a database provider by overriding OnConfiguring in your DbContext class or in the AddDbContext method when setting up services.
I feel like this should be something simple, but I am completely lost here.
ASP.NET Core is based on dependency injection, since your context has been added in your dependendy container, it's automatically injected by the framework when your controller is instanciated.
Edit based on comments :
You can setup your classes to support DI, let's suppose you have two class. One that depend on your context, and then second that depend both on your context and your first class :
public class MyClass
{
private ApplicationDbContext _context;
public MyClass(ApplicationDbContext context)
{
_context = context;
}
}
public class AnotherClass
{
private ApplicationDbContext _context;
private MyClass _myClass;
public AnotherClass(ApplicationDbContext context, MyClass myClass)
{
_context = context;
_myClass = myClass;
}
}
Add your classes as a transient dependency in the sevice collections at startup, and let the service provider resolve their dependencies for you :
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddTransient<MyClass>();
services.AddTransient<AnotherClass>();
//Other configurations removed for brevity
}
Change your controller to accept MyClass as injected dependency :
public class CompanyController : Controller
{
private ApplicationDbContext _context;
private MyClass _myClass;
public CompanyController(ApplicationDbContext context, MyClass myClass)
{
_context = context;
_myClass = myClass;
}
}
You can also have another controller that take AnotherClass as injected dependecy :
public class AnotherController : Controller
{
private AnotherClass _anotherClass;
public AnotherController(AnotherClass anotherClass)
{
_anotherClass = anotherClass;
// _anotherClass will have both ApplicationDbContext and MyClass injected by the service provider
}
}
You should read the docs of dependency injection of ASP.NET Core, it could help to understand basics of DI. Another article from K. Scott Allen that explain some bad practice when you deal with DI.
You can create a service class that receives the DbContext in the same way as the controller.
public class SomeService
{
private ApplicationDbContext MyDbContext { get; set; }
public SomeService(ApplicationDbContext dbContext)
{
MyDbContext = dbContext;
}
public void MethodName()
{
// You can now do MyDbContext.SomeDomainModel
}
}
Then register the service in Startup.cs, in your ConfigureServices method.
public void ConfigureServices(IServiceCollection services) {
// <snipped>
services.AddTransient<SomeService>();
}
And now, in your CompanyController, you can add another parameter in the constructor for the SomeService, just as you have for the ApplicationDbContext.
public class CompanyController : Controller
{
private ApplicationDbContext _context;
private SomeService _someService;
public CompanyController(ApplicationDbContext context, SomeService someService)
{
_context = context;
_someService = someService;
}
}
All that said, I don't think there's anything wrong with doing your logic in your controller actions to build your ViewModel, accessing the DbContext. The DbContext is what's separating your business logic (in the controller) from the DAL. Some may disagree with me, but you don't need to add additional services to further separate them. Most of the code in your action methods are unique to that action and not going to be reused by other actions. IMO, those are the pieces of code to put into services. Things like sending emails and such.

How to mock out the UserManager in ASP.NET 5

I am writing a UI for managing users in an ASP.NET 5 app. I need to show any errors returned by the UserManager in the UI. I have the IdentityResult errors being passed back in the view model but I am a touch adrift when it comes to testing my code.
What is the best way to Mock the UserManager in ASP.NET 5?
Should I be inheriting from UserManager and overriding all the methods I am using and then injecting my version of UserManager into an instance of the Controller in my test project?
I have managed it with the help of the MVC Music Store sample application.
In my Unit Test class, I set up the database context and UserManager like this:
public class DatabaseSetupTests : IDisposable
{
private MyDbContext Context { get; }
private UserManager<ApplicationUser> UserManager { get; }
public DatabaseSetupTests()
{
var services = new ServiceCollection();
services.AddEntityFramework()
.AddInMemoryDatabase()
.AddDbContext<MyDbContext>(options => options.UseInMemoryDatabase());
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<MyDbContext>();
// Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
// IHttpContextAccessor is required for SignInManager, and UserManager
var context = new DefaultHttpContext();
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
var serviceProvider = services.BuildServiceProvider();
Context = serviceProvider.GetRequiredService<MyDbContext>();
UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
}
....
}
Then I can use the UserManager in my unit tests, for example:
[Fact]
public async Task DontCreateAdminUserWhenOtherAdminsPresent()
{
await UserManager.CreateAsync(new ApplicationUser { UserName = "some#user.com" }, "IDoComplyWithTheRules2016!");
...
}
If your Dependency Injector is not able to resolve an IHttpContextAccessor then you will not be able to create a UserManager instance due to it being dependent on it.
I think (and this is just an assumption), that with Asp.Net 5, the UserManager does take care of refreshing cookie based claims when you change them (claims, roles...) for a user and therefore requires some HttpContext for login / logout actions and cookie access.

Categories

Resources