Aracılığıyla paylaş


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

Tarafından Kirk Larkin

Model bağlama, denetleyici eylemlerinin HTTP istekleri yerine model türleriyle (yöntem bağımsız değişkenleri olarak geçirilir) doğrudan çalışmasını sağlar. Gelen istek verileri ve uygulama modelleri arasındaki eşleme, model bağlayıcıları tarafından işlenir. Geliştiriciler, özel model bağlayıcıları uygulayarak yerleşik model bağlama işlevselliğini genişletebilir (ancak genellikle kendi sağlayıcınızı yazmanız gerekmez).

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

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 gereksinimlerini karşılamalıdır. İstekten gelen metin tabanlı girişi doğrudan model türlerine bağlamayı beklerler. Bağlamadan önce girişi dönüştürmeniz gerekebilir. Örneğin, model verilerini aramak için kullanılabilecek bir anahtarınız olduğunda. Anahtara göre veri getirmek için özel model bağlayıcısı kullanabilirsiniz.

Model bağlama basit ve karmaşık türler

Model bağlama, üzerinde çalıştığı türler için belirli tanımları kullanır. Basit bir tür, veya TryParse yöntemi kullanılarak TypeConverter tek bir dizeden dönüştürülür. Karmaşık bir tür birden çok giriş değerinden dönüştürülür. Çerçeve, veya TypeConverter TryParsevarlığını temel alarak farkı belirler. Bir tür dönüştürücüsü oluşturmanızı veya dış kaynaklar veya birden çok giriş gerektirmeyen bir string SomeType dönüştürme için kullanmanızı TryParse öneririz.

Model bağlayıcısının dizelerden dönüştürebileceği türlerin listesi için bkz . Basit türler.

Kendi özel model bağlayıcınızı oluşturmadan önce, mevcut model bağlayıcılarının nasıl uygulandığını gözden geçirmeye değer. ByteArrayModelBinder Base64 ile kodlanmış dizeleri bayt dizilerine dönüştürmek için hangilerinin kullanılabileceğini düşünün. Bayt dizileri genellikle dosya veya veritabanı BLOB alanları olarak depolanır.

ByteArrayModelBinder ile çalışma

İkili verileri temsil etmek için Base64 ile kodlanmış dizeler kullanılabilir. Örneğin, bir görüntü dize olarak kodlanabilir. Örnek, Base64String.txt'da base64 kodlu dize olarak bir görüntü içerir.

ASP.NET Core MVC, base64 ile kodlanmış bir dize alabilir ve bunu bayt dizisine dönüştürmek için bir ByteArrayModelBinder kullanabilir. Bağımsız ByteArrayModelBinderProvider değişkenleri ile ByteArrayModelBindereşlerbyte[]:

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 IModelBinderProvider türünüzü uygulayabilir veya kullanabilirsiniz ModelBinderAttribute.

Aşağıdaki örnekte base64 ile kodlanmış bir dizeyi bir dosyasına dönüştürmek ve sonucu bir dosyaya byte[] kaydetmek için nasıl kullanılacağı ByteArrayModelBinder gösterilmektedir:

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

Kod açıklamalarının İngilizce dışındaki dillere çevirisini görmek isterseniz, bunu bu GitHub tartışma konusunda bize bildirin.

Curl gibi bir araç kullanarak base64 ile kodlanmış bir dizeyi önceki api yöntemine GÖNDERebilirsiniz.

Bağlayıcı, istek verilerini uygun şekilde adlandırılmış özelliklere veya bağımsız değişkenlere bağlayabildiği sürece model bağlama başarılı olur. Aşağıdaki örnekte, görünüm modeliyle nasıl kullanılacağı ByteArrayModelBinder gösterilmektedir:

[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ı sağlayan özel bir model bağlayıcısı uygulayacağız:

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

Aşağıdaki örnek modelde özniteliğini ModelBinder 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 Author için kullanılacak türü IModelBinder belirtir.

Aşağıdaki AuthorEntityBinder sınıf, Entity Framework Core ve kullanarak Author bir veri kaynağından varlık getirerek bir authorIdparametre 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

Önceki AuthorEntityBinder sınıf, özel model bağlayıcısını göstermek için tasarlanmıştır. sınıfı, arama senaryosu için en iyi yöntemleri göstermek üzere tasarlanmamıştır. Arama için öğesini bağlayın authorId ve veritabanını bir eylem yönteminde sorgulayın. Bu yaklaşım model bağlama hatalarını durumlardan NotFound ayırır.

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

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

    return Ok(author);
}

özniteliği, ModelBinder varsayılan kuralları kullanmayan parametrelere uygulamak AuthorEntityBinder 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 authorIdolmadığından özniteliği kullanılarak ModelBinder parametresinde belirtilir. Eylem yönteminde varlığı aramaya kıyasla hem denetleyici hem de eylem yöntemi basitleştirilmiştir. Entity Framework Core kullanarak yazarı getirme mantığı model bağlayıcısına taşınır. Modele bağlanan Author birkaç yönteminiz olduğunda bu önemli bir basitleştirme olabilir.

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

