Associazione di modelli personalizzata in ASP.NET CoreCustom Model Binding in ASP.NET Core

Di Steve Smith e Kirk LarkinBy Steve Smith and Kirk Larkin

Mediante l'associazione di modelli le azioni dei controller possono operare direttamente con i tipi di modello (passati come argomenti dei metodi), anziché con le richieste HTTP.Model binding allows controller actions to work directly with model types (passed in as method arguments), rather than HTTP requests. Il mapping tra i dati delle richieste in ingresso e i modelli applicativi è gestito dagli strumenti di associazione di modelli.Mapping between incoming request data and application models is handled by model binders. Gli sviluppatori possono estendere la funzionalità di associazione di modelli predefinita implementando strumenti di associazione di modelli personalizzati (anche se in genere non è necessario creare un provider personalizzato).Developers can extend the built-in model binding functionality by implementing custom model binders (though typically, you don't need to write your own provider).

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

Limiti degli strumenti di associazione di modelli predefinitiDefault model binder limitations

Gli strumenti di associazione di modelli predefiniti supportano la maggior parte dei tipi di dati .NET Core comuni e soddisfano le esigenze della maggior parte degli sviluppatori.The default model binders support most of the common .NET Core data types and should meet most developers' needs. Tali strumenti associano direttamente l'input di testo della richiesta ai tipi di modello.They expect to bind text-based input from the request directly to model types. Può essere necessario trasformare l'input prima dell'associazione.You might need to transform the input prior to binding it. Un esempio è il caso in cui è presente una chiave che può essere usata per cercare i dati del modello.For example, when you have a key that can be used to look up model data. È possibile usare uno strumento di associazione di modelli personalizzato per il recupero di dati in base alla chiave.You can use a custom model binder to fetch data based on the key.

Analisi dell'associazione di modelliModel binding review

L'associazione di modelli usa definizioni specifiche per i tipi sui quali opera.Model binding uses specific definitions for the types it operates on. Un tipo semplice viene convertito da una stringa singola dell'input.A simple type is converted from a single string in the input. Un tipo complesso viene convertito da più valori di input.A complex type is converted from multiple input values. Il framework determina la differenza in base all'esistenza di un elemento TypeConverter.The framework determines the difference based on the existence of a TypeConverter. È consigliabile creare un convertitore di tipi se è presente un mapping string -> SomeType semplice che non richiede risorse esterne.We recommended you create a type converter if you have a simple string -> SomeType mapping that doesn't require external resources.

Prima di creare uno strumento di associazione di modelli personalizzato, è utile esaminare le modalità di implementazione degli strumenti di associazione di modelli esistenti.Before creating your own custom model binder, it's worth reviewing how existing model binders are implemented. Si consideri il ByteArrayModelBinder che può essere usato per convertire le stringhe con codifica Base64 in matrici di byte.Consider the ByteArrayModelBinder which can be used to convert base64-encoded strings into byte arrays. Le matrici di byte vengono spesso archiviate come file o campi BLOB del database.The byte arrays are often stored as files or database BLOB fields.

Uso di ByteArrayModelBinderWorking with the ByteArrayModelBinder

Le stringhe con codifica Base64 possono essere usate per rappresentare i dati binari.Base64-encoded strings can be used to represent binary data. Un'immagine, ad esempio, può essere codificata come stringa.For example, an image can be encoded as a string. Nell'esempio è inclusa un'immagine come stringa con codifica Base64 in Base64String. txt.The sample includes an image as a base64-encoded string in Base64String.txt.

ASP.NET Core MVC può accettare una stringa con codifica base64 e usare un oggetto ByteArrayModelBinder per convertirla in una matrice di byte.ASP.NET Core MVC can take a base64-encoded string and use a ByteArrayModelBinder to convert it into a byte array. Il ByteArrayModelBinderProvider esegue il mapping degli argomenti di byte[] a ByteArrayModelBinder:The ByteArrayModelBinderProvider maps byte[] arguments to 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 di IModelBinderProvider o usare il ModelBinderAttribute.When creating your own custom model binder, you can implement your own IModelBinderProvider type, or use the ModelBinderAttribute.

