Introduction
As many people already discovered that ASP.NET Identity 2.0 does not work with the same code as they have done for Identity 1.0 just like me, in this article, I tried to implement a simple role based authorization with ASP.NET MVC 5 and Identity 2.0.
Disclaimer
Most of the codes I have used are taken from John Atten's wonderful article Extending Identity Accounts and Implementing Role-Based Authentication in ASP.NET MVC 5. So, if you find similarities in codes and the title, it is not a coincidence. :)
Straight to the Business
I have not become a charming writer yet. So let's get down straight to the business.
Creating the Project
That's the easiest part.
- Go to File >> New Project
- Select Web >> ASP.NET Web Application
- Type in the name of the project: ASPNetMVCExtendingIdentity2Roles
- Click on OK.
- Select MVC as the template.
- Click on OK.
Voyla!!! You are done with the project creation. Now if you have looked at the codes attached with this article, you might have not noticed I did a bit of juggle with the files and folders. But I am pretty sure you will know what to do if you get a build error.
Domain Models
Models are the most important things in MVC. I will start with the domain models first.
ApplicationRole
Create a new class in the Models folder and name it as ApplicationRole
. Put the following code in the file.
using Microsoft.AspNet.Identity.EntityFramework;
namespace ASPNetMVCExtendingIdentity2Roles.Models
{
public class ApplicationRole : IdentityRole
{
public ApplicationRole() : base() { }
public ApplicationRole(string name, string description)
: base(name)
{
this.Description = description;
}
public virtual string Description { get; set; }
}
}
ApplicationUserRole
Time for another class, ApplicationUserRole
this time which will relate the ApplicationUser
to ApplicationRole
.
using Microsoft.AspNet.Identity.EntityFramework;
namespace ASPNetMVCExtendingIdentity2Roles.Models
{
public class ApplicationUserRole : IdentityUserRole
{
public ApplicationUserRole()
: base()
{ }
public ApplicationRole Role { get; set; }
}
}
ApplicationUser
We are almost there. We need just a little finishing touch to the already existing ApplicationUser
class. Modify the class with the following code.
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
namespace ASPNetMVCExtendingIdentity2Roles.Models
{
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
}
DbContext
Aah!!! We have reached to the most exciting part of the article, the DbContext. DbContext is the glue what sticks your application to the database. It should be well taken care of. My one is a bit clumsy though. Why don't you try to make yours beautiful? I included my DbContext initializer, my seeding mechanism and some business functionalities as well in this class, which is definitely not a good practice. But we are here to solve the Identity problem, not for learning software development best practice, are we? So, here is my code.
using ASPNetMVCExtendingIdentity2Roles.Models;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace ASPNetMVCExtendingIdentity2Roles.Context
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<ApplicationRole> Roles { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
if (modelBuilder == null)
{
throw new ArgumentNullException("ModelBuilder is NULL");
}
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>().ToTable("AspNetUsers");
modelBuilder.Entity<ApplicationRole>().HasKey<string>(r => r.Id).ToTable("AspNetRoles");
modelBuilder.Entity<ApplicationUser>().HasMany<ApplicationUserRole>((ApplicationUser u) => u.UserRoles);
modelBuilder.Entity<ApplicationUserRole>().HasKey(r => new { UserId = r.UserId, RoleId = r.RoleId }).ToTable("AspNetUserRoles");
}
public bool Seed(ApplicationDbContext context)
{
#if DEBUG
bool success = false;
ApplicationRoleManager _roleManager = new ApplicationRoleManager(new RoleStore<ApplicationRole>(context));
success = this.CreateRole(_roleManager, "Admin", "Global Access");
if (!success == true) return success;
success = this.CreateRole(_roleManager, "CanEdit", "Edit existing records");
if (!success == true) return success;
success = this.CreateRole(_roleManager, "User", "Restricted to business domain activity");
if (!success) return success;
ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(context));
ApplicationUser user = new ApplicationUser();
PasswordHasher passwordHasher = new PasswordHasher();
user.UserName = "youremail@testemail.com";
user.Email = "youremail@testemail.com";
IdentityResult result = userManager.Create(user, "Pass@123");
success = this.AddUserToRole(userManager, user.Id, "Admin");
if (!success) return success;
success = this.AddUserToRole(userManager, user.Id, "CanEdit");
if (!success) return success;
success = this.AddUserToRole(userManager, user.Id, "User");
if (!success) return success;
return success;
#endif
}
public bool RoleExists(ApplicationRoleManager roleManager, string name)
{
return roleManager.RoleExists(name);
}
public bool CreateRole(ApplicationRoleManager _roleManager, string name, string description = "")
{
var idResult = _roleManager.Create<ApplicationRole, string>(new ApplicationRole(name, description));
return idResult.Succeeded;
}
public bool AddUserToRole(ApplicationUserManager _userManager, string userId, string roleName)
{
var idResult = _userManager.AddToRole(userId, roleName);
return idResult.Succeeded;
}
public void ClearUserRoles(ApplicationUserManager userManager, string userId)
{
var user = userManager.FindById(userId);
var currentRoles = new List<IdentityUserRole>();
currentRoles.AddRange(user.UserRoles);
foreach (ApplicationUserRole role in currentRoles)
{
userManager.RemoveFromRole(userId, role.Role.Name);
}
}
public void RemoveFromRole(ApplicationUserManager userManager, string userId, string roleName)
{
userManager.RemoveFromRole(userId, roleName);
}
public void DeleteRole(ApplicationDbContext context, ApplicationUserManager userManager, string roleId)
{
var roleUsers = context.Users.Where(u => u.UserRoles.Any(r => r.RoleId == roleId));
var role = context.Roles.Find(roleId);
foreach (var user in roleUsers)
{
this.RemoveFromRole(userManager, user.Id, role.Name);
}
context.Roles.Remove(role);
context.SaveChanges();
}
public class DropCreateAlwaysInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
context.Seed(context);
base.Seed(context);
}
}
}
}
Don't forget to change the username and password in the seeding section. To initialize the DbContext the Global.asax file needs to be updated.
using ASPNetMVCExtendingIdentity2Roles.Context;
using System.Data.Entity;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace ASPNetMVCExtendingIdentity2Roles
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
#if DEBUG
Database.SetInitializer<ApplicationDbContext>(new ApplicationDbContext.DropCreateAlwaysInitializer());
#endif
}
}
}
ViewModels
ViewModel is a class that contains the data required for a View. They are not a mandatory part of the MVC pattern, but they make it easy to implement a view supplying the necessary data it requires.
RoleViewModels
using System.ComponentModel.DataAnnotations;
namespace ASPNetMVCExtendingIdentity2Roles.Models
{
public class RoleViewModel
{
public string RoleName { get; set; }
public string Description { get; set; }
public RoleViewModel() { }
public RoleViewModel(ApplicationRole role)
{
this.RoleName = role.Name;
this.Description = role.Description;
}
}
public class SelectRoleEditorViewModel
{
public SelectRoleEditorViewModel() { }
public SelectRoleEditorViewModel(ApplicationRole role)
{
this.RoleName = role.Name;
this.Description = role.Description;
}
public bool Selected { get; set; }
[Required]
public string RoleName { get; set; }
public string Description { get; set; }
}
public class EditRoleViewModel
{
public string OriginalRoleName { get; set; }
public string RoleName { get; set; }
public string Description { get; set; }
public EditRoleViewModel() { }
public EditRoleViewModel(ApplicationRole role)
{
this.OriginalRoleName = role.Name;
this.RoleName = role.Name;
this.Description = role.Description;
}
}
}
Controllers
RolesController
Create the RolesController
and put the following codes in.
using ASPNetMVCExtendingIdentity2Roles.Context;
using ASPNetMVCExtendingIdentity2Roles.Models;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web.Mvc;
namespace ASPNetMVCExtendingIdentity2Roles.Controllers
{
public class RolesController : Controller
{
private ApplicationDbContext _db = new ApplicationDbContext();
public ActionResult Index()
{
var rolesList = new List<RoleViewModel>();
foreach (var role in _db.Roles)
{
var roleModel = new RoleViewModel(role);
rolesList.Add(roleModel);
}
return View(rolesList);
}
[Authorize(Roles = "Admin")]
public ActionResult Create(string message = "")
{
ViewBag.Message = message;
return View();
}
[HttpPost]
[Authorize(Roles = "Admin")]
public ActionResult Create([Bind(Include =
"RoleName,Description")]RoleViewModel model)
{
string message = "That role name has already been used";
if (ModelState.IsValid)
{
var role = new ApplicationRole(model.RoleName, model.Description);
ApplicationRoleManager _roleManager = new ApplicationRoleManager(new RoleStore<ApplicationRole>(_db));
if (_db.RoleExists(_roleManager, model.RoleName))
{
return View(message);
}
else
{
_db.CreateRole(_roleManager, model.RoleName, model.Description);
return RedirectToAction("Index", "Roles");
}
}
return View();
}
[Authorize(Roles = "Admin")]
public ActionResult Edit(string id)
{
var role = _db.Roles.First(r => r.Name == id);
var roleModel = new EditRoleViewModel(role);
return View(roleModel);
}
[HttpPost]
[Authorize(Roles = "Admin")]
public ActionResult Edit([Bind(Include =
"RoleName,OriginalRoleName,Description")] EditRoleViewModel model)
{
if (ModelState.IsValid)
{
var role = _db.Roles.First(r => r.Name == model.OriginalRoleName);
role.Name = model.RoleName;
role.Description = model.Description;
_db.Entry(role).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
[Authorize(Roles = "Admin")]
public ActionResult Delete(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var role = _db.Roles.First(r => r.Name == id);
var model = new RoleViewModel(role);
if (role == null)
{
return HttpNotFound();
}
return View(model);
}
[Authorize(Roles = "Admin")]
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(string id)
{
var role = _db.Roles.First(r => r.Name == id);
ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(_db));
_db.DeleteRole(_db, userManager, role.Id);
return RedirectToAction("Index");
}
}
}
Views
Right click on the Actions in the RolesController
and create the respective views.
Create
@model ASPNetMVCExtendingIdentity2Roles.Models.RoleViewModel
@{
ViewBag.Title = "Create";
}
<h2>Create Role</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>RolesViewModel</h4>
<hr />
@Html.ValidationSummary(true)
@if (ViewBag.Message != "")
{
<p style="color: red">ViewBag.Message</p>
}
<div class="form-group">
@Html.LabelFor(model => model.RoleName, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.RoleName)
@Html.ValidationMessageFor(model => model.RoleName)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Description)
@Html.ValidationMessageFor(model => model.Description)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Delete
@model ASPNetMVCExtendingIdentity2Roles.Models.RoleViewModel
@{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>RolesViewModel</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.RoleName)
</dt>
<dd>
@Html.DisplayFor(model => model.RoleName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Description)
</dt>
<dd>
@Html.DisplayFor(model => model.Description)
</dd>
</dl>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
@Html.ActionLink("Back to List", "Index")
</div>
}
</div>
Edit
@model ASPNetMVCExtendingIdentity2Roles.Models.EditRoleViewModel
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>EditRolesViewModel</h4>
<hr />
@Html.ValidationSummary(true)
@*Hide the original name away for later:*@
@Html.HiddenFor(model => model.OriginalRoleName)
<div class="form-group">
@Html.LabelFor(model => model.RoleName, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.RoleName)
@Html.ValidationMessageFor(model => model.RoleName)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Description)
@Html.ValidationMessageFor(model => model.Description)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Index
@model IEnumerable<ASPNetMVCExtendingIdentity2Roles.Models.RoleViewModel>
@{
ViewBag.Title = "Application Roles";
}
<h2>Application Roles</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.RoleName)
</th>
<th>
@Html.DisplayNameFor(model => model.Description)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.RoleName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Description)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = item.RoleName }) |
@Html.ActionLink("Delete", "Delete", new { id = item.RoleName })
</td>
</tr>
}
</table>
Conclusion
That is all. Run your application. Login with the username and password you used in seeding, hit on the Roles link. Thank you for reading this far.