Vazba vlastního modelu v ASP.NET Core

Od Kirk Larkin

Vazba modelu umožňuje, aby akce kontroleru fungovaly přímo s typy modelů (předané jako argumenty metody) místo požadavků HTTP. Mapování mezi příchozími daty požadavků a aplikačními modely se zpracovává pomocí pořadačů modelů. Vývojáři můžou rozšířit integrovanou funkci vazby modelu implementací vlastních pořadačů modelů (obvykle ale nemusíte psát vlastního poskytovatele).

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Omezení výchozího pořadače modelu

Výchozí pořadače modelů podporují většinu běžných datových typů .NET Core a měly by splňovat potřeby většiny vývojářů. Očekávají vazbu textového vstupu z požadavku přímo na typy modelů. Před vazbou může být nutné transformovat vstup. Pokud máte například klíč, který se dá použít k vyhledání dat modelu. K načtení dat na základě klíče můžete použít vlastní pořadač modelů.

Jednoduché a komplexní typy vazby modelu

Vazba modelu používá pro typy, na kterých pracuje, konkrétní definice. Jednoduchý typ je převeden z jednoho řetězce pomocí TypeConverter nebo TryParse metody. Komplexní typ je převeden z více vstupních hodnot. Rámec určuje rozdíl na základě existence nebo TypeConverterTryParse. Doporučujeme vytvořit převaděč typů nebo použít TryParse převod stringSomeType , který nevyžaduje externí prostředky nebo více vstupů.

Seznam typů, které může pořadač modelů převést z řetězců, najdete v části Jednoduché typy .

Před vytvořením vlastního pořadače modelu je vhodné zkontrolovat, jak se implementují existující pořadače modelů. Představte si, které lze použít k převodu ByteArrayModelBinder řetězců s kódováním base64 na bajtová pole. Pole bajtů se často ukládají jako soubory nebo pole objektů blob databáze.

Práce s ByteArrayModelBinder

Řetězce s kódováním Base64 lze použít k reprezentaci binárních dat. Obrázek lze například zakódovat jako řetězec. Ukázka obsahuje obrázek jako řetězec kódovaný v base64 v Base64String.txt.

ASP.NET Core MVC může převést řetězec s kódováním base64 a použít ByteArrayModelBinder ho k jeho převodu na bajtové pole. Argumenty ByteArrayModelBinderProvider mapuje byte[] na ByteArrayModelBinder:

public IModelBinder GetBinder(ModelBinderProviderContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }

    if (context.Metadata.ModelType == typeof(byte[]))
    {
        var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
        return new ByteArrayModelBinder(loggerFactory);
    }

    return null;
}

Při vytváření vlastního pořadače modelů můžete implementovat vlastní IModelBinderProvider typ nebo použít ModelBinderAttribute.

Následující příklad ukazuje, jak použít ByteArrayModelBinder k převodu řetězce s kódováním base64 na byte[] a uložení výsledku do souboru:

[HttpPost]
public void Post([FromForm] byte[] file, string filename)
{
    // Don't trust the file name sent by the client. Use
    // Path.GetRandomFileName to generate a safe random
    // file name. _targetFilePath receives a value
    // from configuration (the appsettings.json file in
    // the sample app).
    var trustedFileName = Path.GetRandomFileName();
    var filePath = Path.Combine(_targetFilePath, trustedFileName);

    if (System.IO.File.Exists(filePath))
    {
        return;
    }

    System.IO.File.WriteAllBytes(filePath, file);
}

Pokud chcete zobrazit komentáře ke kódu přeložené do jiných jazyků, než je angličtina, dejte nám vědět v této diskuzi na GitHubu.

Řetězec s kódováním base64 můžete postovat na předchozí metodu rozhraní API pomocí nástroje, jako je curl.

Pokud binder může vytvořit vazbu dat požadavku na správně pojmenované vlastnosti nebo argumenty, vazba modelu bude úspěšná. Následující příklad ukazuje, jak se používá ByteArrayModelBinder s modelem zobrazení:

