Condividi tramite


Associazione di modelli personalizzata in ASP.NET Core

Di Kirk Larkin

L'associazione di modelli consente alle azioni del controller di lavorare direttamente con i tipi di modello (passati come argomenti del metodo), anziché con le richieste HTTP. Il mapping tra i dati delle richieste in ingresso e i modelli di applicazione viene gestito dagli strumenti di associazione di modelli. Gli sviluppatori possono estendere la funzionalità predefinita di associazione di modelli implementando gli strumenti di associazione di modelli personalizzati ( anche se in genere non è necessario scrivere il proprio provider).

Visualizzare o scaricare il codice di esempio (procedura per il download)

Limitazioni del gestore di associazione di modelli predefinite

Gli strumenti di associazione di modelli predefiniti supportano la maggior parte dei tipi di dati .NET Core comuni e devono soddisfare la maggior parte delle esigenze degli sviluppatori. Si prevede di associare l'input basato su testo dalla richiesta direttamente ai tipi di modello. Potrebbe essere necessario trasformare l'input prima di associarlo. Ad esempio, quando si dispone di una chiave che può essere usata per cercare i dati del modello. È possibile usare un associatore di modelli personalizzato per recuperare i dati in base alla chiave.

Associazione dei tipi semplici e complessi nei modelli

L'associazione di modelli usa definizioni specifiche per i tipi sui quali opera. Un tipo semplice viene convertito da una singola stringa utilizzando TypeConverter o un TryParse metodo . Un tipo complesso viene convertito da più valori di input. Il framework determina la differenza in base all'esistenza di un TypeConverter o TryParse. È consigliabile creare un convertitore di tipi o usare TryParse per una string conversione da a SomeType che non richiede risorse esterne o più input.

Per un elenco di tipi che il gestore di associazione di modelli può convertire da stringhe, vedere Tipi semplici .

Prima di creare uno strumento di associazione di modelli personalizzato, è opportuno esaminare il modo in cui vengono implementati gli strumenti di associazione di modelli esistenti. Si consideri l'oggetto ByteArrayModelBinder che può essere usato per convertire stringhe con codifica Base64 in matrici di byte. Le matrici di byte vengono spesso archiviate come file o campi BLOB di database.

Uso di ByteArrayModelBinder

Le stringhe con codifica Base64 possono essere usate per rappresentare i dati binari. Ad esempio, un'immagine può essere codificata come stringa. L'esempio include un'immagine come stringa codificata in base64 in Base64String.txt.

ASP.NET Core MVC può accettare una stringa con codifica Base64 e usare un oggetto ByteArrayModelBinder per convertirlo in una matrice di byte. ByteArrayModelBinderProvider mappa gli argomenti byte[] in 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;
}

Quando si crea un gestore di associazione di modelli personalizzato, è possibile implementare il proprio tipo IModelBinderProvider o utilizzare il ModelBinderAttribute.

Nell'esempio seguente viene illustrato come usare ByteArrayModelBinder per convertire una stringa con codifica Base64 in un byte[] oggetto e salvare il risultato in un file:

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

Per visualizzare i commenti del codice tradotti in lingue diverse dall'inglese, segnalarlo in questo problema di discussione su GitHub.

È possibile pubblicare una stringa con codifica Base64 al metodo api precedente usando uno strumento come curl.

Se il binder può associare i dati della richiesta a proprietà o argomenti denominati in modo appropriato, l'associazione di modelli avrà esito positivo. L'esempio seguente illustra come usare ByteArrayModelBinder con un modello di visualizzazione:

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

Esempio di custom model binder per modello personalizzato

In questa sezione implementeremo un binder di modelli personalizzato che:

  • Converte i dati richieste ricevute in argomenti chiave fortemente tipizzati.
  • Usa Entity Framework Core per recuperare l'entità associata.
  • Passa l'entità associata come argomento al metodo di azione.

L'esempio seguente usa l'attributo ModelBinder nel Author modello:

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

Nel codice precedente, l'attributo ModelBinder specifica il tipo di IModelBinder che deve essere utilizzato per associare i parametri di azione Author.

