Pengikatan Model Kustom di ASP.NET Core
Oleh Kirk Larkin
Pengikatan model memungkinkan tindakan pengontrol bekerja langsung dengan jenis model (diteruskan sebagai argumen metode), bukan permintaan HTTP. Pemetaan antara data permintaan masuk dan model aplikasi ditangani oleh pengikat model. Pengembang dapat memperluas fungsionalitas pengikatan model bawaan dengan menerapkan pengikat model kustom (meskipun biasanya, Anda tidak perlu menulis penyedia Anda sendiri).
Melihat atau mengunduh kode sampel (cara mengunduh)
Batasan binder model default
Pengikat model default mendukung sebagian besar jenis data .NET Core umum dan harus memenuhi kebutuhan sebagian besar pengembang. Mereka berharap untuk mengikat input berbasis teks dari permintaan langsung ke jenis model. Anda mungkin perlu mengubah input sebelum mengikatnya. Misalnya, ketika Anda memiliki kunci yang dapat digunakan untuk mencari data model. Anda dapat menggunakan pengikat model kustom untuk mengambil data berdasarkan kunci.
Pengikatan model jenis sederhana dan kompleks
Pengikatan model menggunakan definisi tertentu untuk jenis yang dioperasikannya. Jenis sederhana dikonversi dari satu string menggunakan TypeConverter atau TryParse
metode . Jenis kompleks dikonversi dari beberapa nilai input. Kerangka kerja menentukan perbedaan berdasarkan keberadaan TypeConverter
atau TryParse
. Sebaiknya buat pengonversi jenis atau gunakan TryParse
untuk string
konversi ke SomeType
yang tidak memerlukan sumber daya eksternal atau beberapa input.
Lihat Jenis sederhana untuk daftar jenis yang dapat dikonversi oleh pengikat model dari string.
Sebelum membuat binder model kustom Anda sendiri, ada baiknya meninjau bagaimana pengikat model yang ada diterapkan. ByteArrayModelBinder Pertimbangkan yang dapat digunakan untuk mengonversi string yang dikodekan base64 menjadi array byte. Array byte sering disimpan sebagai file atau bidang BLOB database.
Bekerja dengan ByteArrayModelBinder
String yang dikodekan Base64 dapat digunakan untuk mewakili data biner. Misalnya, gambar dapat dikodekan sebagai string. Sampel menyertakan gambar sebagai string yang dikodekan base64 dalam Base64String.txt.
ASP.NET Core MVC dapat mengambil string yang dikodekan base64 dan menggunakan ByteArrayModelBinder
untuk mengonversinya menjadi array byte. Argumen ByteArrayModelBinderProvider peta byte[]
ke 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;
}
Saat membuat binder model kustom Anda sendiri, Anda dapat menerapkan jenis Anda sendiri IModelBinderProvider
, atau menggunakan ModelBinderAttribute.
Contoh berikut menunjukkan cara menggunakan ByteArrayModelBinder
untuk mengonversi string yang dikodekan base64 menjadi byte[]
dan menyimpan hasilnya ke file:
[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);
}
Jika Anda ingin melihat komentar kode yang diterjemahkan ke bahasa selain bahasa Inggris, beri tahu kami dalam masalah diskusi GitHub ini.
Anda dapat MEMPOSTING string yang dikodekan base64 ke metode api sebelumnya menggunakan alat seperti curl.
Selama pengikat dapat mengikat data permintaan ke properti atau argumen bernama dengan tepat, pengikatan model akan berhasil. Contoh berikut menunjukkan cara menggunakan ByteArrayModelBinder
dengan model tampilan:
[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; }
}
Sampel pengikat model kustom
Di bagian ini kita akan menerapkan pengikat model kustom yang:
- Mengonversi data permintaan masuk menjadi argumen kunci yang ditik dengan kuat.
- Menggunakan Entity Framework Core untuk mengambil entitas terkait.
- Meneruskan entitas terkait sebagai argumen ke metode tindakan.
Sampel berikut menggunakan ModelBinder
atribut pada Author
model:
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; }
}
}
Dalam kode sebelumnya, ModelBinder
atribut menentukan jenis IModelBinder
yang harus digunakan untuk mengikat Author
parameter tindakan.
Kelas berikut AuthorEntityBinder
mengikat Author
parameter dengan mengambil entitas dari sumber data menggunakan Entity Framework Core dan 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;
}
}
Catatan
Kelas sebelumnya dimaksudkan untuk mengilustrasikan pengilustrasikan pengikat AuthorEntityBinder
model kustom. Kelas tidak dimaksudkan untuk mengilustrasikan praktik terbaik untuk skenario pencarian. Untuk pencarian, ikat authorId
dan kueri database dalam metode tindakan. Pendekatan ini memisahkan kegagalan pengikatan model dari NotFound
kasus.
Kode berikut menunjukkan cara menggunakan AuthorEntityBinder
dalam metode tindakan:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Atribut ModelBinder
dapat digunakan untuk menerapkan AuthorEntityBinder
parameter ke yang tidak menggunakan konvensi default:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Dalam contoh ini, karena nama argumen bukan default authorId
, itu ditentukan pada parameter menggunakan ModelBinder
atribut . Pengontrol dan metode tindakan disederhanakan dibandingkan dengan mencari entitas dalam metode tindakan. Logika untuk mengambil penulis menggunakan Entity Framework Core dipindahkan ke pengikat model. Ini bisa menjadi penyederhanaan yang cukup besar ketika Anda memiliki beberapa metode yang mengikat Author
model.
Anda dapat menerapkan ModelBinder
atribut ke properti model individual (seperti pada viewmodel) atau parameter metode tindakan untuk menentukan pengikat model atau nama model tertentu hanya untuk jenis atau tindakan tersebut.
Menerapkan ModelBinderProvider
Alih-alih menerapkan atribut, Anda dapat menerapkan IModelBinderProvider
. Ini adalah bagaimana pengikat kerangka kerja bawaan diimplementasikan. Saat Anda menentukan jenis binder yang dioperasikan, Anda menentukan jenis argumen yang dihasilkannya, bukan input yang diterima binder Anda. Penyedia pengikat berikut bekerja dengan AuthorEntityBinder
. Saat ditambahkan ke kumpulan penyedia MVC, Anda tidak perlu menggunakan ModelBinder
atribut pada Author
parameter atau Author
-typed.
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;
}
}
}
Catatan: Kode sebelumnya mengembalikan
BinderTypeModelBinder
.BinderTypeModelBinder
bertindak sebagai pabrik untuk pengikat model dan menyediakan injeksi dependensi (DI).AuthorEntityBinder
mengharuskan DI untuk mengakses EF Core. GunakanBinderTypeModelBinder
jika pengikat model Anda memerlukan layanan dari DI.
Untuk menggunakan penyedia pengikat model kustom, tambahkan di ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
Saat mengevaluasi pengikat model, pengumpulan penyedia diperiksa secara berurutan. Penyedia pertama yang mengembalikan pengikat yang cocok dengan model input digunakan. Menambahkan penyedia Anda ke akhir koleksi dapat mengakibatkan pengikat model bawaan dipanggil sebelum pengikat kustom Anda memiliki kesempatan. Dalam contoh ini, penyedia kustom ditambahkan ke awal koleksi untuk memastikannya selalu digunakan untuk Author
argumen tindakan.
Pengikatan model polimorfik
Pengikatan ke berbagai model jenis turunan dikenal sebagai pengikatan model polimorfik. Pengikatan model kustom polimorfik diperlukan ketika nilai permintaan harus terikat ke jenis model turunan tertentu. Pengikatan model polimorfik:
- Tidak khas untuk REST API yang dirancang untuk beroperasi dengan semua bahasa.
- Menyulitkan alasan tentang model terikat.
Namun, jika aplikasi memerlukan pengikatan model polimorfik, implementasi mungkin terlihat seperti kode berikut:
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,
};
}
}
}
Rekomendasi dan praktik terbaik
Pengikat model kustom:
- Tidak boleh mencoba mengatur kode status atau mengembalikan hasil (misalnya, 404 Tidak Ditemukan). Jika pengikatan model gagal, filter tindakan atau logika dalam metode tindakan itu sendiri harus menangani kegagalan.
- Paling berguna untuk menghilangkan kode berulang dan masalah lintas pemotongan dari metode tindakan.
- Biasanya tidak boleh digunakan untuk mengonversi string menjadi jenis kustom, TypeConverter biasanya merupakan opsi yang lebih baik.
Oleh Steve Smith
Pengikatan model memungkinkan tindakan pengontrol bekerja langsung dengan jenis model (diteruskan sebagai argumen metode), bukan permintaan HTTP. Pemetaan antara data permintaan masuk dan model aplikasi ditangani oleh pengikat model. Pengembang dapat memperluas fungsionalitas pengikatan model bawaan dengan menerapkan pengikat model kustom (meskipun biasanya, Anda tidak perlu menulis penyedia Anda sendiri).
Melihat atau mengunduh kode sampel (cara mengunduh)
Batasan binder model default
Pengikat model default mendukung sebagian besar jenis data .NET Core umum dan harus memenuhi kebutuhan sebagian besar pengembang. Mereka berharap untuk mengikat input berbasis teks dari permintaan langsung ke jenis model. Anda mungkin perlu mengubah input sebelum mengikatnya. Misalnya, ketika Anda memiliki kunci yang dapat digunakan untuk mencari data model. Anda dapat menggunakan pengikat model kustom untuk mengambil data berdasarkan kunci.
Tinjauan pengikatan model
Pengikatan model menggunakan definisi tertentu untuk jenis yang dioperasikannya. Jenis sederhana dikonversi dari satu string dalam input. Jenis kompleks dikonversi dari beberapa nilai input. Kerangka kerja menentukan perbedaan berdasarkan keberadaan TypeConverter
. Sebaiknya buat pengonversi jenis jika Anda memiliki pemetaan sederhana string
yang>SomeType
tidak memerlukan sumber daya eksternal.
Sebelum membuat binder model kustom Anda sendiri, ada baiknya meninjau bagaimana pengikat model yang ada diterapkan. ByteArrayModelBinder Pertimbangkan yang dapat digunakan untuk mengonversi string yang dikodekan base64 menjadi array byte. Array byte sering disimpan sebagai file atau bidang BLOB database.
Bekerja dengan ByteArrayModelBinder
String yang dikodekan Base64 dapat digunakan untuk mewakili data biner. Misalnya, gambar dapat dikodekan sebagai string. Sampel menyertakan gambar sebagai string yang dikodekan base64 dalam Base64String.txt.
ASP.NET Core MVC dapat mengambil string yang dikodekan base64 dan menggunakan ByteArrayModelBinder
untuk mengonversinya menjadi array byte. Argumen ByteArrayModelBinderProvider peta byte[]
ke ByteArrayModelBinder
:
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(byte[]))
{
return new ByteArrayModelBinder();
}
return null;
}
Saat membuat binder model kustom Anda sendiri, Anda dapat menerapkan jenis Anda sendiri IModelBinderProvider
, atau menggunakan ModelBinderAttribute.
Contoh berikut menunjukkan cara menggunakan ByteArrayModelBinder
untuk mengonversi string yang dikodekan base64 menjadi byte[]
dan menyimpan hasilnya ke file:
[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);
}
Anda dapat MEMPOSTING string yang dikodekan base64 ke metode api sebelumnya menggunakan alat seperti curl.
Selama pengikat dapat mengikat data permintaan ke properti atau argumen bernama dengan tepat, pengikatan model akan berhasil. Contoh berikut menunjukkan cara menggunakan ByteArrayModelBinder
dengan model tampilan:
[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; }
}
Sampel pengikat model kustom
Di bagian ini kita akan menerapkan pengikat model kustom yang:
- Mengonversi data permintaan masuk menjadi argumen kunci yang ditik dengan kuat.
- Menggunakan Entity Framework Core untuk mengambil entitas terkait.
- Meneruskan entitas terkait sebagai argumen ke metode tindakan.
Sampel berikut menggunakan ModelBinder
atribut pada Author
model:
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; }
}
}
Dalam kode sebelumnya, ModelBinder
atribut menentukan jenis IModelBinder
yang harus digunakan untuk mengikat Author
parameter tindakan.
Kelas berikut AuthorEntityBinder
mengikat Author
parameter dengan mengambil entitas dari sumber data menggunakan Entity Framework Core dan authorId
:
public class AuthorEntityBinder : IModelBinder
{
private readonly AppDbContext _db;
public AuthorEntityBinder(AppDbContext db)
{
_db = db;
}
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 = _db.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Catatan
Kelas sebelumnya dimaksudkan untuk mengilustrasikan pengilustrasikan pengikat AuthorEntityBinder
model kustom. Kelas tidak dimaksudkan untuk mengilustrasikan praktik terbaik untuk skenario pencarian. Untuk pencarian, ikat authorId
dan kueri database dalam metode tindakan. Pendekatan ini memisahkan kegagalan pengikatan model dari NotFound
kasus.
Kode berikut menunjukkan cara menggunakan AuthorEntityBinder
dalam metode tindakan:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Atribut ModelBinder
dapat digunakan untuk menerapkan AuthorEntityBinder
parameter ke yang tidak menggunakan konvensi default:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Dalam contoh ini, karena nama argumen bukan default authorId
, itu ditentukan pada parameter menggunakan ModelBinder
atribut . Pengontrol dan metode tindakan disederhanakan dibandingkan dengan mencari entitas dalam metode tindakan. Logika untuk mengambil penulis menggunakan Entity Framework Core dipindahkan ke pengikat model. Ini bisa menjadi penyederhanaan yang cukup besar ketika Anda memiliki beberapa metode yang mengikat Author
model.
Anda dapat menerapkan ModelBinder
atribut ke properti model individual (seperti pada viewmodel) atau parameter metode tindakan untuk menentukan pengikat model atau nama model tertentu hanya untuk jenis atau tindakan tersebut.
Menerapkan ModelBinderProvider
Alih-alih menerapkan atribut, Anda dapat menerapkan IModelBinderProvider
. Ini adalah bagaimana pengikat kerangka kerja bawaan diimplementasikan. Saat Anda menentukan jenis binder yang dioperasikan, Anda menentukan jenis argumen yang dihasilkannya, bukan input yang diterima binder Anda. Penyedia pengikat berikut bekerja dengan AuthorEntityBinder
. Saat ditambahkan ke kumpulan penyedia MVC, Anda tidak perlu menggunakan ModelBinder
atribut pada Author
parameter atau Author
-typed.
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;
}
}
}
Catatan: Kode sebelumnya mengembalikan
BinderTypeModelBinder
.BinderTypeModelBinder
bertindak sebagai pabrik untuk pengikat model dan menyediakan injeksi dependensi (DI).AuthorEntityBinder
mengharuskan DI untuk mengakses EF Core. GunakanBinderTypeModelBinder
jika pengikat model Anda memerlukan layanan dari DI.
Untuk menggunakan penyedia pengikat model kustom, tambahkan di ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("App"));
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Saat mengevaluasi pengikat model, pengumpulan penyedia diperiksa secara berurutan. Penyedia pertama yang mengembalikan pengikat digunakan. Menambahkan penyedia Anda ke akhir koleksi dapat mengakibatkan pengikat model bawaan dipanggil sebelum pengikat kustom Anda memiliki kesempatan. Dalam contoh ini, penyedia kustom ditambahkan ke awal koleksi untuk memastikannya digunakan untuk Author
argumen tindakan.
Pengikatan model polimorfik
Pengikatan ke berbagai model jenis turunan dikenal sebagai pengikatan model polimorfik. Pengikatan model kustom polimorfik diperlukan ketika nilai permintaan harus terikat ke jenis model turunan tertentu. Pengikatan model polimorfik:
- Tidak khas untuk REST API yang dirancang untuk beroperasi dengan semua bahasa.
- Menyulitkan alasan tentang model terikat.
Namun, jika aplikasi memerlukan pengikatan model polimorfik, implementasi mungkin terlihat seperti kode berikut:
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,
};
}
}
}
Rekomendasi dan praktik terbaik
Pengikat model kustom:
- Tidak boleh mencoba mengatur kode status atau mengembalikan hasil (misalnya, 404 Tidak Ditemukan). Jika pengikatan model gagal, filter tindakan atau logika dalam metode tindakan itu sendiri harus menangani kegagalan.
- Paling berguna untuk menghilangkan kode berulang dan masalah lintas pemotongan dari metode tindakan.
- Biasanya tidak boleh digunakan untuk mengonversi string menjadi jenis kustom, TypeConverter biasanya merupakan opsi yang lebih baik.
ASP.NET Core
Saran dan Komentar
https://aka.ms/ContentUserFeedback.
Segera hadir: Sepanjang tahun 2024 kami akan menghentikan penggunaan GitHub Issues sebagai mekanisme umpan balik untuk konten dan menggantinya dengan sistem umpan balik baru. Untuk mengetahui informasi selengkapnya, lihat:Kirim dan lihat umpan balik untuk