[HttpPost("Profile")]
public void SaveProfile([FromForm] ProfileViewModel model)
{
    // Don't trust the file name sent by the client. Use
    // Path.GetRandomFileName to generate a safe random
    // file name. _targetFilePath receives a value
    // from configuration (the appsettings.json file in
    // the sample app).
    var trustedFileName = Path.GetRandomFileName();
    var filePath = Path.Combine(_targetFilePath, trustedFileName);

    if (System.IO.File.Exists(filePath))
    {
        return;
    }

    System.IO.File.WriteAllBytes(filePath, model.File);
}

public class ProfileViewModel
{
    public byte[] File { get; set; }
    public string FileName { get; set; }
}

Ukázka pořadače vlastního modelu

V této části implementujeme vlastní pořadač modelů, který:

  • Převede příchozí data požadavku na klíčové argumenty silného typu.
  • Používá Entity Framework Core k načtení přidružené entity.
  • Předá přidruženou entitu jako argument metodě akce.

Následující ukázka používá ModelBinder atribut modelu Author :

using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;

namespace CustomModelBindingSample.Data
{
    [ModelBinder(BinderType = typeof(AuthorEntityBinder))]
    public class Author
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string GitHub { get; set; }
        public string Twitter { get; set; }
        public string BlogUrl { get; set; }
    }
}

V předchozím kódu atribut určuje typIModelBinder, ModelBinder který se má použít k vytvoření vazby Author parametrů akce.

Následující AuthorEntityBinder třída sváže Author parametr načtením entity ze zdroje dat pomocí Entity Framework Core a:authorId

public class AuthorEntityBinder : IModelBinder
{
    private readonly AuthorContext _context;

    public AuthorEntityBinder(AuthorContext context)
    {
        _context = context;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var modelName = bindingContext.ModelName;

        // Try to fetch the value of the argument by name
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;

        // Check if the argument value is null or empty
        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        if (!int.TryParse(value, out var id))
        {
            // Non-integer arguments result in model state errors
            bindingContext.ModelState.TryAddModelError(
                modelName, "Author Id must be an integer.");

            return Task.CompletedTask;
        }

        // Model will be null if not found, including for
        // out of range id values (0, -3, etc.)
        var model = _context.Authors.Find(id);
        bindingContext.Result = ModelBindingResult.Success(model);
        return Task.CompletedTask;
    }
}

Poznámka:

AuthorEntityBinder Předchozí třída je určena k ilustraci vlastního pořadače modelu. Třída není určená k ilustraci osvědčených postupů pro scénář vyhledávání. Pro vyhledávání vytvořte vazbu authorId databáze a dotazování v metodě akce. Tento přístup odděluje chyby vazby modelu od NotFound případů.

Následující kód ukazuje, jak použít metodu AuthorEntityBinder akce:

[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

Atribut ModelBinder lze použít k použití AuthorEntityBinder parametrů, které nepoužívají výchozí konvence:

[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

V tomto příkladu je vzhledem k tomu, že název argumentu není výchozí authorId, je zadaný u parametru pomocí atributu ModelBinder . Kontroler i metoda akce jsou ve srovnání s vyhledáním entity v metodě akce zjednodušené. Logika pro načtení autora pomocí Entity Framework Core se přesune do pořadače modelu. To může být značné zjednodušení, pokud máte několik metod, které se sváže s modelem Author .

Atribut můžete použít ModelBinder na jednotlivé vlastnosti modelu (například na modelu viewmodel) nebo na parametry metody akce, které určují určitý pořadač modelu nebo název modelu pouze pro daný typ nebo akci.

Implementace ModelBinderProvider

Místo použití atributu můžete implementovat IModelBinderProvider. Takto se implementují integrované pořadače architektury. Když zadáte typ, se který pořadač pracuje, zadáte typ argumentu, který vytvoří, nikoli vstup, který váš pořadač přijme. Následující zprostředkovatel pořadače pracuje s zprostředkovatelem AuthorEntityBinder. Když se přidá do kolekce zprostředkovatelů MVC, nemusíte atribut používat ModelBinder u Author parametrů nebo Authorparametr -typed.

using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;

namespace CustomModelBindingSample.Binders
{
    public class AuthorEntityBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.Metadata.ModelType == typeof(Author))
            {
                return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
            }

            return null;
        }
    }
}

