Vazba vlastního modelu v ASP.NET Core
Od Kirk Larkin
Vazba modelu umožňuje, aby akce kontroleru fungovaly přímo s typy modelů (předané jako argumenty metody) místo požadavků HTTP. Mapování mezi příchozími daty požadavků a aplikačními modely se zpracovává pomocí pořadačů modelů. Vývojáři můžou rozšířit integrovanou funkci vazby modelu implementací vlastních pořadačů modelů (obvykle ale nemusíte psát vlastního poskytovatele).
Zobrazení nebo stažení ukázkového kódu (postup stažení)
Omezení výchozího pořadače modelu
Výchozí pořadače modelů podporují většinu běžných datových typů .NET Core a měly by splňovat potřeby většiny vývojářů. Očekávají vazbu textového vstupu z požadavku přímo na typy modelů. Před vazbou může být nutné transformovat vstup. Pokud máte například klíč, který se dá použít k vyhledání dat modelu. K načtení dat na základě klíče můžete použít vlastní pořadač modelů.
Jednoduché a komplexní typy vazby modelu
Vazba modelu používá pro typy, na kterých pracuje, konkrétní definice. Jednoduchý typ je převeden z jednoho řetězce pomocí TypeConverter nebo TryParse
metody. Komplexní typ je převeden z více vstupních hodnot. Rámec určuje rozdíl na základě existence nebo TypeConverter
TryParse
. Doporučujeme vytvořit převaděč typů nebo použít TryParse
převod string
SomeType
, který nevyžaduje externí prostředky nebo více vstupů.
Seznam typů, které může pořadač modelů převést z řetězců, najdete v části Jednoduché typy .
Před vytvořením vlastního pořadače modelu je vhodné zkontrolovat, jak se implementují existující pořadače modelů. Představte si, které lze použít k převodu ByteArrayModelBinder řetězců s kódováním base64 na bajtová pole. Pole bajtů se často ukládají jako soubory nebo pole objektů blob databáze.
Práce s ByteArrayModelBinder
Řetězce s kódováním Base64 lze použít k reprezentaci binárních dat. Obrázek lze například zakódovat jako řetězec. Ukázka obsahuje obrázek jako řetězec kódovaný v base64 v Base64String.txt.
ASP.NET Core MVC může převést řetězec s kódováním base64 a použít ByteArrayModelBinder
ho k jeho převodu na bajtové pole. Argumenty ByteArrayModelBinderProvider mapuje byte[]
na 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;
}
Při vytváření vlastního pořadače modelů můžete implementovat vlastní IModelBinderProvider
typ nebo použít ModelBinderAttribute.
Následující příklad ukazuje, jak použít ByteArrayModelBinder
k převodu řetězce s kódováním base64 na byte[]
a uložení výsledku do souboru:
[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);
}
Pokud chcete zobrazit komentáře ke kódu přeložené do jiných jazyků, než je angličtina, dejte nám vědět v této diskuzi na GitHubu.
Řetězec s kódováním base64 můžete postovat na předchozí metodu rozhraní API pomocí nástroje, jako je curl.
Pokud binder může vytvořit vazbu dat požadavku na správně pojmenované vlastnosti nebo argumenty, vazba modelu bude úspěšná. Následující příklad ukazuje, jak se používá ByteArrayModelBinder
s modelem zobrazení:
[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; }
}
Ukázka pořadače vlastního modelu
V této části implementujeme vlastní pořadač modelů, který:
- Převede příchozí data požadavku na klíčové argumenty silného typu.
- Používá Entity Framework Core k načtení přidružené entity.
- Předá přidruženou entitu jako argument metodě akce.
Následující ukázka používá ModelBinder
atribut modelu Author
:
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; }
}
}
V předchozím kódu atribut určuje typIModelBinder
, ModelBinder
který se má použít k vytvoření vazby Author
parametrů akce.
Následující AuthorEntityBinder
třída sváže Author
parametr načtením entity ze zdroje dat pomocí Entity Framework Core a: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;
}
}
Poznámka:
AuthorEntityBinder
Předchozí třída je určena k ilustraci vlastního pořadače modelu. Třída není určená k ilustraci osvědčených postupů pro scénář vyhledávání. Pro vyhledávání vytvořte vazbu authorId
databáze a dotazování v metodě akce. Tento přístup odděluje chyby vazby modelu od NotFound
případů.
Následující kód ukazuje, jak použít metodu AuthorEntityBinder
akce:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Atribut ModelBinder
lze použít k použití AuthorEntityBinder
parametrů, které nepoužívají výchozí konvence:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
V tomto příkladu je vzhledem k tomu, že název argumentu není výchozí authorId
, je zadaný u parametru pomocí atributu ModelBinder
. Kontroler i metoda akce jsou ve srovnání s vyhledáním entity v metodě akce zjednodušené. Logika pro načtení autora pomocí Entity Framework Core se přesune do pořadače modelu. To může být značné zjednodušení, pokud máte několik metod, které se sváže s modelem Author
.
Atribut můžete použít ModelBinder
na jednotlivé vlastnosti modelu (například na modelu viewmodel) nebo na parametry metody akce, které určují určitý pořadač modelu nebo název modelu pouze pro daný typ nebo akci.
Implementace ModelBinderProvider
Místo použití atributu můžete implementovat IModelBinderProvider
. Takto se implementují integrované pořadače architektury. Když zadáte typ, se který pořadač pracuje, zadáte typ argumentu, který vytvoří, nikoli vstup, který váš pořadač přijme. Následující zprostředkovatel pořadače pracuje s zprostředkovatelem AuthorEntityBinder
. Když se přidá do kolekce zprostředkovatelů MVC, nemusíte atribut používat ModelBinder
u Author
parametrů nebo Author
parametr -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;
}
}
}
Poznámka: Předchozí kód vrátí znak .
BinderTypeModelBinder
BinderTypeModelBinder
funguje jako továrna pro pořadače modelů a poskytuje injektáž závislostí (DI). VyžadujeAuthorEntityBinder
přístup k EF Coredinátu . PoužijteBinderTypeModelBinder
, pokud váš pořadač modelů vyžaduje služby z DI.
Pokud chcete použít vlastního zprostředkovatele pořadače modelů, přidejte ho do ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
Při vyhodnocování pořadačů modelů se kolekce zprostředkovatelů zkoumá v pořadí. Použije se první zprostředkovatel, který vrátí pořadač, který odpovídá vstupnímu modelu. Přidání zprostředkovatele na konec kolekce může vést k tomu, že se volá předdefinovaný pořadač modelů, než bude mít váš vlastní pořadač šanci. V tomto příkladu se vlastní zprostředkovatel přidá na začátek kolekce, aby se zajistilo, že se vždy používá pro Author
argumenty akce.
Polymorfní vazba modelu
Vazba na různé modely odvozených typů se označuje jako polymorfní vazba modelu. Polymorfní vlastní vazba modelu je vyžadována, pokud musí být hodnota požadavku vázána na konkrétní odvozený typ modelu. Polymorfní vazba modelu:
- Není typické pro REST rozhraní API, které je navržené pro spolupráci se všemi jazyky.
- Znesnadňuje zdůvodnění vázaných modelů.
Pokud ale aplikace vyžaduje vazbu polymorfních modelů, implementace může vypadat jako následující kód:
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,
};
}
}
}
Doporučení a osvědčené postupy
Vlastní pořadače modelů:
- Neměli byste se pokoušet nastavit stavové kódy nebo vrátit výsledky (například 404 Nenalezena). Pokud vazba modelu selže, měl by se selháním zpracovat filtr akcí nebo logika v rámci samotné metody akce.
- Jsou nejužitečnější pro odstranění opakujících se kódu a průřezových obav z metod akcí.
- Obvykle by se nemělo používat k převodu řetězce na vlastní typ, TypeConverter obvykle je lepší volbou.
Autor: Steve Smith
Vazba modelu umožňuje, aby akce kontroleru fungovaly přímo s typy modelů (předané jako argumenty metody) místo požadavků HTTP. Mapování mezi příchozími daty požadavků a aplikačními modely se zpracovává pomocí pořadačů modelů. Vývojáři můžou rozšířit integrovanou funkci vazby modelu implementací vlastních pořadačů modelů (obvykle ale nemusíte psát vlastního poskytovatele).
Zobrazení nebo stažení ukázkového kódu (postup stažení)
Omezení výchozího pořadače modelu
Výchozí pořadače modelů podporují většinu běžných datových typů .NET Core a měly by splňovat potřeby většiny vývojářů. Očekávají vazbu textového vstupu z požadavku přímo na typy modelů. Před vazbou může být nutné transformovat vstup. Pokud máte například klíč, který se dá použít k vyhledání dat modelu. K načtení dat na základě klíče můžete použít vlastní pořadač modelů.
Kontrola vazby modelu
Vazba modelu používá pro typy, na kterých pracuje, konkrétní definice. Jednoduchý typ je převeden z jednoho řetězce ve vstupu. Komplexní typ je převeden z více vstupních hodnot. Rámec určuje rozdíl na základě existence TypeConverter
. Pokud máte jednoduchý string
>SomeType
mapování, které nevyžaduje externí prostředky, doporučujeme vytvořit převaděč typů.
Před vytvořením vlastního pořadače modelu je vhodné zkontrolovat, jak se implementují existující pořadače modelů. Představte si, které lze použít k převodu ByteArrayModelBinder řetězců s kódováním base64 na bajtová pole. Pole bajtů se často ukládají jako soubory nebo pole objektů blob databáze.
Práce s ByteArrayModelBinder
Řetězce s kódováním Base64 lze použít k reprezentaci binárních dat. Obrázek lze například zakódovat jako řetězec. Ukázka obsahuje obrázek jako řetězec kódovaný v base64 v Base64String.txt.
ASP.NET Core MVC může převést řetězec s kódováním base64 a použít ByteArrayModelBinder
ho k jeho převodu na bajtové pole. Argumenty ByteArrayModelBinderProvider mapuje byte[]
na 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;
}
Při vytváření vlastního pořadače modelů můžete implementovat vlastní IModelBinderProvider
typ nebo použít ModelBinderAttribute.
Následující příklad ukazuje, jak použít ByteArrayModelBinder
k převodu řetězce s kódováním base64 na byte[]
a uložení výsledku do souboru:
[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);
}
Řetězec s kódováním base64 můžete postovat na předchozí metodu rozhraní API pomocí nástroje, jako je curl.
Pokud binder může vytvořit vazbu dat požadavku na správně pojmenované vlastnosti nebo argumenty, vazba modelu bude úspěšná. Následující příklad ukazuje, jak se používá ByteArrayModelBinder
s modelem zobrazení:
[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; }
}
Ukázka pořadače vlastního modelu
V této části implementujeme vlastní pořadač modelů, který:
- Převede příchozí data požadavku na klíčové argumenty silného typu.
- Používá Entity Framework Core k načtení přidružené entity.
- Předá přidruženou entitu jako argument metodě akce.
Následující ukázka používá ModelBinder
atribut modelu Author
:
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; }
}
}
V předchozím kódu atribut určuje typIModelBinder
, ModelBinder
který se má použít k vytvoření vazby Author
parametrů akce.
Následující AuthorEntityBinder
třída sváže Author
parametr načtením entity ze zdroje dat pomocí Entity Framework Core a: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;
}
}
Poznámka:
AuthorEntityBinder
Předchozí třída je určena k ilustraci vlastního pořadače modelu. Třída není určená k ilustraci osvědčených postupů pro scénář vyhledávání. Pro vyhledávání vytvořte vazbu authorId
databáze a dotazování v metodě akce. Tento přístup odděluje chyby vazby modelu od NotFound
případů.
Následující kód ukazuje, jak použít metodu AuthorEntityBinder
akce:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Atribut ModelBinder
lze použít k použití AuthorEntityBinder
parametrů, které nepoužívají výchozí konvence:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
V tomto příkladu je vzhledem k tomu, že název argumentu není výchozí authorId
, je zadaný u parametru pomocí atributu ModelBinder
. Kontroler i metoda akce jsou ve srovnání s vyhledáním entity v metodě akce zjednodušené. Logika pro načtení autora pomocí Entity Framework Core se přesune do pořadače modelu. To může být značné zjednodušení, pokud máte několik metod, které se sváže s modelem Author
.
Atribut můžete použít ModelBinder
na jednotlivé vlastnosti modelu (například na modelu viewmodel) nebo na parametry metody akce, které určují určitý pořadač modelu nebo název modelu pouze pro daný typ nebo akci.
Implementace ModelBinderProvider
Místo použití atributu můžete implementovat IModelBinderProvider
. Takto se implementují integrované pořadače architektury. Když zadáte typ, se který pořadač pracuje, zadáte typ argumentu, který vytvoří, nikoli vstup, který váš pořadač přijme. Následující zprostředkovatel pořadače pracuje s zprostředkovatelem AuthorEntityBinder
. Když se přidá do kolekce zprostředkovatelů MVC, nemusíte atribut používat ModelBinder
u Author
parametrů nebo Author
parametr -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;
}
}
}
Poznámka: Předchozí kód vrátí znak .
BinderTypeModelBinder
BinderTypeModelBinder
funguje jako továrna pro pořadače modelů a poskytuje injektáž závislostí (DI). VyžadujeAuthorEntityBinder
přístup k EF Coredinátu . PoužijteBinderTypeModelBinder
, pokud váš pořadač modelů vyžaduje služby z DI.
Pokud chcete použít vlastního zprostředkovatele pořadače modelů, přidejte ho do 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);
}
Při vyhodnocování pořadačů modelů se kolekce zprostředkovatelů zkoumá v pořadí. Použije se první zprostředkovatel, který vrací pořadač. Přidání zprostředkovatele na konec kolekce může vést k tomu, že se volá předdefinovaný pořadač modelů, než bude mít váš vlastní pořadač šanci. V tomto příkladu se vlastní zprostředkovatel přidá na začátek kolekce, aby se zajistilo, že se používá pro Author
argumenty akce.
Polymorfní vazba modelu
Vazba na různé modely odvozených typů se označuje jako polymorfní vazba modelu. Polymorfní vlastní vazba modelu je vyžadována, pokud musí být hodnota požadavku vázána na konkrétní odvozený typ modelu. Polymorfní vazba modelu:
- Není typické pro REST rozhraní API, které je navržené pro spolupráci se všemi jazyky.
- Znesnadňuje zdůvodnění vázaných modelů.
Pokud ale aplikace vyžaduje vazbu polymorfních modelů, implementace může vypadat jako následující kód:
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,
};
}
}
}
Doporučení a osvědčené postupy
Vlastní pořadače modelů:
- Neměli byste se pokoušet nastavit stavové kódy nebo vrátit výsledky (například 404 Nenalezena). Pokud vazba modelu selže, měl by se selháním zpracovat filtr akcí nebo logika v rámci samotné metody akce.
- Jsou nejužitečnější pro odstranění opakujících se kódu a průřezových obav z metod akcí.
- Obvykle by se nemělo používat k převodu řetězce na vlastní typ, TypeConverter obvykle je lepší volbou.