ASP.NET Core'de Özel Model Bağlama

Steve Smith ve Smith Larkin

Model bağlama, denetleyici eylemlerinin HTTP istekleri yerine model türleriyle (yöntem bağımsız değişkenleri olarak geçirilen) doğrudan çalışmasına olanak sağlar. Gelen istek verileri ile uygulama modelleri arasındaki eşleme, model bağlayıcıları tarafından işleme alır. Geliştiriciler, özel model bağlayıcıları kullanarak yerleşik model bağlama işlevini genişletebilirsiniz (ancak genellikle kendi sağlayıcınızı yazmanız gerekli değildir).

Örnek kodu görüntüleme veya indirme ( nasılindir)

Varsayılan model bağlayıcı sınırlamaları

Varsayılan model bağlayıcıları yaygın .NET Core veri türlerinin çoğunu destekler ve çoğu geliştiricinin ihtiyaçlarını karşılaması gerekir. İstekten gelen metin tabanlı girişi doğrudan model türlerine bağlamayı beklerler. Bağlamadan önce girişi dönüştürmeniz gerekir. Örneğin, model verilerini bakmak için kullanılan bir anahtarınız olduğunda. Anahtarı temel alan verileri getirmek için özel model bağlayıcısı kullanabilirsiniz.

Model bağlama gözden geçirmesi

Model bağlaması, üzerinde çalışan türler için belirli tanımlar kullanır. Basit bir tür, girişte tek bir dizeden dönüştürülür. Karmaşık bir tür birden çok giriş değerine dönüştürülür. Çerçeve, bir varlığını temel alarak farkı TypeConverter belirler. Dış kaynaklar gerektirmeyen basit bir eşleme varsa tür dönüştürücü string -> SomeType oluşturmanızı öneririz.

Kendi özel model bağlayıcınızı oluşturmadan önce, mevcut model bağlayıcıların nasıl uygulanacağını gözden geçirmeniz gerekir. ByteArrayModelBinderbase64 ile kodlanmış dizeleri byte dizilerine dönüştürmek için kullanılmaktadır. Byte dizileri genellikle dosya veya veritabanı BLOB alanları olarak depolanır.

ByteArrayModelBinder ile çalışma

Base64 ile kodlanmış dizeler, ikili verileri temsil etmek için kullanılabilir. Örneğin, bir görüntü dize olarak kodlanmış olabilir. Örnek, içinde base64 kodlanmış dize olarak bir görüntü Base64String.txt.

ASP.NET Core MVC base64 ile kodlanmış bir dize alıp bir kullanarak bunu ByteArrayModelBinder bir byte dizisine dönüştürür. bağımsız ByteArrayModelBinderProvider değişkenleri byte[] ile ByteArrayModelBinder eşler:

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

Kendi özel model bağlayıcınızı oluştururken kendi türlerinizi kullanabilir IModelBinderProvider veya ModelBinderAttribute kullanabilirsiniz.

Aşağıdaki örnekte base64 ile kodlanmış dizeyi bir dizesine dönüştürmek ve sonucu bir dosyaya kaydetmek için ByteArrayModelBinder byte[] nasıl kullanabileceğiniz gösterir:

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

Ingilizce dışındaki dillere çevrilmiş kod açıklamalarını görmek isterseniz, Bu GitHub tartışma sorununubize tanıyın.

Postman gibi bir araç kullanarak base64 ile kodlanmış bir dizeyi bu API yöntemine POST ile paylaşabilirsiniz:

Postacı

Bağlayıcı, istek verilerini uygun şekilde adlandırılmış özelliklere veya bağımsız değişkenlere bağlayana kadar model bağlaması başarılı olur. Aşağıdaki örnekte, bir görünüm ByteArrayModelBinder modeliyle nasıl kullanabileceğiniz gösterir:

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

Özel model bağlayıcı örneği

Bu bölümde şunları uygulayan özel bir model bağlayıcısı uygulay geçireceğiz:

  • Gelen istek verilerini kesin olarak türü kesin olarak girili anahtar bağımsız değişkenlerine dönüştürür.
  • İlişkili Entity Framework Core getirmek için Entity Framework Core'i kullanır.
  • İlişkili varlığı eylem yöntemine bağımsız değişken olarak iletir.

Aşağıdaki örnek ModelBinder modelde özniteliğini Author kullanır:

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

Yukarıdaki kodda ModelBinder özniteliği, eylem parametrelerini bağlamak IModelBinder için kullanılacak Author türünü belirtir.

