Custom Identity Usermanager from C# to F# - f#

We currently have the following code
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<ApplicationUser> passwordHasher, IEnumerable<IUserValidator<ApplicationUser>> userValidators, IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<ApplicationUser>> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}
public override async Task<IdentityResult> ResetPasswordAsync(ApplicationUser user, string token, string newPassword)
{
var result = await base.ResetPasswordAsync(user, token, newPassword);
if (result.Succeeded)
{
user.ChangePasswordDate = DateTime.Now.AddYears(1);
user.ChangePassword = false;
await base.UpdateAsync(user);
}
return result;
}
}
How would i convert this to f#?
Any help would be appreciated
Greetings,
Glenn

If you're using F# 6, you can use the new task builder, like this:
open Microsoft.AspNetCore.Identity
type ApplicationUserManager(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) =
inherit UserManager<ApplicationUser>(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
member private _.BaseResetPasswordAsync(user, token, newPassword) =
base.ResetPasswordAsync(user, token, newPassword)
override this.ResetPasswordAsync(user, token, newPassword) =
task {
let! result = this.BaseResetPasswordAsync(user, token, newPassword)
if result.Succeeded then
user.ChangePasswordDate <- DateTime.Now.AddYears(1)
user.ChangePassword <- false
let! _result = this.UpdateAsync(user)
ignore _result
return result;
}
The private Base method is required because you can't refer directly to base within a computation expression.
Note that you're ignoring the result of UpdateAsync in your C# code. I've done the same thing here, but I don't know if it's wise.

Related

Dependency Injection discrepancy between .NET 3.1 and .NET 6.0

We are currently encountering unexpected behaviour in our upgrade of an Azure Function from .NET 3.1 to .NET 6.
A DocumentClientProvider providing an HttpClient using a singleton implementation is registered as a singleton service. In order to perform a security check, an DelegatingHandler is added to the HttpClient. This message handler checks the existence of a custom header and if the header value is the same as the one in our SecurityContext.
public class DocumentClientProvider : IDocumentClientProvider
{
private static HttpClient singletonInstance;
private static readonly object padlock = new object();
public DocumentClientProvider(DelegatingHandler messageHandler)
{
if (singletonInstance == null)
{
lock (padlock)
{
if (singletonInstance == null)
{
singletonInstance =
new HttpClient(messageHandler);
}
}
}
}
public HttpClient GetClient() => singletonInstance;
}
public class MyMessageHandler : DelegatingHandler
{
private readonly IServiceProvider serviceProvider;
public MyMessageHandler(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var securityContext = serviceProvider.GetRequiredService<ISecurityContext>();
var requestHeaderValue = request.Headers.GetValues("X-Custom-Header").FirstOrDefault();
var validHeaderValue = $"{securityContext.ClientId.ToString().ToLower()}";
if (requestHeaderValue != validHeaderValue)
{
var message = $"Value of custom header is {requestHeaderValue} and it should be {validHeaderValue}.";
throw new Exception(message);
}
return base.SendAsync(request, cancellationToken);
}
}
The SecurityContext is a small data class containing information on the identity of the caller of the function and is registered as a scoped service.
public class SecurityContext : ISecurityContext
{
public Guid ClientId { get; set; }
}
A function simply receives a call, retrieves the HttpClient via the DocumentClientProvider and invokes an endpoint.
public class Function
{
private readonly HttpClient httpClient;
private readonly ISecurityContext securityContext;
public Function(IDocumentClientProvider documentClientProvider, ISecurityContext securityContext)
{
httpClient = documentClientProvider.GetClient();
this.securityContext = securityContext;
}
[FunctionName("Function")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
ILogger log)
{
securityContext.ClientId = Guid.NewGuid();
string name = req.Query["name"];
name = name ?? "Dummy";
var httpRequestMessage = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri($"https://www.google.com/search?q={name}"),
Headers = {
{ "X-Custom-Header", securityContext.ClientId.ToString() }
}
};
var response = await httpClient.SendAsync(httpRequestMessage);
return new OkObjectResult(response);
}
}
The different bits are wired up as follows:
builder.Services.AddSingleton<IDocumentClientProvider>(ctx =>
new DocumentClientProvider(new MyMessageHandler(ctx) {
InnerHandler = new HttpClientHandler()
}));
builder.Services.AddScoped<ISecurityContext, SecurityContext>();
In .NET 3.1, the function works as expected. The SecurityContext in the DelegatingHandler is resolved to the same object throughout the complete function execution. However, in .NET 6, the SecurityContext in the DelegatingHandler is resolved to a different object. The ClientId in the SecurityContext is an empty Guid and an exception is thrown.
Does anyone have any insights as to why the different versions of the Azure Functions present different behaviour related to dependency injection?

