ASP.NET Core: Model Binding tùy chỉnh trong ASP.NET Core


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

zzzLiên kết mô hình cho phép các hành động của bộ điều khiển hoạt động trực tiếp với các loại mô hình (được truyền vào dưới dạng đối số phương thức), thay vì các yêu cầu HTTP. Việc ánh xạ giữa dữ liệu yêu cầu đến và các mô hình ứng dụng được xử lý bởi các chất kết dính mô hình. Các nhà phát triển có thể mở rộng chức năng liên kết mô hình tích hợp sẵn bằng cách triển khai các liên kết mô hình tùy chỉnh (mặc dù thông thường, bạn không cần phải viết nhà cung cấp của riêng mình).

Xem hoặc tải xuống mã mẫu  ( cách tải xuống )

Giới hạn chất kết dính mô hình mặc định

Các chất kết dính mô hình mặc định hỗ trợ hầu hết các loại dữ liệu .NET Core phổ biến và sẽ đáp ứng hầu hết nhu cầu của nhà phát triển. Họ mong đợi liên kết trực tiếp dữ liệu đầu vào dựa trên văn bản từ yêu cầu với các loại mô hình. Bạn có thể cần phải chuyển đổi đầu vào trước khi liên kết nó. Ví dụ: khi bạn có một khóa có thể dùng để tra cứu dữ liệu mô hình. Bạn có thể sử dụng chất kết dính mô hình tùy chỉnh để tìm nạp dữ liệu dựa trên khóa.

Model liên kết các loại đơn giản và phức tạp

Liên kết mô hình sử dụng các định nghĩa cụ thể cho các loại mà nó hoạt động. Một  kiểu đơn giản  được chuyển đổi từ một chuỗi bằng cách sử dụng  TypeConverter  hoặc một  TryParse phương thức. Một  loại phức tạp  được chuyển đổi từ nhiều giá trị đầu vào. Khung xác định sự khác biệt dựa trên sự tồn tại của a  TypeConverter hoặc  TryParse. Chúng tôi khuyên bạn nên tạo một trình chuyển đổi loại hoặc sử dụng  TryParse để  chuyển đổi string sang  SomeType loại không yêu cầu tài nguyên bên ngoài hoặc nhiều đầu vào.

Xem  Các loại đơn giản  để biết danh sách các loại mà trình kết dính mô hình có thể chuyển đổi từ chuỗi.

Trước khi tạo chất kết dính mô hình tùy chỉnh của riêng bạn, bạn nên xem lại cách triển khai chất kết dính mô hình hiện tại. Hãy xem xét  ByteArrayModelBinder  có thể được sử dụng để chuyển đổi các chuỗi được mã hóa base64 thành mảng byte. Mảng byte thường được lưu trữ dưới dạng tệp hoặc trường BLOB cơ sở dữ liệu.

Làm việc với ByteArrayModelBinder

Các chuỗi được mã hóa Base64 có thể được sử dụng để biểu diễn dữ liệu nhị phân. Ví dụ: một hình ảnh có thể được mã hóa dưới dạng chuỗi. Mẫu bao gồm hình ảnh dưới dạng chuỗi được mã hóa base64 trong  Base64String.txt .

ASP.NET Core MVC có thể lấy một chuỗi được mã hóa base64 và sử dụng a  ByteArrayModelBinder để chuyển đổi nó thành một mảng byte. ByteArrayModelBinderProvider  ánh  xạ  byte[] các đối số tới  ByteArrayModelBinder:

public IModelBinder GetBinder(ModelBinderProviderContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }

    if (context.Metadata.ModelType == typeof(byte[]))
    {
        var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
        return new ByteArrayModelBinder(loggerFactory);
    }

    return null;
}

Khi tạo chất kết dính mô hình tùy chỉnh của riêng bạn, bạn có thể triển khai  IModelBinderProvider loại của riêng mình hoặc sử dụng  ModelBinderAttribution .

Ví dụ sau đây cho thấy cách sử dụng  ByteArrayModelBinder để chuyển đổi một chuỗi được mã hóa base64 thành a  byte[] và lưu kết quả vào một tệp:

[HttpPost]
public void Post([FromForm] byte[] file, string filename)
{
    // Don't trust the file name sent by the client. Use
    // Path.GetRandomFileName to generate a safe random
    // file name. _targetFilePath receives a value
    // from configuration (the appsettings.json file in
    // the sample app).
    var trustedFileName = Path.GetRandomFileName();
    var filePath = Path.Combine(_targetFilePath, trustedFileName);

    if (System.IO.File.Exists(filePath))
    {
        return;
    }

    System.IO.File.WriteAllBytes(filePath, file);
}

Nếu bạn muốn xem các nhận xét về mã được dịch sang các ngôn ngữ khác ngoài tiếng Anh, hãy cho chúng tôi biết trong  vấn đề thảo luận GitHub này .