ModelBinderProvider Uygulama

Özniteliği uygulamak yerine uygulayabilirsiniz IModelBinderProvider. Yerleşik çerçeve bağlayıcıları bu şekilde uygulanır. Bağlayıcınızın üzerinde çalıştığı türü belirttiğinizde, ciltçinizin kabul ettiğiniz 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 eklendiğinde veya -typed parametrelerinde Author Authorözniteliğini ModelBinder kullanmanız gerekmez.

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 BinderTypeModelBinderdöndürür. BinderTypeModelBinder model bağlayıcıları için fabrika görevi görür ve bağımlılık ekleme (DI) sağlar. için AuthorEntityBinder DI'nin erişmesi EF Coregerekir. Model bağlayıcınız DI hizmetlerini gerektiriyorsa kullanın BinderTypeModelBinder .

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

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ğerlendirilirken sağlayıcı koleksiyonu sırayla inceleniyor. Giriş modeliyle eşleşen bir bağlayıcı döndüren ilk sağlayıcı kullanılır. Böylece sağlayıcınızı koleksiyonun sonuna eklemek, özel bağlayıcınızın bir şansı olmadan önce yerleşik bir model bağlayıcısının çağrılmasıyla sonuçlanabilir. Bu örnekte özel sağlayıcı, her zaman eylem bağımsız değişkenleri için kullanıldığından emin olmak için Author koleksiyonun başına eklenir.

Polimorfik model bağlama

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

  • Tüm dillerle birlikte çalışabilecek şekilde tasarlanmış bir REST API için tipik değildir.
  • İlişkili modeller hakkında mantık yürütmeyi zorlaştırır.

Ancak, bir uygulama çok biçimli model bağlaması gerektiriyorsa, bir uygulama aşağıdaki kod gibi görünebilir:

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ı ayarlamaya veya sonuçları döndürmeye çalışmamalıdır (örneğin, 404 Bulunamadı). Model bağlama başarısız olursa, eylem yönteminin içindeki bir eylem filtresi veya mantık hatayı işlemelidir.
  • Tekrarlanan kodu ve eylem yöntemlerinden çapraz kesme endişelerini ortadan kaldırmak için en kullanışlı olanlardır.
  • Genellikle bir dizeyi özel bir türe dönüştürmek için kullanılmamalıdır; TypeConverter genellikle daha iyi bir seçenektir.

Tarafından Steve Smith

Model bağlama, denetleyici eylemlerinin HTTP istekleri yerine model türleriyle (yöntem bağımsız değişkenleri olarak geçirilir) doğrudan çalışmasını sağlar. Gelen istek verileri ve uygulama modelleri arasındaki eşleme, model bağlayıcıları tarafından işlenir. Geliştiriciler, özel model bağlayıcıları uygulayarak yerleşik model bağlama işlevselliğini genişletebilir (ancak genellikle kendi sağlayıcınızı yazmanız gerekmez).

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

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 gereksinimlerini karşılamalıdır. İstekten gelen metin tabanlı girişi doğrudan model türlerine bağlamayı beklerler. Bağlamadan önce girişi dönüştürmeniz gerekebilir. Örneğin, model verilerini aramak için kullanılabilecek bir anahtarınız olduğunda. Anahtara göre veri getirmek için özel model bağlayıcısı kullanabilirsiniz.

Model bağlama gözden geçirmesi

Model bağlama, üzerinde çalıştığı türler için belirli tanımları kullanır. Basit bir tür , girişteki tek bir dizeden dönüştürülür. Karmaşık bir tür birden çok giriş değerinden dönüştürülür. Çerçeve, bir TypeConvertervarlığını temel alarak farkı belirler. Dış kaynak gerektirmeyen basit string>SomeType bir eşlemeniz varsa bir tür dönüştürücü oluşturmanızı öneririz.

Kendi özel model bağlayıcınızı oluşturmadan önce, mevcut model bağlayıcılarının nasıl uygulandığını gözden geçirmeye değer. ByteArrayModelBinder Base64 ile kodlanmış dizeleri bayt dizilerine dönüştürmek için hangilerinin kullanılabileceğini düşünün. Bayt dizileri genellikle dosya veya veritabanı BLOB alanları olarak depolanır.

ByteArrayModelBinder ile çalışma

İkili verileri temsil etmek için Base64 ile kodlanmış dizeler kullanılabilir. Örneğin, bir görüntü dize olarak kodlanabilir. Örnek, Base64String.txt'da base64 kodlu dize olarak bir görüntü içerir.

