ASP.NET Core: Model Binding tùy chỉnh trong ASP.NET Core
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 :
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 authorId
nê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 Author
cá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
BinderTypeModelBinder
.BinderTypeModelBinder
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êuAuthorEntityBinder
cầu DI truy cập EF Core. Sử dụngBinderTypeModelBinder
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.