L'esempio indica come usare ByteArrayModelBinder per convertire una stringa con codifica base64 in un byte[] e salvare il risultato in un file:The following example shows how to use ByteArrayModelBinder to convert a base64-encoded string to a byte[] and save the result to a 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, segnalare il problema in questo argomento di discussione su GitHub.If you would like to see code comments translated to languages other than English, let us know in this GitHub discussion issue.

È possibile pubblicare (POST) una stringa con codifica base64 in questo metodo API usando uno strumento come Postman:You can POST a base64-encoded string to this api method using a tool like Postman:

Postmanpostman

Se lo strumento di associazione riesce ad associare i dati della richiesta a proprietà o argomenti denominati in modo appropriato, l'associazione di modelli ha esito positivo.As long as the binder can bind request data to appropriately named properties or arguments, model binding will succeed. L'esempio seguente indica come usare ByteArrayModelBinder con un modello di visualizzazione:The following example shows how to use ByteArrayModelBinder with a view model:

[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 strumento di associazione di modelli personalizzatoCustom model binder sample

In questa sezione viene implementato uno strumento di associazione di modelli personalizzato che:In this section we'll implement a custom model binder that:

  • Converte i dati della richiesta in ingresso in argomenti chiave fortemente tipizzati.Converts incoming request data into strongly typed key arguments.
  • Usa Entity Framework Core per recuperare l'entità associata.Uses Entity Framework Core to fetch the associated entity.
  • Passa l'entità associata come argomento al metodo di azione.Passes the associated entity as an argument to the action method.

L'esempio seguente usa l'attributo ModelBinder nel modello Author:The following sample uses the ModelBinder attribute on the Author model:

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 da usare per associare i parametri di azione Author.In the preceding code, the ModelBinder attribute specifies the type of IModelBinder that should be used to bind Author action parameters.

La classe AuthorEntityBinder seguente associa un parametro Author recuperando l'entità da un'origine dati tramite Entity Framework Core e un authorId:The following AuthorEntityBinder class binds an Author parameter by fetching the entity from a data source using Entity Framework Core and an 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;
    }
}

Nota

La classe AuthorEntityBinder precedente è destinata a illustrare uno strumento di associazione di modelli personalizzato.The preceding AuthorEntityBinder class is intended to illustrate a custom model binder. La classe non ha lo scopo di illustrare procedure consigliate per uno scenario di ricerca.The class isn't intended to illustrate best practices for a lookup scenario. Per la ricerca, associare l'elemento authorId ed eseguire query nel database in un metodo di azione.For lookup, bind the authorId and query the database in an action method. Questo approccio separa gli errori di associazione di modelli dai casi NotFound.This approach separates model binding failures from NotFound cases.

L'esempio di codice seguente indica come usare AuthorEntityBinder in un metodo di azione:The following code shows how to use the AuthorEntityBinder in an action method:

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

    return Ok(author);
}

L'attributo ModelBinder può essere usato per applicare gli elementi AuthorEntityBinder ai parametri che non usano convenzioni predefinite:The ModelBinder attribute can be used to apply the AuthorEntityBinder to parameters that don't use default conventions:

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

    return Ok(author);
}

In questo esempio, dato che il nome dell'argomento non è il valore authorId predefinito, viene specificato nel parametro usando l'attributo ModelBinder.In this example, since the name of the argument isn't the default authorId, it's specified on the parameter using the ModelBinder attribute. Il controller e il metodo di azione sono semplificati rispetto alla ricerca dell'entità nel metodo di azione.Both the controller and action method are simplified compared to looking up the entity in the action method. La logica per recuperare l'autore usando Entity Framework Core viene spostata nello strumento di associazione di modelli.The logic to fetch the author using Entity Framework Core is moved to the model binder. Ciò può rappresentare una semplificazione notevole in presenza di vari metodi associati al modello Author.This can be a considerable simplification when you have several methods that bind to the Author model.

È possibile applicare l'attributo ModelBinder a singole proprietà del modello (ad esempio a ViewModel) o a parametri del metodo di azione per specificare un determinato strumento di associazione di modelli o nome di modello solo per quel determinato tipo o azione.You can apply the ModelBinder attribute to individual model properties (such as on a viewmodel) or to action method parameters to specify a certain model binder or model name for just that type or action.

Implementazione di un elemento ModelBinderProviderImplementing a ModelBinderProvider