ASP.NET Core MVC, base64 ile kodlanmış bir dize alabilir ve bunu bayt dizisine dönüştürmek için bir ByteArrayModelBinder kullanabilir. Bağımsız ByteArrayModelBinderProvider değişkenleri ile ByteArrayModelBindereşlerbyte[]:

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 IModelBinderProvider türünüzü uygulayabilir veya kullanabilirsiniz ModelBinderAttribute.

Aşağıdaki örnekte base64 ile kodlanmış bir dizeyi bir dosyasına dönüştürmek ve sonucu bir dosyaya byte[] kaydetmek için nasıl kullanılacağı ByteArrayModelBinder gösterilmektedir:

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

Curl gibi bir araç kullanarak base64 ile kodlanmış bir dizeyi önceki api yöntemine GÖNDERebilirsiniz.

Bağlayıcı, istek verilerini uygun şekilde adlandırılmış özelliklere veya bağımsız değişkenlere bağlayabildiği sürece model bağlama başarılı olur. Aşağıdaki örnekte, görünüm modeliyle nasıl kullanılacağı ByteArrayModelBinder gösterilmektedir:

[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ı sağlayan özel bir model bağlayıcısı uygulayacağız:

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

Aşağıdaki örnek modelde özniteliğini ModelBinder 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 Author için kullanılacak türü IModelBinder belirtir.

Aşağıdaki AuthorEntityBinder sınıf, Entity Framework Core ve kullanarak Author bir veri kaynağından varlık getirerek bir authorIdparametre 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

Önceki AuthorEntityBinder sınıf, özel model bağlayıcısını göstermek için tasarlanmıştır. sınıfı, arama senaryosu için en iyi yöntemleri göstermek üzere tasarlanmamıştır. Arama için öğesini bağlayın authorId ve veritabanını bir eylem yönteminde sorgulayın. Bu yaklaşım model bağlama hatalarını durumlardan NotFound ayırır.

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

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

özniteliği, ModelBinder varsayılan kuralları kullanmayan parametrelere uygulamak AuthorEntityBinder 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 authorIdolmadığından özniteliği kullanılarak ModelBinder parametresinde belirtilir. Eylem yönteminde varlığı aramaya kıyasla hem denetleyici hem de eylem yöntemi basitleştirilmiştir. Entity Framework Core kullanarak yazarı getirme mantığı model bağlayıcısına taşınır. Modele bağlanan Author birkaç yönteminiz olduğunda bu önemli bir basitleştirme olabilir.

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

ModelBinderProvider Uygulama

Özniteliği uygulamak yerine uygulayabilirsiniz IModelBinderProvider. Yerleşik çerçeve bağlayıcıları bu şekilde uygulanır. Bağlayıcınızın üzerinde çalıştığı türü belirttiğinizde, ciltçinizin kabul ettiğiniz 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 eklendiğinde veya -typed parametrelerinde Author Authorözniteliğini ModelBinder kullanmanız gerekmez.

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 BinderTypeModelBinderdöndürür. BinderTypeModelBinder model bağlayıcıları için fabrika görevi görür ve bağımlılık ekleme (DI) sağlar. için AuthorEntityBinder DI'nin erişmesi EF Coregerekir. Model bağlayıcınız DI hizmetlerini gerektiriyorsa kullanın BinderTypeModelBinder .

Özel model bağlayıcı sağlayıcısı kullanmak için içine ekleyin 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);
}

Model bağlayıcıları değerlendirilirken sağlayıcı koleksiyonu sırayla inceleniyor. Bağlayıcı döndüren ilk sağlayıcı kullanılır. Sağlayıcınızın koleksiyonun sonuna eklenmesi, özel bağlayıcınızın bir şansı olmadan önce yerleşik bir model bağlayıcısının çağrılmasıyla sonuçlanabilir. Bu örnekte özel sağlayıcı, eylem bağımsız değişkenleri için kullanıldığından emin olmak için Author koleksiyonun başına eklenir.

Polimorfik model bağlama

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

  • Tüm dillerle birlikte çalışabilecek şekilde tasarlanmış bir REST API için tipik değildir.
  • İlişkili modeller hakkında mantık yürütmeyi zorlaştırır.

Ancak, bir uygulama çok biçimli model bağlaması gerektiriyorsa, bir uygulama aşağıdaki kod gibi görünebilir:

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ı ayarlamaya veya sonuçları döndürmeye çalışmamalıdır (örneğin, 404 Bulunamadı). Model bağlama başarısız olursa, eylem yönteminin içindeki bir eylem filtresi veya mantık hatayı işlemelidir.
  • Tekrarlanan kodu ve eylem yöntemlerinden çapraz kesme endişelerini ortadan kaldırmak için en kullanışlı olanlardır.
  • Genellikle bir dizeyi özel bir türe dönüştürmek için kullanılmamalıdır; TypeConverter genellikle daha iyi bir seçenektir.