La classe seguente AuthorEntityBinder associa un Author parametro recuperando l'entità da un'origine dati usando Entity Framework Core e un oggetto 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

La classe precedente AuthorEntityBinder è progettata per illustrare un gestore di associazione di modelli personalizzato. La classe non è progettata per illustrare le procedure consigliate per uno scenario di ricerca. Per la ricerca, associare il authorId ed eseguire query sul database in un metodo di azione. Questo approccio separa gli errori di associazione di modelli dai NotFound casi.

Il codice seguente illustra come usare il AuthorEntityBinder in un metodo d'azione.

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

    return Ok(author);
}

L'attributo ModelBinder può essere usato per applicare a AuthorEntityBinder parametri che non usano convenzioni predefinite:

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

    return Ok(author);
}

In questo esempio, poiché il nome dell'argomento non è l'impostazione predefinita authorId, viene specificato nel parametro usando l'attributo ModelBinder . Sia il controller che il metodo di azione risultano semplificati rispetto al procedimento di ricerca dell'entità all'interno del metodo di azione. La logica per recuperare l'autore usando Entity Framework Core viene spostata nello strumento di associazione di modelli. Questa può essere una notevole semplificazione quando si dispone di diversi metodi che si associano al Author modello.

È possibile applicare l'attributo ModelBinder alle singole proprietà del modello (ad esempio in un modello di visualizzazione) o ai parametri del metodo di azione per specificare un determinato gestore di associazione di modelli o un nome di modello solo per quel tipo o azione.

Implementazione di un ModelBinderProvider

Anziché applicare un attributo, è possibile implementare IModelBinderProvider. Questo è il modo in cui vengono implementati i binder integrati nel framework. Quando si specifica il tipo su cui opera il binder, si stabilisce il tipo di argomento da esso prodotto, non l'input che il binder accetta. Il seguente fornitore di raccoglitori funziona con il AuthorEntityBinder. Quando viene aggiunto alla raccolta di provider di MVC, non è necessario usare l'attributo ModelBinder nei parametri di tipo Author o 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;
        }
    }
}

Nota: il codice precedente restituisce un oggetto BinderTypeModelBinder. BinderTypeModelBinder funge da factory per gli strumenti di associazione di modelli e fornisce l'inserimento delle dipendenze. Il AuthorEntityBinder richiede l'injection di dipendenze per accedere al EF Core. Usare BinderTypeModelBinder se lo strumento di associazione di modelli richiede servizi di inserimento delle dipendenze.

Per utilizzare un fornitore di associatore di modelli personalizzato, aggiungerlo in ConfigureServices:

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

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

Quando si valutano le associazioni di modelli, la collezione di provider viene esaminata in ordine. Viene usato il primo provider che restituisce un binder che corrisponde al modello di input. L'aggiunta del provider alla fine della raccolta può quindi comportare la chiamata di un gestore di associazione di modelli predefinito prima che il gestore di associazione personalizzato abbia la possibilità. In questo esempio, il provider personalizzato viene aggiunto all'inizio della raccolta per assicurarsi che esso venga sempre usato per gli argomenti dell'azione Author.

Associazione di modelli polimorfici

L'associazione a modelli diversi di tipi derivati è nota come associazione di modelli polimorfici. L'associazione di modelli personalizzati polimorfici è necessaria quando il valore della richiesta deve essere associato al tipo di modello derivato specifico. Associazione di modelli polimorfici:

  • Non è tipico per un'API REST progettata per interagire con tutti i linguaggi.
  • Rende difficile ragionare sui modelli associati.

Tuttavia, se un'app richiede l'associazione di modelli polimorfici, un'implementazione potrebbe essere simile al codice seguente:

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

Raccomandazioni e procedure consigliate

Strumenti di associazione di modelli personalizzati:

  • Non è consigliabile tentare di impostare codici di stato o restituire risultati (ad esempio, 404 Non trovato). Se l'associazione di modelli ha esito negativo, un filtro di azione o una logica all'interno del metodo di azione stesso deve gestire l'errore.
  • Sono particolarmente utili per eliminare il codice ripetitivo e le problematiche trasversali dai metodi di azione.
  • In genere non deve essere usato per convertire una stringa in un tipo personalizzato, un TypeConverter è in genere un'opzione migliore.

