ASP.NET Core: Tạo ứng dụng web ASP.NET Core với dữ liệu người dùng được bảo vệ bằng ủy quyền (authorization)


Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên

Bài viết này sẽ hướng dẫn cách tạo một ứng dụng web ASP.NET Core với dữ liệu người dùng được bảo vệ bằng ủy quyền (authorization). Nó hiển thị danh sách liên hệ (contact) mà người dùng đã chứng thực (đã đăng ký) đã tạo. Có ba nhóm bảo mật:

  • Người dùng đã đăng ký có thể xem tất cả dữ liệu đã được phê duyệt và có thể chỉnh sửa/xóa dữ liệu của chính họ.
  • Người quản lý có thể phê duyệt hoặc từ chối dữ liệu liên hệ. Chỉ những người liên hệ được phê duyệt mới hiển thị với người dùng.
  • Quản trị viên có thể phê duyệt/từ chối và chỉnh sửa/xóa bất kỳ dữ liệu nào.

Hình ảnh trong tài liệu này không khớp hoàn toàn với các mẫu mới nhất.

Trong hình ảnh sau đây, người dùng Rick (rick@example.com) đã đăng nhập. Rick chỉ có thể xem các liên hệ đã được phê duyệt và Edit/Delete/Create New cho các liên hệ của mình. Chỉ bản ghi cuối cùng do Rick tạo mới hiển thị các liên kết EditDelete. Những người dùng khác sẽ không nhìn thấy bản ghi cuối cùng cho đến khi người quản lý hoặc quản trị viên thay đổi trạng thái thành "Approved".

Ảnh chụp màn hình hiển thị Rick đã đăng nhập

Trong hình ảnh sau đây, manager@contoso.com được đăng nhập và có vai trò là người quản lý:

Ảnh chụp màn hình hiển thị manager@contoso.com đã đăng nhập

Hình ảnh sau đây hiển thị chế độ xem chi tiết của người quản lý về một liên hệ:

Chế độ xem của người quản lý về một liên hệ

Các nút Approve và Reject chỉ được hiển thị cho người quản lý và quản trị viên.

Trong hình ảnh sau đây, admin@contoso.com được đăng nhập và ở vai trò quản trị viên:

Ảnh chụp màn hình hiển thị admin@contoso.com đã đăng nhập

Quản trị viên có tất cả các đặc quyền. Quản trị viên có thể đọc, chỉnh sửa hoặc xóa bất kỳ liên hệ nào và thay đổi trạng thái của liên hệ.

Ứng dụng này được tạo bằng cách xây dựng model Contact sau:

public class Contact
{
    public int ContactId { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }
}

Mẫu chứa các trình xử lý ủy quyền sau:

  • ContactIsOwnerAuthorizationHandler: Đảm bảo rằng người dùng chỉ có thể chỉnh sửa dữ liệu của họ.
  • ContactManagerAuthorizationHandler: Cho phép người quản lý phê duyệt hoặc từ chối liên hệ.
  • ContactAdministratorsAuthorizationHandler: Cho phép quản trị viên phê duyệt hoặc từ chối liên hệ và chỉnh sửa/xóa liên hệ.

Điều kiện tiên quyết

Hướng dẫn này là nâng cao. Bạn nên làm quen với:

Ứng dụng khởi đầu và hoàn thành

Tải xuống ứng dụng đã hoàn thànhKiểm tra ứng dụng đã hoàn thiện để bạn làm quen với các tính năng bảo mật của ứng dụng đó.

Ứng dụng khởi đầu

Tải xuống ứng dụng khởi đầu.

Chạy ứng dụng, nhấn vào liên kết ContactManager và xác minh rằng bạn có thể tạo, chỉnh sửa và xóa một liên hệ. Để tạo ứng dụng khởi đầu, hãy xem Tạo ứng dụng khởi đầu.

Bảo mật dữ liệu người dùng

Các phần sau đây có tất cả các bước chính để tạo ứng dụng bảo mật dữ liệu người dùng. Bạn có thể thấy hữu ích khi tham khảo dự án đã hoàn thành.

Liên kết dữ liệu liên hệ với người dùng

Sử dụng ID người dùng ASP.NET Identity để đảm bảo người dùng có thể chỉnh sửa dữ liệu của họ chứ không phải dữ liệu của người dùng khác. Thêm OwnerID và ContactStatus vào model Contact:

public class Contact
{
    public int ContactId { get; set; }

    // user ID from AspNetUser table.
    public string? OwnerID { get; set; }

    public string? Name { get; set; }
    public string? Address { get; set; }
    public string? City { get; set; }
    public string? State { get; set; }
    public string? Zip { get; set; }
    [DataType(DataType.EmailAddress)]
    public string? Email { get; set; }

    public ContactStatus Status { get; set; }
}

public enum ContactStatus
{
    Submitted,
    Approved,
    Rejected
}