Poznámka: Předchozí kód vrátí znak .BinderTypeModelBinder BinderTypeModelBinder funguje jako továrna pro pořadače modelů a poskytuje injektáž závislostí (DI). Vyžaduje AuthorEntityBinder přístup k EF Coredinátu . Použijte BinderTypeModelBinder , pokud váš pořadač modelů vyžaduje služby z DI.

Pokud chcete použít vlastního zprostředkovatele pořadače modelů, přidejte ho do ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));

    services.AddControllers(options =>
    {
        options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
    });
}

Při vyhodnocování pořadačů modelů se kolekce zprostředkovatelů zkoumá v pořadí. Použije se první zprostředkovatel, který vrátí pořadač, který odpovídá vstupnímu modelu. Přidání zprostředkovatele na konec kolekce může vést k tomu, že se volá předdefinovaný pořadač modelů, než bude mít váš vlastní pořadač šanci. V tomto příkladu se vlastní zprostředkovatel přidá na začátek kolekce, aby se zajistilo, že se vždy používá pro Author argumenty akce.

Polymorfní vazba modelu

Vazba na různé modely odvozených typů se označuje jako polymorfní vazba modelu. Polymorfní vlastní vazba modelu je vyžadována, pokud musí být hodnota požadavku vázána na konkrétní odvozený typ modelu. Polymorfní vazba modelu:

  • Není typické pro REST rozhraní API, které je navržené pro spolupráci se všemi jazyky.
  • Znesnadňuje zdůvodnění vázaných modelů.

Pokud ale aplikace vyžaduje vazbu polymorfních modelů, implementace může vypadat jako následující kód:

public abstract class Device
{
    public string Kind { get; set; }
}

public class Laptop : Device
{
    public string CPUIndex { get; set; }
}

public class SmartPhone : Device
{
    public string ScreenSize { get; set; }
}

public class DeviceModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType != typeof(Device))
        {
            return null;
        }

        var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };

        var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
        foreach (var type in subclasses)
        {
            var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
            binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
        }

        return new DeviceModelBinder(binders);
    }
}

public class DeviceModelBinder : IModelBinder
{
    private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;

    public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
    {
        this.binders = binders;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
        var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;

        IModelBinder modelBinder;
        ModelMetadata modelMetadata;
        if (modelTypeValue == "Laptop")
        {
            (modelMetadata, modelBinder) = binders[typeof(Laptop)];
        }
        else if (modelTypeValue == "SmartPhone")
        {
            (modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
        }
        else
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return;
        }

        var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
            bindingContext.ActionContext,
            bindingContext.ValueProvider,
            modelMetadata,
            bindingInfo: null,
            bindingContext.ModelName);

        await modelBinder.BindModelAsync(newBindingContext);
        bindingContext.Result = newBindingContext.Result;

        if (newBindingContext.Result.IsModelSet)
        {
            // Setting the ValidationState ensures properties on derived types are correctly 
            bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
            {
                Metadata = modelMetadata,
            };
        }
    }
}

Doporučení a osvědčené postupy

Vlastní pořadače modelů:

  • Neměli byste se pokoušet nastavit stavové kódy nebo vrátit výsledky (například 404 Nenalezena). Pokud vazba modelu selže, měl by se selháním zpracovat filtr akcí nebo logika v rámci samotné metody akce.
  • Jsou nejužitečnější pro odstranění opakujících se kódu a průřezových obav z metod akcí.
  • Obvykle by se nemělo používat k převodu řetězce na vlastní typ, TypeConverter obvykle je lepší volbou.