Aşağıdaki AuthorEntityBinder sınıf, bir Author ve bir kullanarak bir veri kaynağından varlık getirerek Entity Framework Core authorId bağlar:

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

Not

Yukarıdaki AuthorEntityBinder sınıf, özel bir model bağlayıcısı göstermek için tasarlanmıştır. sınıfı, arama senaryosuna yönelik en iyi yöntemleri göstermek için hazır değildir. Arama için, bağlama authorId ve veritabanını bir eylem yönteminde sorgulama. Bu yaklaşım, model bağlama hatalarını durumlardan birbirinden ayıran bir NotFound yaklaşımdır.

Aşağıdaki kod, bir eylem AuthorEntityBinder yönteminde yönteminin nasıl kullanıla bir gösterir:

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

    return Ok(author);
}

ModelBinderözniteliği, varsayılan kuralları AuthorEntityBinder kullanmayan parametrelere uygulamak için kullanılabilir:

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

    return Ok(author);
}

Bu örnekte, bağımsız değişkenin adı varsayılan değil, parametresinde authorId özniteliği kullanılarak ModelBinder belirtilir. Hem denetleyici hem de eylem yöntemi, eylem yönteminde varlığı aramayla karşılaştırıldığında basitleştirilmiştir. Entity Framework Core kullanarak yazarı getirme mantığı model bağlayıcıya taşınır. Modele bağlanan çeşitli yöntemleriniz olduğunda bu önemli bir basitleştirme Author olabilir.

özniteliğini tek tek model özelliklerine (görünüm modellerinde olduğu gibi) veya eylem yöntemi parametrelerine uygulayarak yalnızca bu tür veya eylem için belirli bir model bağlayıcısı veya ModelBinder model adı belirtebilirsiniz.

ModelBinderProvider Uygulama

Bir öznitelik uygulamak yerine, IModelBinderProvider uygulayanın. Yerleşik çerçeve bağlayıcıları bu şekilde uygulanır. Bağlayıcının üzerinde çalışma türünü belirttiğinizde, bağlayıcının kabul edilen girişi değil, ürettiği bağımsız değişkenin türünü belirtirsiniz. Aşağıdaki bağlayıcı sağlayıcısı ile AuthorEntityBinder çalışır. MVC'nin sağlayıcı koleksiyonuna ekleniyorsa, veya türünde parametrelerde ModelBinder Author Author özniteliğini kullanmana gerek yok.

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

Not: Yukarıdaki kod bir BinderTypeModelBinder döndürür. BinderTypeModelBinder , model bağlayıcıları için fabrika olarak davranır ve bağımlılık ekleme (DI) sağlar. , AuthorEntityBinder di'nin EF Core. Model BinderTypeModelBinder bağlayıcınız DI'den hizmetler gerektiriyorsa kullanın.

Özel bir model bağlayıcısı sağlayıcısı kullanmak için içinde ConfigureServices ekleyin:

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

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

Model bağlayıcıları değerlendirirken sağlayıcı koleksiyonu sırayla incelendi. Giriş modeliyle eşleşen bir bağlayıcı döndüren ilk sağlayıcı kullanılır. Bu nedenle, sağlayıcınızı koleksiyonun sonuna eklemek, özel bağlayıcınız fırsat elde etmeden önce yerleşik bir model bağlayıcının çağrılarak sonuçlandırabilirsiniz. Bu örnekte, özel sağlayıcı her zaman eylem bağımsız değişkenleri için kullanılıyor olduğundan emin olmak için koleksiyonun başına Author eklenir.

Çok biçimli model bağlama

Türetilmiş türlerin farklı modellerine bağlama, çok biçimli model bağlaması olarak bilinir. İstek değerinin belirli türetilmiş model türüne bağlı olması gerektiğinde çok biçimli özel model bağlaması gerekir. Çok biçimli model bağlama:

  • Tüm dillerle birlikte REST API tasarlanmış bir uygulama için tipik değildir.
  • Bağlı modeller hakkında gerekçenin zor olması.

Ancak, bir uygulama çok biçimli model bağlaması gerektiriyorsa, bir uygulama aşağıdaki koda benzer olabilir:

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

Öneriler ve en iyi yöntemler

Özel model bağlayıcıları:

  • Durum kodlarını ayarlamayı veya sonuçları (örneğin, 404 Bulunamadı) aramama gerekir. Model bağlama başarısız olursa, eylem yönteminin içindeki bir eylem filtresi veya mantık bu hatayı işlemeli.
  • Tekrarlanan kodu ve eylem yöntemlerinden gelen çapraz kesme endişelerini ortadan kaldırmak için en yararlıdır.
  • Genellikle bir dizeyi özel bir türe dönüştürmek için kullanılmaması gerekir, genellikle TypeConverter daha iyi bir seçenektir.