OwnerID là ID của người dùng từ bảng AspNetUser trong cơ sở dữ liệu Identity. Trường Status xác định xem người dùng thông thường có thể xem được một liên hệ hay không.

Tạo một migration mới và cập nhật cơ sở dữ liệu:

dotnet ef migrations add userID_Status
dotnet ef database update

Thêm dịch vụ Vai trò (Role) vào Danh tính

Nạp AddRoles để thêm dịch vụ Vai trò:

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(
    options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Yêu cầu người dùng được chứng thực

Đặt chính sách ủy quyền dự phòng để yêu cầu người dùng được chứng thực:

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(
    options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddRazorPages();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

Đoạn code được đánh dấu ở trên đó đặt chính sách ủy quyền dự phòng. Chính sách ủy quyền dự phòng yêu cầu tất cả người dùng phải được chứng thực, ngoại trừ Razor Pages, controller hoặc phương thức action có attribute ủy quyền. Ví dụ: Razor Pages, controller hoặc phương thức action có [AllowAnonymous] hoặc [Authorize(PolicyName="MyPolicy")] sử dụng attribute ủy quyền được áp dụng thay vì chính sách ủy quyền dự phòng.

RequireAuthenticatedUser thêm DenyAnonymousAuthorizationRequirement vào thể hiện hiện tại để buộc người dùng hiện tại phải được chứng thực.

Chính sách ủy quyền dự phòng:

  • Được áp dụng cho tất cả các yêu cầu không chỉ định rõ ràng chính sách ủy quyền. Đối với các yêu cầu được phân phát bằng định tuyến điểm cuối, điều này bao gồm mọi điểm cuối không chỉ định attribute ủy quyền. Đối với các yêu cầu được phân phối bởi middleware khác sau middleware ủy quyền, chẳng hạn như các file tĩnh, thì chính sách này sẽ áp dụng cho tất cả các yêu cầu.

Việc đặt chính sách ủy quyền dự phòng để yêu cầu người dùng được chứng thực sẽ bảo vệ các Razor Pages và controller mới được thêm vào. Việc có ủy quyền được yêu cầu theo mặc định sẽ an toàn hơn việc dựa vào controller và Razor Pages mới để bao gồm attribute [Authorize].

Lớp AuthorizationOptions cũng chứa AuthorizationOptions.DefaultPolicy. DefaultPolicy là chính sách được sử dụng với attribute [Authorize] khi không có chính sách nào được chỉ định. [Authorize] không chứa chính sách được đặt tên, không giống như [Authorize(PolicyName="MyPolicy")].

Để biết thêm thông tin về chính sách, hãy xem Ủy quyền dựa trên chính sách trong ASP.NET Core.

Một cách khác để controller MVC và Razor Pages yêu cầu tất cả người dùng phải được chứng thực là thêm bộ lọc ủy quyền được thể hiện như sau:

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(
    options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddRazorPages();

builder.Services.AddControllers(config =>
{
    var policy = new AuthorizationPolicyBuilder()
                     .RequireAuthenticatedUser()
                     .Build();
    config.Filters.Add(new AuthorizeFilter(policy));
});

var app = builder.Build();

Đoạn code trên sử dụng bộ lọc ủy quyền, cài đặt chính sách dự phòng sử dụng định tuyến điểm cuối. Đặt chính sách dự phòng là cách ưu tiên để yêu cầu tất cả người dùng phải được chứng thực.

Thêm AllowAnonymous vào các trang Index và Privacy để người dùng ẩn danh có thể lấy thông tin về trang web trước khi họ đăng ký:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace ContactManager.Pages;

[AllowAnonymous]
public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;

    public IndexModel(ILogger<IndexModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {

    }
}

Định cấu hình tài khoản thử nghiệm

Lớp SeedData tạo hai tài khoản: quản trị viên và người quản lý. Sử dụng công cụ Secret Manager để đặt mật khẩu cho các tài khoản này. Đặt mật khẩu từ thư mục dự án (thư mục chứa Program.cs):

dotnet user-secrets set SeedUserPW <PW>

Nếu mật khẩu mạnh không được chỉ định, một ngoại lệ sẽ được đưa ra khi SeedData.Initialize được gọi.

Cập nhật ứng dụng để sử dụng mật khẩu kiểm tra:

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(
    options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddRazorPages();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

// Authorization handlers.
builder.Services.AddScoped<IAuthorizationHandler,
                      ContactIsOwnerAuthorizationHandler>();

builder.Services.AddSingleton<IAuthorizationHandler,
                      ContactAdministratorsAuthorizationHandler>();

builder.Services.AddSingleton<IAuthorizationHandler,
                      ContactManagerAuthorizationHandler>();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<ApplicationDbContext>();
    context.Database.Migrate();
    // requires using Microsoft.Extensions.Configuration;
    // Set password with the Secret Manager tool.
    // dotnet user-secrets set SeedUserPW <pw>

    var testUserPw = builder.Configuration.GetValue<string>("SeedUserPW");

   await SeedData.Initialize(services, testUserPw);
}

Tạo tài khoản thử nghiệm và cập nhật danh bạ

Cập nhật phương thức Initialize trong lớp SeedData để tạo tài khoản thử nghiệm:

public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw)
{
    using (var context = new ApplicationDbContext(
        serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
    {
        // For sample purposes seed both with the same password.
        // Password is set with the following:
        // dotnet user-secrets set SeedUserPW <pw>
        // The admin user can do anything

        var adminID = await EnsureUser(serviceProvider, testUserPw, "admin@contoso.com");
        await EnsureRole(serviceProvider, adminID, Constants.ContactAdministratorsRole);

        // allowed user can create and edit contacts that they create
        var managerID = await EnsureUser(serviceProvider, testUserPw, "manager@contoso.com");
        await EnsureRole(serviceProvider, managerID, Constants.ContactManagersRole);

        SeedDB(context, adminID);
    }
}

private static async Task<string> EnsureUser(IServiceProvider serviceProvider,
                                            string testUserPw, string UserName)
{
    var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();

    var user = await userManager.FindByNameAsync(UserName);
    if (user == null)
    {
        user = new IdentityUser
        {
            UserName = UserName,
            EmailConfirmed = true
        };
        await userManager.CreateAsync(user, testUserPw);
    }

    if (user == null)
    {
        throw new Exception("The password is probably not strong enough!");
    }

    return user.Id;
}

private static async Task<IdentityResult> EnsureRole(IServiceProvider serviceProvider,
                                                              string uid, string role)
{
    var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();

    if (roleManager == null)
    {
        throw new Exception("roleManager null");
    }

    IdentityResult IR;
    if (!await roleManager.RoleExistsAsync(role))
    {
        IR = await roleManager.CreateAsync(new IdentityRole(role));
    }

    var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();

    //if (userManager == null)
    //{
    //    throw new Exception("userManager is null");
    //}

    var user = await userManager.FindByIdAsync(uid);

    if (user == null)
    {
        throw new Exception("The testUserPw password was probably not strong enough!");
    }

    IR = await userManager.AddToRoleAsync(user, role);

    return IR;
}

Thêm ID người dùng quản trị viên và ContactStatus vào danh bạ. Đặt một trong các địa chỉ liên hệ là "Đã gửi" và một địa chỉ liên hệ là "Đã từ chối". Thêm ID người dùng và trạng thái vào tất cả các liên hệ. Chỉ có một liên hệ được hiển thị:

public static void SeedDB(ApplicationDbContext context, string adminID)
{
    if (context.Contact.Any())
    {
        return;   // DB has been seeded
    }

    context.Contact.AddRange(
        new Contact
        {
            Name = "Debra Garcia",
            Address = "1234 Main St",
            City = "Redmond",
            State = "WA",
            Zip = "10999",
            Email = "debra@example.com",
            Status = ContactStatus.Approved,
            OwnerID = adminID
        },

Tạo trình xử lý ủy quyền của chủ sở hữu, người quản lý và quản trị viên

Tạo một lớp ContactIsOwnerAuthorizationHandler trong thư mục Authorization. ContactIsOwnerAuthorizationHandler xác minh rằng người dùng hành động trên một tài nguyên sở hữu tài nguyên đó.

using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;

namespace ContactManager.Authorization
{
    public class ContactIsOwnerAuthorizationHandler
                : AuthorizationHandler<OperationAuthorizationRequirement, Contact>
    {
        UserManager<IdentityUser> _userManager;

        public ContactIsOwnerAuthorizationHandler(UserManager<IdentityUser> 
            userManager)
        {
            _userManager = userManager;
        }

        protected override Task
            HandleRequirementAsync(AuthorizationHandlerContext context,
                                   OperationAuthorizationRequirement requirement,
                                   Contact resource)
        {
            if (context.User == null || resource == null)
            {
                return Task.CompletedTask;
            }

            // If not asking for CRUD permission, return.

            if (requirement.Name != Constants.CreateOperationName &&
                requirement.Name != Constants.ReadOperationName   &&
                requirement.Name != Constants.UpdateOperationName &&
                requirement.Name != Constants.DeleteOperationName )
            {
                return Task.CompletedTask;
            }

            if (resource.OwnerID == _userManager.GetUserId(context.User))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

ContactIsOwnerAuthorizationHandler gọi context.Succeed nếu người dùng được chứng thực hiện tại là chủ sở hữu liên hệ. Trình xử lý ủy quyền nói chung:

  • Gọi context.Succeed khi đạt yêu cầu.
  • Trả lại Task.CompletedTask khi không đạt yêu cầu. Việc trả về Task.CompletedTask mà không gọi trước context.Success hay context.Fail, thì không phải là thành công hay thất bại, nó cho phép các trình xử lý ủy quyền khác chạy.

Nếu bạn cần thất bại một cách rõ ràng, hãy gọi context.Fail.

Ứng dụng cho phép chủ sở hữu liên hệ edit/delete/create dữ liệu của riêng họ. ContactIsOwnerAuthorizationHandler không cần kiểm tra thao tác được truyền trong tham số yêu cầu.

Tạo trình xử lý ủy quyền của người quản lý

Tạo một lớp ContactManagerAuthorizationHandler trong thư mục Authorization. ContactManagerAuthorizationHandler xác minh người dùng hành động trên tài nguyên là người quản lý. Chỉ người quản lý mới có thể phê duyệt hoặc từ chối các thay đổi nội dung (mới hoặc đã thay đổi).

using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;

namespace ContactManager.Authorization
{
    public class ContactManagerAuthorizationHandler :
        AuthorizationHandler<OperationAuthorizationRequirement, Contact>
    {
        protected override Task
            HandleRequirementAsync(AuthorizationHandlerContext context,
                                   OperationAuthorizationRequirement requirement,
                                   Contact resource)
        {
            if (context.User == null || resource == null)
            {
                return Task.CompletedTask;
            }

            // If not asking for approval/reject, return.
            if (requirement.Name != Constants.ApproveOperationName &&
                requirement.Name != Constants.RejectOperationName)
            {
                return Task.CompletedTask;
            }

            // Managers can approve or reject.
            if (context.User.IsInRole(Constants.ContactManagersRole))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

Tạo trình xử lý ủy quyền quản trị viên

Tạo một lớp ContactAdministratorsAuthorizationHandler trong thư mục Authorization. ContactAdministratorsAuthorizationHandler xác minh người dùng hành động trên tài nguyên là quản trị viên. Người quản trị có thể thực hiện mọi thao tác.

using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;

namespace ContactManager.Authorization
{
    public class ContactAdministratorsAuthorizationHandler
                    : AuthorizationHandler<OperationAuthorizationRequirement, Contact>
    {
        protected override Task HandleRequirementAsync(
                                              AuthorizationHandlerContext context,
                                    OperationAuthorizationRequirement requirement, 
                                     Contact resource)
        {
            if (context.User == null)
            {
                return Task.CompletedTask;
            }

            // Administrators can do anything.
            if (context.User.IsInRole(Constants.ContactAdministratorsRole))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

Đăng ký trình xử lý ủy quyền

Các dịch vụ sử dụng Entity Framework Core phải được đăng ký để chèn phụ thuộc (Dependency Injection) bằng AddScoped. ContactIsOwnerAuthorizationHandler sử dụng ASP.NET Core Identity, được xây dựng trên Entity Framework Core. Đăng ký các trình xử lý với collection dịch vụ để chúng sẵn sàng cho việc ContactsController thông qua chèn phụ thuộc. Thêm đoạn code sau vào cuối của ConfigureServices:

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(
    options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddRazorPages();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

// Authorization handlers.
builder.Services.AddScoped<IAuthorizationHandler,
                      ContactIsOwnerAuthorizationHandler>();

builder.Services.AddSingleton<IAuthorizationHandler,
                      ContactAdministratorsAuthorizationHandler>();

builder.Services.AddSingleton<IAuthorizationHandler,
                      ContactManagerAuthorizationHandler>();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<ApplicationDbContext>();
    context.Database.Migrate();
    // requires using Microsoft.Extensions.Configuration;
    // Set password with the Secret Manager tool.
    // dotnet user-secrets set SeedUserPW <pw>

    var testUserPw = builder.Configuration.GetValue<string>("SeedUserPW");

   await SeedData.Initialize(services, testUserPw);
}

ContactAdministratorsAuthorizationHandler và ContactManagerAuthorizationHandler được thêm vào dưới dạng các singleton. Chúng là những singleton vì chúng không sử dụng EF và tất cả thông tin cần thiết đều có trong tham số Context của phương thức HandleRequirementAsync.

Hỗ trợ ủy quyền

Trong phần này, bạn cập nhật Razor Pages và thêm lớp yêu cầu hoạt động.

Xem lại lớp yêu cầu về hoạt động liên hệ

Xem lại lớp ContactOperations. Lớp này chứa các yêu cầu mà ứng dụng hỗ trợ:

using Microsoft.AspNetCore.Authorization.Infrastructure;

namespace ContactManager.Authorization
{
    public static class ContactOperations
    {
        public static OperationAuthorizationRequirement Create =   
          new OperationAuthorizationRequirement {Name=Constants.CreateOperationName};
        public static OperationAuthorizationRequirement Read = 
          new OperationAuthorizationRequirement {Name=Constants.ReadOperationName};  
        public static OperationAuthorizationRequirement Update = 
          new OperationAuthorizationRequirement {Name=Constants.UpdateOperationName}; 
        public static OperationAuthorizationRequirement Delete = 
          new OperationAuthorizationRequirement {Name=Constants.DeleteOperationName};
        public static OperationAuthorizationRequirement Approve = 
          new OperationAuthorizationRequirement {Name=Constants.ApproveOperationName};
        public static OperationAuthorizationRequirement Reject = 
          new OperationAuthorizationRequirement {Name=Constants.RejectOperationName};
    }

    public class Constants
    {
        public static readonly string CreateOperationName = "Create";
        public static readonly string ReadOperationName = "Read";
        public static readonly string UpdateOperationName = "Update";
        public static readonly string DeleteOperationName = "Delete";
        public static readonly string ApproveOperationName = "Approve";
        public static readonly string RejectOperationName = "Reject";

        public static readonly string ContactAdministratorsRole = 
                                                              "ContactAdministrators";
        public static readonly string ContactManagersRole = "ContactManagers";
    }
}

Tạo một lớp cơ sở cho Razor Pages Contacts

Tạo một lớp cơ sở chứa các dịch vụ được sử dụng trong Razor Pages Contacts. Lớp cơ sở đặt code khởi tạo ở một vị trí:

using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace ContactManager.Pages.Contacts
{
    public class DI_BasePageModel : PageModel
    {
        protected ApplicationDbContext Context { get; }
        protected IAuthorizationService AuthorizationService { get; }
        protected UserManager<IdentityUser> UserManager { get; }

        public DI_BasePageModel(
            ApplicationDbContext context,
            IAuthorizationService authorizationService,
            UserManager<IdentityUser> userManager) : base()
        {
            Context = context;
            UserManager = userManager;
            AuthorizationService = authorizationService;
        } 
    }
}

Trong đoạn code trên:

  • Thêm dịch vụ IAuthorizationService để truy cập vào trình xử lý ủy quyền.
  • Thêm dịch vụ Nhận dạng UserManager.
  • Thêm file ApplicationDbContext.

Cập nhật CreateModel

Cập nhật model trang tạo:

  • Hàm tạo để sử dụng lớp cơ sở DI_BasePageModel.
  • Phương thức OnPostAsync để:
    • Thêm ID người dùng vào model Contact.
    • Gọi trình xử lý ủy quyền để xác minh người dùng có quyền tạo liên hệ.
using ContactManager.Authorization;
using ContactManager.Data;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace ContactManager.Pages.Contacts
{
    public class CreateModel : DI_BasePageModel
    {
        public CreateModel(
            ApplicationDbContext context,
            IAuthorizationService authorizationService,
            UserManager<IdentityUser> userManager)
            : base(context, authorizationService, userManager)
        {
        }

        public IActionResult OnGet()
        {
            return Page();
        }

        [BindProperty]
        public Contact Contact { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            Contact.OwnerID = UserManager.GetUserId(User);

            var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                        User, Contact,
                                                        ContactOperations.Create);
            if (!isAuthorized.Succeeded)
            {
                return Forbid();
            }

            Context.Contact.Add(Contact);
            await Context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

Cập nhật IndexModel

Cập nhật phương thức OnGetAsync để chỉ những người liên hệ được phê duyệt mới được hiển thị cho người dùng thông thường:

public class IndexModel : DI_BasePageModel
{
    public IndexModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    public IList<Contact> Contact { get; set; }

    public async Task OnGetAsync()
    {
        var contacts = from c in Context.Contact
                       select c;

        var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
                           User.IsInRole(Constants.ContactAdministratorsRole);

        var currentUserId = UserManager.GetUserId(User);

        // Only approved contacts are shown UNLESS you're authorized to see them
        // or you are the owner.
        if (!isAuthorized)
        {
            contacts = contacts.Where(c => c.Status == ContactStatus.Approved
                                        || c.OwnerID == currentUserId);
        }

        Contact = await contacts.ToListAsync();
    }
}

Cập nhật EditModel

Thêm trình xử lý ủy quyền để xác minh người dùng sở hữu liên hệ. Vì ủy quyền tài nguyên đang được xác thực nên attribute [Authorize] là không đủ. Ứng dụng không có quyền truy cập vào tài nguyên khi đánh giá các attribute. Ủy quyền dựa trên tài nguyên phải là bắt buộc. Việc kiểm tra phải được thực hiện sau khi ứng dụng có quyền truy cập vào tài nguyên, bằng cách tải tài nguyên đó trong model trang hoặc bằng cách tải tài nguyên đó trong chính trình xử lý. Bạn thường xuyên truy cập tài nguyên bằng cách chuyển khóa tài nguyên.

public class EditModel : DI_BasePageModel
{
    public EditModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    [BindProperty]
    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact? contact = await Context.Contact.FirstOrDefaultAsync(
                                                         m => m.ContactId == id);
        if (contact == null)
        {
            return NotFound();
        }

        Contact = contact;

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                  User, Contact,
                                                  ContactOperations.Update);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        // Fetch Contact from DB to get OwnerID.
        var contact = await Context
            .Contact.AsNoTracking()
            .FirstOrDefaultAsync(m => m.ContactId == id);

        if (contact == null)
        {
            return NotFound();
        }

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                 User, contact,
                                                 ContactOperations.Update);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        Contact.OwnerID = contact.OwnerID;

        Context.Attach(Contact).State = EntityState.Modified;

        if (Contact.Status == ContactStatus.Approved)
        {
            // If the contact is updated after approval, 
            // and the user cannot approve,
            // set the status back to submitted so the update can be
            // checked and approved.
            var canApprove = await AuthorizationService.AuthorizeAsync(User,
                                    Contact,
                                    ContactOperations.Approve);

            if (!canApprove.Succeeded)
            {
                Contact.Status = ContactStatus.Submitted;
            }
        }

        await Context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Cập nhật DeleteModel

Cập nhật model trang delete để sử dụng trình xử lý ủy quyền nhằm xác minh người dùng có quyền xóa đối với người liên hệ.

public class DeleteModel : DI_BasePageModel
{
    public DeleteModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    [BindProperty]
    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact? _contact = await Context.Contact.FirstOrDefaultAsync(
                                             m => m.ContactId == id);

        if (_contact == null)
        {
            return NotFound();
        }
        Contact = _contact;

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                 User, Contact,
                                                 ContactOperations.Delete);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id)
    {
        var contact = await Context
            .Contact.AsNoTracking()
            .FirstOrDefaultAsync(m => m.ContactId == id);

        if (contact == null)
        {
            return NotFound();
        }

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                 User, contact,
                                                 ContactOperations.Delete);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        Context.Contact.Remove(contact);
        await Context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Chèn (Inject) dịch vụ ủy quyền vào View

Hiện tại, giao diện người dùng hiển thị các liên kết chỉnh sửa và xóa đối với các liên hệ mà người dùng không thể sửa đổi.

Đưa dịch vụ ủy quyền vào file Pages/_ViewImports.cshtml để tất cả các view đều có thể sử dụng dịch vụ này:

@using Microsoft.AspNetCore.Identity
@using ContactManager
@using ContactManager.Data
@namespace ContactManager.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using ContactManager.Authorization;
@using Microsoft.AspNetCore.Authorization
@using ContactManager.Models
@inject IAuthorizationService AuthorizationService

Cập nhật các liên kết EditDelete trong Pages/Contacts/Index.cshtml để chúng chỉ được hiển thị cho những người dùng có quyền thích hợp:

@page
@model ContactManager.Pages.Contacts.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Address)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].City)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].State)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Zip)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Email)
            </th>
             <th>
                @Html.DisplayNameFor(model => model.Contact[0].Status)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model.Contact) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Address)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.City)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.State)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Zip)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Email)
            </td>
                           <td>
                    @Html.DisplayFor(modelItem => item.Status)
                </td>
                <td>
                    @if ((await AuthorizationService.AuthorizeAsync(
                     User, item,
                     ContactOperations.Update)).Succeeded)
                    {
                        <a asp-page="./Edit" asp-route-id="@item.ContactId">Edit</a>
                        <text> | </text>
                    }

                    <a asp-page="./Details" asp-route-id="@item.ContactId">Details</a>

                    @if ((await AuthorizationService.AuthorizeAsync(
                     User, item,
                     ContactOperations.Delete)).Succeeded)
                    {
                        <text> | </text>
                        <a asp-page="./Delete" asp-route-id="@item.ContactId">Delete</a>
                    }
                </td>
            </tr>
        }
    </tbody>
</table>

Cảnh báo

Việc ẩn liên kết khỏi những người dùng không có quyền thay đổi dữ liệu sẽ không bảo mật ứng dụng. Việc ẩn các liên kết giúp ứng dụng thân thiện hơn với người dùng bằng cách chỉ hiển thị các liên kết hợp lệ. Người dùng có thể hack các URL đã tạo để thực hiện các thao tác chỉnh sửa và xóa trên dữ liệu mà họ không sở hữu. Razor Pages hoặc controller phải thực thi kiểm tra quyền truy cập để bảo mật dữ liệu.

Cập nhật chi tiết

Cập nhật view chi tiết để người quản lý có thể phê duyệt hoặc từ chối liên hệ:

        @*Preceding markup omitted for brevity.*@
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Contact.Email)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Contact.Email)
        </dd>
    <dt>
            @Html.DisplayNameFor(model => model.Contact.Status)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Contact.Status)
        </dd>
    </dl>
