- ASP.NET với Entity Framework
- Tích hợp Identity vào ASP.NET MVC
- Tạo Model - AppUser
- Tạo DbContext - AppDbContext
- Đăng ký AppDbContext và các dịch vụ Identity vào hệ thống
- Phát sinh Database từ AppDbContext
- Sử dụng mã nguồn Identity từ ví dụ cũ
Bài thực hành này tiếp tục trên ví dụ cũ mvcblog
:
Route trong MVC,
tải mã nguồn về mở ra tiếp tục phát triển
tag/ex068-v1
Sau khi dự án MVC đơn giản trên được tạo ra, dùng Visual Studio Code mở ra để bắt đầu thực hành.
ASP.NET MVC với Entity Framework làm việc với SQL Server
Việc tích hợp Entity Framework vào APS.NET MVC thực hoàn hoàn toàn giống với các bài đã hướng dẫn ở Razor Page - quy trình thực hiện theo các bước tại (ASP.NET Razor) Ứng dụng EF làm việc với cơ sở dữ liệu
Thực hiện thêm các package để làm việc với EF kết nối đến MS SQL Server và các công cụ trợ giúp phát sinh code:
dotnet tool install --global dotnet-ef dotnet tool install --global dotnet-aspnet-codegenerator dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package MySql.Data.EntityFramework
Nếu chưa có máy chủ MS SQL Server có thể làm theo hướng dẫn:
chuẩn bị MS SQL Server
,
sau đó viết chuỗi kết nối vào file appsettings.json
để sau này EF sử dụng.
Ứng dụng này ta chọn đặt tên CSDL trong MS SQL Server là myblog
, nên cấu hình chuỗi kết nối có thể là:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "MyBlogContext": "Data Source=localhost,1433; Initial Catalog=myblog; User ID=SA;Password=Password123" } }
Lúc này bạn có thể tạo ra các Model, DbContext sau đó đăng ký vào hệ thống theo kiến thức các bài đã trình bày như: Tạo DbContext trong EF , tạo DbContext EF trong Razor Page ...
Tuy nhiên ta sẽ không tạo database context kế thừa trực tiếp lớp DbContext
mà sẽ kế thừa từ lớp
phái sinh từ DbContext
là IdentityDbContext
- để tích hợp vào ứng dụng Identity,
thư viện về xác thực trong ASP.NET
Tích hợp Identity vào ASP.NET MVC
Việc tích hợp Identity vào MVC thực hiện hoàn toàn giống với các bài viết về Identity trong Razor Page, bắt đầu từ bài Sử dụng Identity trong Razor Page
Hãy cài đặt các gói cần thiết
dotnet add package System.Data.SqlClient dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Microsoft.Extensions.DependencyInjection dotnet add package Microsoft.Extensions.Logging.Console dotnet add package Microsoft.AspNetCore.Identity dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design dotnet add package Microsoft.AspNetCore.Identity.UI dotnet add package Microsoft.AspNetCore.Authentication dotnet add package Microsoft.AspNetCore.Http.Abstractions dotnet add package Microsoft.AspNetCore.Authentication.Cookies dotnet add package Microsoft.AspNetCore.Authentication.Facebook dotnet add package Microsoft.AspNetCore.Authentication.Google dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer dotnet add package Microsoft.AspNetCore.Authentication.MicrosoftAccount dotnet add package Microsoft.AspNetCore.Authentication.oAuth dotnet add package Microsoft.AspNetCore.Authentication.OpenIDConnect dotnet add package Microsoft.AspNetCore.Authentication.Twitter dotnet add package MailKit dotnet add package MimeKit
Tạo model AppUser
Lớp này kế thừa IdentityUser, nó có các trường định nghĩa sẵn (xem Model Identity ), định nghĩa thêm FullName, Address, Birthday
using System; using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Identity; namespace mvcblog.Models { public class AppUser : IdentityUser { [MaxLength (100)] public string FullName { set; get; } [MaxLength (255)] public string Address { set; get; } [DataType (DataType.Date)] public DateTime? Birthday { set; get; } } }
Tạo model AppDbContext
Tạo một lớp kế thừa từ IdentityDbContext (có sẵn các bảng về Identity), ánh xạ nội dụng với CSDL (xem tạo AppDbContext )
Nội dung AppDbContext ở thời điểm này có thể như sau:
using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using mvcblog.Models; namespace mvcblog.Data { public class AppDbContext : IdentityDbContext<AppUser> { public AppDbContext (DbContextOptions<AppDbContext> options) : base (options) { } protected override void OnModelCreating (ModelBuilder builder) { base.OnModelCreating (builder); // Bỏ tiền tố AspNet của các bảng: mặc định foreach (var entityType in builder.Model.GetEntityTypes ()) { var tableName = entityType.GetTableName (); if (tableName.StartsWith ("AspNet")) { entityType.SetTableName (tableName.Substring (6)); } } } } }
Đăng ký AppDbContext và các dịch vụ Identity vào hệ thống
Cập nhật Startup.ConfigureServices
// Đăng ký AppDbContext, sử dụng kết nối đến MS SQL Server services.AddDbContext<AppDbContext> (options => { string connectstring = Configuration.GetConnectionString ("MyBlogContext"); options.UseSqlServer (connectstring); }); // Đăng ký các dịch vụ của Identity services.AddIdentity<AppUser, IdentityRole> () .AddEntityFrameworkStores<AppDbContext> () .AddDefaultTokenProviders (); // Truy cập IdentityOptions services.Configure<IdentityOptions> (options => { // Thiết lập về Password options.Password.RequireDigit = false; // Không bắt phải có số options.Password.RequireLowercase = false; // Không bắt phải có chữ thường options.Password.RequireNonAlphanumeric = false; // Không bắt ký tự đặc biệt options.Password.RequireUppercase = false; // Không bắt buộc chữ in options.Password.RequiredLength = 3; // Số ký tự tối thiểu của password options.Password.RequiredUniqueChars = 1; // Số ký tự riêng biệt // Cấu hình Lockout - khóa user options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes (5); // Khóa 5 phút options.Lockout.MaxFailedAccessAttempts = 5; // Thất bại 5 lầ thì khóa options.Lockout.AllowedForNewUsers = true; // Cấu hình về User. options.User.AllowedUserNameCharacters = // các ký tự đặt tên user "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; options.User.RequireUniqueEmail = true; // Email là duy nhất // Cấu hình đăng nhập. options.SignIn.RequireConfirmedEmail = true; // Cấu hình xác thực địa chỉ email (email phải tồn tại) options.SignIn.RequireConfirmedPhoneNumber = false; // Xác thực số điện thoại }); // Cấu hình Cookie services.ConfigureApplicationCookie (options => { // options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(30); options.LoginPath = $"/login/"; // Url đến trang đăng nhập options.LogoutPath = $"/logout/"; options.AccessDeniedPath = $"/Identity/Account/AccessDenied"; // Trang khi User bị cấm truy cập }); services.Configure<SecurityStampValidatorOptions>(options => { // Trên 5 giây truy cập lại sẽ nạp lại thông tin User (Role) // SecurityStamp trong bảng User đổi -> nạp lại thông tinn Security options.ValidationInterval = TimeSpan.FromSeconds(5); });
Thêm vào pipeline của ứng dụng, thêm tại Startup.configure
app.UseAuthentication(); // Phục hồi thông tin đăng nhập (xác thực) app.UseAuthorization (); // Phục hồi thông tinn về quyền của User
Phát sinh cơ sở dữ liệu từ AddDbContext
Ở đây dùng kỹ thuật migration trong EntityFramework
Chạy lệnh để tạo Migration và tạo db
dotnet ef migrations add Init dotnet ef database update
Kết quả đã sinh ra CSDL ban đầu với cấu trúc:
Đến đây đã có thư viện Identity và CSDL đầy đủ, tuy nhiên ta sẽ không sử dụng code giao diện mặc định của Identity để có thể tùy biến. Bạn có thể thực hiện từ đầu theo các hướng dẫn bắt đầu tại: Phát sinh code (scaffold) Identity
Sử dụng lại mã nguồn Identity từ ví dụ trước
Để nhanh chóng có tất cả các trang chức năng đăng nhập, đăng ký, quyên mật khẩu ... ,
ta sẽ lấy lại toàn bộ kết quả của các ví dụ cũ về Identity của Razor Page đưa vào
MVC, copy toàn bộ thư mục /Areas
tại
Album/Areas
vào thư mục Areas của ứng dụng này
.
Ở code cũ lớp AppUser
ở namespace Album.Models
và lớp AppDbContext ở namespace Album.Data
trong khi ở ví dụ này thì hai lớp này định nghĩa lại
ở namespace mvcblog.Data
và mvcblog.Models
nên hãy dùng tính năng
tìm kiếm và thay thế để thay toàn bộ Album.Models
, Album.Data
thành
, mvcblog.Models
mvcblog.Data
Đồng thời cũng thay thế /Pages/Shared/_Layout.cshtml
bằng /Views/Shared/_Layout.cshtml
Tiếp theo copy mã nguồn ViewComponent - MessagePage (để đúng cấu cấu thư mục) để tạo thông báo khi chuyển hướng, mã nguồn này đã xây dựng tại Tạo ViewComponent - MessagePage thông báo khi chuyển hướng
Tạm thời comment lại dòng
using Album.Binder;
và
[ModelBinder(BinderType=typeof(DayMonthYearBinder))]
trong Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
Trong file Areas/Admin/Pages/Role/_ViewStart.cshtml
và
Areas/Admin/Pages/Role/_ViewStart.cshtml
sửa thành
@{ Layout = "/Views/Shared/_Layout.cshtml"; }
Triển khai dịch vụ gửi mail với IEmailSender
Dịch vụ gửi mail để Identity gửi mail trong trường hợp khi đăng ký tài khoản, lấy lại mật khẩu ... Sử dụng IEmailSender dùng gmail để gửi làm theo hướng dẫn Triển khai IEmailSender
Giờ các chức năng của Identity đã hoạt động như đã từng thực hành trên Razor Page, đã có thể đăng ký, đăng nhập ...
Thêm _LoginPartial.cshtml
Xây dựng thêm Partial có tên _LoginPartial.cshtml
để chèn vào menu chính các mục chọn đăng nhập,
đăng ký ..., xây dựng file này như sau:
Views/Shared/_LoginPartial.cshtml
@using Microsoft.AspNetCore.Identity @using mvcblog.Models @using Microsoft.AspNetCore.Mvc.ViewEngines @inject SignInManager<AppUser> SignInManager @inject UserManager<AppUser> UserManager @inject ICompositeViewEngine Engine <ul class="navbar-nav"> @if (SignInManager.IsSignedIn(User)) { <li class="nav-item"> <a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Xin chào @UserManager.GetUserName(User)!</a> </li> <li class="nav-item"> <form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/Index", new { area = "" })"> <button id="logout" type="submit" class="nav-link btn btn-link text-dark">Đăng xuất</button> </form> </li> @if (Engine.FindView(ViewContext, "_AdminDropdownMenu", false).Success) { @await Html.PartialAsync("_AdminDropdownMenu") } } else { <li class="nav-item"> <a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Đăng ký</a> </li> <li class="nav-item"> <a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Đăng nhập</a> </li> } </ul>
Trong file này để chèn HTML các mục menu để thêm vào thanh menu chính, nó xuất hiện ở bên phải, lưu ý ở file này:
Đoạn code:
@inject ICompositeViewEngine Engine
Để Inject đối tượng ICompositeViewEngine, để có thể kiểm tra một partial có tồn tại hay không trước khi render, trong file này có render _AdminDropdownMenu.cshtml
@if (Engine.FindView(ViewContext, "_AdminDropdownMenu", false).Success) { @await Html.PartialAsync("_AdminDropdownMenu") }
_AdminDropdownMenu.cshtml
tạo một Dropdown menu - chung cấp các mục chọn đến chức năng quản lý
role trong hệ thống, menu này chỉ xuất hiện khi User thỏa mãn policy
: AdminDropdown
policy AdminDropdown
được tạo như sau (trong Startup.ConfigureServices
)
Xem thêm: chứng thực quyền theo chính sách policy
services.AddAuthorization(options => { // User thỏa mãn policy khi có roleclaim: permission với giá trị manage.user options.AddPolicy("AdminDropdown", policy => { policy.RequireClaim("permission", "manage.user"); }); });
Nội dung _AdminDropdownMenu.cshtml
như sau:
@using Microsoft.AspNetCore.Identity @using mvcblog.Models @using Microsoft.AspNetCore.Authorization @inject SignInManager<AppUser> SignInManager @inject Microsoft.AspNetCore.Authorization.IAuthorizationService authorizationService @if (SignInManager.IsSignedIn(User) && (await authorizationService.AuthorizeAsync(User, "AdminDropdown")).Succeeded) { <li class="nav-item dropdown"> <a class="nav-item nav-link dropdown-toggle mr-md-2" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Manager </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="bd-versions"> <a class="dropdown-item" asp-page="/Role/Index" asp-area="Admin">Quản lý role</a> <div class="dropdown-divider"></div> <a class="dropdown-item" asp-page="/Role/User" asp-area="Admin">Gán role cho User</a> </div> </li> }
Trong Views/Shared/_Layout.cshtml
thêm vào nội dung để chèn partial _LoginPartial.cshtml
:
@await Html.PartialAsync("_LoginPartial")
, vị trí chèn như sau:
<!DOCTYPE html> /.. <body> <header> <nav class="navbar ... "> <div class="container"> / ... @await Html.PartialAsync("_LoginPartial") </div> </nav> </header> /... </body> </html>
Thêm chức năng tạo HTML Paging
Để có chức năng phân trang (ví dụ liệt kê 10 User / 1 trang) thì làm theo hướng dẫn
Tạo partial phân trang HTML BootStrap trong ASP.NET truy vấn phân trang LINQ
,
tạo file Views/Shared/_Paging.cshtml
và copy mã nguồn ở link trên vào
Tích hợp multiple-select
Trong mã nguồn có một số chỗ sử dụng thư viện JS
multiple-select
(như trong file Areas/Admin/Pages/Role/AddUserRole.cshtml
),
để phần tử HTML Select ở dạng dễ chọn hơn
File nào tích hợp thường có đoạn mã nạp và sử dụng thư viện
<script src="~/lib/multiple-select/multiple-select.min.js"></script> <link rel="stylesheet" href="~/lib/multiple-select/multiple-select.min.css" /> <script> $('#selectrole').multipleSelect({ selectAll: false, keepOpen: false, isOpen: false }); </script>
Nên hãy đảm bảo có 2 file thư viện là:
wwwroot/lib/multiple-select/multiple-select.min.css
vả
wwwroot/lib/multiple-select/multiple-select.min.js
Để kiểm tra, cần đăng ký User, đăng nhập - tạo role cho user
(https://localhost:5001/admin/role/
), role này có roleclaim với tên và giá trị:
permission: manage.user
Mã nguồn tham khảo ASP_NET_CORE/mvcblog, hoặc tải về bản bài này ex068-identity