There is very little documentation about using the new Asp.net Identity Security Framework.
I have pieced together what I could to try and create a new Role and add a User to it. I tried the following: Add role in ASP.NET Identity
which looks like it may have gotten the info from this blog: building a simple to-do application with asp.net identity and associating users with to-does
I have added the code to a Database Initializer that is run whenever the model changes. It fails on the RoleExists function with the following error:
System.InvalidOperationException occurred in mscorlib.dll
The entity type IdentityRole is not part of the model for the current context.
protected override void Seed (MyContext context)
{
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
var RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
// Create Admin Role
string roleName = "Admins";
IdentityResult roleResult;
// Check to see if Role Exists, if not create it
if (!RoleManager.RoleExists(roleName))
{
roleResult = RoleManager.Create(new IdentityRole(roleName));
}
}
Any help is appreciated.
Here we go:
var roleManager = new RoleManager<Microsoft.AspNet.Identity.EntityFramework.IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
if(!roleManager.RoleExists("ROLE NAME"))
{
var role = new Microsoft.AspNet.Identity.EntityFramework.IdentityRole();
role.Name = "ROLE NAME";
roleManager.Create(role);
}
Verify you have following signature of your MyContext class
public class MyContext : IdentityDbContext<MyUser>
Or
public class MyContext : IdentityDbContext
The code is working for me, without any modification!!!
Here is the complete article describing how to create roles, modify roles, delete roles and manage roles using ASP.NET Identity. This also contains the User interface, controller methods, etc.
http://www.dotnetfunda.com/articles/show/2898/working-with-roles-in-aspnet-identity-for-mvc
In ASP.NET 5 rc1-final, I did following:
Created ApplicationRoleManager (in similar manner as there is ApplicationUser created by template)
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
public ApplicationRoleManager(
IRoleStore<IdentityRole> store,
IEnumerable<IRoleValidator<IdentityRole>> roleValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
ILogger<RoleManager<IdentityRole>> logger,
IHttpContextAccessor contextAccessor)
: base(store, roleValidators, keyNormalizer, errors, logger, contextAccessor)
{
}
}
To ConfigureServices in Startup.cs, I added it as RoleManager
services.
.AddIdentity<ApplicationUser, IdentityRole>()
.AddRoleManager<ApplicationRoleManager>();
For creating new Roles, call from Configure following:
public static class RoleHelper
{
private static async Task EnsureRoleCreated(RoleManager<IdentityRole> roleManager, string roleName)
{
if (!await roleManager.RoleExistsAsync(roleName))
{
await roleManager.CreateAsync(new IdentityRole(roleName));
}
}
public static async Task EnsureRolesCreated(this RoleManager<IdentityRole> roleManager)
{
// add all roles, that should be in database, here
await EnsureRoleCreated(roleManager, "Developer");
}
}
public async void Configure(..., RoleManager<IdentityRole> roleManager, ...)
{
...
await roleManager.EnsureRolesCreated();
...
}
Now, the rules can be assigned to user
await _userManager.AddToRoleAsync(await _userManager.FindByIdAsync(User.GetUserId()), "Developer");
Or used in Authorize attribute
[Authorize(Roles = "Developer")]
public class DeveloperController : Controller
{
}
As an improvement on Peters code above you can use this:
var roleManager = new RoleManager<Microsoft.AspNet.Identity.EntityFramework.IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
if (!roleManager.RoleExists("Member"))
roleManager.Create(new IdentityRole("Member"));
My application was hanging on startup when I used Peter Stulinski & Dave Gordon's code samples with EF 6.0. I changed:
var roleManager = new RoleManager<Microsoft.AspNet.Identity.EntityFramework.IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
to
var roleManager = new RoleManager<Microsoft.AspNet.Identity.EntityFramework.IdentityRole>(new RoleStore<IdentityRole>(**context**));
Which makes sense when in the seed method you don't want instantiate another instance of the ApplicationDBContext. This might have been compounded by the fact that I had Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer()); in the constructor of ApplicationDbContext
Roles View Model
public class RoleViewModel
{
public string Id { get; set; }
[Required(AllowEmptyStrings = false)]
[Display(Name = "RoleName")]
public string Name { get; set; }
}
Controller method
[HttpPost]
public async Task<ActionResult> Create(RoleViewModel roleViewModel)
{
if (ModelState.IsValid)
{
var role = new IdentityRole(roleViewModel.Name);
var roleresult = await RoleManager.CreateAsync(role);
if (!roleresult.Succeeded)
{
ModelState.AddModelError("", roleresult.Errors.First());
return View();
}
return RedirectToAction("some_action");
}
return View();
}
I wanted to share another solution for adding roles:
<h2>Create Role</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<span class="label label-primary">Role name:</span>
<p>
#Html.TextBox("RoleName", null, new { #class = "form-control input-lg" })
</p>
<input type="submit" value="Save" class="btn btn-primary" />
}
Controller:
[HttpGet]
public ActionResult AdminView()
{
return View();
}
[HttpPost]
public ActionResult AdminView(FormCollection collection)
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
if (roleManager.RoleExists(collection["RoleName"]) == false)
{
Guid guid = Guid.NewGuid();
roleManager.Create(new IdentityRole() { Id = guid.ToString(), Name = collection["RoleName"] });
}
return View();
}
If you are using the default template that is created when you select a new ASP.net Web application and selected Individual User accounts as Authentication and trying to create users with Roles so here is the solution. In the Account Controller's Register method which is called using [HttpPost], add the following lines in if condition.
using Microsoft.AspNet.Identity.EntityFramework;
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var roleStore = new RoleStore<IdentityRole>(new ApplicationDbContext());
var roleManager = new RoleManager<IdentityRole>(roleStore);
if(!await roleManager.RoleExistsAsync("YourRoleName"))
await roleManager.CreateAsync(new IdentityRole("YourRoleName"));
await UserManager.AddToRoleAsync(user.Id, "YourRoleName");
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
return RedirectToAction("Index", "Home");
}
This will create first create a role in your database and then add the newly created user to this role.
public static void createUserRole(string roleName)
{
if (!System.Web.Security.Roles.RoleExists(roleName))
{
System.Web.Security.Roles.CreateRole(roleName);
}
}
the method i Use for creating roles is below, assigning them to users in code is also listed. the below code does be in "configuration.cs" in the migrations folder.
string [] roleNames = { "role1", "role2", "role3" };
var RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
IdentityResult roleResult;
foreach(var roleName in roleNames)
{
if(!RoleManager.RoleExists(roleName))
{
roleResult = RoleManager.Create(new IdentityRole(roleName));
}
}
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
UserManager.AddToRole("user", "role1");
UserManager.AddToRole("user", "role2");
context.SaveChanges();
Related
This question already has answers here:
How to seed an Admin user in EF Core 2.1.0?
(7 answers)
Closed 3 years ago.
I have managed to seed the admin's details like the username and password, so they appear in the table. However, the issue I am having is the role "admin" is not being saved anywhere in the table. Am I missing something here? I am new to asp.net core so I'm just trying to wrap my head around it.
Below is my seeding class:
public class ApplicationDbInitializer
{
public static void SeedUsers(UserManager<IdentityUser> userManager)
{
if (userManager.FindByEmailAsync("abc#outlook.com").Result == null)
{
IdentityUser user = new IdentityUser
{
UserName = "abc#outlook.com",
Email = "abc#outlook.com"
};
IdentityResult result = userManager.CreateAsync(user, "Passwordtest123!").Result;
if (result.Succeeded)
{
userManager.AddToRoleAsync(user, "Admin").Wait();
}
}
}
}
Below is my configure method signature:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, UserManager<IdentityUser> userManager)
Below is me invoking my seed method:
ApplicationDbInitializer.SeedUsers(userManager);
Below is my add identity:
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<RestaurantWebContext>();
Is there something in the code that is missing, I can't see admin show up in the role table or the user table.
You need to seed the Roles as well, then link the User to the Role you want.
Check this answer for more information of how you can seed the data.
You can view my full code here
Below is an example
var user = new User
{
Id = new Guid("a1add451-7dd2-46fd-9877-1996e3f1fb4c").ToString(),
Email = "",
NormalizedEmail = "".ToUpper(),
UserName = "",
NormalizedUserName = "tony5".ToUpper(),
EmailConfirmed = true,
PhoneNumberConfirmed = true,
LockoutEnabled = false,
SecurityStamp = Guid.NewGuid().ToString()
};
using (var context = new ApplicationDbContext(
serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
{
var roles = new[]
{"Owner", "Administrator", "Editor", "ContentWriter"};
var roles1 = new[]
{"Administrator"};
var roles2 = new[]
{"Editor"};
var roles4 = new[]
{"Owner", "Administrator"};
if (!context.Roles.Any())
{
foreach (var role in roles)
{
var roleStore = new RoleStore<ApplicationRole>(context);
await roleStore.CreateAsync(new ApplicationRole
{
Name = role,
NormalizedName = role.ToUpper()
});
}
}
if (!context.Users.Any())
{
await SeedUser(user, context, serviceProvider, roles4);
}
}
private static async Task SeedUser(
User user,
ApplicationDbContext context,
IServiceProvider serviceProvider,
string[] roles)
{
var password = new PasswordHasher<User>();
var hashed = password.HashPassword(user, "123456");
user.PasswordHash = hashed;
var userStore = new UserStore<User>(context);
await userStore.CreateAsync(user);
await EnsureRole(serviceProvider, user.Email, roles);
await context.SaveChangesAsync();
}
I created a project based on the Microsoft authorization example which generated a class named ApplicationUser. I am trying to add claims to a user on account creation.
Based on this SO post I would add the claims to the constructor that creates the user. However that constructor doesnt seem to explicitly exist in the ApplicationUser class (but login works fine). How do I add claims to the user that has just been created?
Further, the class ApplicationUser doesnt have the properties UserName and Email that are being assigned to it in the Register method, which makes me think there is a lot going on in the background I'm missing.
ApplicationUser.cs
namespace xxx.Models
{
public class ApplicationUser : IdentityUser
{
}
}
AccountController.cs
private readonly UserManager<ApplicationUser> _userManager;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ILogger<AccountController> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_logger = logger;
}
.........
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
ApplicationUser doesn't have the properties UserName and Email that are being assigned to it in the Register method...
ApplicationUser inherits the UserName and Email properties from the IdentityUser class. Those properties and others are listed here in its documentation.
public class ApplicationUser : IdentityUser
{
}
How do I add claims to the user that has just been created?
One way to add claims to a user is to use the UserManager.AddClaimAsync method. There is an example of doing that here.
In the following snippet, which is similar to your Register method, we add the claim to the new user after checking that result.Succeeded.
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddClaimAsync(user, new Claim("MyClaimType", "MyClaimValue"));
Im working with .NET Core 2.0 MVC and Entity Framework with Individual User Accounts. By default, usernames are the same as email address. I used the following in Startup.cs to create the roles
private async Task CreateRoles(IServiceProvider serviceProvider)
{
//adding custom roles
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
string[] roleNames = { "Admin", "User" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
//creating the roles and seeding them to the database
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
//creating a super user who could maintain the web app
var poweruser = new ApplicationUser
{
UserName = Configuration.GetSection("UserSettings")["UserEmail"],
Email = Configuration.GetSection("UserSettings")["UserEmail"]
};
string UserPassword = Configuration.GetSection("UserSettings")["UserPassword"];
var _user = await UserManager.FindByEmailAsync(Configuration.GetSection("UserSettings")["UserEmail"]);
if (_user == null)
{
var createPowerUser = await UserManager.CreateAsync(poweruser, UserPassword);
if (createPowerUser.Succeeded)
{
//here we tie the new user to the "Admin" role
await UserManager.AddToRoleAsync(poweruser, "Admin");
}
}
}
And called it from the Configure method in Startup.cs. The roles added just fine, and the role was added to the admin.
However when I try to add a role to a user programmatically using the method await _userManager.AddToRoleAsync(applicationUser, "Admin"); in my ApplicationUsersController I get the error
User name is invalid, can only contain letters or digits
The .NET Core 2.0 documentation here https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-configuration?tabs=aspnetcore2x indicates that the "#" and/or "." that may be causing the error are included as valid characters by default in AllowedUserNameCharacters. I'm at a loss of other things to try.
Here is my ApplicationUsersController code:
[Authorize(Roles = "Admin")]
public class ApplicationUsersController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
public ApplicationUsersController(UserManager<ApplicationUser> userManager, ApplicationDbContext context)
{
_userManager = userManager;
_context = context;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("Id,Name,Email,IsAdmin,ConcurrencyStamp,SecurityStamp")] ApplicationUser applicationUser)
{
if (id != applicationUser.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
applicationUser.DateUpdated = DateTime.Now;
applicationUser.NormalizedEmail = applicationUser.Email.ToUpper();
_context.Update(applicationUser);
await _context.SaveChangesAsync();
if (applicationUser.IsAdmin)
{
var x = await _userManager.AddToRoleAsync(applicationUser, "Admin");
if (!x.Succeeded)
{
string s = "";
}
}
else
{
await _userManager.AddToRoleAsync(applicationUser, "User");
}
}
catch (DbUpdateConcurrencyException)
{
if (!ApplicationUserExists(applicationUser.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(applicationUser);
}
To get the currently logged in user in MVC5, all we had to do was:
using Microsoft.AspNet.Identity;
[Authorize]
public IHttpActionResult DoSomething() {
string currentUserId = User.Identity.GetUserId();
}
Now, with ASP.NET Core I thought this should work, but it throws an error.
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Http;
private readonly UserManager<ApplicationUser> _userManager;
[HttpPost]
[Authorize]
public async Task<IActionResult> StartSession() {
var curUser = await _userManager.GetUserAsync(HttpContext.User);
}
Any ideas?
EDIT: Gerardo's response is on track but to get the actual "Id" of the user, this seems to work:
ClaimsPrincipal currentUser = this.User;
var currentUserID = currentUser.FindFirst(ClaimTypes.NameIdentifier).Value;
If your code is inside an MVC controller:
public class MyController : Microsoft.AspNetCore.Mvc.Controller
From the Controller base class, you can get the ClaimsPrincipal from the User property
System.Security.Claims.ClaimsPrincipal currentUser = this.User;
You can check the claims directly (without a round trip to the database):
bool isAdmin = currentUser.IsInRole("Admin");
var id = _userManager.GetUserId(User); // Get user id:
Other fields can be fetched from the database's User entity:
Get the user manager using dependency injection
private UserManager<ApplicationUser> _userManager;
//class constructor
public MyController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
And use it:
var user = await _userManager.GetUserAsync(User);
var email = user.Email;
If your code is a service class, you can use dependency injection to get an IHttpContextAccessor that lets you get the User from the HttpContext.
private IHttpContextAccessor _httpContextAccessor;
public MyClass(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
private void DoSomething()
{
var user = _httpContextAccessor.Context?.User;
}
If you are using Bearing Token Auth, the above samples do not return an Application User.
Instead, use this:
ClaimsPrincipal currentUser = this.User;
var currentUserName = currentUser.FindFirst(ClaimTypes.NameIdentifier).Value;
ApplicationUser user = await _userManager.FindByNameAsync(currentUserName);
This works in apsnetcore 2.0. Have not tried in earlier versions.
For context, I created a project using the ASP.NET Core 2 Web Application template. Then, select the Web Application (MVC) then hit the Change Authentication button and select Individual User accounts.
There is a lot of infrastructure built up for you from this template. Find the ManageController in the Controllers folder.
This ManageController class constructor requires this UserManager variable to populated:
private readonly UserManager<ApplicationUser> _userManager;
Then, take a look at the the [HttpPost] Index method in this class. They get the current user in this fashion:
var user = await _userManager.GetUserAsync(User);
As a bonus note, this is where you want to update any custom fields to the user Profile you've added to the AspNetUsers table. Add the fields to the view, then submit those values to the IndexViewModel which is then submitted to this Post method. I added this code after the default logic to set the email address and phone number:
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.Address1 = model.Address1;
user.Address2 = model.Address2;
user.City = model.City;
user.State = model.State;
user.Zip = model.Zip;
user.Company = model.Company;
user.Country = model.Country;
user.SetDisplayName();
user.SetProfileID();
_dbContext.Attach(user).State = EntityState.Modified;
_dbContext.SaveChanges();
In .NET Core 2.0 the user already exists as part of the underlying inherited controller. Just use the User as you would normally or pass across to any repository code.
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Policy = "TENANT")]
[HttpGet("issue-type-selection"), Produces("application/json")]
public async Task<IActionResult> IssueTypeSelection()
{
try
{
return new ObjectResult(await _item.IssueTypeSelection(User));
}
catch (ExceptionNotFound)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new
{
error = "invalid_grant",
error_description = "Item Not Found"
});
}
}
This is where it inherits it from
#region Assembly Microsoft.AspNetCore.Mvc.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// C:\Users\BhailDa\.nuget\packages\microsoft.aspnetcore.mvc.core\2.0.0\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Core.dll
#endregion
using System;
using System.IO;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc
{
//
// Summary:
// A base class for an MVC controller without view support.
[Controller]
public abstract class ControllerBase
{
protected ControllerBase();
//
// Summary:
// Gets the System.Security.Claims.ClaimsPrincipal for user associated with the
// executing action.
public ClaimsPrincipal User { get; }
Just if any one is interested this worked for me. I have a custom Identity which uses int for a primary key so I overrode the GetUserAsync method
Override GetUserAsync
public override Task<User> GetUserAsync(ClaimsPrincipal principal)
{
var userId = GetUserId(principal);
return FindByNameAsync(userId);
}
Get Identity User
var user = await _userManager.GetUserAsync(User);
If you are using a regular Guid primary key you don't need to override GetUserAsync. This is all assuming that you token is configured correctly.
public async Task<string> GenerateTokenAsync(string email)
{
var user = await _userManager.FindByEmailAsync(email);
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_tokenProviderOptions.SecretKey);
var userRoles = await _userManager.GetRolesAsync(user);
var roles = userRoles.Select(o => new Claim(ClaimTypes.Role, o));
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString(CultureInfo.CurrentCulture)),
new Claim(JwtRegisteredClaimNames.GivenName, user.FirstName),
new Claim(JwtRegisteredClaimNames.FamilyName, user.LastName),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
}
.Union(roles);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(_tokenProviderOptions.Expires),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return Task.FromResult(new JwtSecurityTokenHandler().WriteToken(token)).Result;
}
private readonly UserManager<AppUser> _userManager;
public AccountsController(UserManager<AppUser> userManager)
{
_userManager = userManager;
}
[Authorize(Policy = "ApiUser")]
[HttpGet("api/accounts/GetProfile", Name = "GetProfile")]
public async Task<IActionResult> GetProfile()
{
var userId = ((ClaimsIdentity)User.Identity).FindFirst("Id").Value;
var user = await _userManager.FindByIdAsync(userId);
ProfileUpdateModel model = new ProfileUpdateModel();
model.Email = user.Email;
model.FirstName = user.FirstName;
model.LastName = user.LastName;
model.PhoneNumber = user.PhoneNumber;
return new OkObjectResult(model);
}
I have put something like this in my Controller class and it worked:
IdentityUser user = await userManager.FindByNameAsync(HttpContext.User.Identity.Name);
where userManager is an instance of Microsoft.AspNetCore.Identity.UserManager class (with all weird setup that goes with it).
I am doing registration on which i am asking for 5 things:
FullName,EmailId,Password,ContactNumber,Gender
Now emailid and password i am storing with register method and given in below two link:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
using var context = new MyEntities())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
var DataModel = new UserMaster();
DataModel.Gender = model.Gender.ToString();
DataModel.Name = string.Empty;
var result = await UserManager.CreateAsync(user, model.Password);//Doing entry in AspnetUser even if transaction fails
if (result.Succeeded)
{
await this.UserManager.AddToRoleAsync(user.Id, model.Role.ToString());
this.AddUser(DataModel, context);
transaction.Commit();
return View("DisplayEmail");
}
AddErrors(result);
}
catch (Exception ex)
{
transaction.Rollback();
return null;
}
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public int AddUser(UserMaster _addUser, MyEntities _context)
{
_context.UserMaster.Add(_addUser);
_context.SaveChanges();
return 0;
}
Now with this below 2 lines:
var result = await UserManager.CreateAsync(user, model.Password);//entry is done in AspnetUsers table.
await this.UserManager.AddToRoleAsync(user.Id, model.Role.ToString());//entry is done is Aspnetuserrole table
Now this Fullname,contactno,gender i am having in another table that is UserMaster.
So when i will submit my registration form i will save this details in UserMaster and AspnetUsers,AspnetUserinrole table.
But consider if there any problem occurs while saving record in UserMaster then i dont want to save entry in Aspnetuser and Aspnetuserinrole too.
I would like to create a transaction where i would rollback if any problem occurs during saving any record in any table i.e no entry should be done in AspnetUser,AspnetUserinRole nd userMaster.
Records should be saved successfully only if there is no problem in saving record in this 3 tables otherwise whiole transaction should be role back.
I am using Microsoft.AspNet.Identity for login,Register,role management and other and following this tutorial:
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset
http://www.asp.net/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity
But as await UserManager.CreateAsync and UserManager.AddToRoleAsync method are built in method how would i synchonize it to work with entity framework.
So can anybody guide me how to create such transaction or anything that would solve this?
IdentityConfig:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.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.
manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Your security code is {0}"
});
manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
// Configure the application sign-in manager which is used in this application.
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
}
You should not create a new db context, but use the existing one.
var context = Request.GetOwinContext().Get<MyEntities>()
It is created per request if you use default implementation.
app.CreatePerOwinContext(ApplicationDbContext.Create);
Update:
OK, since you are using two different contexts your code will look something like this:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var appDbContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();
using( var context = new MyEntities())
using (var transaction = appDbContext.Database.BeginTransaction())
{
try
{
var DataModel = new UserMaster();
DataModel.Gender = model.Gender.ToString();
DataModel.Name = string.Empty;
// Doing entry in AspnetUser even if transaction fails
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await this.UserManager.AddToRoleAsync(user.Id, model.Role.ToString());
this.AddUser(DataModel, context);
transaction.Commit();
return View("DisplayEmail");
}
AddErrors(result);
}
catch (Exception ex)
{
transaction.Rollback();
return null;
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public int AddUser(UserMaster _addUser, MyEntities _context)
{
_context.UserMaster.Add(_addUser);
_context.SaveChanges();
return 0;
}
Here, appDbContext is the same context that is used by UserManager.
You can solve it with TransactionScope class:
using (TransactionScope scope = new TransactionScope())
{
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await this.UserManager.AddToRoleAsync(user.Id, model.Role.ToString());
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
return View("DisplayEmail");
}
scope.Complete();
}
So, both actions will be done in one transaction and if method Comlete does not call, both actions will be canceled (roolback).
If you want to solve it with EF only (without TransactionScope), you need to refactor your code. I don't know implementation of class UserManager and methods CreateAsync and AddToRoleAsync, but I guess that they creates new DBContext for each operation. So, first of all, for all transactional operations you need one DBContext (for EF solution). If you add this methods, I'll modify my answer according to EF solution.
Backs alternative works for me, when i use this method: TransactionScope(TransactionScopeAsyncFlowOption.Enabled)
source: https://learn.microsoft.com/en-us/ef/ef6/saving/transactions