Delen via


Aangepaste modelbinding in ASP.NET Core

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 authorIdis, 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). De AuthorEntityBinder vereist DI voor toegang tot EF Core. Gebruik BinderTypeModelBinder 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 authorIdis, 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). De AuthorEntityBinder vereist DI voor toegang tot EF Core. Gebruik BinderTypeModelBinder 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.