Thứ Tư, 18 tháng 1, 2017

[Series] Hướng dẫn sử dụng ASP.NET Identity (phần 4)

Ở bài viết này mình sẽ hướng dẫn bạn tạo trang quản lý role, tạo trang quản lý user và cuối cùng là phân quyền user thao tác trên controller và các action method.



Nội dung chính



1. Tạo trang quản lý Role


Chúng ta cần phải tạo trang để cho phép người quản trị có thể thêm, xóa, sửa các role (quyền).

Bước đầu tiên bạn tạo 1 area đặt tên bất kỳ theo ý muốn, trong bài viết này mình sẽ tạo 1 area là Admin. Sau đó tạo 2 empty controller là ManageRoleController, ManageUserController trong area này.



Tiếp theo là mở ManageRoleController lên, tiến hành khai báo biến context của class ApplicationDbContext và thay thế nội dung trong action method Index.



Lệnh context.Roles.AsEnumerable(); để lấy tất cả các roles hiện có trong bảng dbo.AspNetRoles ở CSDL, sau đó gán qua biến model và truyền qua view Index.

Tiếp theo tạo view Index.cshtml và thay thế nội dung trong view bằng đoạn lệnh sau:

@using Microsoft.AspNet.Identity.EntityFramework;

@model IEnumerable<IdentityRole>