Anziché applicare un attributo, è possibile implementare IModelBinderProvider.Instead of applying an attribute, you can implement IModelBinderProvider. Gli strumenti di associazione del framework incorporati vengono implementati in questo modo.This is how the built-in framework binders are implemented. Quando si specifica il tipo sul quale opera lo strumento di associazione, si indica il tipo di argomento prodotto dallo strumento, non l'input accettato dallo strumento.When you specify the type your binder operates on, you specify the type of argument it produces, not the input your binder accepts. Il provider strumento di associazione seguente funziona con l'elemento AuthorEntityBinder.The following binder provider works with the AuthorEntityBinder. Quando viene aggiunto alla raccolta di provider di MVC, non è necessario usare l'attributo ModelBinder sui parametri tipizzati Author o Author.When it's added to MVC's collection of providers, you don't need to use the ModelBinder attribute on Author or Author-typed parameters.

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 elemento BinderTypeModelBinder.Note: The preceding code returns a BinderTypeModelBinder. BinderTypeModelBinder funziona come factory per gli strumenti di associazione di modelli e implementa la funzionalità DI (Dependency Injection, Inserimento di dipendenze).BinderTypeModelBinder acts as a factory for model binders and provides dependency injection (DI). AuthorEntityBinder richiede DI (Dependency Injection, Inserimento di dipendenze) per l'accesso a EF Core.The AuthorEntityBinder requires DI to access EF Core. Usare BinderTypeModelBinder se lo strumento di associazione di modelli richiede servizi da DI (Dependency Injection, Inserimento di dipendenze).Use BinderTypeModelBinder if your model binder requires services from DI.

Per usare un provider dello strumento di associazione di modelli personalizzato, aggiungerlo in ConfigureServices:To use a custom model binder provider, add it in ConfigureServices:

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

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

Durante la valutazione degli strumenti di associazione di modelli, la raccolta di provider viene esaminata in ordine.When evaluating model binders, the collection of providers is examined in order. Viene usato il primo provider che restituisce uno strumento di associazione.The first provider that returns a binder is used. Se il provider personalizzato viene aggiunto alla fine della raccolta, è possibile che uno strumento di associazione di modelli incorporato venga chiamato prima dello strumento di associazione di modelli personalizzato.Adding your provider to the end of the collection may result in a built-in model binder being called before your custom binder has a chance. In questo esempio il provider personalizzato viene aggiunto all'inizio della raccolta, per garantire che venga usato per gli argomenti dell'azione Author.In this example, the custom provider is added to the beginning of the collection to ensure it's used for Author action arguments.

Associazione di modelli polimorficiPolymorphic model binding

L'associazione a modelli diversi di tipi derivati è nota come associazione di modelli polimorfici.Binding to different models of derived types is known as polymorphic model binding. L'associazione di modelli personalizzati polimorfici è obbligatoria quando il valore della richiesta deve essere associato al tipo di modello derivato specifico.Polymorphic custom model binding is required when the request value must be bound to the specific derived model type. Associazione di modelli polimorfici:Polymorphic model binding:

  • Non è tipico per un'API REST progettata per interagire con tutti i linguaggi.Isn't typical for a REST API that's designed to interoperate with all languages.
  • Rende difficile la motivazione dei modelli associati.Makes it difficult to reason about the bound models.

Tuttavia, se un'app richiede l'associazione di modelli polimorfici, un'implementazione potrebbe avere un aspetto simile al codice seguente:However, if an app requires polymorphic model binding, an implementation might look like the following code:

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] = new ValidationStateEntry
            {
                Metadata = modelMetadata,
            };
        }
    }
}

Suggerimenti e procedure ottimaliRecommendations and best practices

Gli strumenti di associazione di modelli personalizzati:Custom model binders:

  • Non devono provare a impostare codici di stato o restituire risultati (ad esempio 404 Non trovato).Shouldn't attempt to set status codes or return results (for example, 404 Not Found). Se si verifica un errore nell'associazione di modelli, l'errore deve essere gestito da un filtro azioni o da logica inclusa nel metodo di azione.If model binding fails, an action filter or logic within the action method itself should handle the failure.
  • Sono particolarmente utili per eliminare codice ripetitivo e problemi di montaggio incrociato dai metodi di azione.Are most useful for eliminating repetitive code and cross-cutting concerns from action methods.
  • In genere non è consigliabile usare per convertire una stringa in un tipo personalizzato, un TypeConverter è in genere un'opzione migliore.Typically shouldn't be used to convert a string into a custom type, a TypeConverter is usually a better option.