</div>

@if (Model.Contact.Status != ContactStatus.Approved)
{
    @if ((await AuthorizationService.AuthorizeAsync(
     User, Model.Contact, ContactOperations.Approve)).Succeeded)
    {
        <form style="display:inline;" method="post">
            <input type="hidden" name="id" value="@Model.Contact.ContactId" />
            <input type="hidden" name="status" value="@ContactStatus.Approved" />
            <button type="submit" class="btn btn-xs btn-success">Approve</button>
        </form>
    }
}

@if (Model.Contact.Status != ContactStatus.Rejected)
{
    @if ((await AuthorizationService.AuthorizeAsync(
     User, Model.Contact, ContactOperations.Reject)).Succeeded)
    {
        <form style="display:inline;" method="post">
            <input type="hidden" name="id" value="@Model.Contact.ContactId" />
            <input type="hidden" name="status" value="@ContactStatus.Rejected" />
            <button type="submit" class="btn btn-xs btn-danger">Reject</button>
        </form>
    }
}

<div>
    @if ((await AuthorizationService.AuthorizeAsync(
         User, Model.Contact,
         ContactOperations.Update)).Succeeded)
    {
        <a asp-page="./Edit" asp-route-id="@Model.Contact.ContactId">Edit</a>
        <text> | </text>
    }
    <a asp-page="./Index">Back to List</a>