Steve Smith tarafından

Model bağlama, denetleyici eylemlerinin HTTP istekleri yerine model türleriyle (yöntem bağımsız değişkenleri olarak geçirilen) doğrudan çalışmasına olanak sağlar. Gelen istek verileri ile uygulama modelleri arasındaki eşleme, model bağlayıcıları tarafından işleme alır. Geliştiriciler, özel model bağlayıcıları kullanarak yerleşik model bağlama işlevini genişletebilirsiniz (ancak genellikle kendi sağlayıcınızı yazmanız gerekli değildir).

Örnek kodu görüntüleme veya indirme ( nasılindir)

Varsayılan model bağlayıcı sınırlamaları

Varsayılan model bağlayıcıları yaygın .NET Core veri türlerinin çoğunu destekler ve çoğu geliştiricinin ihtiyaçlarını karşılaması gerekir. İstekten gelen metin tabanlı girişi doğrudan model türlerine bağlamayı beklerler. Bağlamadan önce girişi dönüştürmeniz gerekir. Örneğin, model verilerini bakmak için kullanılan bir anahtarınız olduğunda. Anahtarı temel alan verileri getirmek için özel model bağlayıcısı kullanabilirsiniz.

Model bağlama gözden geçirmesi

Model bağlaması, üzerinde çalışan türler için belirli tanımlar kullanır. Basit bir tür, girişte tek bir dizeden dönüştürülür. Karmaşık bir tür birden çok giriş değerine dönüştürülür. Çerçeve, bir varlığını temel alarak farkı TypeConverter belirler. Dış kaynaklar gerektirmeyen basit bir eşleme varsa tür dönüştürücü string -> SomeType oluşturmanızı öneririz.

Kendi özel model bağlayıcınızı oluşturmadan önce, mevcut model bağlayıcıların nasıl uygulanacağını gözden geçirmeniz gerekir. ByteArrayModelBinderbase64 ile kodlanmış dizeleri byte dizilerine dönüştürmek için kullanılmaktadır. Byte dizileri genellikle dosya veya veritabanı BLOB alanları olarak depolanır.

ByteArrayModelBinder ile çalışma

Base64 ile kodlanmış dizeler, ikili verileri temsil etmek için kullanılabilir. Örneğin, bir görüntü dize olarak kodlanmış olabilir. Örnek, içinde base64 kodlanmış dize olarak bir görüntü Base64String.txt.

ASP.NET Core MVC base64 ile kodlanmış bir dize alıp bir kullanarak bunu ByteArrayModelBinder bir byte dizisine dönüştürür. bağımsız ByteArrayModelBinderProvider değişkenleri byte[] ile ByteArrayModelBinder eşler:

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

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

    return null;
}

Kendi özel model bağlayıcınızı oluştururken kendi türlerinizi kullanabilir IModelBinderProvider veya ModelBinderAttribute kullanabilirsiniz.

Aşağıdaki örnekte base64 ile kodlanmış dizeyi bir dizesine dönüştürmek ve sonucu bir dosyaya kaydetmek için ByteArrayModelBinder byte[] nasıl kullanabileceğiniz gösterir:

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

Postman gibi bir araç kullanarak base64 ile kodlanmış bir dizeyi bu API yöntemine POST ile paylaşabilirsiniz:

Postacı

Bağlayıcı, istek verilerini uygun şekilde adlandırılmış özelliklere veya bağımsız değişkenlere bağlayana kadar model bağlaması başarılı olur. Aşağıdaki örnekte, bir görünüm ByteArrayModelBinder modeliyle nasıl kullanabileceğiniz gösterir:

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

Özel model bağlayıcı örneği

Bu bölümde şunları uygulayan özel bir model bağlayıcısı uygulay geçireceğiz:

  • Gelen istek verilerini kesin olarak türü kesin olarak girili anahtar bağımsız değişkenlerine dönüştürür.
  • İlişkili Entity Framework Core getirmek için Entity Framework Core'i kullanır.
  • İlişkili varlığı eylem yöntemine bağımsız değişken olarak iletir.

Aşağıdaki örnek ModelBinder modelde özniteliğini Author kullanır:

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

Yukarıdaki kodda ModelBinder özniteliği, eylem parametrelerini bağlamak IModelBinder için kullanılacak Author türünü belirtir.

Aşağıdaki AuthorEntityBinder sınıf, bir Author ve bir kullanarak bir veri kaynağından varlık getirerek Entity Framework Core authorId bağlar:

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

