Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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ą funkcjonalność wiązania modelu, implementując niestandardowe bindery modelu (choć zwykle nie trzeba pisać własnego dostawcy).
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Ograniczenia domyślnego bindowania 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 ich powiązaniem. Jeśli na przykład masz klucz, który może służyć do wyszukiwania danych modelu. Można użyć niestandardowego powiązania modelu do pobierania danych na podstawie klucza.
Powiązanie modelu z prostymi i złożonymi typami
Powiązanie modelu używa określonych definicji dla typów, na których działa. Prosty typ jest konwertowany z jednego ciągu za pomocą metody TypeConverter lub TryParse. 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 do konwersji z string na SomeType, 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 niestandardowego wiązania modelu, warto zapoznać się z implementacją istniejących wiązań modeli. 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 typ IModelBinderProvider lub użyć ModelBinderAttribute.
W poniższym przykładzie pokazano, jak użyć ByteArrayModelBinder do konwersji ciągu zakodowanego w formacie base64 na byte[] i zapisania wyniku do 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.
Możesz wysłać zakodowany ciąg base64 do poprzedniej metody interfejsu API, używając narzędzia takiego jak curl.
Jeśli binder może powiązać dane żądania z odpowiednio nazwanymi właściwościami lub argumentami, powiązanie modelu zakończy się sukcesem. 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 powiązania modelu niestandardowego
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 skojarzoną encję jako argument do metody akcji.
W poniższym przykładzie użyto atrybutu ModelBinder na 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; }
}
}
W poprzednim kodzie atrybut ModelBinder określa typ IModelBinder, którego należy użyć do wiązania parametrów akcji Author.
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;
}
}
Note
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 i wykonaj zapytanie do bazy danych w metodzie akcji. Takie podejście oddziela błędy powiązań modelu od NotFound przypadków.
Poniższy kod pokazuje, jak używać AuthorEntityBinder 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 ModelBinder można zastosować do poszczególnych właściwości modelu (takich jak viewmodel) lub do parametrów metody akcji, aby ustalić konkretne powiązanie modelu lub nazwę modelu tylko dla tego typu lub akcji.
Implementowanie obiektu ModelBinderProvider
Zamiast stosować atrybut, można zaimplementować IModelBinderProviderelement . 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 dla parametrów typu Author lub Author.
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.BinderTypeModelBinderdziała jako fabryka wiązań modelu i zapewnia wstrzykiwanie zależności (DI).AuthorEntityBinderwymaga DI, aby 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 niestandardowe powiązanie modelu będzie miało szansę. W tym przykładzie na początku kolekcji dodawany jest dostawca niestandardowy, aby zapewnić, że zawsze będzie on używany do argumentów akcji Author.
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 powinna obsłużyć błąd.
- Są najbardziej przydatne do eliminowania powtarzających się fragmentów kodu i ogólnych problemów związanych z metodami 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ą funkcjonalność wiązania modelu, implementując niestandardowe bindery modelu (choć zwykle nie trzeba pisać własnego dostawcy).
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Ograniczenia domyślnego bindowania 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 ich powiązaniem. Jeśli na przykład masz klucz, który może służyć do wyszukiwania danych modelu. Można użyć niestandardowego powiązania modelu do pobierania danych na podstawie klucza.
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 niestandardowego wiązania modelu, warto zapoznać się z implementacją istniejących wiązań modeli. 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 typ IModelBinderProvider lub użyć ModelBinderAttribute.
W poniższym przykładzie pokazano, jak użyć ByteArrayModelBinder do konwersji ciągu zakodowanego w formacie base64 na byte[] i zapisania wyniku do 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);
}
Możesz wysłać zakodowany ciąg base64 do poprzedniej metody interfejsu API, używając narzędzia takiego jak curl.
Jeśli binder może powiązać dane żądania z odpowiednio nazwanymi właściwościami lub argumentami, powiązanie modelu zakończy się sukcesem. 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 powiązania modelu niestandardowego
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 skojarzoną encję jako argument do metody akcji.
W poniższym przykładzie użyto atrybutu ModelBinder na 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; }
}
}
W poprzednim kodzie atrybut ModelBinder określa typ IModelBinder, którego należy użyć do wiązania parametrów akcji Author.
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;
}
}
Note
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 i wykonaj zapytanie do bazy danych w metodzie akcji. Takie podejście oddziela błędy powiązań modelu od NotFound przypadków.
Poniższy kod pokazuje, jak używać AuthorEntityBinder 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 ModelBinder można zastosować do poszczególnych właściwości modelu (takich jak viewmodel) lub do parametrów metody akcji, aby ustalić konkretne powiązanie modelu lub nazwę modelu tylko dla tego typu lub akcji.
Implementowanie obiektu ModelBinderProvider
Zamiast stosować atrybut, można zaimplementować IModelBinderProviderelement . 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 dla parametrów typu Author lub Author.
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.BinderTypeModelBinderdziała jako fabryka wiązań modelu i zapewnia wstrzykiwanie zależności (DI).AuthorEntityBinderwymaga DI, aby 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 powinna obsłużyć błąd.
- Są najbardziej przydatne do eliminowania powtarzających się fragmentów kodu i ogólnych problemów związanych z metodami akcji.
- Zazwyczaj nie należy używać do konwertowania ciągu na typ niestandardowy, a TypeConverter zazwyczaj jest to lepsza opcja.