摘要:ASP.NET Identity Example - ToDo
測試環境: VS2013、MV5、Windows7
上次的貼文在ASP.NET MVC5中建置以角色為基礎的授權機制 中,已經只道如何完成以"角色"為基礎的授權機制,這次要再深入些,試試看除了不同角色可以執行不同功能外,在同一功能中,不同使用者只能維護屬於自己的資料。本例中有二種角色,一是Admin(特殊身份者)、另一個是一般使用者。只有Admin 可以使用"管理工作清單"這個功能,在這個功能中可以看到所有人的所有工作任務資料。另一個功能"工作清單",在這功能中每一位使用者只能看到屬於自己的資料(同時可以維護它)。
貼文內容:
-
建立MVC5新專案
-
修改相關 Models
-
擴展Identity Management Model
-
加入新欄位
-
建立Helper Class
-
-
新增 ToDo Class
-
擴展Account Management ViewModels (AccountViewModels.cs)
-
在RegisterViewModel加入新欄位
-
-
-
維護改相關 Controllers
-
修改AccountController 中 Register Method 加入 Authorize attribute
-
新增ToDoController.cs
-
-
維護相關 Views
-
修改Register.cshtml View
-
新增All method的View
-
-
在主頁面上新增功能按鈕
-
啟動 Migration功能
-
在Seed()方法中加入建立測試資料的程式碼
-
更新資料庫
-
執行結果
建立MVC5新專案
修改相關 Models
1. 擴展Identity Management Model (IdentityModels.cs)
-
加入新欄位:為使用者資料多加二個屬性欄位,分別是HomeTown、ToDoes。
-
建立Helper Class:利用Asp.Net Identity API建立一個 Identity Management Helper class: IdentityManager class,包含有建立角色、建立使用者...等功能。
IdentityModels.cs 完整程式
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using System.Collections.Generic; using System.Data.Entity; namespace ToDoProject.Models { // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more. public class ApplicationUser : IdentityUser { public string HomeTown { get; set; } public virtual ICollection<ToDo> ToDoes { get; set; } } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection") { } public DbSet<ToDo> ToDoes { get; set; } } public class IdentityManager { // 判斷角色是否已在存在 public bool RoleExists(string name) { var rm = new RoleManager<IdentityRole>( new RoleStore<IdentityRole>(new ApplicationDbContext())); return rm.RoleExists(name); } // 新增角色 public bool CreateRole(string name) { var rm = new RoleManager<IdentityRole>( new RoleStore<IdentityRole>(new ApplicationDbContext())); var idResult = rm.Create(new IdentityRole(name)); return idResult.Succeeded; } // 新增角色 public bool CreateUser(ApplicationUser user, string password) { var um = new UserManager<ApplicationUser>( new UserStore<ApplicationUser>(new ApplicationDbContext())); var idResult = um.Create(user, password); return idResult.Succeeded; } // 將使用者加入角色中 public bool AddUserToRole(string userId, string roleName) { var um = new UserManager<ApplicationUser>( new UserStore<ApplicationUser>(new ApplicationDbContext())); var idResult = um.AddToRole(userId, roleName); return idResult.Succeeded; } // 清除使用者的角色設定 public void ClearUserRoles(string userId) { var um = new UserManager<ApplicationUser>( new UserStore<ApplicationUser>(new ApplicationDbContext())); var user = um.FindById(userId); var currentRoles = new List<IdentityUserRole>(); currentRoles.AddRange(user.Roles); foreach (var role in currentRoles) { um.RemoveFromRole(userId, role.Role.Name); } } } } |
-
新增 ToDo Class
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace ToDoProject.Models { public class ToDo { public int Id { get; set; } public string Description { get; set; } public bool IsDone { get; set; } public virtual ApplicationUser User { get; set; } } } |
2. 擴展Account Management ViewModels (AccountViewModels.cs)
-
在RegisterViewModel加入新欄位
public class RegisterViewModel { [Required] [Display(Name = "使用者名稱")] public string UserName { get; set; } [Required] [StringLength(100, ErrorMessage = "{0} 的長度至少必須為 {2} 個字元。", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "密碼")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "確認密碼")] [Compare("Password", ErrorMessage = "密碼和確認密碼不相符。")] public string ConfirmPassword { get; set; } [Display(Name = "居住城市")] public string HomeTown { get; set; } } |
維護相關 Controllers
-
修改AccountController中Register Method
public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser() { UserName = model.UserName, HomeTown = model.HomeTown }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } // 如果執行到這裡,發生某項失敗,則重新顯示表單 return View(model); } |
-
新增ToDoController
ToDoController.cs 內容調整如下:
All method 加入了 [Authorize(Roles="Admin")],只有具Admin角色者可以執行此action。同在 class 上加入[Authorize]表示僅有登入者可以使用class 中的 action。
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using ToDoProject.Models; namespace ToDoProject.Controllers { [Authorize] public class ToDoController : Controller { private ApplicationDbContext db = new ApplicationDbContext(); private UserManager<ApplicationUser> manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())); // GET: /ToDo/ // 只回傳屬於自己的資料 public ActionResult Index() { var currentUser = manager.FindById(User.Identity.GetUserId()); return View(db.ToDoes.ToList().Where(t => t.User.Id == currentUser.Id)); //return View(db.ToDoes.ToList()); } [Authorize(Roles="Admin")] // 管理者可以看所有人的資料 public async Task<ActionResult> All() { return View(await db.ToDoes.ToListAsync()); } // GET: /ToDo/Details/5 // 只查看屬於自己的資料 public async Task<ActionResult> Details(int? id) { var currentUser = await manager.FindByIdAsync(User.Identity.GetUserId()); if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } ToDo todo = await db.ToDoes.FindAsync(id); if (todo == null) { return HttpNotFound(); } if (todo.User.Id != currentUser.Id) { return new HttpStatusCodeResult(HttpStatusCode.Unauthorized); } return View(todo); } // GET: /ToDo/Create public ActionResult Create() { return View(); } // POST: /ToDo/Create // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需 // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。 // 在新增的資料中寫入使用者資訊 [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Create([Bind(Include = "Id,Description,IsDone")] ToDo todo) { var currentUser = await manager.FindByIdAsync(User.Identity.GetUserId()); if (ModelState.IsValid) { todo.User = currentUser; db.ToDoes.Add(todo); await db.SaveChangesAsync(); return RedirectToAction("Index"); } return View(todo); } // GET: /ToDo/Edit/5 // 只編輯屬於自己的資料 public async Task<ActionResult> Edit(int? id) { var currentUser = await manager.FindByIdAsync(User.Identity.GetUserId()); if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } ToDo todo = await db.ToDoes.FindAsync(id); if (todo == null) { return HttpNotFound(); } if (todo.User.Id != currentUser.Id) { return new HttpStatusCodeResult(HttpStatusCode.Unauthorized); } return View(todo); } // POST: /ToDo/Edit/5 // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需 // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。 // 只編輯屬於自己的資料 [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Edit([Bind(Include = "Id,Description,IsDone")] ToDo todo) { if (ModelState.IsValid) { db.Entry(todo).State = EntityState.Modified; await db.SaveChangesAsync(); return RedirectToAction("Index"); } return View(todo); } // GET: /ToDo/Delete/5 public async Task<ActionResult> Delete(int? id) { var currentUser = await manager.FindByIdAsync(User.Identity.GetUserId()); if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } ToDo todo = await db.ToDoes.FindAsync(id); if (todo == null) { return HttpNotFound(); } if (todo.User.Id != currentUser.Id) { return new HttpStatusCodeResult(HttpStatusCode.Unauthorized); } return View(todo); } // POST: /ToDo/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public async Task<ActionResult> DeleteConfirmed(int id) { ToDo todo = db.ToDoes.Find(id); db.ToDoes.Remove(todo); await db.SaveChangesAsync(); return RedirectToAction("Index"); } protected override void Dispose(bool disposing) { if (disposing) { db.Dispose(); } base.Dispose(disposing); } } } |
維護相關 Views
-
修改Register.cshtml View
@model ToDoProject.Models.RegisterViewModel @{ ViewBag.Title = "註冊"; } <h2>@ViewBag.Title.</h2> @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { @Html.AntiForgeryToken() <h4>建立新的帳戶。</h4> <hr /> @Html.ValidationSummary() <div class="form-group"> @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.HomeTown, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.HomeTown, new { @class = "form-control" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" class="btn btn-default" value="註冊" /> </div> </div> } @section Scripts { @Scripts.Render("~/bundles/jqueryval") } |
-
新增ToDoController Views - All (給管理者所使用的功能)
@model IEnumerable<ToDoProject.Models.ToDo> @{ ViewBag.Title = "Index"; } <h2>顯示所有使用者資料</h2> <table class="table"> <tr> <th> @Html.DisplayNameFor(model => model.Description) </th> <th> @Html.DisplayNameFor(model => model.IsDone) </th> <th> @Html.DisplayNameFor(model => model.User.UserName) </th> <th> @Html.DisplayNameFor(model => model.User.HomeTown) </th> <th></th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Description) </td> <td> @Html.DisplayFor(modelItem => item.IsDone) </td> <td> @Html.DisplayFor(modelItem => item.User.UserName) </td> <td> @Html.DisplayFor(modelItem => item.User.HomeTown) </td> </tr> } </table> |
在主頁面上新增功能按鈕
多加入了"工作清單"、及"管理工作清單"二個功能。
啟動 Migration功能
在Seed()方法中加入建立測試資料的程式碼
完整程式:
namespace RoleBaseProject.Migrations { using RoleBaseProject.Models; using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; internal sealed class Configuration : DbMigrationsConfiguration<RoleBaseProject.Models.ApplicationDbContext> { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(RoleBaseProject.Models.ApplicationDbContext context) { // This method will be called after migrating to the latest version. // You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. E.g. // // context.People.AddOrUpdate( // p => p.FullName, // new Person { FullName = "Andrew Peters" }, // new Person { FullName = "Brice Lambson" }, // new Person { FullName = "Rowan Miller" } // ); // this.AddUserAndRoles(); } bool AddUserAndRoles() { bool success = false; var idManager = new IdentityManager(); success = idManager.CreateRole("Admin"); if (!success == true) return success; success = idManager.CreateRole("CanEdit"); if (!success == true) return success; success = idManager.CreateRole("User"); if (!success) return success; var newUser = new ApplicationUser() { UserName = "jatten", FirstName = "John", LastName = "Atten", Email = "jatten@typecastexception.com" }; success = idManager.CreateUser(newUser, "Password1"); if (!success) return success; success = idManager.AddUserToRole(newUser.Id, "Admin"); if (!success) return success; success = idManager.AddUserToRole(newUser.Id, "CanEdit"); if (!success) return success; success = idManager.AddUserToRole(newUser.Id, "User"); if (!success) return success; return success; } } } |
更新資料庫
執行完 update-database指令後系統會自動建立好資料庫,除了ASP.NET Identity 所使用的相關Table 外,由我們自行定義的 ToDoes Table 也建立成功了
執行結果
以具有Admin角色的使用者登入後執行帳戶管理功能:
可以看到所有人的待辦任務
使用一般使用者登入時只能維護自己的資料