</div>

Cập nhật page model chi tiết

public class DetailsModel : DI_BasePageModel
{
    public DetailsModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact? _contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);

        if (_contact == null)
        {
            return NotFound();
        }
        Contact = _contact;

        var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
                           User.IsInRole(Constants.ContactAdministratorsRole);

        var currentUserId = UserManager.GetUserId(User);

        if (!isAuthorized
            && currentUserId != Contact.OwnerID
            && Contact.Status != ContactStatus.Approved)
        {
            return Forbid();
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id, ContactStatus status)
    {
        var contact = await Context.Contact.FirstOrDefaultAsync(
                                                  m => m.ContactId == id);

        if (contact == null)
        {
            return NotFound();
        }

        var contactOperation = (status == ContactStatus.Approved)
                                                   ? ContactOperations.Approve
                                                   : ContactOperations.Reject;

        var isAuthorized = await AuthorizationService.AuthorizeAsync(User, contact,
                                    contactOperation);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }
        contact.Status = status;
        Context.Contact.Update(contact);
        await Context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Thêm hoặc xóa người dùng vào một vai trò (role)

Xem vấn đề này để biết thông tin về:

  • Xóa đặc quyền khỏi người dùng. Ví dụ: tắt tiếng người dùng trong ứng dụng trò chuyện.
  • Thêm đặc quyền cho người dùng.

