Megosztás a következőn keresztül:


Egyéni modellkötés a ASP.NET Core-ban

Készítette : Kirk Larkin

A modellkötés lehetővé teszi, hogy a vezérlőműveletek a HTTP-kérések helyett közvetlenül modelltípusokkal (metódusargumentumokként átadva) működjenek. A bejövő kérelmek adatai és az alkalmazásmodellek közötti leképezést modellkötők kezelik. A fejlesztők egyéni modellkötők implementálásával bővíthetik a beépített modellkötési funkciókat (bár általában nincs szükség arra, hogy saját szolgáltatót írjanak).

Mintakód megtekintése vagy letöltése (hogyan töltsd le)

Alapértelmezett modellkötő korlátozások

Az alapértelmezett modellkötők támogatják a legtöbb gyakori .NET Core-adattípust, és a legtöbb fejlesztő igényeit kielégítik. Azt várják, hogy a kérés szövegalapú bemenetét közvetlenül a modelltípusokhoz kötik. Előfordulhat, hogy a kötés előtt át kell alakítania a bemenetet. Ha például rendelkezik egy olyan kulccsal, amellyel modelladatokat kereshet. Saját modellkötő használatával lekérheti az adatokat a kulcs alapján.

Egyszerű és összetett modellkötési típusok

A modellkötés meghatározott definíciókat használ az általa használt típusokhoz. A egyszerű típus egyetlen sztringből konvertálódik TypeConverter vagy TryParse metódus használatával. A összetett típus több bemeneti értékből konvertálódik. A keretrendszer egy TypeConverter vagy TryParsemegléte alapján határozza meg a különbséget . Javasoljuk, hogy hozzon létre egy típuskonvertert, vagy használjon TryParse egy stringSomeType átalakításhoz, amely nem igényel külső erőforrásokat vagy több bemenetet.

A modellkötő által sztringekből konvertálható típusok listája az Egyszerű típusok című részben található.

Mielőtt saját egyéni modellkötőt hozna létre, érdemes áttekinteni, hogy a meglévő modellkötők hogyan vannak implementálva. ByteArrayModelBinder A base64 kódolású sztringek bájttömbökké alakításához használható. A bájttömböket gyakran fájlokként vagy adatbázis BLOB-mezőkként tárolják.

A ByteArrayModelBinder használata

A Base64 kódolású sztringek bináris adatok megjelenítésére használhatók. Egy kép például sztringként kódolható. A minta egy képet tartalmaz alap64 kódolású sztringként aBase64String.txt.

ASP.NET Core MVC képes egy base64-kódolt karakterláncot felvenni, és ByteArrayModelBinder segítségével bájttömbbé alakítani. A ByteArrayModelBinderProvider az byte[] argumentumokat a ByteArrayModelBinder-ra/-re képezi le:

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;
}

Saját egyéni modellkötés létrehozásakor implementálhatja a saját IModelBinderProvider típusát, vagy használhatja a ModelBinderAttribute.

Az alábbi példa bemutatja, hogyan lehet egy base64-kódolású stringet ByteArrayModelBinder segítségével byte[] formátummá alakítani, és az eredményt fájlba menteni.

[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);
}

Ha szeretné, hogy a kódkommentárok angolon kívül más nyelvekre is le legyenek fordítva, jelezze nekünk a GitHub vitafórumkérdésénél.

Egy base64 kódolású sztringet postázhat az előző API-metódushoz egy olyan eszközzel, mint a curl.

Mindaddig, amíg a kötési eszköz képes adatokat kötni a megfelelő névvel ellátott tulajdonságokhoz vagy argumentumokhoz, a modellkötés sikeres lesz. Az alábbi példa bemutatja, hogyan használható ByteArrayModelBinder nézetmodellekkel:

[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; }
}

Egyéni modell iratgyűjtő mintája

Ebben a szakaszban egy egyéni modellkötőt implementálunk, amely a következő:

  • A bejövő kérések adatait erősen beírt kulcsargumentumokká alakítja.
  • Az Entity Framework Core használatával lekéri a társított entitást.
  • A társított entitást argumentumként továbbítja a műveletmetódusnak.

A következő minta a ModelBinder modell attribútumát Author használja:

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; }
    }
}

Az előző kódban az ModelBinder attribútum határozza meg a műveletparaméterek kötéséhez IModelBinderAuthor használandó típust.

