ASP.NET Identity Example - ToDo

摘要: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角色的使用者登入後執行帳戶管理功能:

 

可以看到所有人的待辦任務


使用一般使用者登入時只能維護自己的資料


 

 

文章來源: http://blogs.msdn.com/b/webdev/archive/2013/10/20/building-a-simple-todo-application-with-asp-net-identity-and-associating-users-with-todoes.aspx