CreateAsync doesn't save IdentityUser to database with derived IdentityUser - asp.net-core

When Posting the following code works fine until it reaches the _userManager.CreateAsync method. No data is saved to the database.
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly ApplicationDbContext _appDbContext;
private readonly UserManager<IdentityUser> _userManager;
public ValuesController(UserManager<IdentityUser> userManager, ApplicationDbContext appDbContext)
{
_userManager = userManager;
_appDbContext = appDbContext;
}
[HttpPost]
public async Task<IActionResult> Post()
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
AppUser user = new AppUser();
user.Email = "email#mail.co.uk";
user.FirstName = "mark";
user.LastName = "Macneill";
user.UserName = "Saints22";
await _userManager.CreateAsync(user, "P4$$word");
return new OkObjectResult("Account created");
}
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}

You don't seem to be using the IdentityUser directly but instead a AppUser class. This might be a class extending IdentityUser<> or IdentityUser. I am not sure if you have the rest of your setup right, so here is the process:
If you have created a custom AppUser class, let's say you created it as follows:
class AppUser : IdentityUser<int>
This would mean you have assigned the primary key as in int. However, if you extended IdentityUser instead, note that IdentityUser extends IdentityUser<string>, meaning your key is a string. Going ahead I am going to assume your key is int. If not, you can make the changes accordingly (change all ints to your key type.
In your startup class, you need to be adding the following to register this as your user class used for Identity
services.AddIdentity<AppUser, IdentityRole<int>>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Your ApplicationDbContext needs to be defined as follows:
public class ApplicationDbContext : IdentityDbContext<AppUser, IdentityRole<int>, int>
If you don't want to use IdentityRole<int> in the above two pieces of code, you can define a custom role class as follows:
public AppUserRole : IdentityRole<int>
Once you have these setup, you need to inject UserManager<AppUser> not UserManager<IdentityUser>.

Thank you neville-nazerane
I had IdentityUser in dependency injection and all I had to do was change IdentityUser to AppUser.
Startup.cs
services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

Related

Role based authorization not working in .net 5

Problem Statement : I had this issue, role base Authorization ([Authorize(Roles = "Admin")]) wasn't working as expected for me in asp .net 5, role claims where not added to identity.
I googled and tried many solutions and finally I was able to figure out. So I am sharing few scenario so that it might help someone.
If you have a custom User that inherits from IdentityUser
For example:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
And you have your services configured properly, like so:
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>() /* Added role manager*/
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>();
That should work fine if you are not using a custom claim principal for UserPrincipal.
For example:
public class AppUserClaimsIdentityFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
public AppUserClaimsIdentityFactory(
UserManager<ApplicationUser> userManager,
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, optionsAccessor)
{
}
/* code implementation */
}
and injected it using DI like below:
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppUserClaimsIdentityFactory>();
In that case it doesn't work, you'll need to use a different constructor overload for UserClaimsPrincipalFactory and add role manager to it.
Update your custom UserClaimsIdentityFactory:
public class AppUserClaimsIdentityFactory :
UserClaimsPrincipalFactory<ApplicationUser,
IdentityRole>
/* Note: you can use your custom Role class or identity default */
{
public AppUserClaimsIdentityFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager, /* Add role manager */
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, roleManager /* send it to base constructor */, optionsAccessor)
{
}
/* code implementation */
}
You don't have to change anything in Dependency Injection container for your custom UserClaimsPrincipalFactory.
Everything thing should work fine as expected.

When dealing with Razor Pages and a Page Model class is the constructor called on each request?

Something I have struggled with is understanding the lifecycle of the Page Model class for my Razor Pages usages. I'm trying to think about how and when to deal with common data I pass to my business logic like the userId that is running the request. So many times I need to save this information with the results of the action.
So where is the best place to handle something over and over like geting User details that might be in the persistence model and not in the context of the page model's User from the HTTPContext?
I should mention I am using authorize tags with cookie backed authenication to a webservice.
For accessing Reuqest from other layers except the Razor Page, you could try IHttpContextAccessor.
For general way to handling user details from request, you could create a service like below:
public interface IUserService
{
IdentityUser GetUser();
}
public class UserService:IUserService
{
private readonly ApplicationDbContext _context;
private readonly HttpContext _httpContext;
public UserService(ApplicationDbContext context
, IHttpContextAccessor httpContextAccessor)
{
_context = context;
_httpContext = httpContextAccessor.HttpContext;
}
public IdentityUser GetUser()
{
StringValues userId = "";
if (_httpContext.Request.Headers.TryGetValue("userId", out userId))
{
var user = _context.Users.FirstOrDefault(u => u.Id == userId);
return user;
}
else
{
return null;
}
}
}
And then register like
services.AddScoped<IUserService, UserService>();
Then, you could resolve IUserService from DI to use them when you want to access user info.
public class IndexModel : PageModel
{
private readonly IUserService _userService;
public IndexModel(IUserService userService)
{
_userService = userService;
}
public void OnGet()
{
var user = _userService.GetUser();
}
}