A következő AuthorEntityBinder osztály az Entity Framework Core használatával beolvassa az entitást egy adatforrásból, hogy kötést végezzen egy Author paraméterhez és egy authorId-hez.

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

Az előző AuthorEntityBinder osztály egy egyedi modellkötő szemléltetésére szolgál. Az osztály nem egy keresési forgatókönyv ajánlott eljárásainak szemléltetésére szolgál. Kereséshez kösse össze a authorId az adatbázissal, és lekérdezi azt egy műveleti metódusban. Ez a megközelítés elkülöníti a modellkötési hibákat az esetektől NotFound .

Az alábbi kód bemutatja, hogyan használható a AuthorEntityBinder műveletmetódus:

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

    return Ok(author);
}

Az ModelBinder attribútum az alapértelmezett konvenciókat nem használó paraméterekre alkalmazható AuthorEntityBinder :

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

    return Ok(author);
}

Ebben a példában, mivel az argumentum neve nem az alapértelmezett authorId, az attribútum használatával van megadva a ModelBinder paraméteren. A vezérlő és a műveleti módszer is egyszerűbb, mint az entitás keresése a műveletmetódusban. A szerző Entity Framework Core használatával történő lekérésének logikája a modellkötőbe kerül. Ez jelentős egyszerűsítést jelenthet, ha több módszer is kapcsolódik a Author modellhez.

Az attribútumot alkalmazhatja az ModelBinder egyes modelltulajdonságokra (például egy nézetmodellen), vagy a műveleti módszer paramétereire, hogy csak az adott típushoz vagy művelethez adjon meg egy bizonyos modellkötőt vagy modellnevet.

ModelBinderProvider implementálása

Attribútum alkalmazása helyett implementálhatja a műveletet IModelBinderProvider. Így implementálják a beépített keretrendszer-kötéseket. Amikor megadja, hogy milyen típusú kötést használ, az általa előállított argumentumtípust kell megadnia, nem pedig azt a bemenetet, amit a kötéskészítő elfogad. A következő iratgyűjtő-szolgáltató együttműködik a AuthorEntityBinder. Amikor hozzáadja az MVC szolgáltatóinak gyűjteményéhez, nem kell használnia a ModelBinder attribútumot a Author vagy Author típusú paramétereknél.

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;
        }
    }
}

Megjegyzés: Az előző kód egy BinderTypeModelBinder. BinderTypeModelBinder a modellkötők gyáraként működik, és függőséginjektálást (DI) biztosít. A AuthorEntityBinder megköveteli a DI-t, hogy hozzáférjen EF Core. Akkor használja BinderTypeModelBinder , ha a modell iratgyűjtőjéhez a diától származó szolgáltatások szükségesek.

Egyéni modell iratgyűjtő szolgáltató használatához vegye fel a következőbe ConfigureServices:

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

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

A modellkötők kiértékelésekor a szolgáltatók gyűjteményének vizsgálata sorrendben történik. A rendszer az első olyan szolgáltatót használja, amely a bemeneti modellnek megfelelő iratgyűjtőt ad vissza. Ha a szolgáltatót hozzáadja a gyűjtemény végéhez, az azt eredményezheti, hogy a rendszer meghív egy beépített modellkötőt, mielőtt az egyéni kötőnek esélye lesz rá. Ebben a példában egy testreszabott szolgáltatót adunk hozzá a gyűjtemény elejére, hogy biztosítsuk annak használatát a műveletargumentumokhoz Author .

Polimorf modell kötése

A származtatott típusok különböző modelljeihez való kötést polimorf modellkötésnek nevezzük. Polimorfikus egyéni modellkötésre van szükség, ha a kérelem értékét az adott származtatott modelltípushoz kell kötni. Polimorf modell kötése:

  • Nem jellemző az REST olyan API-kra, amelyek minden nyelvvel együttműködnek.
  • Megnehezíti a kötött modellek magyarázatát.

Ha azonban egy alkalmazás polimorf modellkötést igényel, az implementáció a következő kódhoz hasonlóan nézhet ki:

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,
            };
        }
    }
}

Javaslatok és ajánlott eljárások

Egyéni modellkötők alkalmazása

  • Ne próbáljon meg állapotkódokat beállítani vagy eredményeket visszaadni (például: 404 Nem található). Ha a modellkötés sikertelen, a műveletmetóduson belüli műveletszűrőnek vagy logikának kell kezelnie a hibát.
  • A leghatékonyabbak az ismétlődő kód és a műveletmetszetekkel kapcsolatos keresztvágási problémák kiküszöbölésére.
  • A sztringeket általában nem szabad egyéni típussá alakítani, TypeConverter ez általában jobb megoldás.