@{

    ViewBag.Title = "Quản lý quyền hạn người dùng";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

<div class="row">

    <div class="col-sm-6">

        <h2>@ViewBag.Title</h2>

        <hr />

        @Html.ActionLink("Tạo role", "Create", null, new { @class = "btn btn-primary" })

        <br/><br/>

        <table class="table table-bordered">

            <thead>

                <tr>

                    <th>ID</th>

                    <th>Tên quyền</th>

                    <th>Thao tác</th>

                </tr>

            </thead>

            <tbody>

                @foreach (var item in Model)

                {

                    <tr>

                        <td>@item.Id</td>

                        <td>@item.Name</td>

                        <td>@Html.ActionLink("Xóa", "Delete", new { id = item.Id })</td>

                    </tr>

                }

            </tbody>

        </table>

    </div>

</div>


Vòng lặp foreach trong view để lặp qua biến model được truyền tự action Index qua và hiển thị thông tin mỗi role trên mỗi dòng trong thẻ table. Bạn cũng đừng quên khai báo 2 dòng đầu trong view nhé. Ngoài ra mình cũng thiết kế thêm 2 button là "Tạo role" và "Xóa" để thực hiện chức năng tạo mới role và xóa role được chọn.

Build lại project và truy cập vào đường dẫn http://localhost:52531/admin/managerole (port của bạn có thể khác đó nhé) thì bạn sẽ nhận được lỗi như sau:

Multiple object sets per type are not supported. The object sets 'ApplicationUsers' and 'Users' can both contain instances of type 'Demo_ASP.NET_Identity.Models.ApplicationUser'.



Đừng hoảng sợ. Để khắc phục lỗi này bạn hãy mở file IdentityModels.cs trong thư mục Models và xóa đoạn lệnh public System.Data.Entity.DbSet<Demo_ASP.NET_Identity.Models.ApplicationUser> ApplicationUsers { get; set; }. trong class ApplicationDbContext.

Sau khi thực hiện xong bạn build lại project và truy cập lại vào link trên thì nhận được kết quả như hình dưới:


Tất nhiên là table chưa hiển thị role nào vì bạn chưa hề tạo role nào cả. Bây giờ chúng ta cần tiến hành bổ sung chức năng tạo role.

Đầu tiên bạn cần khai báo thêm namespace Microsoft.AspNet.Identity.EntityFramework. Sau đó bổ sung thêm 2 action method Create trong ManageRoleController như sau:

        public ViewResult Create()

        {

            return View();

        }

        [HttpPost]

        [ValidateAntiForgeryToken]

        public ActionResult Create(IdentityRole role)

        {

            try

            {

                if (ModelState.IsValid)

                {

                    context.Roles.Add(role);

                    context.SaveChanges();

                }

                return RedirectToAction("Index");

            }

            catch(Exception ex)

            {

                ModelState.AddModelError("", ex.Message);

            }

            return View(role);

        }

Tiếp theo là add view Create và thay thế nội dung trong view thành như sau:

@model Microsoft.AspNet.Identity.EntityFramework.IdentityRole

@{

    ViewBag.Title = "Tạo role";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

<div class="row">

    <div class="col-sm-6">

        <h2>@ViewBag.Title</h2>

        <hr />

        @using (Html.BeginForm())

        {

            @Html.AntiForgeryToken()

            @Html.ValidationSummary("", new { @class = "text-danger" })

            <div class="form-group">

                <label for="Id">Id</label>

                @Html.TextBoxFor(m => m.Id, new { id = "Id", @class = "form-control", required = "required" })

            </div>

            <div class="form-group">

                <label for="Name">Name</label>

                @Html.TextBoxFor(m => m.Name, new { id = "Name", @class = "form-control", required = "required" })

            </div>

            <div class="form-group">

                <button type="submit" class="btn btn-primary">Tạo role</button>

            </div>

        }

    </div>

</div>

Build project và click vào button "Tạo role" để đến trang Tạo role và tiến hành tạo role. Ở bài viết này mình sẽ ví dụ bằng cách tạo 1 role là Admin. Mình nhập Id là Ad và Name là Admin. 

Chú ý: Id là khóa chính của bảng dbo.AspNetRoles nên khi tạo role bạn không được nhập Id trùng nhau.



Sau đó mình click "Tạo role" thì được kết quả là tạo thành công 1 role Admin như hình dưới




Bây giờ tiến hành nốt chức năng còn lại là xóa Role nhé. Bạn bổ sung thêm 2 action method Delete DeleteConfirmed vào ManageRoleController.

        public ActionResult Delete(string Id)

        {

            var model = context.Roles.Find(Id);

            return View(model);

        }

     

        [HttpPost]

        [ValidateAntiForgeryToken]

        [ActionName("Delete")]

        public ActionResult DeleteConfirmed(string Id)

        {

            IdentityRole model = null;

            try

            {

                model = context.Roles.Find(Id);

                context.Roles.Remove(model);

                context.SaveChanges();

                return RedirectToAction("Index");

            }

            catch(Exception ex)

            {

                ModelState.AddModelError("", ex.Message);

            }

            return View(model);

        }

Và không quên tạo view Delete cho action. Dưới đây là nội dung trong view Delete:

@model Microsoft.AspNet.Identity.EntityFramework.IdentityRole

@{

    ViewBag.Title = "Xóa role";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

<div class="row">

    <div class="col-md-12 ">

        <h2>@ViewBag.Title</h2>

        <hr />

        <div class="text-center">

            <h5>Bạn có chắc muốn xóa role @Model.Name ? </h5>

            @using (Html.BeginForm())

            {

                @Html.AntiForgeryToken()

                @Html.ValidationSummary("", new { @class="text-danger"})

                <button type="submit" class="btn btn-primary">Chấp nhận</button>

                <a href="@Url.Action("Index")" class="btn btn-default">Không xóa</a>

            }

        </div>

    </div>

</div>

Nội dung trong view Delete hiển thị câu hỏi để xác nhận xem người quản trị có chắc muốn xóa role được chọn không, đi kèm là nút submit "Chấp nhận" để chấp nhận xóa và nút "Không xóa" để điều hướng về trang Index nếu người dùng không muốn xóa nữa.

Bây giờ bạn build project và thử tạo mới role ABC và xóa luôn role ABC vừa tạo xem OK ko nhé.

Vậy là chúng ta đã cài đặt xong chức năng quản lý role.

2. Tạo trang quản lý User


Có quản lý role thì cũng phải tất nhiên cũng phải có quản lý user đúng chứ. Bây giờ chúng ta cần tạo trang để hiển thị toàn bộ user trong website. Đầu tiên bạn mở ManageUserController lên và thay thế nội dung trong controller thành như sau:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Mvc;

using Demo_ASP.NET_Identity.Models;   // Sửa lại cho đúng với namespace của project bạn

using Microsoft.AspNet.Identity.EntityFramework;

namespace Demo_ASP.NET_Identity.Areas.Admin.Controllers

{

    [Authorize(Users = "nguyenaituan@yahoo.com")]

    public class ManageUserController : Controller

    {

        ApplicationDbContext context = new ApplicationDbContext();

        // GET: Admin/ManageUser

        public ActionResult Index()

        {

            IEnumerable<ApplicationUser> model = context.Users.AsEnumerable();

            return View(model);

        }

        public ActionResult Edit(string Id)

        {

            ApplicationUser model = context.Users.Find(Id);

            return View(model);

        }

        [HttpPost]

        [ValidateAntiForgeryToken]

        public ActionResult Edit(ApplicationUser model)

        {

            try

            {

                    context.Entry(model).State = System.Data.Entity.EntityState.Modified;

                    context.SaveChanges();

                    return RedirectToAction("Index");

            }

            catch (Exception ex)

            {

                ModelState.AddModelError("", ex.Message);

                return View(model);

            }

        }

        public ActionResult EditRole(string Id)

        {

            ApplicationUser model = context.Users.Find(Id);

            ViewBag.RoleId = new SelectList(context.Roles.ToList().Where(item=> model.Roles.FirstOrDefault(r=>r.RoleId == item.Id) == null).ToList(), "Id", "Name");

            return View(model);

        }

        [HttpPost]

        [ValidateAntiForgeryToken]

        public ActionResult AddToRole(string UserId, string[] RoleId)

        {

            ApplicationUser model = context.Users.Find(UserId);

            if (RoleId != null && RoleId.Count() > 0)

            {

                foreach (string item in RoleId)

                {

                    IdentityRole role = context.Roles.Find(RoleId);

                    model.Roles.Add(new IdentityUserRole() { UserId = UserId, RoleId = item });

                }

                context.SaveChanges();

            }

            ViewBag.RoleId = new SelectList(context.Roles.ToList().Where(item => model.Roles.FirstOrDefault(r => r.RoleId == item.Id) == null).ToList(), "Id", "Name");

            return RedirectToAction("EditRole", new { Id = UserId });

        }

        [HttpPost]

        [ValidateAntiForgeryToken]

        public ActionResult DeleteRoleFromUser(string UserId, string RoleId)

        {

            ApplicationUser model = context.Users.Find(UserId);

            model.Roles.Remove(model.Roles.Single(m => m.RoleId == RoleId));

            context.SaveChanges();

            ViewBag.RoleId = new SelectList(context.Roles.ToList().Where(item => model.Roles.FirstOrDefault(r => r.RoleId == item.Id) == null).ToList(), "Id", "Name");

            return RedirectToAction("EditRole", new { Id = UserId });

        }

        public ActionResult Delete(string Id)

        {

            var model = context.Users.Find(Id);

            return View(model);

        }

        [HttpPost]

        [ValidateAntiForgeryToken]

        [ActionName("Delete")]

        public ActionResult DeleteConfirmed(string Id)

        {

            ApplicationUser model = null;

            try

            {

                model = context.Users.Find(Id);

                context.Users.Remove(model);

                context.SaveChanges();

                return RedirectToAction("Index");

            }

            catch (Exception ex)

            {

                ModelState.AddModelError("", ex.Message);

                return View("Delete", model);

            }

        }

    }

}

Về chức năng quản lý User thì mình thấy không cần thiết phải có chức năng tạo User vì đã có chức năng đăng ký thành viên rồi nên bạn sẽ không thấy action method Create nhé, còn nếu muốn thì bạn có thể tự bổ sung luôn cũng OK. Trong controller ngoài các action method Index, Edit, Delete đã quen thuộc với bạn thì bạn sẽ thấy mình đã bổ sung thêm 3 action method khác là EditRole, AddToRole, DeleteRoleFromUser. Mình giải thích xíu về chức năng của 3 action này như sau:

  • EditRole: đây là action method dạng Get để hiển thị view cho người quản trị có thể sửa role (thêm role vào hoặc bỏ bớt role) của 1 user nào đó. Bạn lưu ý là trong ASP.NET Identity thì 1 user có thể có nhiều role (bảng dbo.AspNetUserRoles cho thấy điều đó)
  • AddToRole: đây là action method dạng Post xử lý khi người quản trị submit form, form AddToRole mình sẽ thiết kế nó nằm luôn trong view EditRole.
  • DeleteRoleFromUser: đây là action method dạng Post xử lý xóa 1 role khỏi 1 user bất kỳ, form DeleteRoleFromUser mình sẽ thiết kế nó nằm luôn trong view EditRole.

Dưới đây là code view và hình ảnh của các view trong ManageUserController:

1. Index.cshtml

@using Demo_ASP.NET_Identity.Models;

@model IEnumerable<ApplicationUser>

@{

    ViewBag.Title = "Quản lý thành viên";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

<div class="row">

    <div class="col-sm-12">

        <h2>@ViewBag.Title</h2>

        <hr />

        <table class="table table-borderer">

            <thead>

                <tr>

                    <th>Họ tên</th>

                    <th>Email</th>

                    <th>Giới tính</th>

                    <th>Địa chỉ</th>

                    <th></th>

                </tr>

            </thead>

            <tbody>

                @foreach (var item in Model)

                {

                    <tr>

                        <td>@item.FullName</td>

                        <td>@item.Email</td>

                        <td>@(item.Gender == true ? "Nam" : "Nữ")</td>

                        <td>@item.Address</td>

                        <td>

                            @Html.ActionLink("Sửa thông tin", "Edit", new { Id = item.Id }, null) |

                            @Html.ActionLink("Sửa quyền", "EditRole", new { Id = item.Id }, null) |

                            @Html.ActionLink("Xóa", "Delete", new { Id = item.Id }, null)

                        </td>

                    </tr>

                }

            </tbody>

        </table>

    </div>

</div>


View Index hiển thị danh sách user. Ở đây mình hiển thị các thông tin họ tên, email, giới tính, địa chỉ còn bạn có thể bổ sung thêm thông tin hiển thị ra view Index tùy thuộc vào bảng dbo.AspNetUsers của bạn có bao nhiêu cột thông tin. Bên cạnh các thông tin của user thì mình còn tạo thêm 3 liên kết "Sửa thông tin", "Sửa quyền", "Xóa".

2. Edit.cshtml

@model Demo_ASP.NET_Identity.Models.ApplicationUser

@{

    ViewBag.Title = "Sửa thông tin";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

<div class="row">

    <div class="col-sm-8">

        <h2>@ViewBag.Title</h2>

        <hr/>

        @using (Html.BeginForm())

        {

            @Html.ValidationSummary("",new { @class="text-danger"})

            @Html.AntiForgeryToken()

         

            @Html.HiddenFor(m => m.UserName)

            @Html.HiddenFor(m => m.Id)

            @Html.HiddenFor(m=>m.Email)

            @Html.HiddenFor(m=>m.AccessFailedCount)

            @Html.HiddenFor(m => m.Claims)

            @Html.HiddenFor(m => m.EmailConfirmed)

            @Html.HiddenFor(m => m.LockoutEnabled)

            @Html.HiddenFor(m => m.LockoutEndDateUtc)

            @Html.HiddenFor(m => m.PasswordHash)

            @Html.HiddenFor(m => m.PhoneNumber)

            @Html.HiddenFor(m => m.PhoneNumberConfirmed)

            @Html.HiddenFor(m => m.SecurityStamp)

            @Html.HiddenFor(m => m.TwoFactorEnabled)

            <div class="form-group">

                <label>Họ tên:</label>

                @Html.TextBoxFor(m=>m.FullName, new { @class="form-control" ,required="required"})

            </div>

            <div class="form-group">

                <label>Địa chỉ:</label>

                @Html.TextBoxFor(m => m.Address, new { @class = "form-control", required = "required" })

            </div>

            <div class="form-group">

                <label>Giới tính:</label>

                 Nam @Html.CheckBox("Gender", Model.Gender == true ? true : false)

            </div>

            <div class="form-group">

                <button type="submit" class="btn btn-primary">Lưu</button>

            </div>

        }

    </div>

</div>



View Edit để quản trị viên chỉnh sửa thông tin thành viên.  Các bạn sẽ thấy trong form có 1 loạt các lệnh Html.HiddenFor, đó là để tạo ra các thẻ input có type="hidden" nhằm giữ lại giá trị các cột của user mà quản trị viên không có nhu cầu chỉnh sửa.

3. Delete.cshtml

@model Demo_ASP.NET_Identity.Models.ApplicationUser

@{

    ViewBag.Title = "Xóa thành viên";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

<div class="row">

    <div class="col-sm-12">

        <h2>@ViewBag.Title</h2>

        <hr/>

        <div class="text-center">

            <h5>Bạn có chắc muốn xóa thành viên @Model.Email?</h5>

            @using (Html.BeginForm())

            {

                @Html.AntiForgeryToken()

                @Html.ValidationSummary("",new { @class="text-danger"})

                <button type="submit" class="btn btn-primary">Chấp nhận</button>

                <a href="@Url.Action("Index")" class="btn btn-default">Không xóa</a>

            }

        </div>

    </div>

</div>



Nội dung trong view này cũng đơn giản là chứa câu hỏi xác thực xóa và 1 form chứa button submit và thẻ a để redirect về trang chủ nếu người quản trị không muốn xóa.

4. EditRole.cshtml

@model Demo_ASP.NET_Identity.Models.ApplicationUser

@{

    ViewBag.Title = "Sửa quyền";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

<div class="row">

    <div class="col-sm-6">

        <h2>@ViewBag.Title</h2>

        <hr />

        <h5><strong>Các quyền hiện tại của thành viên @Model.Email:</strong> </h5>

        <table class="table">

            <thead>

                <tr>

                    <th>Quyền</th>

                    <th></th>

                </tr>

            </thead>

            <tbody>

                @if (Model.Roles.Count > 0)

                {

                foreach (var item in Model.Roles)

                {

                <tr>

                    <td>@item.RoleId</td>

                    <td>

                        <form action="@Url.Action("DeleteRoleFromUser", new { UserId = item.UserId, RoleId = item.RoleId })" method="post">

                            @Html.AntiForgeryToken()

                            <button type="submit" class="btn btn-xs btn-danger">Gỡ bỏ</button>

                        </form>

                    </td>

                </tr>

                    }

                }

                else

                {

                    <tr>

                        <td colspan="2">

                            <p>Thành viên chưa có quyền nào</p>

                        </td>

                    </tr>

                }

            </tbody>

        </table>

        <h5><strong>Thêm quyền cho thành viên này:</strong></h5>

        <form action="@Url.Action("AddToRole")" method="post">

            <input type="hidden" name="UserId" value="@Url.RequestContext.RouteData.Values["Id"]" />

            @Html.AntiForgeryToken()

            @Html.ListBox("RoleId", null, new { @class = "form-control", multiple = "multiple" })

            <div class="form-group">

                <br />

                <button type="submit" class="btn btn-primary">Thêm </button>

            </div>

        </form>

    </div>

</div>

3 view kia có thể rất quen thuộc với bạn nếu bạn đã có kiến thức căn bản về ASP.NET MVC. Tuy nhiên view EditRole này là khá mới lạ đòi hỏi chúng ta phải cùng nghiễn ngẫm xíu về nội dung trong view.

Đầu tiên view hiển thị 1 table chứa danh sách các role hiện có của user. Vòng lặp foreach (var item in Model.Roles) sẽ lặp qua các record trong bảng dbo.AspNetUserRoles và hiển thị các role mà user có. Nếu user không có role nào thì sẽ in ra Thành viên chưa có quyền nào. Mỗi dòng ở table sẽ hiển thị tên role và 1 form method post gọi đến action DeleteRoleFromUser để khi quản trị click vào button Gỡ bỏ thì xóa role này khỏi thành viên đó.

Bên dưới table là 1 form method post gọi đến action method AddToRole để thêm các role mà user chưa có vào user. Lệnh @Html.ListBox("RoleId", null, new { @class = "form-control", multiple = "multiple" }) sẽ hiển thị một Listbox nhận giá trị từ ViewBag.RoleId từ action EditRole truyền vào để hiển thị các role mà user chưa có. Các bạn cũng chú ý là ListBox này cho phép chúng ta chọn nhiều item một lúc vì mình đã bổ sung attribute multiple.

Đây là hình ảnh của view EditRole với user được chọn là nguyenaituan@yahoo.com


Mình thử chọn item Admin và click nút Thêm user nguyenaituan@yahoo.com sẽ có được quyền Admin


Giờ thử xóa role Admin khỏi user nguyenaituan@yahoo.com bằng cách click button Gỡ bỏ thì bạn sẽ thấy role Admin không thuộc về user nguyenaituan@yahoo.com nữa.

Vậy là chúng ta đã hoàn thành tất cả các chức năng cần thiết trong ManageUserController.

3. Phân quyền User


Để phân quyền 1 user hay 1 role nào đó được phép truy xuất đến 1 action method hay cả 1 controller bất kỳ thì chúng ta sẽ sử dụng attribute Authorize trong ASP.NET MVC. Như các bạn thấy việc quản lý role và user thì chỉ có những user có quyền là Admin mới được phép thực hiện đúng không nào. Để cấu hình được điều này thì ta làm như sau:

Mở ManageRoleController và ManageUserController lên và thêm attribute Authorize trên đầu khai báo controller như hình:




Trong attribute Authorize chúng ta có thể truyền vào những Roles hoặc danh sách những Users được phép truy cập vào Controller. Nếu bạn viết như hình dưới thì chỉ có user nguyenaituan@yahoo.com mới được phép truy cập.


Nếu bạn không truyền tham số gì cả thì chỉ những user đã login mới được truy cập. Còn nếu có tham số Roles hay Users hoặc kết hợp cả 2 thì phải thỏa 2 điều kiện sau:

  1. Đã đăng nhập vào website
  2. Thỏa mãn ràng buộc về tham số trong attribute

Sau này giả sử bạn tạo một controller là ManageProductController chứa các action method như Index, Create, Edit, Detail, Delete. Bạn chia 2 phân hệ là Admin và Mod và bạn muốn rằng Admin có toàn quyền thao tác trên controller và Mod thì chỉ được phép truy cập vào action method Index, Create để xem danh sách sản phẩm và tạo mới sản phẩm thì bạn không đặt attribute Authorize lên đầu controller mà phải đặt lên từng action method trong controller và xác định các action nào admin được truy cập, mod được truy cập.

Nếu không thỏa điều kiện ràng buộc mà truy cập vào thì người dùng sẽ được redirect về trang login. Giả sử mình là 1 khách thăm website chưa đăng nhập và khi mình truy cập đến URL http://localhost:52531/admin/manageproduct thì mình sẽ được chuyển đến trang Login hoặc khi mình đã login nhưng mình chỉ là member hoặc mod mà mình lại truy cập vào URL http://localhost:52531/admin/manageproduct/edit thì mình cũng sẽ bị yêu cầu login. Bạn thử test xem phải vậy không nhé :)

4. Tổng kết


Qua bài viết này thì các bạn có thể hiểu được khái niệm user và role trong ASP.NET Identity, biết cách tạo các trang để quản lý role, user và cuối cùng là sử dụng attribute Authorize để phân quyền user. Đây cũng là bài viết cuối cùng trong series Hướng dẫn sử dụng ASP.NET Identity. Chúc các bạn thực hành thành công và ăn tết vui vẻ.


EmoticonEmoticon