Sự khác biệt giữa Thử thách (Challenge) và Cấm (Forbid)

Ứng dụng này đặt chính sách mặc định để yêu cầu người dùng được chứng thực. Đoạn mã sau cho phép người dùng ẩn danh. Người dùng ẩn danh được phép thể hiện sự khác biệt giữa Thử thách và Cấm.

[AllowAnonymous]
public class Details2Model : DI_BasePageModel
{
    public Details2Model(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact? _contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);

        if (_contact == null)
        {
            return NotFound();
        }
        Contact = _contact;

        if (!User.Identity!.IsAuthenticated)
        {
            return Challenge();
        }

        var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
                           User.IsInRole(Constants.ContactAdministratorsRole);

        var currentUserId = UserManager.GetUserId(User);

        if (!isAuthorized
            && currentUserId != Contact.OwnerID
            && Contact.Status != ContactStatus.Approved)
        {
            return Forbid();
        }

        return Page();
    }
}

Trong đoạn code trên:

  • Khi người dùng không được chứng thực, thì một ChallengeResult sẽ được trả về. Khi ChallengeResult được trả về thì người dùng sẽ được chuyển hướng đến trang đăng nhập.
  • Khi người dùng được chứng thực nhưng không được ủy quyền, thì một ForbidResult sẽ được trả về. Khi ForbidResult được trả về, thì người dùng sẽ được chuyển hướng đến trang bị từ chối truy cập.