Bạn có thể POST một chuỗi được mã hóa base64 lên phương thức api này bằng cách sử dụng công cụ như  Postman :

Công cụ đưa thư

Miễn là chất kết dính có thể liên kết dữ liệu yêu cầu với các thuộc tính hoặc đối số được đặt tên phù hợp, thì liên kết mô hình sẽ thành công. Ví dụ sau đây cho thấy cách sử dụng  ByteArrayModelBinder với mô hình khung nhìn:

[HttpPost("Profile")]
public void SaveProfile([FromForm] ProfileViewModel model)
{
    // Don't trust the file name sent by the client. Use
    // Path.GetRandomFileName to generate a safe random
    // file name. _targetFilePath receives a value
    // from configuration (the appsettings.json file in
    // the sample app).
    var trustedFileName = Path.GetRandomFileName();
    var filePath = Path.Combine(_targetFilePath, trustedFileName);

    if (System.IO.File.Exists(filePath))
    {
        return;
    }

    System.IO.File.WriteAllBytes(filePath, model.File);
}

public class ProfileViewModel
{
    public byte[] File { get; set; }
    public string FileName { get; set; }
}

Mẫu chất kết dính mô hình tùy chỉnh

Trong phần này, chúng tôi sẽ triển khai một mô hình liên kết tùy chỉnh:

  • Chuyển đổi dữ liệu yêu cầu đến thành các đối số chính được gõ mạnh.
  • Sử dụng Entity Framework Core để tìm nạp thực thể được liên kết.
  • Truyền thực thể liên quan làm đối số cho phương thức hành động.

Mẫu sau sử dụng  ModelBinder thuộc tính trên  Author mô hình:

using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;

namespace CustomModelBindingSample.Data
{
    [ModelBinder(BinderType = typeof(AuthorEntityBinder))]
    public class Author
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string GitHub { get; set; }
        public string Twitter { get; set; }
        public string BlogUrl { get; set; }
    }
}

Trong đoạn mã trước,  ModelBinder thuộc tính chỉ định loại thuộc  IModelBinder tính sẽ được sử dụng để liên kết  Author các tham số hành động.

Lớp sau  AuthorEntityBinder liên kết một  Author tham số bằng cách tìm nạp thực thể từ nguồn dữ liệu bằng cách sử dụng Entity Framework Core và  authorId:

public class AuthorEntityBinder : IModelBinder
{
    private readonly AuthorContext _context;

    public AuthorEntityBinder(AuthorContext context)
    {
        _context = context;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var modelName = bindingContext.ModelName;

        // Try to fetch the value of the argument by name
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;

        // Check if the argument value is null or empty
        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        if (!int.TryParse(value, out var id))
        {
            // Non-integer arguments result in model state errors
            bindingContext.ModelState.TryAddModelError(
                modelName, "Author Id must be an integer.");

            return Task.CompletedTask;
        }

        // Model will be null if not found, including for
        // out of range id values (0, -3, etc.)
        var model = _context.Authors.Find(id);
        bindingContext.Result = ModelBindingResult.Success(model);
        return Task.CompletedTask;
    }
}

 Ghi chú

Lớp trước  AuthorEntityBinder nhằm minh họa một chất kết dính mô hình tùy chỉnh. Lớp học này không nhằm mục đích minh họa các phương pháp thực hành tốt nhất cho một kịch bản tra cứu. Để tra cứu, hãy liên kết  authorId và truy vấn cơ sở dữ liệu theo một phương thức hành động. Cách tiếp cận này tách các lỗi liên kết mô hình khỏi  NotFound các trường hợp.

Đoạn mã sau đây cho thấy cách sử dụng  AuthorEntityBinder trong một phương thức hành động:

[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

Thuộc tính này  ModelBinder có thể được sử dụng để áp dụng  AuthorEntityBinder cho các tham số không sử dụng quy ước mặc định:

[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

Trong ví dụ này, vì tên của đối số không phải là mặc định  authorIdnên nó được chỉ định trên tham số bằng  ModelBinder thuộc tính. Cả bộ điều khiển và phương thức hành động đều được đơn giản hóa so với việc tra cứu thực thể trong phương thức hành động. Logic để tìm nạp tác giả bằng Entity Framework Core được chuyển sang mô hình liên kết. Đây có thể là sự đơn giản hóa đáng kể khi bạn có một số phương pháp liên kết với  Author mô hình.

Bạn có thể áp dụng  ModelBinder thuộc tính cho các thuộc tính mô hình riêng lẻ (chẳng hạn như trên một mô hình xem) hoặc cho các tham số phương thức hành động để chỉ định một chất kết dính mô hình hoặc tên mô hình nhất định cho loại hoặc hành động đó.

Triển khai ModelBinderProvider

Thay vì áp dụng một thuộc tính, bạn có thể triển khai  IModelBinderProvider. Đây là cách các chất kết dính khung tích hợp được triển khai. Khi bạn chỉ định loại chất kết dính của bạn hoạt động, bạn chỉ định loại đối số mà nó tạo ra, chứ không phải đầu vào mà chất kết dính của bạn chấp nhận. Nhà cung cấp chất kết dính sau hoạt động với  AuthorEntityBinder. Khi nó được thêm vào bộ sưu tập các nhà cung cấp của MVC, bạn không cần sử dụng thuộc  ModelBinder tính trên  Author hoặc  Authorcác tham số được gõ.

using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;

namespace CustomModelBindingSample.Binders
{
    public class AuthorEntityBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.Metadata.ModelType == typeof(Author))
            {
                return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
            }

            return null;
        }
    }
}