Steve SmithBy Steve Smith

Mediante l'associazione di modelli le azioni dei controller possono operare direttamente con i tipi di modello (passati come argomenti dei metodi), anziché con le richieste HTTP.Model binding allows controller actions to work directly with model types (passed in as method arguments), rather than HTTP requests. Il mapping tra i dati delle richieste in ingresso e i modelli applicativi è gestito dagli strumenti di associazione di modelli.Mapping between incoming request data and application models is handled by model binders. Gli sviluppatori possono estendere la funzionalità di associazione di modelli predefinita implementando strumenti di associazione di modelli personalizzati (anche se in genere non è necessario creare un provider personalizzato).Developers can extend the built-in model binding functionality by implementing custom model binders (though typically, you don't need to write your own provider).

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

Limiti degli strumenti di associazione di modelli predefinitiDefault model binder limitations

Gli strumenti di associazione di modelli predefiniti supportano la maggior parte dei tipi di dati .NET Core comuni e soddisfano le esigenze della maggior parte degli sviluppatori.The default model binders support most of the common .NET Core data types and should meet most developers' needs. Tali strumenti associano direttamente l'input di testo della richiesta ai tipi di modello.They expect to bind text-based input from the request directly to model types. Può essere necessario trasformare l'input prima dell'associazione.You might need to transform the input prior to binding it. Un esempio è il caso in cui è presente una chiave che può essere usata per cercare i dati del modello.For example, when you have a key that can be used to look up model data. È possibile usare uno strumento di associazione di modelli personalizzato per il recupero di dati in base alla chiave.You can use a custom model binder to fetch data based on the key.

Analisi dell'associazione di modelliModel binding review

L'associazione di modelli usa definizioni specifiche per i tipi sui quali opera.Model binding uses specific definitions for the types it operates on. Un tipo semplice viene convertito da una stringa singola dell'input.A simple type is converted from a single string in the input. Un tipo complesso viene convertito da più valori di input.A complex type is converted from multiple input values. Il framework determina la differenza in base all'esistenza di un elemento TypeConverter.The framework determines the difference based on the existence of a TypeConverter. È consigliabile creare un convertitore di tipi se è presente un mapping string -> SomeType semplice che non richiede risorse esterne.We recommended you create a type converter if you have a simple string -> SomeType mapping that doesn't require external resources.

Prima di creare uno strumento di associazione di modelli personalizzato, è utile esaminare le modalità di implementazione degli strumenti di associazione di modelli esistenti.Before creating your own custom model binder, it's worth reviewing how existing model binders are implemented. Si consideri il ByteArrayModelBinder che può essere usato per convertire le stringhe con codifica Base64 in matrici di byte.Consider the ByteArrayModelBinder which can be used to convert base64-encoded strings into byte arrays. Le matrici di byte vengono spesso archiviate come file o campi BLOB del database.The byte arrays are often stored as files or database BLOB fields.

Uso di ByteArrayModelBinderWorking with the ByteArrayModelBinder

Le stringhe con codifica Base64 possono essere usate per rappresentare i dati binari.Base64-encoded strings can be used to represent binary data. Un'immagine, ad esempio, può essere codificata come stringa.For example, an image can be encoded as a string. Nell'esempio è inclusa un'immagine come stringa con codifica Base64 in Base64String. txt.The sample includes an image as a base64-encoded string in Base64String.txt.

ASP.NET Core MVC può accettare una stringa con codifica base64 e usare un oggetto ByteArrayModelBinder per convertirla in una matrice di byte.ASP.NET Core MVC can take a base64-encoded string and use a ByteArrayModelBinder to convert it into a byte array. Il ByteArrayModelBinderProvider esegue il mapping degli argomenti di byte[] a ByteArrayModelBinder:The ByteArrayModelBinderProvider maps byte[] arguments to 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 di IModelBinderProvider o usare il ModelBinderAttribute.When creating your own custom model binder, you can implement your own IModelBinderProvider type, or use the ModelBinderAttribute.