Kiểm tra ứng dụng đã hoàn thành

Nếu bạn chưa đặt mật khẩu cho tài khoản người dùng hạt giống, hãy sử dụng công cụ Trình quản lý bí mật để đặt mật khẩu:

  • Chọn mật khẩu mạnh: Sử dụng tám ký tự trở lên và ít nhất một ký tự viết hoa, số và ký hiệu. Ví dụ: Passw0rd! đáp ứng các yêu cầu về mật khẩu mạnh.

  • Thực hiện lệnh sau từ thư mục của dự án, trong đó <PW> là mật khẩu:

    dotnet user-secrets set SeedUserPW <PW>
    

Nếu ứng dụng có danh bạ:

  • Xóa tất cả các bản ghi trong bảng Contact.
  • Khởi động lại ứng dụng để tạo cơ sở dữ liệu.

Một cách dễ dàng để kiểm tra ứng dụng đã hoàn thiện là khởi chạy ba trình duyệt khác nhau (hoặc phiên ẩn danh/InPrivate). Trong một trình duyệt, hãy đăng ký một người dùng mới (ví dụ: test@example.com). Đăng nhập vào mỗi trình duyệt bằng một người dùng khác nhau. Xác minh các hoạt động sau:

  • Người dùng đã đăng ký có thể xem tất cả dữ liệu liên hệ đã được phê duyệt.
  • Người dùng đã đăng ký có thể chỉnh sửa/xóa dữ liệu của riêng họ.
  • Người quản lý có thể phê duyệt/từ chối dữ liệu liên hệ. View Details hiển thị các nút Approve và Reject.
  • Quản trị viên có thể phê duyệt/từ chối và chỉnh sửa/xóa tất cả dữ liệu.