Készítette: Steve Smith

A modellkötés lehetővé teszi, hogy a vezérlőműveletek a HTTP-kérések helyett közvetlenül modelltípusokkal (metódusargumentumokként átadva) működjenek. A bejövő kérelmek adatai és az alkalmazásmodellek közötti leképezést modellkötők kezelik. A fejlesztők egyéni modellkötők implementálásával bővíthetik a beépített modellkötési funkciókat (bár általában nincs szükség arra, hogy saját szolgáltatót írjanak).

Mintakód megtekintése vagy letöltése (hogyan töltsd le)

Alapértelmezett modellkötő korlátozások

Az alapértelmezett modellkötők támogatják a legtöbb gyakori .NET Core-adattípust, és a legtöbb fejlesztő igényeit kielégítik. Azt várják, hogy a kérés szövegalapú bemenetét közvetlenül a modelltípusokhoz kötik. Előfordulhat, hogy a kötés előtt át kell alakítania a bemenetet. Ha például rendelkezik egy olyan kulccsal, amellyel modelladatokat kereshet. Saját modellkötő használatával lekérheti az adatokat a kulcs alapján.

Modellkötés áttekintése

A modellkötés meghatározott definíciókat használ az általa használt típusokhoz. Egy egyszerű típus egyetlen sztringből lesz konvertálva a bemenetben. A összetett típus több bemeneti értékből konvertálódik. A keretrendszer a különbséget TypeConverter létezése alapján határozza meg. Javasoljuk, hogy hozzon létre egy típuskonvertert, ha olyan egyszerű string leképezéssel>SomeType rendelkezik, amely nem igényel külső erőforrásokat.

Mielőtt saját egyéni modellkötőt hozna létre, érdemes áttekinteni, hogy a meglévő modellkötők hogyan vannak implementálva. ByteArrayModelBinder A base64 kódolású sztringek bájttömbökké alakításához használható. A bájttömböket gyakran fájlokként vagy adatbázis BLOB-mezőkként tárolják.

A ByteArrayModelBinder használata

A Base64 kódolású sztringek bináris adatok megjelenítésére használhatók. Egy kép például sztringként kódolható. A minta egy képet tartalmaz alap64 kódolású sztringként aBase64String.txt.

ASP.NET Core MVC képes egy base64-kódolt karakterláncot felvenni, és ByteArrayModelBinder segítségével bájttömbbé alakítani. A ByteArrayModelBinderProvider az byte[] argumentumokat a ByteArrayModelBinder-ra/-re képezi le:

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

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

    return null;
}

Saját egyéni modellkötés létrehozásakor implementálhatja a saját IModelBinderProvider típusát, vagy használhatja a ModelBinderAttribute.

Az alábbi példa bemutatja, hogyan lehet egy base64-kódolású stringet ByteArrayModelBinder segítségével byte[] formátummá alakítani, és az eredményt fájlba menteni.

[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);
}

Egy base64 kódolású sztringet postázhat az előző API-metódushoz egy olyan eszközzel, mint a curl.

Mindaddig, amíg a kötési eszköz képes adatokat kötni a megfelelő névvel ellátott tulajdonságokhoz vagy argumentumokhoz, a modellkötés sikeres lesz. Az alábbi példa bemutatja, hogyan használható ByteArrayModelBinder nézetmodellekkel:

[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; }
}

Egyéni modell iratgyűjtő mintája

Ebben a szakaszban egy egyéni modellkötőt implementálunk, amely a következő:

  • A bejövő kérések adatait erősen beírt kulcsargumentumokká alakítja.
  • Az Entity Framework Core használatával lekéri a társított entitást.
  • A társított entitást argumentumként továbbítja a műveletmetódusnak.

A következő minta a ModelBinder modell attribútumát Author használja:

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; }
    }
}

Az előző kódban az ModelBinder attribútum határozza meg a műveletparaméterek kötéséhez IModelBinderAuthor használandó típust.

A következő AuthorEntityBinder osztály az Entity Framework Core használatával beolvassa az entitást egy adatforrásból, hogy kötést végezzen egy Author paraméterhez és egy authorId-hez.

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