Autor: Steve Smith

Vazba modelu umožňuje, aby akce kontroleru fungovaly přímo s typy modelů (předané jako argumenty metody) místo požadavků HTTP. Mapování mezi příchozími daty požadavků a aplikačními modely se zpracovává pomocí pořadačů modelů. Vývojáři můžou rozšířit integrovanou funkci vazby modelu implementací vlastních pořadačů modelů (obvykle ale nemusíte psát vlastního poskytovatele).

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Omezení výchozího pořadače modelu

Výchozí pořadače modelů podporují většinu běžných datových typů .NET Core a měly by splňovat potřeby většiny vývojářů. Očekávají vazbu textového vstupu z požadavku přímo na typy modelů. Před vazbou může být nutné transformovat vstup. Pokud máte například klíč, který se dá použít k vyhledání dat modelu. K načtení dat na základě klíče můžete použít vlastní pořadač modelů.

Kontrola vazby modelu

Vazba modelu používá pro typy, na kterých pracuje, konkrétní definice. Jednoduchý typ je převeden z jednoho řetězce ve vstupu. Komplexní typ je převeden z více vstupních hodnot. Rámec určuje rozdíl na základě existence TypeConverter. Pokud máte jednoduchý string>SomeType mapování, které nevyžaduje externí prostředky, doporučujeme vytvořit převaděč typů.

Před vytvořením vlastního pořadače modelu je vhodné zkontrolovat, jak se implementují existující pořadače modelů. Představte si, které lze použít k převodu ByteArrayModelBinder řetězců s kódováním base64 na bajtová pole. Pole bajtů se často ukládají jako soubory nebo pole objektů blob databáze.

Práce s ByteArrayModelBinder

Řetězce s kódováním Base64 lze použít k reprezentaci binárních dat. Obrázek lze například zakódovat jako řetězec. Ukázka obsahuje obrázek jako řetězec kódovaný v base64 v Base64String.txt.

ASP.NET Core MVC může převést řetězec s kódováním base64 a použít ByteArrayModelBinder ho k jeho převodu na bajtové pole. Argumenty ByteArrayModelBinderProvider mapuje byte[] na ByteArrayModelBinder:

public IModelBinder GetBinder(ModelBinderProviderContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }

    if (context.Metadata.ModelType == typeof(byte[]))
    {
        return new ByteArrayModelBinder();
    }

    return null;
}

Při vytváření vlastního pořadače modelů můžete implementovat vlastní IModelBinderProvider typ nebo použít ModelBinderAttribute.

Následující příklad ukazuje, jak použít ByteArrayModelBinder k převodu řetězce s kódováním base64 na byte[] a uložení výsledku do souboru:

[HttpPost]
public void Post([FromForm] byte[] file, string filename)
{
    // Don't trust the file name sent by the client. Use
    // Path.GetRandomFileName to generate a safe random
    // file name. _targetFilePath receives a value
    // from configuration (the appsettings.json file in
    // the sample app).
    var trustedFileName = Path.GetRandomFileName();
    var filePath = Path.Combine(_targetFilePath, trustedFileName);

    if (System.IO.File.Exists(filePath))
    {
        return;
    }

    System.IO.File.WriteAllBytes(filePath, file);
}

Řetězec s kódováním base64 můžete postovat na předchozí metodu rozhraní API pomocí nástroje, jako je curl.

Pokud binder může vytvořit vazbu dat požadavku na správně pojmenované vlastnosti nebo argumenty, vazba modelu bude úspěšná. Následující příklad ukazuje, jak se používá ByteArrayModelBinder s modelem zobrazení:

[HttpPost("Profile")]
public void SaveProfile([FromForm] ProfileViewModel model)
{
    // Don't trust the file name sent by the client. Use
    // Path.GetRandomFileName to generate a safe random
    // file name. _targetFilePath receives a value
    // from configuration (the appsettings.json file in
    // the sample app).
    var trustedFileName = Path.GetRandomFileName();
    var filePath = Path.Combine(_targetFilePath, trustedFileName);

    if (System.IO.File.Exists(filePath))
    {
        return;
    }

    System.IO.File.WriteAllBytes(filePath, model.File);
}

public class ProfileViewModel
{
    public byte[] File { get; set; }
    public string FileName { get; set; }
}

Ukázka pořadače vlastního modelu

V této části implementujeme vlastní pořadač modelů, který:

  • Převede příchozí data požadavku na klíčové argumenty silného typu.
  • Používá Entity Framework Core k načtení přidružené entity.
  • Předá přidruženou entitu jako argument metodě akce.

Následující ukázka používá ModelBinder atribut modelu Author :

using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;

namespace CustomModelBindingSample.Data
{
    [ModelBinder(BinderType = typeof(AuthorEntityBinder))]
    public class Author
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string GitHub { get; set; }
        public string Twitter { get; set; }
        public string BlogUrl { get; set; }
    }
}

V předchozím kódu atribut určuje typIModelBinder, ModelBinder který se má použít k vytvoření vazby Author parametrů akce.

Následující AuthorEntityBinder třída sváže Author parametr načtením entity ze zdroje dat pomocí Entity Framework Core a:authorId

public class AuthorEntityBinder : IModelBinder
{
    private readonly AppDbContext _db;

    public AuthorEntityBinder(AppDbContext db)
    {
        _db = db;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var modelName = bindingContext.ModelName;

        // Try to fetch the value of the argument by name
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;

        // Check if the argument value is null or empty
        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        if (!int.TryParse(value, out var id))
        {
            // Non-integer arguments result in model state errors
            bindingContext.ModelState.TryAddModelError(
                modelName, "Author Id must be an integer.");

            return Task.CompletedTask;
        }

        // Model will be null if not found, including for 
        // out of range id values (0, -3, etc.)
        var model = _db.Authors.Find(id);
        bindingContext.Result = ModelBindingResult.Success(model);
        return Task.CompletedTask;
    }
}

Poznámka:

AuthorEntityBinder Předchozí třída je určena k ilustraci vlastního pořadače modelu. Třída není určená k ilustraci osvědčených postupů pro scénář vyhledávání. Pro vyhledávání vytvořte vazbu authorId databáze a dotazování v metodě akce. Tento přístup odděluje chyby vazby modelu od NotFound případů.

Následující kód ukazuje, jak použít metodu AuthorEntityBinder akce:

[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
    if (author == null)
    {
        return NotFound();
    }
    
    return Ok(author);
}

Atribut ModelBinder lze použít k použití AuthorEntityBinder parametrů, které nepoužívají výchozí konvence:

[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

V tomto příkladu je vzhledem k tomu, že název argumentu není výchozí authorId, je zadaný u parametru pomocí atributu ModelBinder . Kontroler i metoda akce jsou ve srovnání s vyhledáním entity v metodě akce zjednodušené. Logika pro načtení autora pomocí Entity Framework Core se přesune do pořadače modelu. To může být značné zjednodušení, pokud máte několik metod, které se sváže s modelem Author .

Atribut můžete použít ModelBinder na jednotlivé vlastnosti modelu (například na modelu viewmodel) nebo na parametry metody akce, které určují určitý pořadač modelu nebo název modelu pouze pro daný typ nebo akci.

Implementace ModelBinderProvider

Místo použití atributu můžete implementovat IModelBinderProvider. Takto se implementují integrované pořadače architektury. Když zadáte typ, se který pořadač pracuje, zadáte typ argumentu, který vytvoří, nikoli vstup, který váš pořadač přijme. Následující zprostředkovatel pořadače pracuje s zprostředkovatelem AuthorEntityBinder. Když se přidá do kolekce zprostředkovatelů MVC, nemusíte atribut používat ModelBinder u Author parametrů nebo Authorparametr -typed.

using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;

namespace CustomModelBindingSample.Binders
{
    public class AuthorEntityBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.Metadata.ModelType == typeof(Author))
            {
                return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
            }

            return null;
        }
    }
}