Người dùng Phê duyệt hoặc từ chối liên hệ Tùy chọn
test@example.com No Chỉnh sửa và xóa dữ liệu của họ.
manager@contoso.com Yes Chỉnh sửa và xóa dữ liệu của họ.
quản trị@contoso.com Yes Chỉnh sửa và xóa tất cả dữ liệu.

Tạo một liên hệ trong trình duyệt của quản trị viên. Sao chép URL để xóa và chỉnh sửa từ liên hệ của quản trị viên. Dán các liên kết này vào trình duyệt của người dùng thử nghiệm để xác minh rằng người dùng thử nghiệm không thể thực hiện các thao tác này.

Tạo ứng dụng khởi đầu (starter app)

  • Tạo ứng dụng Razor Pages có tên "ContactManager"
    • Tạo ứng dụng bằng Individual User Accounts.
    • Đặt tên là "ContactManager" để namespace khớp với namespace được sử dụng trong mẫu.
    • -uld chỉ định LocalDB thay vì SQLite
dotnet new webapp -o ContactManager -au Individual -uld
  • Thêm Models/Contact.cs: secure-data\samples\starter6\ContactManager\Models\Contact.cs
using System.ComponentModel.DataAnnotations;

namespace ContactManager.Models
{
    public class Contact
    {
        public int ContactId { get; set; }
        public string? Name { get; set; }
        public string? Address { get; set; }
        public string? City { get; set; }
        public string? State { get; set; }
        public string? Zip { get; set; }
        [DataType(DataType.EmailAddress)]
        public string? Email { get; set; }
    }
}
  • Scaffold model Contact.
  • Tạo migration ban đầu và cập nhật cơ sở dữ liệu:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet-aspnet-codegenerator razorpage -m Contact -udl -dc ApplicationDbContext -outDir Pages\Contacts --referenceScriptLibraries