L'esempio indica come usare ByteArrayModelBinder per convertire una stringa con codifica base64 in un byte[] e salvare il risultato in un file:The following example shows how to use ByteArrayModelBinder to convert a base64-encoded string to a byte[] and save the result to a 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 (POST) una stringa con codifica base64 in questo metodo API usando uno strumento come Postman:You can POST a base64-encoded string to this api method using a tool like Postman:

Postmanpostman

Se lo strumento di associazione riesce ad associare i dati della richiesta a proprietà o argomenti denominati in modo appropriato, l'associazione di modelli ha esito positivo.As long as the binder can bind request data to appropriately named properties or arguments, model binding will succeed. L'esempio seguente indica come usare ByteArrayModelBinder con un modello di visualizzazione:The following example shows how to use ByteArrayModelBinder with a view model:

[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 strumento di associazione di modelli personalizzatoCustom model binder sample

In questa sezione viene implementato uno strumento di associazione di modelli personalizzato che:In this section we'll implement a custom model binder that:

  • Converte i dati della richiesta in ingresso in argomenti chiave fortemente tipizzati.Converts incoming request data into strongly typed key arguments.
  • Usa Entity Framework Core per recuperare l'entità associata.Uses Entity Framework Core to fetch the associated entity.
  • Passa l'entità associata come argomento al metodo di azione.Passes the associated entity as an argument to the action method.

L'esempio seguente usa l'attributo ModelBinder nel modello Author:The following sample uses the ModelBinder attribute on the Author model:

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 da usare per associare i parametri di azione Author.In the preceding code, the ModelBinder attribute specifies the type of IModelBinder that should be used to bind Author action parameters.

La classe AuthorEntityBinder seguente associa un parametro Author recuperando l'entità da un'origine dati tramite Entity Framework Core e un authorId:The following AuthorEntityBinder class binds an Author parameter by fetching the entity from a data source using Entity Framework Core and an 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;
    }
}

Nota

La classe AuthorEntityBinder precedente è destinata a illustrare uno strumento di associazione di modelli personalizzato.The preceding AuthorEntityBinder class is intended to illustrate a custom model binder. La classe non ha lo scopo di illustrare procedure consigliate per uno scenario di ricerca.The class isn't intended to illustrate best practices for a lookup scenario. Per la ricerca, associare l'elemento authorId ed eseguire query nel database in un metodo di azione.For lookup, bind the authorId and query the database in an action method. Questo approccio separa gli errori di associazione di modelli dai casi NotFound.This approach separates model binding failures from NotFound cases.

L'esempio di codice seguente indica come usare AuthorEntityBinder in un metodo di azione:The following code shows how to use the AuthorEntityBinder in an action method:

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

L'attributo ModelBinder può essere usato per applicare gli elementi AuthorEntityBinder ai parametri che non usano convenzioni predefinite:The ModelBinder attribute can be used to apply the AuthorEntityBinder to parameters that don't use default conventions:

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

    return Ok(author);
}

In questo esempio, dato che il nome dell'argomento non è il valore authorId predefinito, viene specificato nel parametro usando l'attributo ModelBinder.In this example, since the name of the argument isn't the default authorId, it's specified on the parameter using the ModelBinder attribute. Il controller e il metodo di azione sono semplificati rispetto alla ricerca dell'entità nel metodo di azione.Both the controller and action method are simplified compared to looking up the entity in the action method. La logica per recuperare l'autore usando Entity Framework Core viene spostata nello strumento di associazione di modelli.The logic to fetch the author using Entity Framework Core is moved to the model binder. Ciò può rappresentare una semplificazione notevole in presenza di vari metodi associati al modello Author.This can be a considerable simplification when you have several methods that bind to the Author model.

È possibile applicare l'attributo ModelBinder a singole proprietà del modello (ad esempio a ViewModel) o a parametri del metodo di azione per specificare un determinato strumento di associazione di modelli o nome di modello solo per quel determinato tipo o azione.You can apply the ModelBinder attribute to individual model properties (such as on a viewmodel) or to action method parameters to specify a certain model binder or model name for just that type or action.

Implementazione di un elemento ModelBinderProviderImplementing a ModelBinderProvider

