Sdílet prostřednictvím


Vázání 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 mohou rozšířit vestavěnou funkcionalitu vázání modelu implementací vlastních vazačů modelů (i když obvykle není potřeba psát vlastního vazače).

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

Omezení výchozího vazač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

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

V části Jednoduché typy naleznete seznam typů, které může vazač modelů převést z řetězců.

Před vytvořením vlastního pořadače modelu je vhodné zkontrolovat, jak se implementují existující pořadače modelů. Uvažujte o ByteArrayModelBinder, který lze použít k převodu řetězců zakódovaných v base64 na bajtová pole. Pole bajtů se často ukládají jako soubory nebo databázová pole BLOB.

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 ModelBinder určuje typ IModelBinder, který se má použít pro vytvoření vazby parametrů akce Author.

Následující AuthorEntityBinder třída váže Author parametr načtením entity ze zdroje dat pomocí Entity Framework Core a používá 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

AuthorEntityBinder Tato předchozí třída je určena k ilustraci vlastního vazače modelů. Třída není určená k ilustraci osvědčených postupů pro scénář vyhledávání. Pro vyhledávání propojte authorId a v metodě akce proveďte dotaz do databáze. Tento přístup odděluje chyby vazby modelu od NotFound případů.

Následující kód ukazuje, jak použít AuthorEntityBinder v metodě 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 byla přesunuta do vazebního modulu 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 poskytovatele ModelBinder

Místo použití atributu můžete implementovat IModelBinderProvider. Takto se implementují integrované pořadače architektury. Když určíte typ, se kterým váš binder pracuje, určíte typ argumentu, který generuje, nikoli vstup, který váš binder přijímá. Poskytovatel vazby, který pracuje s AuthorEntityBinder. Když je zprostředkovatel přidán do kolekce zprostředkovatelů MVC, nemusíte používat atribut ModelBinder u Author-typových parametrů nebo parametrů typu 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;
        }
    }
}

Poznámka: Předchozí kód vrátí BinderTypeModelBinder. BinderTypeModelBinder funguje jako továrna pro modelové vazby a poskytuje vkládání závislostí (DI). Vyžaduje, aby AuthorEntityBinder měl přístup k EF Core. Použijte BinderTypeModelBinder , pokud váš pořadač modelů vyžaduje služby z DI.

Pokud chcete použít poskytovatele vlastního 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í poskytovatel, který vrátí vazbu odpovídající 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 bylo zajištěno, že se vždy používá pro argumenty akce Author.

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, selhání by měl řešit filtr akcí nebo logika v rámci samotné metody akce.
  • Jsou nejužitečnější pro odstranění opakujícího se kódu a průřezových problémů z metod akcí.
  • Obvykle by se nemělo používat k převodu řetězce na vlastní typ; TypeConverter je obvykle 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 mohou rozšířit vestavěnou funkcionalitu vázání modelu implementací vlastních vazačů modelů (i když obvykle není potřeba psát vlastního vazače).

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

Omezení výchozího vazač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

Modelové přiřazení používá konkrétní definice pro typy, se kterými pracuje. Jednoduchý typ je převeden z jediného řetězce zadaného jako vstup. Komplexní typ je převeden z více vstupních hodnot. Rámec určuje rozdíl na základě existence TypeConverter. Doporučujeme vytvořit převaděč typů, pokud máte jednoduché string>SomeType mapování, které nevyžaduje externí prostředky.

Před vytvořením vlastního pořadače modelu je vhodné zkontrolovat, jak se implementují existující pořadače modelů. Uvažujte o ByteArrayModelBinder, který lze použít k převodu řetězců zakódovaných v base64 na bajtová pole. Pole bajtů se často ukládají jako soubory nebo databázová pole BLOB.

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 ModelBinder určuje typ IModelBinder, který se má použít pro vytvoření vazby parametrů akce Author.

Následující AuthorEntityBinder třída váže Author parametr načtením entity ze zdroje dat pomocí Entity Framework Core a používá 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

AuthorEntityBinder Tato předchozí třída je určena k ilustraci vlastního vazače modelů. Třída není určená k ilustraci osvědčených postupů pro scénář vyhledávání. Pro vyhledávání propojte authorId a v metodě akce proveďte dotaz do databáze. Tento přístup odděluje chyby vazby modelu od NotFound případů.

Následující kód ukazuje, jak použít AuthorEntityBinder v metodě 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 byla přesunuta do vazebního modulu 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 poskytovatele ModelBinder

Místo použití atributu můžete implementovat IModelBinderProvider. Takto se implementují integrované pořadače architektury. Když určíte typ, se kterým váš binder pracuje, určíte typ argumentu, který generuje, nikoli vstup, který váš binder přijímá. Poskytovatel vazby, který pracuje s AuthorEntityBinder. Když je zprostředkovatel přidán do kolekce zprostředkovatelů MVC, nemusíte používat atribut ModelBinder u Author-typových parametrů nebo parametrů typu 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;
        }
    }
}

Poznámka: Předchozí kód vrátí BinderTypeModelBinder. BinderTypeModelBinder funguje jako továrna pro modelové vazby a poskytuje vkládání závislostí (DI). Vyžaduje, aby AuthorEntityBinder měl přístup k EF Core. Použijte BinderTypeModelBinder , pokud váš pořadač modelů vyžaduje služby z DI.

Pokud chcete použít poskytovatele vlastního 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í vazbu. 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 bylo zajištěno, že bude použito u argumentů akce Author.

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, selhání by měl řešit filtr akcí nebo logika v rámci samotné metody akce.
  • Jsou nejužitečnější pro odstranění opakujícího se kódu a průřezových problémů z metod akcí.
  • Obvykle by se nemělo používat k převodu řetězce na vlastní typ; TypeConverter je obvykle lepší volbou.