dotnet ef database drop -f
dotnet ef migrations add initial
dotnet ef database update

Ghi chú

Theo mặc định, kiến ​​trúc của các tệp nhị phân .NET cần cài đặt đại diện cho kiến ​​trúc hệ điều hành hiện đang chạy. Để chỉ định kiến ​​trúc hệ điều hành khác, hãy xem cài đặt công cụ dotnet, tùy chọn --arch. Để biết thêm thông tin, hãy xem vấn đề GitHub dotnet/AspNetCore.Docs #29262.

  • Cập nhật link ContactManager trong file Pages/Shared/_Layout.cshtml:
<a class="nav-link text-dark" asp-area="" asp-page="/Contacts/Index">Contact Manager</a>
  • Kiểm tra ứng dụng bằng cách tạo, chỉnh sửa và xóa liên hệ

Tạo cơ sở dữ liệu

Thêm lớp SeedData vào thư mục Data:

using ContactManager.Models;
using Microsoft.EntityFrameworkCore;

// dotnet aspnet-codegenerator razorpage -m Contact -dc ApplicationDbContext -udl -outDir Pages\Contacts --referenceScriptLibraries

namespace ContactManager.Data
{
    public static class SeedData
    {
        public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw="")
        {
            using (var context = new ApplicationDbContext(
                serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
            {
                SeedDB(context, testUserPw);
            }
        }

        public static void SeedDB(ApplicationDbContext context, string adminID)
        {
            if (context.Contact.Any())
            {
                return;   // DB has been seeded
            }

            context.Contact.AddRange(
                new Contact
                {
                    Name = "Debra Garcia",
                    Address = "1234 Main St",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "debra@example.com"
                },
                new Contact
                {
                    Name = "Thorsten Weinrich",
                    Address = "5678 1st Ave W",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "thorsten@example.com"
                },
                new Contact
                {
                    Name = "Yuhong Li",
                    Address = "9012 State st",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "yuhong@example.com"
                },
                new Contact
                {
                    Name = "Jon Orton",
                    Address = "3456 Maple St",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "jon@example.com"
                },
                new Contact
                {
                    Name = "Diliana Alexieva-Bosseva",
                    Address = "7890 2nd Ave E",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "diliana@example.com"
                }
             );
            context.SaveChanges();
        }

    }
}

Gọi SeedData.Initialize từ Program.cs:

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ContactManager.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    await SeedData.Initialize(services);
}

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

Kiểm tra xem ứng dụng đã tạo cơ sở dữ liệu chưa. Nếu có bất kỳ hàng nào trong cơ sở dữ liệu liên hệ thì phương thức hạt giống sẽ không chạy.

Tài nguyên bổ sung

Nguồn: learn.microsoft.com
» Tiếp: Triển khai ứng dụng web ASP.NET
Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Copied !!!