Anziché applicare un attributo, è possibile implementare IModelBinderProvider.Instead of applying an attribute, you can implement IModelBinderProvider. Gli strumenti di associazione del framework incorporati vengono implementati in questo modo.This is how the built-in framework binders are implemented. Quando si specifica il tipo sul quale opera lo strumento di associazione, si indica il tipo di argomento prodotto dallo strumento, non l'input accettato dallo strumento.When you specify the type your binder operates on, you specify the type of argument it produces, not the input your binder accepts. Il provider strumento di associazione seguente funziona con l'elemento AuthorEntityBinder.The following binder provider works with the AuthorEntityBinder. Quando viene aggiunto alla raccolta di provider di MVC, non è necessario usare l'attributo ModelBinder sui parametri tipizzati Author o Author.When it's added to MVC's collection of providers, you don't need to use the ModelBinder attribute on Author or Author-typed parameters.

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 elemento BinderTypeModelBinder.Note: The preceding code returns a BinderTypeModelBinder. BinderTypeModelBinder funziona come factory per gli strumenti di associazione di modelli e implementa la funzionalità DI (Dependency Injection, Inserimento di dipendenze).BinderTypeModelBinder acts as a factory for model binders and provides dependency injection (DI). AuthorEntityBinder richiede DI (Dependency Injection, Inserimento di dipendenze) per l'accesso a EF Core.The AuthorEntityBinder requires DI to access EF Core. Usare BinderTypeModelBinder se lo strumento di associazione di modelli richiede servizi da DI (Dependency Injection, Inserimento di dipendenze).Use BinderTypeModelBinder if your model binder requires services from DI.

Per usare un provider dello strumento di associazione di modelli personalizzato, aggiungerlo in ConfigureServices:To use a custom model binder provider, add it 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);
}

Durante la valutazione degli strumenti di associazione di modelli, la raccolta di provider viene esaminata in ordine.When evaluating model binders, the collection of providers is examined in order. Viene usato il primo provider che restituisce uno strumento di associazione.The first provider that returns a binder is used. Se il provider personalizzato viene aggiunto alla fine della raccolta, è possibile che uno strumento di associazione di modelli incorporato venga chiamato prima dello strumento di associazione di modelli personalizzato.Adding your provider to the end of the collection may result in a built-in model binder being called before your custom binder has a chance. In questo esempio il provider personalizzato viene aggiunto all'inizio della raccolta, per garantire che venga usato per gli argomenti dell'azione Author.In this example, the custom provider is added to the beginning of the collection to ensure it's used for Author action arguments.

Associazione di modelli polimorficiPolymorphic model binding

L'associazione a modelli diversi di tipi derivati è nota come associazione di modelli polimorfici.Binding to different models of derived types is known as polymorphic model binding. L'associazione di modelli personalizzati polimorfici è obbligatoria quando il valore della richiesta deve essere associato al tipo di modello derivato specifico.Polymorphic custom model binding is required when the request value must be bound to the specific derived model type. Associazione di modelli polimorfici:Polymorphic model binding:

  • Non è tipico per un'API REST progettata per interagire con tutti i linguaggi.Isn't typical for a REST API that's designed to interoperate with all languages.
  • Rende difficile la motivazione dei modelli associati.Makes it difficult to reason about the bound models.

Tuttavia, se un'app richiede l'associazione di modelli polimorfici, un'implementazione potrebbe avere un aspetto simile al codice seguente:However, if an app requires polymorphic model binding, an implementation might look like the following code:

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] = new ValidationStateEntry
            {
                Metadata = modelMetadata,
            };
        }
    }
}

Suggerimenti e procedure ottimaliRecommendations and best practices

Gli strumenti di associazione di modelli personalizzati:Custom model binders:

  • Non devono provare a impostare codici di stato o restituire risultati (ad esempio 404 Non trovato).Shouldn't attempt to set status codes or return results (for example, 404 Not Found). Se si verifica un errore nell'associazione di modelli, l'errore deve essere gestito da un filtro azioni o da logica inclusa nel metodo di azione.If model binding fails, an action filter or logic within the action method itself should handle the failure.
  • Sono particolarmente utili per eliminare codice ripetitivo e problemi di montaggio incrociato dai metodi di azione.Are most useful for eliminating repetitive code and cross-cutting concerns from action methods.
  • In genere non è consigliabile usare per convertire una stringa in un tipo personalizzato, un TypeConverter è in genere un'opzione migliore.Typically shouldn't be used to convert a string into a custom type, a TypeConverter is usually a better option.