Dependency Injection in Startup.cs difficulties

I am trying to inject some Data into the Startup.cs file without success. For example, this injection works fine on my Email Class and I am able to read the _data
public class EmailSender : IEmailSender
{
private readonly DataConfig _data;
private readonly UserManager<ApplicationUser> _signManager;
public EmailSender(UserManager<ApplicationUser> signManager, IOptions<DataConfig> data)
{
_data = data.Value;
_signManager = signManager;
}
// I am able to retrieve the _data.SG without issues from the SendEmailAsync method.
public Task SendEmailAsync(string email, string subject, string message)
{
var sendGridKey = _data.SG;
return Execute(sendGridKey, subject, message, email);
}
}
However, when I try to perform the same operation from Startup.cs, the Program.cs crashes:
public class Startup
{
public IConfiguration Configuration { get; }
private readonly DataConfig _data;
public Startup(IConfiguration configuration, IOptions<DataConfig> data)
{
Configuration = configuration;
_data = data.Value;
}
}
Then, Program.cs crashes :
An error occurred while starting the application.
InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Options.IOptions`1[JSRoles.Services.DataConfig]' while attempting to activate 'JSRoles.Startup'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider)
InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Options.IOptions`1[JSRoles.Services.DataConfig]' while attempting to activate 'JSRoles.Startup'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider)
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, object[] parameters)
Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services)
Microsoft.AspNetCore.Hosting.GenericWebHostBuilder+<>c__DisplayClass12_0.<UseStartup>b__0(HostBuilderContext context, IServiceCollection services)
Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
Microsoft.Extensions.Hosting.HostBuilder.Build()
JSRoles.Program.Main(string[] args) in Program.cs
-
namespace JSRoles
{
public class Program
{
public async static Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
try
{
JSRoles.Program.<Main>(string[] args)
How do I get around this issue, and still manage to get the data that I need in StartUp.cs?

Register Open Generic Decorator with Scrutor

I have a standard .Net core Api and want to use a Open Generic IReposistory and decorate that with a DomainEventPublisher for pushing out events to servicsBus after persisting.
However, I have used Simple Injector a lot earlier which I'm a big fan of. But now when using MediatR Im trying to simplify DI by using just .net Core DI together with Scrutor package for decorating.
Problem is an error I get:
"The number of generic arguments provided doesn't equal the arity of the generic type definition." from Scrutor when trying to register decorator in Startup (2nd line below).
services.AddSingleton(typeof(IRepository<>), typeof(Repository<>));
services.Decorate(typeof(IRepository<>), typeof(DomainEventPublisher<>));
I have closed these generic classes/interfaces and then it works. But Im not good with that. I would to i the right way like I used to do in Simpleinjector, and register open generic decorator.
Any suggestions what might be the problem?
public class Repository<TEntity> : IRepository<TEntity>
{
private readonly CosmosClient _client;
private readonly IDataContext<TEntity> _context;
private readonly Container _container;
public Repository(CosmosClient client, IDataContext<TEntity> context)
{
_client = client;
_context = context ?? throw new ArgumentNullException(nameof(context));
_container = _client.GetContainer(_context.GetDatabase(), _context.GetContainer());
}
public virtual async Task Add(TEntity entity)
{
try
{
var response = await _container.CreateItemAsync(entity, new PartitionKey(_context.GetPartitionKey(entity)));
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public virtual async Task<TEntity> Get(string id)
{
var response = await _container.ReadItemAsync<TEntity>(id, new PartitionKey(_context.GetPartitionKey(id)));
return response.Resource;
}
public virtual async Task<TEntity> Update(TEntity entity)
{
var response = await _container.UpsertItemAsync(entity, new PartitionKey(_context.GetPartitionKey(entity)));
return response.Resource;
}
public async Task Remove(string id)
{
var response = await _container.DeleteItemAsync<TEntity>(id, new PartitionKey(_context.GetPartitionKey(id)));
}
public class DomainEventPublisher<TEntity> : IRepository<TEntity>
{
private readonly IRepository<TEntity> _decoratedRepository;
private readonly ITopicAdapter _bus;
private readonly IMapper _mapper;
private List<IDomainEvent> _eventsToProcess = new List<IDomainEvent>();
public DomainEventPublisher(IRepository<TEntity> decoratedRepository, ITopicAdapter bus, IMapper mapper)
{
_decoratedRepository = decoratedRepository ?? throw new ArgumentNullException(nameof(decoratedRepository));
_bus = bus ?? throw new ArgumentNullException(nameof(bus));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public async Task Add(TEntity entity)
{
// Get all domain events raised by source entity
var events = CollectEvents(entity);
await _decoratedRepository.Add(entity);
await HandleEvents(events);
}
public async Task<TEntity> Get(string id)
{
return await _decoratedRepository.Get(id);
}
public async Task<TEntity> Update(TEntity entity)
{
// Get all domain events raised by source entity
var events = CollectEvents(entity);
var result = await _decoratedRepository.Update(entity);
await HandleEvents(events);
return result;
}
public async Task Remove(string id)
{
await _decoratedRepository.Remove(id);
}
private List<IDomainEvent> CollectEvents(TEntity entity)
{
if (entity is IEntity entityWithEvents)
return entityWithEvents.Events;
return new List<IDomainEvent>();
}
private async Task HandleEvents(List<IDomainEvent> events)
{
// if we ended up on this line we know that repository persisted changes and now send events to bus
foreach (var domainEvent in events)
{
await _bus.Send(_mapper.MapTo(domainEvent));
}
}
}
It's impossible to apply decorators to open-generic registration with Scrutor. This is discussed here on the Scrutor forum. This is due to a limitation of the underlying Microsoft DI Container. This is a limitation that can't be circumvented by Scrutor.
Instead, switch to one of the mature DI Containers that do support this.

Getting exception suddenly from entityframework

I get this exception from time to time :
The 'Email' property on 'User' could not be set to a 'System.Int64' value. You must set this property to a non-null value of type 'System.String'. Method Message:, LogException: System.InvalidOperationException: The 'Email' property on 'User' could not be set to a 'System.Int64' value. You must set this property to a non-null value of type 'System.String'.
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.ErrorHandlingValueReader1.GetValue(DbDataReader reader, Int32 ordinal)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.HandleEntityAppendOnly[TEntity](Func2 constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper1.SimpleEnumerator.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
at Project.Services.UserService.FindById(Int64 userId)
I'm using Asp.net Identity in MVC project.
My User class like :
public class User : IdentityUser<long, IdentityConfig.UserLogin, IdentityConfig.UserRole, IdentityConfig.UserClaim>
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(IdentityConfig.CustomUserManager manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[MaxLength(256)]
[Index(IsUnique = true)]
[Required]
public override string Email { get; set; }
[Required]
[MaxLength(256)]
public string FirstName { get; set; }
// rest of properties
....
}
UserManager :
public class CustomUserManager : UserManager<User, long>
{
public CustomUserManager(IUserStore<User, long> store, IdentityFactoryOptions<CustomUserManager> options) : base(store)
{
this.UserValidator = new UserValidator<User, long>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
PasswordValidator = new PasswordValidator
{
RequiredLength = 8,
RequireLowercase = true,
RequireUppercase = true,
RequireDigit = true
};
// Configure user lockout defaults
UserLockoutEnabledByDefault = true;
DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
RegisterTwoFactorProvider("Google Authentication", new GoogleAuthenticatorTokenProvider());
var provider = new MachineKeyProtectionProvider();
UserTokenProvider = new DataProtectorTokenProvider<User,long>(provider.Create("ResetPasswordPurpose"));
}
}
UserService:
public class UserService : EntityService<User>, IUserService
{
private readonly IdentityConfig.CustomUserManager _userManager;
public UserService(MyDbContext context, IdentityConfig.CustomUserManager userManager) : base(context)
{
_userManager = userManager;
}
public User FindById(long userId)
{
return _userManager.Users.FirstOrDefault(x => x.Id == userId);
}
// other methods..
}
Register Autofac:
builder.RegisterModule(new ServiceModule());
builder.RegisterModule(new EfModule());
builder.RegisterType<IdentityConfig.RoleStore>().As<IRoleStore<IdentityConfig.Role, long>>().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomUserStore>().As<IUserStore<User, long>>().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomUserManager>().AsSelf().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomSignInManager>().AsSelf().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomRoleManager>().AsSelf().InstancePerRequest();
builder.Register<IAuthenticationManager>(c => HttpContext.Current.GetOwinContext().Authentication);
builder.Register(c => new IdentityFactoryOptions<IdentityConfig.CustomUserManager>
{
DataProtectionProvider = new DpapiDataProtectionProvider("MyWebAppName"),
Provider = new IdentityFactoryProvider<IdentityConfig.CustomUserManager>()
}).InstancePerRequest();
public class ServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("Project.Services"))
.Where(t => t.Name.EndsWith("Service") || t.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
public class EfModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType(typeof(MyDbContext)).AsSelf().WithParameter("connectionString", ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString).InstancePerRequest();
}
}
What I noticed also is this error affect some of other entities not just the user !
The problem is the application runs for some time and then gives this kind of errors too much, which does not make any sense to me and makes me mad.
I'm using Azure SQL , Azure web services, Autofac.
Same issue here. It happens on medium to high demand. I don't know what to do anymore. I'm recycling 5x/day.
I couldn't find any standard. The exception throws in many different methods. No standard. Looks completely random.
It seems that i'm trying to retrieve an information on DB and it always returns blank data, so an error is thrown when it tries to cast the null data to the model.

MVC 6 (vNext) UserManager - DI - create user in specific database

I'm developing multitenant application. I use separate databases for each tenant. UserData are for each tenant in separate database TOO.
My problem is how can i create admin account for each tenant in "custom" database independently on DI. In MVC 5 was possible to instantiate UserManager base on UserStore(connection string). But UserManager in mvc6 depends on HttpContext... No documentation found...
Exist please some way how to do it??? I need in mvc 6 something like this in mvc 5:
UserStore<TenantUser> store = new UserStore<TenantUser>(new TenantDbContext("CONNECTION STRING")); //!!! NO POSSIBLE CREATE USER IN CUSTOM DATABASE
UserManager<TenantUser> t = new UserManager<TenantUser>(store);
t.CreateAsync(user, password);
Update:
public class TenantDbContext : IdentityDbContext<TenantUser, TenantRole, Guid>
{
private string _connectionString { get; set; }
private readonly IHttpContextAccessor _contextAccessor;
private readonly ApplicationDbContext _applicationDbContext;
//THIS SUB UNCOMENT ONLY IF CREATE MIGRATIONS (dnx ef...)
/*
public TenantDbContext(DbContextOptions<TenantDbContext> options) : base(options)
{
this._connectionString = "CONNECTION STRING";
}
*/
public TenantDbContext(DbContextOptions<TenantDbContext> options, IHttpContextAccessor contextAccessor, ApplicationDbContext applicationDbContext) : base(options) {
_contextAccessor = contextAccessor;
_applicationDbContext = applicationDbContext;
TenantResolver resolver = new TenantResolver(_contextAccessor, _applicationDbContext);
string con = resolver.GetConnectionString();
if (con != string.Empty)
{
this._connectionString = con; }
else
{
this._connectionString = "CONNECTION STRING"; //Development connection string
}
}
public TenantDbContext() //Posibility to create TenantDbContext migration and development database with no connectionString in constructor
{
//this._connectionString = "CONNECTION STRING";
}
public TenantDbContext(string ConnectionString)
{
this._connectionString = ConnectionString;
}
public static TenantDbContext Create(string ConnectionString)
{
return new TenantDbContext(ConnectionString);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
I hope that I correctly understand you. Let us we forget about the performance and the caching of multiple connections, which already opened to the databases. I suppose that you have multiple databases, which have the same schema. You need to access the databases using (sharing) the same database context.
I can suggest you two solutions.
The first solution consists from registering one context and reopening it if the one opened co change the connection string.
Let us you have TenantDbContext, which could be opened with different destination databases. For example with
#"Server=(localdb)\mssqllocaldb;Database=TenantDb1;Trusted_Connection=True;"
or
#"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;"
First of all you remove OnConfiguring like
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=TenantDb;Trusted_Connection=True;");
}
which could exist in the definition of TenantDbContext and you use the following code in ConfigureServices of Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
var connection1 = #"Server=(localdb)\mssqllocaldb;Database=TenantDb1;Trusted_Connection=True;";
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TenantDbContext>(options => options.UseSqlServer(connection1));
services.AddMvc();
...
}
In the way you inject TenantDbContext with one from the database (TenantDb1). Let us the TenantDbContext contains some entity set like Blog for example. Thus your can define some MVC controller in the following way
public class TenantsController : Controller
{
private TenantDbContext _context;
public TenantsController (TenantDbContext context)
{
_context = context;
}
public IActionResult Index() {
var con = _context.Database.GetDbConnection();
// now the con uses either TenantDb2 or TenantDb2
// con.ConnectionString can be used to get or set the
// connection string
string needConStr = #"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;";
if (con.ConnectionString != needConStr) { // can be compared more carefully
_context.Database.CloseConnection();
con.ConnectionString = needConStr;
}
// get some data from the TenantDbContext
var blog = _context.Blog.ToList();
return View(blog);
}
}
The second solution don't need to inject any TenantDbContext using DependencyInjection. Instead of that you need just add one simple constructor to TenantDbContext:
public TenantDbContext(DbContextOptions optionsBuilder): base (optionsBuilder)
{
}
Such simple constructor will allows you to create the context at any time when you need it:
public class TenantsController : Controller
{
public IActionResult Index() {
var contextOptions = new DbContextOptionsBuilder();
contextOptions.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;");
var context = new BloggingContext(contextOptions.Options);
context.Database.OpenConnection();
// get some data from the TenantDbContext
var blog = context.Blog.ToList();
return View(blog);
}
}
I used all the connection strings directly in the code. You can easy modify the above code to get all connection strings from the config file appsettings.json.
Solved.
1. CreateCustomUserStore
public class TenantUserStore : UserStore<TenantUser, TenantRole, TenantDbContext, Guid>
{
public TenantUserStore(TenantDbContext context, IdentityErrorDescriber describer = null): base(context, describer)
{
}
}
And here is code how to instantiate UserManager with custom database:
IUserStore<TenantUser> CustomStore = new TenantUserStore(new TenantDbContext(coonection), null);
UserManager<TenantUser> manager = new UserManager<TenantUser>(CustomStore, _optionsAccessor, _passwordHasher, _userValidators,
_passwordValidators, _keyNormalizer, _errors, _services, _logger, _contextAccessor);
And DI used only for rest of UserManager Constructor:
public class TenantsController : Controller
{
private readonly IHttpContextAccessor _contextAccessor;
private readonly IOptions<IdentityOptions> _optionsAccessor;
private readonly IPasswordHasher<TenantUser> _passwordHasher;
private readonly IEnumerable<IUserValidator<TenantUser>> _userValidators;
private readonly IEnumerable<IPasswordValidator<TenantUser>> _passwordValidators;
private readonly ILookupNormalizer _keyNormalizer;
private readonly IdentityErrorDescriber _errors;
private readonly IServiceProvider _services;
private readonly ILogger<UserManager<TenantUser>> _logger;
public TenantsController(IHttpContextAccessor contextAccessor,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<TenantUser> passwordHasher,
IEnumerable<IUserValidator<TenantUser>> userValidators,
IEnumerable<IPasswordValidator<TenantUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<TenantUser>> logger
)
{
_optionsAccessor = optionsAccessor;
_passwordHasher = passwordHasher;
_userValidators = userValidators;
_passwordValidators = passwordValidators;
_keyNormalizer = keyNormalizer;
_errors = errors;
_services = services;
_logger = logger;
_contextAccessor = contextAccessor;
}

Resources