Az előző AuthorEntityBinder osztály egy egyedi modellkötő szemléltetésére szolgál. Az osztály nem egy keresési forgatókönyv ajánlott eljárásainak szemléltetésére szolgál. Kereséshez kösse össze a authorId az adatbázissal, és lekérdezi azt egy műveleti metódusban. Ez a megközelítés elkülöníti a modellkötési hibákat az esetektől NotFound .

Az alábbi kód bemutatja, hogyan használható a AuthorEntityBinder műveletmetódus:

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

Az ModelBinder attribútum az alapértelmezett konvenciókat nem használó paraméterekre alkalmazható AuthorEntityBinder :

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

    return Ok(author);
}

Ebben a példában, mivel az argumentum neve nem az alapértelmezett authorId, az attribútum használatával van megadva a ModelBinder paraméteren. A vezérlő és a műveleti módszer is egyszerűbb, mint az entitás keresése a műveletmetódusban. A szerző Entity Framework Core használatával történő lekérésének logikája a modellkötőbe kerül. Ez jelentős egyszerűsítést jelenthet, ha több módszer is kapcsolódik a Author modellhez.

Az attribútumot alkalmazhatja az ModelBinder egyes modelltulajdonságokra (például egy nézetmodellen), vagy a műveleti módszer paramétereire, hogy csak az adott típushoz vagy művelethez adjon meg egy bizonyos modellkötőt vagy modellnevet.

ModelBinderProvider implementálása

Attribútum alkalmazása helyett implementálhatja a műveletet IModelBinderProvider. Így implementálják a beépített keretrendszer-kötéseket. Amikor megadja, hogy milyen típusú kötést használ, az általa előállított argumentumtípust kell megadnia, nem pedig azt a bemenetet, amit a kötéskészítő elfogad. A következő iratgyűjtő-szolgáltató együttműködik a AuthorEntityBinder. Amikor hozzáadja az MVC szolgáltatóinak gyűjteményéhez, nem kell használnia a ModelBinder attribútumot a Author vagy Author típusú paramétereknél.

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;
        }
    }
}

Megjegyzés: Az előző kód egy BinderTypeModelBinder. BinderTypeModelBinder a modellkötők gyáraként működik, és függőséginjektálást (DI) biztosít. A AuthorEntityBinder megköveteli a DI-t, hogy hozzáférjen EF Core. Akkor használja BinderTypeModelBinder , ha a modell iratgyűjtőjéhez a diától származó szolgáltatások szükségesek.

Egyéni modell iratgyűjtő szolgáltató használatához vegye fel a következőbe 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);
}

A modellkötők kiértékelésekor a szolgáltatók gyűjteményének vizsgálata sorrendben történik. A rendszer az első olyan szolgáltatót használja, amely egy kötőanyagot ad vissza. Ha a szolgáltatót hozzáadja a gyűjtemény végéhez, a rendszer meghívhat egy beépített modell-iratgyűjtőt, mielőtt az egyéni iratgyűjtőnek esélye lesz rá. Ebben a példában az egyéni szolgáltatót a gyűjtemény elejére adjuk hozzá, hogy biztosítsuk annak használatát a Author műveletargumentumokhoz.

Polimorf modell kötése

A származtatott típusok különböző modelljeihez való kötést polimorf modellkötésnek nevezzük. Polimorfikus egyéni modellkötésre van szükség, ha a kérelem értékét az adott származtatott modelltípushoz kell kötni. Polimorf modell kötése:

  • Nem jellemző az REST olyan API-kra, amelyek minden nyelvvel együttműködnek.
  • Megnehezíti a kötött modellek magyarázatát.

Ha azonban egy alkalmazás polimorf modellkötést igényel, az implementáció a következő kódhoz hasonlóan nézhet ki:

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,
            };
        }
    }
}

Javaslatok és ajánlott eljárások

Egyéni modellkötők alkalmazása

  • Ne próbáljon meg állapotkódokat beállítani vagy eredményeket visszaadni (például: 404 Nem található). Ha a modellkötés sikertelen, a műveletmetóduson belüli műveletszűrőnek vagy logikának kell kezelnie a hibát.
  • A leghatékonyabbak az ismétlődő kód és a műveletmetszetekkel kapcsolatos keresztvágási problémák kiküszöbölésére.
  • A sztringeket általában nem szabad egyéni típussá alakítani, TypeConverter ez általában jobb megoldás.