Di Steve Smith

L'associazione di modelli consente alle azioni del controller di lavorare direttamente con i tipi di modello (passati come argomenti del metodo), anziché con le richieste HTTP. Il mapping tra i dati delle richieste in ingresso e i modelli di applicazione viene gestito dagli strumenti di associazione di modelli. Gli sviluppatori possono estendere la funzionalità predefinita di associazione di modelli implementando gli strumenti di associazione di modelli personalizzati ( anche se in genere non è necessario scrivere il proprio provider).

Visualizzare o scaricare il codice di esempio (procedura per il download)

Limitazioni del gestore di associazione di modelli predefinite

Gli strumenti di associazione di modelli predefiniti supportano la maggior parte dei tipi di dati .NET Core comuni e devono soddisfare la maggior parte delle esigenze degli sviluppatori. Si prevede di associare l'input basato su testo dalla richiesta direttamente ai tipi di modello. Potrebbe essere necessario trasformare l'input prima di associarlo. Ad esempio, quando si dispone di una chiave che può essere usata per cercare i dati del modello. È possibile usare un associatore di modelli personalizzato per recuperare i dati in base alla chiave.

Revisione dell'associazione di modelli

L'associazione di modelli usa definizioni specifiche per i tipi sui quali opera. Un tipo semplice viene convertito da una singola stringa nell'input. Un tipo complesso viene convertito da più valori di input. Il framework determina la differenza in base all'esistenza di un oggetto TypeConverter. È consigliabile creare un convertitore di tipi se si dispone di un semplice string>SomeType mapping che non richiede risorse esterne.

Prima di creare uno strumento di associazione di modelli personalizzato, è opportuno esaminare il modo in cui vengono implementati gli strumenti di associazione di modelli esistenti. Si consideri l'oggetto ByteArrayModelBinder che può essere usato per convertire stringhe con codifica Base64 in matrici di byte. Le matrici di byte vengono spesso archiviate come file o campi BLOB di database.

Uso di ByteArrayModelBinder

Le stringhe con codifica Base64 possono essere usate per rappresentare i dati binari. Ad esempio, un'immagine può essere codificata come stringa. L'esempio include un'immagine come stringa codificata in base64 in Base64String.txt.

ASP.NET Core MVC può accettare una stringa con codifica Base64 e usare un oggetto ByteArrayModelBinder per convertirlo in una matrice di byte. ByteArrayModelBinderProvider mappa gli argomenti byte[] in 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;
}

Quando si crea un gestore di associazione di modelli personalizzato, è possibile implementare il proprio tipo IModelBinderProvider o utilizzare il ModelBinderAttribute.

Nell'esempio seguente viene illustrato come usare ByteArrayModelBinder per convertire una stringa con codifica Base64 in un byte[] oggetto e salvare il risultato in un file:

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

È possibile pubblicare una stringa con codifica Base64 al metodo api precedente usando uno strumento come curl.

Se il binder può associare i dati della richiesta a proprietà o argomenti denominati in modo appropriato, l'associazione di modelli avrà esito positivo. L'esempio seguente illustra come usare ByteArrayModelBinder con un modello di visualizzazione:

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

Esempio di custom model binder per modello personalizzato

In questa sezione implementeremo un binder di modelli personalizzato che:

  • Converte i dati richieste ricevute in argomenti chiave fortemente tipizzati.
  • Usa Entity Framework Core per recuperare l'entità associata.
  • Passa l'entità associata come argomento al metodo di azione.

L'esempio seguente usa l'attributo ModelBinder nel Author modello:

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

Nel codice precedente, l'attributo ModelBinder specifica il tipo di IModelBinder che deve essere utilizzato per associare i parametri di azione Author.

La classe seguente AuthorEntityBinder associa un Author parametro recuperando l'entità da un'origine dati usando Entity Framework Core e un oggetto 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