Not

Yukarıdaki AuthorEntityBinder sınıf, özel bir model bağlayıcısı göstermek için tasarlanmıştır. sınıfı, arama senaryosuna yönelik en iyi yöntemleri göstermek için hazır değildir. Arama için, bağlama authorId ve veritabanını bir eylem yönteminde sorgulama. Bu yaklaşım, model bağlama hatalarını durumlardan birbirinden ayıran bir NotFound yaklaşımdır.

Aşağıdaki kod, bir eylem AuthorEntityBinder yönteminde yönteminin nasıl kullanıla bir gösterir:

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

ModelBinderözniteliği, varsayılan kuralları AuthorEntityBinder kullanmayan parametrelere uygulamak için kullanılabilir:

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

    return Ok(author);
}

Bu örnekte, bağımsız değişkenin adı varsayılan değil, parametresinde authorId özniteliği kullanılarak ModelBinder belirtilir. Hem denetleyici hem de eylem yöntemi, eylem yönteminde varlığı aramayla karşılaştırıldığında basitleştirilmiştir. Entity Framework Core kullanarak yazarı getirme mantığı model bağlayıcıya taşınır. Modele bağlanan çeşitli yöntemleriniz olduğunda bu önemli bir basitleştirme Author olabilir.

özniteliğini tek tek model özelliklerine (görünüm modellerinde olduğu gibi) veya eylem yöntemi parametrelerine uygulayarak yalnızca bu tür veya eylem için belirli bir model bağlayıcısı veya ModelBinder model adı belirtebilirsiniz.

ModelBinderProvider Uygulama

Bir öznitelik uygulamak yerine, IModelBinderProvider uygulayanın. Yerleşik çerçeve bağlayıcıları bu şekilde uygulanır. Bağlayıcının üzerinde çalışma türünü belirttiğinizde, bağlayıcının kabul edilen girişi değil, ürettiği bağımsız değişkenin türünü belirtirsiniz. Aşağıdaki bağlayıcı sağlayıcısı ile AuthorEntityBinder çalışır. MVC'nin sağlayıcı koleksiyonuna ekleniyorsa, veya türünde parametrelerde ModelBinder Author Author özniteliğini kullanmana gerek yok.

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

Not: Yukarıdaki kod bir BinderTypeModelBinder döndürür. BinderTypeModelBinder , model bağlayıcıları için fabrika olarak davranır ve bağımlılık ekleme (DI) sağlar. , AuthorEntityBinder di'nin EF Core. Model BinderTypeModelBinder bağlayıcınız DI'den hizmetler gerektiriyorsa kullanın.

Özel bir model bağlayıcısı sağlayıcısı kullanmak için içinde ConfigureServices ekleyin:

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

Model bağlayıcıları değerlendirirken sağlayıcı koleksiyonu sırayla incelendi. Bağlayıcı döndüren ilk sağlayıcı kullanılır. Sağlayıcınızı koleksiyonun sonuna eklemek, özel bağlayıcınız fırsat elde etmeden önce yerleşik bir model bağlayıcının çağrılarak sonuçlandırabilirsiniz. Bu örnekte, özel sağlayıcı, eylem bağımsız değişkenleri için kullanılıyor olduğundan emin olmak için koleksiyonun başına Author eklenir.

Çok biçimli model bağlama

Türetilmiş türlerin farklı modellerine bağlama, çok biçimli model bağlaması olarak bilinir. İstek değerinin belirli türetilmiş model türüne bağlı olması gerektiğinde çok biçimli özel model bağlaması gerekir. Çok biçimli model bağlama:

  • Tüm dillerle birlikte REST API tasarlanmış bir uygulama için tipik değildir.
  • Bağlı modeller hakkında gerekçenin zor olması.

Ancak, bir uygulama çok biçimli model bağlaması gerektiriyorsa, bir uygulama aşağıdaki koda benzer olabilir:

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

Öneriler ve en iyi yöntemler

Özel model bağlayıcıları:

  • Durum kodlarını ayarlamayı veya sonuçları (örneğin, 404 Bulunamadı) aramama gerekir. Model bağlama başarısız olursa, eylem yönteminin içindeki bir eylem filtresi veya mantık bu hatayı işlemeli.
  • Tekrarlanan kodu ve eylem yöntemlerinden gelen çapraz kesme endişelerini ortadan kaldırmak için en yararlıdır.
  • Genellikle bir dizeyi özel bir türe dönüştürmek için kullanılmaması gerekir, genellikle TypeConverter daha iyi bir seçenektir.