Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Door Kirk Larkin
Met modelbinding kunnen controlleracties rechtstreeks werken met modeltypen (doorgegeven als methodeargumenten) in plaats van HTTP-aanvragen. De toewijzing van binnenkomende aanvraaggegevens aan toepassingsmodellen wordt afgehandeld door modelbinders. Ontwikkelaars kunnen de ingebouwde modelbindingsfunctionaliteit uitbreiden door aangepaste modelbindingen te implementeren (hoewel u doorgaans niet uw eigen provider hoeft te schrijven).
Voorbeeldcode bekijken of downloaden (hoe download je)
Beperkingen van de standaardmodelbinder
De standaardmodelbindingen ondersteunen de meeste algemene .NET Core-gegevenstypen en moeten voldoen aan de meeste behoeften van ontwikkelaars. Ze verwachten dat invoer op basis van tekst van de aanvraag rechtstreeks aan modeltypen wordt gekoppeld. Mogelijk moet u de invoer transformeren voordat u deze bindt. Als u bijvoorbeeld een sleutel hebt die kan worden gebruikt om modelgegevens op te zoeken. U kunt een aangepaste modelbinding gebruiken om gegevens op te halen op basis van de sleutel.
Eenvoudige en complexe typen modelbinding
Modelbinding maakt gebruik van specifieke definities voor de typen waarop deze werkt. Een eenvoudig type wordt geconverteerd van één tekenreeks met behulp van TypeConverter of een TryParse
methode. Een complex type wordt geconverteerd van meerdere invoerwaarden. Het framework bepaalt het verschil op basis van het bestaan van een TypeConverter
of TryParse
. Wij raden aan om een typeconverter te maken of het gebruik van TryParse
voor een string
naar SomeType
conversie waarvoor geen externe middelen of meerdere invoer nodig zijn.
Zie Eenvoudige typen voor een lijst met typen die door de modelbindinger kunnen worden geconverteerd uit tekenreeksen.
Voordat u uw eigen aangepaste modelbinding maakt, is het de moeite waard om te controleren hoe bestaande modelbindingen worden geïmplementeerd. Overweeg de ByteArrayModelBinder die gebruikt kan worden om base64-gecodeerde tekenreeksen om te zetten in bytes. De bytematrices worden vaak opgeslagen als bestanden of database-BLOB-velden.
Werken met de ByteArrayModelBinder
Met Base64 gecodeerde tekenreeksen kunnen binaire gegevens worden weergegeven. Een afbeelding kan bijvoorbeeld worden gecodeerd als een tekenreeks. Het voorbeeld bevat een afbeelding als een met base64 gecodeerde tekenreeks in Base64String.txt.
ASP.NET Core MVC kan een base64-gecodeerde tekenreeks gebruiken en een ByteArrayModelBinder
tekenreeks gebruiken om deze te converteren naar een bytematrix. De ByteArrayModelBinderProvider koppelt byte[]
argumenten aan 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;
}
Wanneer u een eigen aangepaste modelbinder maakt, kunt u uw eigen IModelBinderProvider
-type implementeren of de ModelBinderAttribute gebruiken.
In het volgende voorbeeld ziet u hoe u met ByteArrayModelBinder
een met base64 gecodeerde tekenreeks converteert naar een byte[]
en het resultaat in een bestand opslaat.
[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);
}
Als u codeopmerkingen wilt zien die zijn vertaald naar andere talen dan Engels, laat het ons dan weten in dit GitHub-discussieprobleem.
U kunt een met base64 gecodeerde tekenreeks posten naar de vorige API-methode met behulp van een hulpprogramma zoals curl.
Zolang de binder aanvraaggegevens kan binden aan de juiste benoemde eigenschappen of argumenten, slaagt de modelbinding. In het volgende voorbeeld ziet u hoe u dit kunt gebruiken ByteArrayModelBinder
met een weergavemodel:
[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; }
}
Voorbeeld van aangepaste modelbinder
In deze sectie implementeren we een aangepaste modelbinding die:
- Converteert binnenkomende aanvraaggegevens naar sterk getypte sleutelargumenten.
- Maakt gebruik van Entity Framework Core om de gekoppelde entiteit op te halen.
- Geeft de gekoppelde entiteit als argument door aan de actiemethode.
In het volgende voorbeeld wordt het ModelBinder
kenmerk van het Author
model gebruikt:
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; }
}
}
In de voorgaande code geeft het ModelBinder
kenmerk het type IModelBinder
op dat moet worden gebruikt om Author
actieparameters te binden.
Met de volgende AuthorEntityBinder
klasse wordt een Author
parameter gekoppeld door de entiteit op te halen uit een gegevensbron met behulp van Entity Framework Core en een 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
De voorgaande AuthorEntityBinder
klasse is bedoeld om een aangepaste modelbinding te illustreren. De klasse is niet bedoeld om best practices voor een opzoekscenario te illustreren. Bind de database in een actiemethode om de authorId
database op te zoeken en er query's op uit te voeren. Met deze methode worden modelbindingsfouten gescheiden van NotFound
gevallen.
De volgende code laat zien hoe u de AuthorEntityBinder
in een actiemethode gebruikt:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Het ModelBinder
kenmerk kan worden gebruikt om de AuthorEntityBinder
parameters toe te passen die geen standaardconventies gebruiken:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Omdat in dit voorbeeld de naam van het argument niet de standaardwaarde authorId
is, wordt deze opgegeven voor de parameter met behulp van het ModelBinder
kenmerk. Zowel de controller als de actiemethode worden vereenvoudigd vergeleken met het opzoeken van de entiteit in de actiemethode. De logica voor het ophalen van de auteur met behulp van Entity Framework Core wordt verplaatst naar de modelbinder. Dit kan een aanzienlijke vereenvoudiging zijn wanneer u verschillende methoden hebt die aan het Author
model binden.
U kunt het ModelBinder
kenmerk toepassen op afzonderlijke modeleigenschappen (zoals op een viewmodel) of op parameters voor actiemethoden om een bepaalde modelbinding of modelnaam op te geven voor alleen dat type of de actie.
Een ModelBinderProvider implementeren
In plaats van een kenmerk toe te passen, kunt u implementeren IModelBinderProvider
. Dit is hoe de ingebouwde framework-binders worden geïmplementeerd. Wanneer u het type opgeeft waarop de binder werkt, geeft u het type argument op dat het produceert, niet de invoer die uw binder accepteert. De volgende binderleverancier werkt met de AuthorEntityBinder
. Wanneer deze wordt toegevoegd aan de verzameling providers van MVC, hoeft u het ModelBinder
kenmerk niet te gebruiken voor Author
of Author
-getypte parameters.
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;
}
}
}
Opmerking: De voorgaande code retourneert een
BinderTypeModelBinder
.BinderTypeModelBinder
fungeert als een fabriek voor modelbindingen en biedt afhankelijkheidsinjectie (DI). DeAuthorEntityBinder
vereist DI voor toegang tot EF Core. GebruikBinderTypeModelBinder
als uw modelbinder services van DI vereist.
Als u een aangepaste modelbindingsprovider wilt gebruiken, voegt u deze toe in ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
Bij het evalueren van modelbindingen wordt de verzameling providers op volgorde onderzocht. De eerste provider die een binder retourneert die overeenkomt met het invoermodel, wordt gebruikt. Als u uw provider aan het einde van de verzameling toevoegt, kan dit ertoe leiden dat een ingebouwde modelbinder wordt aangeroepen voordat uw aangepaste binder de kans krijgt. In dit voorbeeld wordt de aangepaste provider toegevoegd aan het begin van de verzameling om ervoor te zorgen dat deze altijd wordt gebruikt voor Author
actieargumenten.
Polymorfe modelbinding
Binding met verschillende modellen van afgeleide typen wordt polymorf modelbinding genoemd. Polymorf aangepaste modelbinding is vereist wanneer de aanvraagwaarde moet worden gebonden aan het specifieke afgeleide modeltype. Polymorfe model-binding
- Dit is niet gebruikelijk voor een REST API die is ontworpen voor samenwerking met alle talen.
- Maakt het moeilijk om redeneren over de afhankelijke modellen.
Als voor een app echter polymorfe modelbinding is vereist, kan een implementatie eruitzien als de volgende code:
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,
};
}
}
}
Aanbevelingen en best practices
Modelbinders op maat:
- Probeer geen statuscodes in te stellen of resultaten te retourneren (bijvoorbeeld 404 Niet gevonden). Als modelbinding mislukt, moet een actiefilter of logica binnen de actiemethode zelf de fout afhandelen.
- Zijn het handigst voor het elimineren van terugkerende code en kruislingse problemen van actiemethoden.
- Gewoonlijk moet een tekenreeks niet worden gebruikt om naar een aangepast type te converteren; een TypeConverter is meestal een betere optie.
Door Steve Smith
Met modelbinding kunnen controlleracties rechtstreeks werken met modeltypen (doorgegeven als methodeargumenten) in plaats van HTTP-aanvragen. De toewijzing van binnenkomende aanvraaggegevens aan toepassingsmodellen wordt afgehandeld door modelbinders. Ontwikkelaars kunnen de ingebouwde modelbindingsfunctionaliteit uitbreiden door aangepaste modelbindingen te implementeren (hoewel u doorgaans niet uw eigen provider hoeft te schrijven).
Voorbeeldcode bekijken of downloaden (hoe download je)
Beperkingen van de standaardmodelbinder
De standaardmodelbindingen ondersteunen de meeste algemene .NET Core-gegevenstypen en moeten voldoen aan de meeste behoeften van ontwikkelaars. Ze verwachten dat invoer op basis van tekst van de aanvraag rechtstreeks aan modeltypen wordt gekoppeld. Mogelijk moet u de invoer transformeren voordat u deze bindt. Als u bijvoorbeeld een sleutel hebt die kan worden gebruikt om modelgegevens op te zoeken. U kunt een aangepaste modelbinding gebruiken om gegevens op te halen op basis van de sleutel.
Beoordeling van modelbinding
Modelbinding maakt gebruik van specifieke definities voor de typen waarop deze werkt. Een eenvoudig type wordt geconverteerd van één tekenreeks in de invoer. Een complex type wordt geconverteerd van meerdere invoerwaarden. Het framework bepaalt het verschil op basis van het bestaan van een TypeConverter
. U wordt aangeraden een typeconversieprogramma te maken als u een eenvoudige string
>SomeType
toewijzing hebt waarvoor geen externe resources nodig zijn.
Voordat u uw eigen aangepaste modelbinding maakt, is het de moeite waard om te controleren hoe bestaande modelbindingen worden geïmplementeerd. Overweeg de ByteArrayModelBinder die gebruikt kan worden om base64-gecodeerde tekenreeksen om te zetten in bytes. De bytematrices worden vaak opgeslagen als bestanden of database-BLOB-velden.
Werken met de ByteArrayModelBinder
Met Base64 gecodeerde tekenreeksen kunnen binaire gegevens worden weergegeven. Een afbeelding kan bijvoorbeeld worden gecodeerd als een tekenreeks. Het voorbeeld bevat een afbeelding als een met base64 gecodeerde tekenreeks in Base64String.txt.
ASP.NET Core MVC kan een base64-gecodeerde tekenreeks gebruiken en een ByteArrayModelBinder
tekenreeks gebruiken om deze te converteren naar een bytematrix. De ByteArrayModelBinderProvider koppelt byte[]
argumenten aan 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;
}
Wanneer u een eigen aangepaste modelbinder maakt, kunt u uw eigen IModelBinderProvider
-type implementeren of de ModelBinderAttribute gebruiken.
In het volgende voorbeeld ziet u hoe u met ByteArrayModelBinder
een met base64 gecodeerde tekenreeks converteert naar een byte[]
en het resultaat in een bestand opslaat.
[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);
}
U kunt een met base64 gecodeerde tekenreeks posten naar de vorige API-methode met behulp van een hulpprogramma zoals curl.
Zolang de binder aanvraaggegevens kan binden aan de juiste benoemde eigenschappen of argumenten, slaagt de modelbinding. In het volgende voorbeeld ziet u hoe u dit kunt gebruiken ByteArrayModelBinder
met een weergavemodel:
[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; }
}
Voorbeeld van aangepaste modelbinder
In deze sectie implementeren we een aangepaste modelbinding die:
- Converteert binnenkomende aanvraaggegevens naar sterk getypte sleutelargumenten.
- Maakt gebruik van Entity Framework Core om de gekoppelde entiteit op te halen.
- Geeft de gekoppelde entiteit als argument door aan de actiemethode.
In het volgende voorbeeld wordt het ModelBinder
kenmerk van het Author
model gebruikt:
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; }
}
}
In de voorgaande code geeft het ModelBinder
kenmerk het type IModelBinder
op dat moet worden gebruikt om Author
actieparameters te binden.
Met de volgende AuthorEntityBinder
klasse wordt een Author
parameter gekoppeld door de entiteit op te halen uit een gegevensbron met behulp van Entity Framework Core en een 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
De voorgaande AuthorEntityBinder
klasse is bedoeld om een aangepaste modelbinding te illustreren. De klasse is niet bedoeld om best practices voor een opzoekscenario te illustreren. Bind de database in een actiemethode om de authorId
database op te zoeken en er query's op uit te voeren. Met deze methode worden modelbindingsfouten gescheiden van NotFound
gevallen.
De volgende code laat zien hoe u de AuthorEntityBinder
in een actiemethode gebruikt:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Het ModelBinder
kenmerk kan worden gebruikt om de AuthorEntityBinder
parameters toe te passen die geen standaardconventies gebruiken:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Omdat in dit voorbeeld de naam van het argument niet de standaardwaarde authorId
is, wordt deze opgegeven voor de parameter met behulp van het ModelBinder
kenmerk. Zowel de controller als de actiemethode worden vereenvoudigd vergeleken met het opzoeken van de entiteit in de actiemethode. De logica voor het ophalen van de auteur met behulp van Entity Framework Core wordt verplaatst naar de modelbinder. Dit kan een aanzienlijke vereenvoudiging zijn wanneer u verschillende methoden hebt die aan het Author
model binden.
U kunt het ModelBinder
kenmerk toepassen op afzonderlijke modeleigenschappen (zoals op een viewmodel) of op parameters voor actiemethoden om een bepaalde modelbinding of modelnaam op te geven voor alleen dat type of de actie.
Een ModelBinderProvider implementeren
In plaats van een kenmerk toe te passen, kunt u implementeren IModelBinderProvider
. Dit is hoe de ingebouwde framework-binders worden geïmplementeerd. Wanneer u het type opgeeft waarop de binder werkt, geeft u het type argument op dat het produceert, niet de invoer die uw binder accepteert. De volgende binderleverancier werkt met de AuthorEntityBinder
. Wanneer deze wordt toegevoegd aan de verzameling providers van MVC, hoeft u het ModelBinder
kenmerk niet te gebruiken voor Author
of Author
-getypte parameters.
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;
}
}
}
Opmerking: De voorgaande code retourneert een
BinderTypeModelBinder
.BinderTypeModelBinder
fungeert als een fabriek voor modelbindingen en biedt afhankelijkheidsinjectie (DI). DeAuthorEntityBinder
vereist DI voor toegang tot EF Core. GebruikBinderTypeModelBinder
als uw modelbinder services van DI vereist.
Als u een aangepaste modelbindingsprovider wilt gebruiken, voegt u deze toe in 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);
}
Bij het evalueren van modelbindingen wordt de verzameling providers op volgorde onderzocht. De eerste provider die een binder retourneert, wordt gebruikt. Als u uw provider toevoegt aan het einde van de verzameling, kan dit ertoe leiden dat er een ingebouwde modelbinding wordt aangeroepen voordat uw aangepaste binder een kans heeft. In dit voorbeeld wordt de aangepaste provider toegevoegd aan het begin van de verzameling om ervoor te zorgen dat deze wordt gebruikt voor Author
actieargumenten.
Polymorfe modelbinding
Binding met verschillende modellen van afgeleide typen wordt polymorf modelbinding genoemd. Polymorf aangepaste modelbinding is vereist wanneer de aanvraagwaarde moet worden gebonden aan het specifieke afgeleide modeltype. Polymorfe model-binding
- Dit is niet gebruikelijk voor een REST API die is ontworpen voor samenwerking met alle talen.
- Maakt het moeilijk om redeneren over de afhankelijke modellen.
Als voor een app echter polymorfe modelbinding is vereist, kan een implementatie eruitzien als de volgende code:
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,
};
}
}
}
Aanbevelingen en best practices
Modelbinders op maat:
- Probeer geen statuscodes in te stellen of resultaten te retourneren (bijvoorbeeld 404 Niet gevonden). Als modelbinding mislukt, moet een actiefilter of logica binnen de actiemethode zelf de fout afhandelen.
- Zijn het handigst voor het elimineren van terugkerende code en kruislingse problemen van actiemethoden.
- Gewoonlijk moet een tekenreeks niet worden gebruikt om naar een aangepast type te converteren; een TypeConverter is meestal een betere optie.