La classe precedente AuthorEntityBinder è progettata per illustrare un gestore di associazione di modelli personalizzato. La classe non è progettata per illustrare le procedure consigliate per uno scenario di ricerca. Per la ricerca, associare il authorId ed eseguire query sul database in un metodo di azione. Questo approccio separa gli errori di associazione di modelli dai NotFound casi.

Il codice seguente illustra come usare il AuthorEntityBinder in un metodo d'azione.

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

L'attributo ModelBinder può essere usato per applicare a AuthorEntityBinder parametri che non usano convenzioni predefinite:

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

    return Ok(author);
}

In questo esempio, poiché il nome dell'argomento non è l'impostazione predefinita authorId, viene specificato nel parametro usando l'attributo ModelBinder . Sia il controller che il metodo di azione risultano semplificati rispetto al procedimento di ricerca dell'entità all'interno del metodo di azione. La logica per recuperare l'autore usando Entity Framework Core viene spostata nello strumento di associazione di modelli. Questa può essere una notevole semplificazione quando si dispone di diversi metodi che si associano al Author modello.

È possibile applicare l'attributo ModelBinder alle singole proprietà del modello (ad esempio in un modello di visualizzazione) o ai parametri del metodo di azione per specificare un determinato gestore di associazione di modelli o un nome di modello solo per quel tipo o azione.

Implementazione di un ModelBinderProvider

Anziché applicare un attributo, è possibile implementare IModelBinderProvider. Questo è il modo in cui vengono implementati i binder integrati nel framework. Quando si specifica il tipo su cui opera il binder, si stabilisce il tipo di argomento da esso prodotto, non l'input che il binder accetta. Il seguente fornitore di raccoglitori funziona con il AuthorEntityBinder. Quando viene aggiunto alla raccolta di provider di MVC, non è necessario usare l'attributo ModelBinder nei parametri di tipo Author o 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;
        }
    }
}

Nota: il codice precedente restituisce un oggetto BinderTypeModelBinder. BinderTypeModelBinder funge da factory per gli strumenti di associazione di modelli e fornisce l'inserimento delle dipendenze. Il AuthorEntityBinder richiede l'injection di dipendenze per accedere al EF Core. Usare BinderTypeModelBinder se lo strumento di associazione di modelli richiede servizi di inserimento delle dipendenze.

Per utilizzare un fornitore di associatore di modelli personalizzato, aggiungerlo in 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);
}

Quando si valutano le associazioni di modelli, la collezione di provider viene esaminata in ordine. Viene utilizzato il primo provider che restituisce un binder. L'aggiunta del provider alla fine della raccolta può comportare la chiamata di un gestore di associazione di modelli predefinito prima che il gestore di associazione personalizzato abbia la possibilità. In questo esempio il provider personalizzato viene aggiunto all'inizio della raccolta per assicurarsi che venga usato per Author gli argomenti dell'azione.

Associazione di modelli polimorfici

L'associazione a modelli diversi di tipi derivati è nota come associazione di modelli polimorfici. L'associazione di modelli personalizzati polimorfici è necessaria quando il valore della richiesta deve essere associato al tipo di modello derivato specifico. Associazione di modelli polimorfici:

  • Non è tipico per un'API REST progettata per interagire con tutti i linguaggi.
  • Rende difficile ragionare sui modelli associati.

Tuttavia, se un'app richiede l'associazione di modelli polimorfici, un'implementazione potrebbe essere simile al codice seguente:

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

Raccomandazioni e procedure consigliate

Strumenti di associazione di modelli personalizzati:

  • Non è consigliabile tentare di impostare codici di stato o restituire risultati (ad esempio, 404 Non trovato). Se l'associazione di modelli ha esito negativo, un filtro di azione o una logica all'interno del metodo di azione stesso deve gestire l'errore.
  • Sono particolarmente utili per eliminare il codice ripetitivo e le problematiche trasversali dai metodi di azione.
  • In genere non deve essere usato per convertire una stringa in un tipo personalizzato, un TypeConverter è in genere un'opzione migliore.