Poznámka: Předchozí kód vrátí znak .BinderTypeModelBinder BinderTypeModelBinder funguje jako továrna pro pořadače modelů a poskytuje injektáž závislostí (DI). Vyžaduje AuthorEntityBinder přístup k EF Coredinátu . Použijte BinderTypeModelBinder , pokud váš pořadač modelů vyžaduje služby z DI.

Pokud chcete použít vlastního zprostředkovatele pořadače modelů, přidejte ho do ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("App"));

    services.AddMvc(options =>
        {
            // add custom binder to beginning of collection
            options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

Při vyhodnocování pořadačů modelů se kolekce zprostředkovatelů zkoumá v pořadí. Použije se první zprostředkovatel, který vrací pořadač. Přidání zprostředkovatele na konec kolekce může vést k tomu, že se volá předdefinovaný pořadač modelů, než bude mít váš vlastní pořadač šanci. V tomto příkladu se vlastní zprostředkovatel přidá na začátek kolekce, aby se zajistilo, že se používá pro Author argumenty akce.

Polymorfní vazba modelu

Vazba na různé modely odvozených typů se označuje jako polymorfní vazba modelu. Polymorfní vlastní vazba modelu je vyžadována, pokud musí být hodnota požadavku vázána na konkrétní odvozený typ modelu. Polymorfní vazba modelu:

  • Není typické pro REST rozhraní API, které je navržené pro spolupráci se všemi jazyky.
  • Znesnadňuje zdůvodnění vázaných modelů.

Pokud ale aplikace vyžaduje vazbu polymorfních modelů, implementace může vypadat jako následující kód:

public abstract class Device
{
    public string Kind { get; set; }
}

public class Laptop : Device
{
    public string CPUIndex { get; set; }
}

public class SmartPhone : Device
{
    public string ScreenSize { get; set; }
}

public class DeviceModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType != typeof(Device))
        {
            return null;
        }

        var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };

        var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
        foreach (var type in subclasses)
        {
            var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
            binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
        }

        return new DeviceModelBinder(binders);
    }
}

public class DeviceModelBinder : IModelBinder
{
    private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;

    public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
    {
        this.binders = binders;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
        var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;

        IModelBinder modelBinder;
        ModelMetadata modelMetadata;
        if (modelTypeValue == "Laptop")
        {
            (modelMetadata, modelBinder) = binders[typeof(Laptop)];
        }
        else if (modelTypeValue == "SmartPhone")
        {
            (modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
        }
        else
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return;
        }

        var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
            bindingContext.ActionContext,
            bindingContext.ValueProvider,
            modelMetadata,
            bindingInfo: null,
            bindingContext.ModelName);

        await modelBinder.BindModelAsync(newBindingContext);
        bindingContext.Result = newBindingContext.Result;

        if (newBindingContext.Result.IsModelSet)
        {
            // Setting the ValidationState ensures properties on derived types are correctly 
            bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
            {
                Metadata = modelMetadata,
            };
        }
    }
}

Doporučení a osvědčené postupy

Vlastní pořadače modelů:

  • Neměli byste se pokoušet nastavit stavové kódy nebo vrátit výsledky (například 404 Nenalezena). Pokud vazba modelu selže, měl by se selháním zpracovat filtr akcí nebo logika v rámci samotné metody akce.
  • Jsou nejužitečnější pro odstranění opakujících se kódu a průřezových obav z metod akcí.
  • Obvykle by se nemělo používat k převodu řetězce na vlastní typ, TypeConverter obvykle je lepší volbou.