Я думал о модели DI и зависимостях моего контроллера.Мне нужно было всего несколько методов из UserManager
, поэтому я теоретизировал об удалении зависимости от UserManager
из UsersController
и замене ее на какой-то интерфейс, который реализует те же сигнатуры, которые мне нужны от UserManager
.Давайте назовем этот интерфейс IMYUserManager
:
public interface IMYUserManager
{
Task<IdentityUser> FindByIdAsync(string uniqueid);
Task<IdentityResult> CreateAsync(IdentityUser IdentityUser);
Task<IdentityResult> UpdateAsync(IdentityUser IdentityUser);
Task<IdentityResult> DeleteAsync(IdentityUser result);
}
Далее мне нужно было создать класс, который является производным от UserManager
и также реализует IMYUserManager
.Идея здесь заключается в том, что реализация методов из интерфейса просто станет переопределением для производного класса, так что я получаю возможность пометить FindByIdAsync
(и все остальное) как методы расширения и требовать переноса в статический класс.Вот MyUserManager
:
public class MYUserManager : UserManager<IdentityUser>, IMYUserManager
{
public MYUserManager(IUserStore<IdentityUser> store, IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<IdentityUser> passwordHasher, IEnumerable<IUserValidator<IdentityUser>> userValidators,
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<IdentityUser>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}
public override Task<IdentityUser> FindByIdAsync(string userId)
{
return base.FindByIdAsync(userId);
}
//Removed other overridden methods for brevity; They also call the base class method
}
Почти дома.Затем я естественным образом обновил UsersController
для использования интерфейса IMYUserManager
:
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private ILogger<UsersController> _logger;
private readonly IMYUserManager _usermanager;
public UsersController(ILogger<UsersController> logger, IMYUserManager
usermanager)
{
_usermanager = usermanager;
_logger = logger;
}
}
И, естественно, после этого я должен сделать эту зависимость доступной для сервисного контейнера для всех, кто желает наслаждаться:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMYUserManager, MYUserManager>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
И, наконец, после проверки того, что на самом деле строит , я обновил тестовый класс:
public class UsersControllerTests
{
public UsersController _usersController;
private Mock<IMYUserManager> _userManager;
public UsersControllerTests()
{
_userManager = new Mock<IMYUserManager>();
_usersController = new UsersController((new Mock<ILogger<UsersController>>
()).Object, _userManager.Object);
}
[Fact]
public async Task GetUser_ReturnsOkObjectResult_WhenModelStateIsValid()
{
//Setup
_userManager.Setup(x => x.FindByIdAsync("realuser"))
.Returns(Task.Run(() => new IdentityUser("realuser","realuser1")));
_usersController.ModelState.Clear();
//Test
ObjectResult response = await _usersController.GetUser("realuser");
//Assert
//Should receive 200 and user data content body
response.StatusCode.Should().Be((int)System.Net.HttpStatusCode.OK);
response.Value.Should().NotBeNull();
}
}
Что делает это хорошим решением?
Несколько вещей:
Удаление зависимости от UserManager
из UsersController
соответствовало модели DI.Абстрагирование зависимостей (следовательно, абстрагирование деталей реализации, таких как методы расширения) и обеспечение их доступности не только для моделирования, но и для всего IServiceCollection
означает, что у меня есть только 3 очень простых шага, когда мне нужно реализовать другой метод дляuser manager:
- Добавить сигнатуру метода в
IMYUserManager
- Переопределить метод и вызвать реализацию базового класса в
MYUserManager
- Смоделировать новую зависимость внутримодульные тесты
Я могу вернуться к сфере обслуживания, я выбрал AddScoped()
просто для подтверждения концепции, но требования к производительности и бизнесу будут определять, останется ли он неизменным.