Owin ApplicationDbContext did not update

I want to change my connectionstring at runtime based on the code user enters at login screen. I did the following
ApplicationDbContext
public static ApplicationDbContext Create(string scCode){
return new ApplicationDbContext("name=GEContext_" + scCode);
}
And at login i change the connectionstring as follows
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
System.Web.HttpContext.Current.Session["SchoolCode"] = model.SchoolCode;
var appDbContext = ApplicationDbContext.Create(model.SchoolCode);
HttpContext.GetOwinContext().Set<ApplicationDbContext>(appDbContext);
....
}
}
}
Now it is still referring to the original database...what am i missing?
P.S. For a history/detail consider this post
Hi I Got the Answer from here
The problem was in ApplicationDbContext where we need to specify a default database whereas in my scenario that default database had to change.
So i changed it using
var appDbContext = ApplicationDbContext.Create(System.Web.HttpContext.Current.Session["SchoolCode"].ToString());//new ApplicationDbContext("name=GEContext", System.Web.HttpContext.Current.Session["SchoolCode"].ToString());
HttpContext.GetOwinContext().Set<ApplicationDbContext>(appDbContext);
HttpContext.GetOwinContext().Set<ApplicationUserManager>(new ApplicationUserManager(new UserStore<ApplicationUser, Role, int, UserLogin, UserRole, UserClaim>(appDbContext)));
return HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();

ASP.NET 5 (MVC6) How to seed users

How would you seed users? I am following their documents here, but they only show how to seed data that is inserted directly by the ApplicationDbContext.
In the Account controller, the UserManager is created through DI. How would I instantiate a UserManager in the Seed method?
public class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
var context = serviceProvider.GetService<ApplicationDbContext>();
var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
Then in Startup.cs in the Configure method:
SeedData.Initialize(app.ApplicationServices);
In the startup.cs in the configure method, you can add the following code to get the UserManager and from there seed the users.
var userManager = app.ApplicationServices.GetService<UserManager<ApplicationUser>>();
You would create a class in the lines of
public class UserSeed
{
private ApplicationDbContext _context;
private UserManager<ApplicationUser> _mgr;
public UserSeed(ApplicationDbContext context,UserManager<ApplicationUser> userManager)
{
_context = context;
_mgr = users;
}
public void UserSeedData()
{
var user = new ApplicationUser { UserName = "foo#foo.com", Email = "foo#foo.com" };
var result = _mgr.CreateAsync(user,"fooPassword");
......
......
}
}
In the configure method of your startup.cs take userSeed through DI like
public async void Configure(...., UserSeed userSeedData)
and then inside the method you call
userSeedData.UserSeedData();
Don't use this code as is, you would probably want to check if the user already exists before you create it.
Note: This method will run once every time your application starts.

DbContext cannot find database

The connection string for our app is set in appsettings.json
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Customers;Trusted_Connection=True;MultipleActiveResultSets=true",
In ConfigureServices we have
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<CustomersContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
This seems to work in cases like this
var membershipUser = await _userManager.FindByEmailAsync(email);
and this
var result = await _userManager.CreateAsync(newUser);
but falls over when I try this
using (var customers = new CustomersContext())
{
var deviceList = customers.Devices.Where(d => d.UserId == membershipUser.Id);
The error is InvalidOperationException: 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.
If I try this
public partial class CustomersContext : IdentityDbContext<ApplicationUser>
// note this inherits from IdentityDbContext<ApplicationUser> not DbContext
// refer http://stackoverflow.com/questions/19902756/asp-net-identity-dbcontext-confusion
{
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(#"Server=(localdb)\\mssqllocaldb;Database=Customers;Trusted_Connection=True;MultipleActiveResultSets=true");
}
I get this error
Local Database Runtime error occurred. Specified LocalDB instance name is invalid
Why is it my app can find the database in some cases but not others?
The problem is that although you've configured a CustomerContext with the DI services as shown here:
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<CustomersContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
You are not having the CustomerContext injected, instead you are newing it up like this:
using (var customers = new CustomersContext())
{
...
}
using a constructor that takes no parameters, so your CustomersContext is not configured like the one in startup and it has no connection string.
Since you mention you need it in the AccountController, then all you need to do is add CustomersContext to the constructor of AccountController, so that the one you configured in startup will get injected. Like this:
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly ISmsSender _smsSender;
private readonly ILogger _logger;
private CustomersContext _customerContext;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory,
CustomersContext customerContext)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
_customerContext = customerContext;
}
That way you get a properly configured CusotmersContext and you don't have to new it up yourself. If for some reason you did want to new it up yourself you need to do so using a constructor that takes IServiceProvider and DbContextOptions. So you would receive those objects in the constructor of AccountController and you would pass them in as you new up the CustomersContext like this:
using (var customers = new CustomersContext(serviceProvider, dbContextOptions))
{
...
}