Powiązanie modelu niestandardowego w ASP.NET Core
Autor: Kirk Larkin
Powiązanie modelu umożliwia działanie akcji kontrolera bezpośrednio z typami modelu (przekazywanymi jako argumenty metody), a nie żądaniami HTTP. Mapowanie danych przychodzących żądań i modeli aplikacji jest obsługiwane przez powiązania modelu. Deweloperzy mogą rozszerzyć wbudowaną funkcję powiązania modelu, implementując niestandardowe powiązania modelu (zazwyczaj nie trzeba pisać własnego dostawcy).
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Domyślne ograniczenia powiązania modelu
Domyślne powiązania modelu obsługują większość typowych typów danych platformy .NET Core i powinny spełniać potrzeby większości deweloperów. Oczekuje się powiązania danych wejściowych opartych na tekście z żądania bezpośrednio z typami modelu. Może być konieczne przekształcenie danych wejściowych przed jego powiązaniem. Jeśli na przykład masz klucz, który może służyć do wyszukiwania danych modelu. Do pobierania danych na podstawie klucza można użyć niestandardowego powiązania modelu.
Tworzenie prostych i złożonych typów powiązania modelu
Powiązanie modelu używa określonych definicji dla typów, na których działa. Prosty typ jest konwertowany z jednego ciągu przy użyciu TypeConverter metody lub TryParse
metody. Typ złożony jest konwertowany z wielu wartości wejściowych. Struktura określa różnicę na podstawie istnienia obiektu TypeConverter
lub TryParse
. Zalecamy utworzenie konwertera typów lub użycie TryParse
metody do string
SomeType
konwersji, która nie wymaga zasobów zewnętrznych ani wielu danych wejściowych.
Zobacz Proste typy, aby uzyskać listę typów , które binder modelu może konwertować z ciągów.
Przed utworzeniem własnego powiązania modelu niestandardowego warto zapoznać się z implementacją istniejących powiązań modelu. Rozważ użycie ByteArrayModelBinder funkcji , która może służyć do konwertowania ciągów zakodowanych w formacie base64 na tablice bajtowe. Tablice bajtów są często przechowywane jako pliki lub pola obiektu BLOB bazy danych.
Praca z aplikacją ByteArrayModelBinder
Ciągi zakodowane w formacie Base64 mogą służyć do reprezentowania danych binarnych. Na przykład obraz może być zakodowany jako ciąg. Przykład zawiera obraz jako ciąg zakodowany w formacie base64 w Base64String.txt.
ASP.NET Core MVC może przyjąć ciąg zakodowany w formacie base64 i użyć elementu , ByteArrayModelBinder
aby przekonwertować go na tablicę bajtów. Argumenty ByteArrayModelBinderProvider mapowania 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;
}
Podczas tworzenia własnego niestandardowego powiązania modelu można zaimplementować własny IModelBinderProvider
typ lub użyć elementu ModelBinderAttribute.
W poniższym przykładzie pokazano, jak przekonwertować ByteArrayModelBinder
ciąg zakodowany w formacie base64 na byte[]
element i zapisać wynik w pliku:
[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);
}
Jeśli chcesz zobaczyć komentarze kodu przetłumaczone na języki inne niż angielski, poinformuj nas o tym w tym problemie z dyskusją w usłudze GitHub.
Ciąg zakodowany w formacie base64 można utworzyć w poprzedniej metodzie interfejsu API przy użyciu narzędzia takiego jak curl.
Jeśli binder może powiązać dane żądania z odpowiednio nazwanych właściwościami lub argumentami, powiązanie modelu powiedzie się. W poniższym przykładzie pokazano, jak używać z ByteArrayModelBinder
modelem widoku:
[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; }
}
Przykład niestandardowego powiązania modelu
W tej sekcji zaimplementujemy niestandardowy binder modelu, który:
- Konwertuje dane żądań przychodzących na silnie typizowane argumenty klucza.
- Używa programu Entity Framework Core do pobierania skojarzonej jednostki.
- Przekazuje skojarzona jednostka jako argument do metody akcji.
W poniższym przykładzie użyto atrybutu ModelBinder
Author
w modelu:
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; }
}
}
W poprzednim kodzie ModelBinder
atrybut określa typ IModelBinder
, którego należy użyć do powiązania Author
parametrów akcji.
Poniższa AuthorEntityBinder
klasa wiąże Author
parametr przez pobranie jednostki ze źródła danych przy użyciu platformy Entity Framework Core i klasy 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;
}
}
Uwaga
Poprzednia AuthorEntityBinder
klasa ma na celu zilustrowanie niestandardowego powiązania modelu. Klasa nie ma na celu zilustrowania najlepszych rozwiązań dla scenariusza wyszukiwania. Aby wyszukać, powiąż authorId
bazę danych i wykonaj zapytanie względem jej w metodzie akcji. Takie podejście oddziela błędy powiązań modelu od NotFound
przypadków.
Poniższy kod pokazuje, jak używać AuthorEntityBinder
metody w metodzie akcji:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Atrybut ModelBinder
może służyć do stosowania AuthorEntityBinder
parametrów, które nie używają konwencji domyślnych:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
W tym przykładzie, ponieważ nazwa argumentu nie jest wartością domyślną authorId
, jest określona na parametrze przy użyciu atrybutu ModelBinder
. Zarówno kontroler, jak i metoda akcji są uproszczone w porównaniu z wyszukiwaniem jednostki w metodzie akcji. Logika pobierania autora przy użyciu programu Entity Framework Core jest przenoszona do powiązania modelu. Może to być znaczne uproszczenie, gdy istnieje kilka metod, które wiążą się z modelem Author
.
Atrybut można zastosować ModelBinder
do poszczególnych właściwości modelu (takich jak model widoku) lub do parametrów metody akcji w celu określenia określonego powiązania modelu lub nazwy modelu tylko dla tego typu lub akcji.
Implementowanie obiektu ModelBinderProvider
Zamiast stosować atrybut, można zaimplementować IModelBinderProvider
element . W ten sposób są implementowane wbudowane powiązania struktury. Po określeniu typu, na którym działa binder, należy określić typ argumentu, który generuje, a nie dane wejściowe, które akceptuje binder. Następujący dostawca binder współpracuje z programem AuthorEntityBinder
. Po dodaniu do kolekcji dostawców MVC nie trzeba używać atrybutu ModelBinder
w parametrach Author
lub 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;
}
}
}
Uwaga: Powyższy kod zwraca wartość
BinderTypeModelBinder
.BinderTypeModelBinder
działa jako fabryka powiązań modelu i zapewnia wstrzykiwanie zależności (DI). WymagaAuthorEntityBinder
, aby di uzyskać dostęp do EF Core. UżyjBinderTypeModelBinder
, jeśli powiązanie modelu wymaga usług z di.
Aby użyć niestandardowego dostawcy powiązania modelu, dodaj go w pliku ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
Podczas oceniania powiązań modelu kolekcja dostawców jest badana w kolejności. Pierwszy dostawca, który zwraca powiązanie zgodne z modelem wejściowym, jest używany. Dodanie dostawcy na końcu kolekcji może spowodować wywołanie wbudowanego powiązania modelu, zanim niestandardowy binder będzie miał szansę. W tym przykładzie dostawca niestandardowy jest dodawany na początku kolekcji, aby upewnić się, że jest on zawsze używany do Author
argumentów akcji.
Powiązanie modelu polimorficznego
Powiązanie z różnymi modelami typów pochodnych jest nazywane powiązaniem modelu polimorficznego. Powiązanie modelu niestandardowego polimorficznego jest wymagane, gdy wartość żądania musi być powiązana z określonym typem modelu pochodnego. Powiązanie modelu polimorficznego:
- Nie jest typowe dla interfejsu REST API, który jest przeznaczony do współdziałania ze wszystkimi językami.
- Utrudnia rozumowanie o powiązanych modelach.
Jeśli jednak aplikacja wymaga powiązania modelu polimorficznego, implementacja może wyglądać podobnie do następującego kodu:
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,
};
}
}
}
Zalecenia i najlepsze rozwiązania
Niestandardowe powiązania modelu:
- Nie należy próbować ustawiać kodów stanu ani zwracać wyników (na przykład 404 Nie znaleziono). Jeśli powiązanie modelu nie powiedzie się, filtr akcji lub logika w samej metodzie akcji powinien obsłużyć błąd.
- Jest to najbardziej przydatne w przypadku eliminowania powtarzających się problemów związanych z kodem i przecięciem ze strony metod akcji.
- Zazwyczaj nie należy używać do konwertowania ciągu na typ niestandardowy, a TypeConverter zazwyczaj jest to lepsza opcja.
Autor: Steve Smith
Powiązanie modelu umożliwia działanie akcji kontrolera bezpośrednio z typami modelu (przekazywanymi jako argumenty metody), a nie żądaniami HTTP. Mapowanie danych przychodzących żądań i modeli aplikacji jest obsługiwane przez powiązania modelu. Deweloperzy mogą rozszerzyć wbudowaną funkcję powiązania modelu, implementując niestandardowe powiązania modelu (zazwyczaj nie trzeba pisać własnego dostawcy).
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Domyślne ograniczenia powiązania modelu
Domyślne powiązania modelu obsługują większość typowych typów danych platformy .NET Core i powinny spełniać potrzeby większości deweloperów. Oczekuje się powiązania danych wejściowych opartych na tekście z żądania bezpośrednio z typami modelu. Może być konieczne przekształcenie danych wejściowych przed jego powiązaniem. Jeśli na przykład masz klucz, który może służyć do wyszukiwania danych modelu. Do pobierania danych na podstawie klucza można użyć niestandardowego powiązania modelu.
Przegląd powiązania modelu
Powiązanie modelu używa określonych definicji dla typów, na których działa. Prosty typ jest konwertowany z pojedynczego ciągu w danych wejściowych. Typ złożony jest konwertowany z wielu wartości wejściowych. Struktura określa różnicę na podstawie istnienia obiektu TypeConverter
. Zalecamy utworzenie konwertera typów, jeśli masz proste string
—>SomeType
mapowanie, które nie wymaga zasobów zewnętrznych.
Przed utworzeniem własnego powiązania modelu niestandardowego warto zapoznać się z implementacją istniejących powiązań modelu. Rozważ użycie ByteArrayModelBinder funkcji , która może służyć do konwertowania ciągów zakodowanych w formacie base64 na tablice bajtowe. Tablice bajtów są często przechowywane jako pliki lub pola obiektu BLOB bazy danych.
Praca z aplikacją ByteArrayModelBinder
Ciągi zakodowane w formacie Base64 mogą służyć do reprezentowania danych binarnych. Na przykład obraz może być zakodowany jako ciąg. Przykład zawiera obraz jako ciąg zakodowany w formacie base64 w Base64String.txt.
ASP.NET Core MVC może przyjąć ciąg zakodowany w formacie base64 i użyć elementu , ByteArrayModelBinder
aby przekonwertować go na tablicę bajtów. Argumenty ByteArrayModelBinderProvider mapowania 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;
}
Podczas tworzenia własnego niestandardowego powiązania modelu można zaimplementować własny IModelBinderProvider
typ lub użyć elementu ModelBinderAttribute.
W poniższym przykładzie pokazano, jak przekonwertować ByteArrayModelBinder
ciąg zakodowany w formacie base64 na byte[]
element i zapisać wynik w pliku:
[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);
}
Ciąg zakodowany w formacie base64 można utworzyć w poprzedniej metodzie interfejsu API przy użyciu narzędzia takiego jak curl.
Jeśli binder może powiązać dane żądania z odpowiednio nazwanych właściwościami lub argumentami, powiązanie modelu powiedzie się. W poniższym przykładzie pokazano, jak używać z ByteArrayModelBinder
modelem widoku:
[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; }
}
Przykład niestandardowego powiązania modelu
W tej sekcji zaimplementujemy niestandardowy binder modelu, który:
- Konwertuje dane żądań przychodzących na silnie typizowane argumenty klucza.
- Używa programu Entity Framework Core do pobierania skojarzonej jednostki.
- Przekazuje skojarzona jednostka jako argument do metody akcji.
W poniższym przykładzie użyto atrybutu ModelBinder
Author
w modelu:
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; }
}
}
W poprzednim kodzie ModelBinder
atrybut określa typ IModelBinder
, którego należy użyć do powiązania Author
parametrów akcji.
Poniższa AuthorEntityBinder
klasa wiąże Author
parametr przez pobranie jednostki ze źródła danych przy użyciu platformy Entity Framework Core i klasy 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;
}
}
Uwaga
Poprzednia AuthorEntityBinder
klasa ma na celu zilustrowanie niestandardowego powiązania modelu. Klasa nie ma na celu zilustrowania najlepszych rozwiązań dla scenariusza wyszukiwania. Aby wyszukać, powiąż authorId
bazę danych i wykonaj zapytanie względem jej w metodzie akcji. Takie podejście oddziela błędy powiązań modelu od NotFound
przypadków.
Poniższy kod pokazuje, jak używać AuthorEntityBinder
metody w metodzie akcji:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Atrybut ModelBinder
może służyć do stosowania AuthorEntityBinder
parametrów, które nie używają konwencji domyślnych:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
W tym przykładzie, ponieważ nazwa argumentu nie jest wartością domyślną authorId
, jest określona na parametrze przy użyciu atrybutu ModelBinder
. Zarówno kontroler, jak i metoda akcji są uproszczone w porównaniu z wyszukiwaniem jednostki w metodzie akcji. Logika pobierania autora przy użyciu programu Entity Framework Core jest przenoszona do powiązania modelu. Może to być znaczne uproszczenie, gdy istnieje kilka metod, które wiążą się z modelem Author
.
Atrybut można zastosować ModelBinder
do poszczególnych właściwości modelu (takich jak model widoku) lub do parametrów metody akcji w celu określenia określonego powiązania modelu lub nazwy modelu tylko dla tego typu lub akcji.
Implementowanie obiektu ModelBinderProvider
Zamiast stosować atrybut, można zaimplementować IModelBinderProvider
element . W ten sposób są implementowane wbudowane powiązania struktury. Po określeniu typu, na którym działa binder, należy określić typ argumentu, który generuje, a nie dane wejściowe, które akceptuje binder. Następujący dostawca binder współpracuje z programem AuthorEntityBinder
. Po dodaniu do kolekcji dostawców MVC nie trzeba używać atrybutu ModelBinder
w parametrach Author
lub 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;
}
}
}
Uwaga: Powyższy kod zwraca wartość
BinderTypeModelBinder
.BinderTypeModelBinder
działa jako fabryka powiązań modelu i zapewnia wstrzykiwanie zależności (DI). WymagaAuthorEntityBinder
, aby di uzyskać dostęp do EF Core. UżyjBinderTypeModelBinder
, jeśli powiązanie modelu wymaga usług z di.
Aby użyć niestandardowego dostawcy powiązania modelu, dodaj go w pliku 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);
}
Podczas oceniania powiązań modelu kolekcja dostawców jest badana w kolejności. Pierwszy dostawca, który zwraca powiązanie, jest używany. Dodanie dostawcy na końcu kolekcji może spowodować wywołanie wbudowanego powiązania modelu, zanim niestandardowy binder będzie miał szansę. W tym przykładzie dostawca niestandardowy jest dodawany na początku kolekcji, aby upewnić się, że jest używany do Author
argumentów akcji.
Powiązanie modelu polimorficznego
Powiązanie z różnymi modelami typów pochodnych jest nazywane powiązaniem modelu polimorficznego. Powiązanie modelu niestandardowego polimorficznego jest wymagane, gdy wartość żądania musi być powiązana z określonym typem modelu pochodnego. Powiązanie modelu polimorficznego:
- Nie jest typowe dla interfejsu REST API, który jest przeznaczony do współdziałania ze wszystkimi językami.
- Utrudnia rozumowanie o powiązanych modelach.
Jeśli jednak aplikacja wymaga powiązania modelu polimorficznego, implementacja może wyglądać podobnie do następującego kodu:
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,
};
}
}
}
Zalecenia i najlepsze rozwiązania
Niestandardowe powiązania modelu:
- Nie należy próbować ustawiać kodów stanu ani zwracać wyników (na przykład 404 Nie znaleziono). Jeśli powiązanie modelu nie powiedzie się, filtr akcji lub logika w samej metodzie akcji powinien obsłużyć błąd.
- Jest to najbardziej przydatne w przypadku eliminowania powtarzających się problemów związanych z kodem i przecięciem ze strony metod akcji.
- Zazwyczaj nie należy używać do konwertowania ciągu na typ niestandardowy, a TypeConverter zazwyczaj jest to lepsza opcja.