Lưu ý: Mã trước trả về a  BinderTypeModelBinderBinderTypeModelBinder hoạt động như một nhà máy sản xuất chất kết dính mô hình và cung cấp tính năng tiêm phụ thuộc (DI). Yêu  AuthorEntityBinder cầu DI truy cập EF Core. Sử dụng  BinderTypeModelBinder nếu chất kết dính mô hình của bạn yêu cầu dịch vụ từ DI.

Để sử dụng nhà cung cấp chất kết dính mô hình tùy chỉnh, hãy thêm nó vào  ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));

    services.AddControllers(options =>
    {
        options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
    });
}

Khi đánh giá các chất kết dính mô hình, việc thu thập các nhà cung cấp được kiểm tra theo thứ tự. Nhà cung cấp đầu tiên trả về chất kết dính phù hợp với mô hình đầu vào sẽ được sử dụng. Do đó, việc thêm nhà cung cấp của bạn vào cuối bộ sưu tập có thể dẫn đến việc chất kết dính mô hình tích hợp sẵn được gọi trước khi chất kết dính tùy chỉnh của bạn có cơ hội. Trong ví dụ này, nhà cung cấp tùy chỉnh được thêm vào đầu bộ sưu tập để đảm bảo nó luôn được sử dụng cho  Author các đối số hành động.

Liên kết mô hình đa hình

Liên kết với các mô hình khác nhau của các kiểu dẫn xuất được gọi là liên kết mô hình đa hình. Cần phải liên kết mô hình tùy chỉnh đa hình khi giá trị yêu cầu phải được liên kết với loại mô hình dẫn xuất cụ thể. Liên kết mô hình đa hình:

  • Không phải là API REST điển hình được thiết kế để tương tác với tất cả các ngôn ngữ.
  • Gây khó khăn cho việc suy luận về các mô hình bị ràng buộc.

Tuy nhiên, nếu một ứng dụng yêu cầu liên kết mô hình đa hình thì quá trình triển khai có thể trông giống như đoạn mã sau:

public abstract class Device
{
    public string Kind { get; set; }
}

public class Laptop : Device
{
    public string CPUIndex { get; set; }
}

public class SmartPhone : Device
{
    public string ScreenSize { get; set; }
}

public class DeviceModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType != typeof(Device))
        {
            return null;
        }

        var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };

        var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
        foreach (var type in subclasses)
        {
            var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
            binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
        }

        return new DeviceModelBinder(binders);
    }
}

public class DeviceModelBinder : IModelBinder
{
    private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;

    public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
    {
        this.binders = binders;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
        var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;

        IModelBinder modelBinder;
        ModelMetadata modelMetadata;
        if (modelTypeValue == "Laptop")
        {
            (modelMetadata, modelBinder) = binders[typeof(Laptop)];
        }
        else if (modelTypeValue == "SmartPhone")
        {
            (modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
        }
        else
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return;
        }

        var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
            bindingContext.ActionContext,
            bindingContext.ValueProvider,
            modelMetadata,
            bindingInfo: null,
            bindingContext.ModelName);

        await modelBinder.BindModelAsync(newBindingContext);
        bindingContext.Result = newBindingContext.Result;

        if (newBindingContext.Result.IsModelSet)
        {
            // Setting the ValidationState ensures properties on derived types are correctly 
            bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
            {
                Metadata = modelMetadata,
            };
        }
    }
}

Khuyến nghị và thực tiễn tốt nhất

Chất kết dính mô hình tùy chỉnh:

  • Không nên cố gắng đặt mã trạng thái hoặc trả về kết quả (ví dụ: Không tìm thấy 404). Nếu liên kết mô hình không thành công,  bộ lọc hành động  hoặc logic trong chính phương thức hành động sẽ xử lý lỗi.
  • Hữu ích nhất trong việc loại bỏ mã lặp đi lặp lại và các mối quan tâm xuyên suốt khỏi các phương thức hành động.
  • Thông thường không nên sử dụng để chuyển đổi một chuỗi thành loại tùy chỉnh, TypeConverter  thường  là một lựa chọn tốt hơn.
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 !!!