Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In questo articolo
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Questo articolo illustra cos'è l'associazione di modelli, come funziona e come personalizzarne il comportamento.
Sia i controller che le pagine Razor funzionano con i dati provenienti da richieste HTTP. Ad esempio, i dati della route possono fornire una chiave per il record, mentre i campi di un modulo inviato possono offrire valori per le proprietà del modello. La scrittura di codice per recuperare tutti i valori e convertirli da stringhe in tipi .NET sarebbe noiosa e soggetta a errori. L'associazione di modelli consente di automatizzare questo processo. Il sistema di associazione di modelli:
- Recupera i dati da diverse origini, ad esempio i dati di route, i campi modulo e le stringhe di query.
- Fornisce i dati ai controller e Razor alle pagine nei parametri del metodo e nelle proprietà pubbliche.
- Converte dati stringa in tipi .NET.
- Aggiorna le proprietà dei tipi complessi.
Si supponga di avere il metodo di azione seguente:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
E l'app riceve una richiesta con questo URL:
https://contoso.com/api/pets/2?DogsOnly=true
L'associazione di modelli esegue i passaggi seguenti dopo che il sistema di routing seleziona il metodo di azione:
- Trova il primo parametro di
GetById
, un intero denominatoid
. - Esamina le origini disponibili nella richiesta HTTP e trova
id
= "2" nei dati di route. - Converte la stringa "2" nell'intero 2.
- Trova il parametro successivo di
GetById
, un valore booleano denominatodogsOnly
. - Esamina le origini e trova "DogsOnly=true" nella stringa di query. Per la corrispondenza dei nomi non viene applicata la distinzione tra maiuscole e minuscole.
- Converte la stringa "true" nel valore booleano
true
.
Il framework chiama quindi il metodo GetById
, passando 2 per il parametro id
e true
per il parametro dogsOnly
.
Nell'esempio precedente, le destinazioni di associazione del modello sono parametri di metodo che sono tipi semplici . Le destinazioni possono essere anche le proprietà di un tipo complesso. Dopo l'associazione di ogni proprietà, viene eseguita la convalida dei modelli per la proprietà. Il record dei dati associati al modello e di eventuali errori di associazione o convalida viene archiviato in ControllerBase.ModelState oppure PageModel.ModelState. Per scoprire se questo processo ha esito positivo, l'app controlla il flag ModelState.IsValid.
L'associazione dei modelli cerca di trovare i valori per i seguenti tipi di destinazione:
- Parametri del metodo di azione del controller a cui viene indirizzata una richiesta.
- Parametri del metodo del Razor gestore Pages a cui viene indirizzata una richiesta.
- Proprietà pubbliche di un controller o una classe
PageModel
, se specificate dagli attributi.
Può essere applicato a una proprietà pubblica di un controller o una classe PageModel
per fare in modo che l'associazione di modelli usi tale proprietà come destinazione:
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
Può essere applicato a un controller o una classe PageModel
per indicare all'associazione del modello di usare tutte le proprietà pubbliche della classe come destinazione:
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
Per impostazione predefinita, le proprietà non vengono associate per le richieste HTTP GET. In genere, per una richiesta GET è sufficiente un parametro ID record. L'ID record viene usato per cercare l'elemento nel database. Pertanto, non è necessario associare una proprietà che contiene un'istanza del modello. Negli scenari in cui si vogliono associare le proprietà ai dati dalle richieste GET, impostare la proprietà SupportsGet
su true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
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 impostazione predefinita, l'associazione di modelli ottiene i dati sotto forma di coppie chiave-valore dalle origini seguenti in una richiesta HTTP:
- Campi del modulo
- Il corpo della richiesta (per i controller con l'attributo [ApiController].)
- Dati route
- Parametri della stringa di query
- File caricati
Per ogni parametro o proprietà di destinazione, le origini vengono analizzate nell'ordine indicato nell'elenco precedente. Esistono tuttavia alcune eccezioni:
- I dati di route e i valori delle stringhe di query vengono usati solo per i tipi semplici .
- I file caricati vengono associati solo ai tipi di destinazione che implementano
IFormFile
oIEnumerable<IFormFile>
.
Se l'origine predefinita non è corretta, usare uno degli attributi seguenti per specificare l'origine:
-
[FromQuery]
: ottiene i valori dalla stringa di query. -
[FromRoute]
- Ottiene i valori dai dati di route. -
[FromForm]
- Ottiene i valori dai campi modulo inseriti. -
[FromBody]
- Ottiene i valori dal corpo della richiesta. -
[FromHeader]
- Ottiene i valori dalle intestazioni HTTP.
Questi attributi:
Vengono aggiunti alle proprietà del modello singolarmente e non alla classe del modello, come nell'esempio seguente:
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }
Accettare facoltativamente un valore per il nome del modello nel costruttore. Questa opzione è disponibile nel caso in cui il nome della proprietà non corrisponda al valore nella richiesta. Il valore nella richiesta potrebbe, ad esempio, essere un'intestazione il cui nome contiene un trattino, come nell'esempio seguente:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
Applicare l'attributo [FromBody]
a un parametro per popolare le relative proprietà dal corpo di una richiesta HTTP. Il runtime di ASP.NET Core delega la responsabilità di leggere il corpo in un formattatore di input. I formattatori di input sono descritti più avanti in questo articolo.
Quando [FromBody]
viene applicato a un parametro di tipo complesso, tutti gli attributi di origine dell'associazione applicati alle relative proprietà vengono ignorati. Ad esempio, l'azione seguente Create
specifica che il relativo pet
parametro viene popolato dal corpo:
public ActionResult<Pet> Create([FromBody] Pet pet)
La Pet
classe specifica che la relativa Breed
proprietà viene popolata da un parametro della stringa di query:
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
Nell'esempio precedente:
- L'attributo
[FromQuery]
viene ignorato. - La
Breed
proprietà non viene popolata da un parametro di stringa di query.
I formattatori di input leggono solo il corpo e non comprendono gli attributi dell'origine di associazione. Se nel corpo viene trovato un valore appropriato, tale valore viene usato per popolare la Breed
proprietà.
Non applicare [FromBody]
a più di un parametro per ogni metodo di azione. Quando il flusso di richiesta viene letto da un formattatore di input, non è più disponibile per la lettura per l'associazione di altri [FromBody]
parametri.
I dati di origine vengono forniti al sistema di associazione di modelli dai provider di valori. È possibile scrivere e registrare i provider di valori personalizzati che recuperano i dati per l'associazione di modelli da altre origini. Ad esempio, potrebbe essere necessario recuperare dati dai cookie o dallo stato della sessione. Per ottenere dati da una nuova origine:
- Creare una classe che implementi
IValueProvider
. - Creare una classe che implementi
IValueProviderFactory
. - Registrare la classe factory in
Program.cs
.
L'esempio include un provider di valori e un esempio di factory che ottiene i valori dai cookie. Registrare le fabbriche dei provider di valori personalizzati in Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
Il codice precedente inserisce il provider di valori personalizzato dopo tutti i provider di valori predefiniti. Per renderlo il primo nell'elenco, chiamare Insert(0, new CookieValueProviderFactory())
invece di Add
.
Per impostazione predefinita, non viene creato un errore di stato del modello se non viene trovato alcun valore per una proprietà del modello. La proprietà viene impostata su Null o su un valore predefinito:
- I tipi semplici nullable sono impostati su
null
. - I tipi valore non nullable vengono impostati su
default(T)
. Ad esempio, un parametroint id
viene impostato su 0. - Per i tipi complessi, l'associazione di modelli crea un'istanza usando il costruttore predefinito, senza impostare proprietà.
- Le matrici vengono impostate su
Array.Empty<T>()
, ad eccezione delle matricibyte[]
che vengono impostate sunull
.
Se lo stato del modello deve essere invalidato quando non viene trovato nulla nei campi modulo per una proprietà del modello, usare l'attributo [BindRequired]
.
Si noti che questo [BindRequired]
comportamento si applica all'associazione di modelli dai dati del modulo pubblicati, non dai dati JSON o XML in un corpo della richiesta. I dati del corpo della richiesta vengono gestiti dai formattatori di input.
Se viene trovata un'origine ma non può essere convertita nel tipo di destinazione, lo stato del modello viene contrassegnato come non valido. Il parametro o la proprietà di destinazione viene impostato su Null o su un valore predefinito, come indicato nella sezione precedente.
In un controller API con l'attributo [ApiController]
, uno stato del modello non valido genera una risposta HTTP 400 automatica.
In una Razor pagina, visualizzare nuovamente la pagina con un messaggio di errore.
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
Quando la pagina viene riprodotta dal codice precedente, l'input non valido non viene visualizzato nel campo modulo. Questo avviene perché la proprietà del modello è stata impostata su Null o su un valore predefinito. L'input non valido viene visualizzato in un messaggio di errore. Se si desidera riprodurre nuovamente i dati non valido nel campo modulo, è consigliabile impostare la proprietà del modello su una stringa ed eseguire manualmente la conversione dei dati.
La stessa strategia è consigliata se si vuole evitare che gli errori di conversione del tipo causino errori di stato del modello. In tal caso, impostare la proprietà del modello come stringa.
Per una spiegazione dei tipi semplici e complessi, vedere Associazione di modelli di tipi semplici e complessi.
I tipi semplici in cui lo strumento di associazione di modelli può convertire le stringhe di origine includono i seguenti:
- Booleano
- Byte, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Decimale
- Doppio
- Enum
- Guid
- Int16, Int32, Int64
- Singolo
- TimeOnly
- TimeSpan
- UInt16, UInt32, UInt64
- URI
- Versione
L'API IParsable<TSelf>.TryParse
supporta i valori dei parametri di azione del controller di associazione:
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
La classe seguente DateRange
implementa IParsable<TSelf>
per supportare l'associazione di un intervallo di date:
public class DateRange : IParsable<DateRange>
{
public DateOnly? From { get; init; }
public DateOnly? To { get; init; }
public static DateRange Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse(string? value,
IFormatProvider? provider, out DateRange dateRange)
{
var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries
| StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& DateOnly.TryParse(segments[0], provider, out var fromDate)
&& DateOnly.TryParse(segments[1], provider, out var toDate))
{
dateRange = new DateRange { From = fromDate, To = toDate };
return true;
}
dateRange = new DateRange { From = default, To = default };
return false;
}
}
Il codice precedente:
- Converte una stringa che rappresenta due date in un
DateRange
oggetto - Lo strumento di associazione di modelli usa il metodo
IParsable<TSelf>.TryParse
per associare ilDateRange
.
L'azione controller seguente usa la DateRange
classe per associare un intervallo di date:
// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
La classe seguente Locale
implementa IParsable<TSelf>
per supportare l'associazione a CultureInfo
:
public class Locale : CultureInfo, IParsable<Locale>
{
public Locale(string culture) : base(culture)
{
}
public static Locale Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse([NotNullWhen(true)] string? value,
IFormatProvider? provider, out Locale locale)
{
if (value is null)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
try
{
locale = new Locale(value);
return true;
}
catch (CultureNotFoundException)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
}
}
L'azione controller seguente usa la Locale
classe per associare una CultureInfo
stringa:
// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View(weatherForecasts);
}
L'azione controller seguente usa le DateRange
classi e Locale
per associare un intervallo di date a CultureInfo
:
// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
{
ModelState.TryAddModelError(nameof(range),
$"Invalid date range: {range} for locale {locale.DisplayName}");
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
}
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
&& DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
L'app di esempio per le API in GitHub mostra l'esempio precedente per un controller API.
L'API TryParse
supporta i valori dei parametri di azione del controller di associazione:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse
è l'approccio consigliato per l'associazione di parametri perché, a differenza di TryParse
, non dipende dalla reflection.
La classe seguente DateRangeTP
implementa TryParse
:
public class DateRangeTP
{
public DateOnly? From { get; }
public DateOnly? To { get; }
public DateRangeTP(string from, string to)
{
if (string.IsNullOrEmpty(from))
throw new ArgumentNullException(nameof(from));
if (string.IsNullOrEmpty(to))
throw new ArgumentNullException(nameof(to));
From = DateOnly.Parse(from);
To = DateOnly.Parse(to);
}
public static bool TryParse(string? value, out DateRangeTP? result)
{
var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (range?.Length != 2)
{
result = default;
return false;
}
result = new DateRangeTP(range[0], range[1]);
return true;
}
}
L'azione controller seguente usa la DateRangeTP
classe per associare un intervallo di date:
// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
Un tipo complesso deve avere un costruttore predefinito pubblico e proprietà scrivibili pubbliche per essere associato. Quando si verifica il binding del modello, la classe viene istanziata usando il costruttore predefinito pubblico.
Per ogni proprietà del tipo complesso, l'associazione di modelli esamina le origini per il modellodi nome prefix.property_name. Se non viene trovato, cerca semplicemente nome_proprietà senza il prefisso. La decisione di usare il prefisso non viene presa per ogni proprietà. Ad esempio, con una query contenente ?Instructor.Id=100&Name=foo
, associata al metodo OnGet(Instructor instructor)
, l'oggetto risultante di tipo Instructor
contiene:
-
Id
impostato su100
. -
Name
impostato sunull
. L'associazione dei modelli si aspettaInstructor.Name
perché nel parametro di query precedente è stato usatoInstructor.Id
.
Per l'associazione a un parametro, il prefisso è il nome del parametro. Per l'associazione a una proprietà pubblica PageModel
, il prefisso è il nome della proprietà pubblica. Alcuni attributi hanno una proprietà Prefix
che consente di eseguire l'override dell'utilizzo predefinito del nome di un parametro o una proprietà.
Ad esempio, si supponga che il tipo complesso sia la classe Instructor
seguente:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Se il modello da associare è un parametro denominato instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
L'associazione di modelli inizia esaminando le fonti per la chiave instructorToUpdate.ID
. Se non viene trovata, cerca ID
senza prefisso.
Se il modello da associare è una proprietà denominata Instructor
del controller o della classe PageModel
:
[BindProperty]
public Instructor Instructor { get; set; }
L'associazione di modelli inizia esaminando le origini per trovare la chiave Instructor.ID
. Se non viene trovata, cerca ID
senza prefisso.
Se il modello da associare è un parametro denominato instructorToUpdate
e un attributo Bind
specifica Instructor
come prefisso:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
Il binding del modello inizia cercando nelle fonti la chiave Instructor.ID
. Se non viene trovata, cerca ID
senza prefisso.
Sono disponibili vari attributi predefiniti per controllare l'associazione di modelli di tipi complessi:
Avviso
Questi attributi influiscono sul modello di associazione quando i dati di modulo inviati sono l'origine dei valori. Non influiscono sui formattatori di input, che elaborano i corpi delle richieste JSON e XML pubblicati. I formattatori di input sono descritti più avanti in questo articolo.
Può essere applicato a una classe o a un parametro di metodo. Specifica quali proprietà di un modello devono essere incluse nell'associazione di modelli.
[Bind]
non influisce sui formattatori di input.
Nell'esempio seguente vengono associate solo le proprietà specificate del modello Instructor
quando viene chiamato qualsiasi gestore o metodo di azione:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
Nell'esempio seguente vengono associate solo le proprietà specificate del modello Instructor
quando viene chiamato il metodo OnPost
:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
L'attributo [Bind]
può essere usato per evitare l'overposting negli scenari di creazione. Non funziona bene negli scenari di modifica perché le proprietà escluse vengono impostate su Null o su un valore predefinito anziché rimanere inalterate. Per la protezione dall'overposting, è consigliabile usare i modelli di visualizzazione anziché l'attributo [Bind]
. Per maggiori informazioni, vedere Nota sulla sicurezza relativa all'overposting.
ModelBinderAttribute può essere applicato a tipi, proprietà o parametri. Consente di specificare il tipo di associazione modello usato per associare una specifica istanza o un tipo. Ad esempio:
[HttpPost]
public IActionResult OnPost(
[ModelBinder<MyInstructorModelBinder>] Instructor instructor)
L'attributo [ModelBinder]
può essere usato anche per modificare il nome di una proprietà o di un parametro quando è associato al modello:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
L'associazione di modelli induce un errore nello stato del modello se non è possibile eseguire l'associazione per una proprietà del modello. Ecco un esempio:
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
Vedere anche la discussione relativa all'attributo [Required]
in Convalida del modello.
Può essere applicato a una proprietà o a un tipo. Impedisce all'associazione di modelli di modificare una proprietà del modello. Se applicato a un tipo, il sistema di associazione di modelli esclude tutte le proprietà definite dal tipo. Ecco un esempio:
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Per le destinazioni che sono raccolte di tipi semplici, l'associazione dei modelli cerca le corrispondenze per nome_parametro oppure nome_proprietà. Se non viene trovata alcuna corrispondenza, viene cercato uno dei formati supportati senza il prefisso. Ad esempio:
Si supponga che il parametro da associare sia una matrice denominata
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
I dati di modulo o di stringhe di query possono essere in uno dei formati seguenti:
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Evitare l'associazione di un parametro o di una proprietà denominata
index
oIndex
se è adiacente a un valore della raccolta. L'associazione di modelli tenta di usareindex
come indice per la raccolta, che potrebbe comportare un'associazione errata. Si consideri ad esempio l'azione seguente:public IActionResult Post(string index, List<Product> products)
Nel codice precedente, il
index
parametro della stringa di query viene associato al parametro delindex
metodo e viene usato anche per associare la raccolta di prodotti. La ridenominazione del parametro o l'uso di un attributo di associazione di modelli per configurare l'associazioneindex
evita questo problema:public IActionResult Post(string productIndex, List<Product> products)
Il formato seguente è supportato solo nei dati di modulo:
selectedCourses[]=1050&selectedCourses[]=2000
Per tutti i formati di esempio precedenti, l'associazione di modelli passa una matrice di due elementi al parametro
selectedCourses
:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Per i formati di dati che usano numeri con indice (... [0]... [1]...) è necessario assicurarsi che la numerazione sia progressiva a partire da zero. Se sono presenti eventuali interruzioni nella numerazione con indice, tutti gli elementi dopo l'interruzione vengono ignorati. Ad esempio, se i pedici sono 0 e 2 anziché 0 e 1, il secondo elemento viene ignorato.
Per le destinazioni Dictionary
, l'associazione di modelli cerca le corrispondenze per nome_parametro oppure nome_proprietà. Se non viene trovata alcuna corrispondenza, viene cercato uno dei formati supportati senza il prefisso. Ad esempio:
Si supponga che il parametro di destinazione sia un elemento
Dictionary<int, string>
denominatoselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
I dati di modulo o di stringhe di query inviati possono essere simili a uno degli esempi seguenti:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
Per tutti i formati di esempio precedenti, l'associazione di modelli passa un dizionario di due elementi al parametro
selectedCourses
:- selectedCourses["1050"]="Chimica"
- selectedCourses["2000"]="Economia"
L'associazione di modelli richiede che i tipi complessi abbiano un costruttore senza parametri. Sia i formattatori di input basati su System.Text.Json
che quelli basati su Newtonsoft.Json
supportano la deserializzazione delle classi che non hanno un costruttore senza parametri.
I tipi di record sono un ottimo modo per rappresentare in modo conciso i dati in rete. ASP.NET Core supporta l'associazione di modelli e la convalida dei tipi di record con un singolo costruttore:
public record Person(
[Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Person/Index.cshtml
:
@model Person
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
Quando si convalidano i tipi di record, il runtime cerca i metadati di associazione e convalida in modo specifico sui parametri anziché sulle proprietà.
Il framework consente l'associazione ai tipi di record e la convalida:
public record Person([Required] string Name, [Range(0, 100)] int Age);
Per il corretto funzionamento del precedente, il tipo deve:
- Essere un tipo di record.
- Avere esattamente un costruttore pubblico.
- Contenere parametri con una proprietà con lo stesso nome e tipo. I nomi non devono essere diversi in base alle maiuscole e minuscole.
I POCO che non dispongono di costruttori senza parametri non possono essere associati.
Il codice seguente genera un'eccezione che indica che il tipo deve avere un costruttore senza parametri:
public class Person {
public Person(string Name) { }
}
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0)
{
}
}
Tipi di record con costruttori creati manualmente che hanno un aspetto simile al funzionamento dei costruttori primari
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age)
=> (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
Per i tipi di record, vengono usati i metadati di convalida e associazione per i parametri. Tutti i metadati sulle proprietà vengono ignorati
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
La convalida usa i metadati nel parametro, ma usa la proprietà per leggere il valore. Nel caso comune con i costruttori primari, i due sarebbero identici. Tuttavia, ci sono modi per sconfiggerla:
public record Person([Required] string Name)
{
private readonly string _name;
// The following property is never null.
// However this object could have been constructed as "new Person(null)".
public string Name { get; init => _name = value ?? string.Empty; }
}
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
In questo caso, MVC non tenterà di eseguire di nuovo l'associazione Name
. Tuttavia, è consentito aggiornare Age
.
I provider di valori di route di ASP.NET Core e i provider di valori delle stringhe di query:
- Trattare i valori come cultura invariata.
- Si prevede che gli URL siano invarianti rispetto alle impostazioni culturali.
Al contrario, i valori provenienti dai dati del modulo vengono sottoposti a una conversione sensibile alla cultura. Questa operazione è progettata per rendere gli URL condivisibili tra le diverse aree geografiche.
Per fare in modo che il provider di valori di route ASP.NET Core e il provider di valori stringa di query subiscano una conversione sensibile alle impostazioni di cultura:
- Eredita da IValueProviderFactory
- Copiare il codice da QueryStringValueProviderFactory o RouteValueValueProviderFactory
- Sostituire il valore cultura passato al costruttore del provider di valori con CultureInfo.CurrentCulture
- Sostituisci la factory del provider di valori predefinita nelle opzioni di MVC con quella nuova:
public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var query = context.ActionContext.HttpContext.Request.Query;
if (query?.Count > 0)
{
context.ValueProviders.Add(
new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture));
}
return Task.CompletedTask;
}
}
builder.Services.AddControllers(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
.Single());
options.ValueProviderFactories[index] =
new CultureQueryStringValueProviderFactory();
});
Esistono alcuni tipi di dati speciali che l'associazione di modelli può gestire.
Un file caricato incluso nella richiesta HTTP. È anche supportato IEnumerable<IFormFile>
per più file.
Le azioni possono facoltativamente associare un oggetto CancellationToken
come parametro. Questo lega RequestAborted, segnalando quando la connessione alla base della richiesta HTTP viene interrotta. Le azioni possono usare questo parametro per annullare operazioni asincrone a esecuzione prolungata eseguite come parte delle azioni del controller.
Usato per recuperare tutti i valori dai dati di modulo inviati.
I dati nel corpo della richiesta possono essere in formato JSON, XML o in altri formati. Per analizzare questi dati, l'associazione di modelli usa un formattatore di input configurato in modo da gestire un particolare tipo di contenuto. Per impostazione predefinita, ASP.NET Core include i formattatori di input basati su JSON per la gestione dei dati JSON. È possibile aggiungere altri formattatori per altri tipi di contenuto.
ASP.NET Core consente di selezionare i formattatori di input in base all'attributo Consumes. Se non è presente alcun attributo, viene usata l'intestazione Content-Type.
Per usare i formattatori di input XML predefiniti:
In
Program.cs
chiamare AddXmlSerializerFormatters o AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .AddXmlSerializerFormatters();
Applicare l'attributo
Consumes
alle classi controller o ai metodi di azione che devono aspettarsi XML nel corpo della richiesta.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
Per altre informazioni, vedere Introduzione alla serializzazione XML.
Un formattatore di input assume la piena responsabilità di leggere i dati dal corpo della richiesta. Per personalizzare questo processo, configurare le API usate dal formattatore di input. Questa sezione descrive come personalizzare il System.Text.Json
formattatore di input basato su per comprendere un tipo personalizzato denominato ObjectId
.
Si consideri il modello seguente, che contiene una proprietà personalizzata ObjectId
:
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
Per personalizzare il processo di associazione del modello quando si usa System.Text.Json
, creare una classe derivata da JsonConverter<T>:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<int>(ref reader, options));
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
=> writer.WriteNumberValue(value.Id);
}
Per usare un convertitore personalizzato, applicare l'attributo JsonConverterAttribute al tipo . Nell'esempio seguente il ObjectId
tipo è configurato con ObjectIdConverter
come convertitore personalizzato:
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
Per altre informazioni, vedere Come scrivere convertitori personalizzati.
Il comportamento dei sistemi di associazione e convalida del modello è basato su ModelMetadata. È possibile personalizzare ModelMetadata
mediante l'aggiunta di un provider di dettagli a MvcOptions.ModelMetadataDetailsProviders. Sono disponibili provider di dettagli predefiniti per la disabilitazione dell'associazione di modelli o la convalida per i tipi specificati.
Per disabilitare l'associazione di modelli per tutti i modelli di un tipo specificato, aggiungere un ExcludeBindingMetadataProvider in Program.cs
. Ad esempio, per disabilitare l'associazione di modelli per tutti i modelli di tipo System.Version
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Per disabilitare la convalida per le proprietà di un tipo specificato, aggiungere un SuppressChildValidationMetadataProvider in Program.cs
. Ad esempio, per disabilitare la convalida per le proprietà di tipo System.Guid
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
È possibile estendere l'associazione di modelli scrivendo uno strumento di associazione di modelli personalizzato e usando l'attributo [ModelBinder]
per selezionarlo per una determinata destinazione. Altre informazioni sull'associazione di modelli personalizzata.
L'associazione di modelli può essere richiamata manualmente usando il metodo TryUpdateModelAsync. Il metodo è definito in entrambe le classi ControllerBase
e PageModel
. I sovraccarichi del metodo consentono di specificare il prefisso e il provider di valori da utilizzare. Il metodo restituisce false
se l'associazione di modelli non riesce. Ecco un esempio:
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync usa provider di valori per ottenere dati dal corpo del modulo, dalla stringa di query e dai dati di route.
TryUpdateModelAsync
è in genere:
- Usato con Razor app Pages e MVC che utilizzano controller e viste per prevenire l'eccessivo invio di dati.
- Non usato con un'API Web, a meno che non venga utilizzato dai dati del modulo, dalle stringhe di query e dai dati di route. Gli endpoint API Web che utilizzano JSON usano formattatori di input per deserializzare il corpo della richiesta in un oggetto .
Per altre informazioni, vedere TryUpdateModelAsync.
Il nome di questo attributo segue il modello degli attributi di associazione di modelli che specificano un'origine dati. Non si tratta però dell'associazione di dati da un provider di valori. Ottiene un'istanza di un tipo dal contenitore di iniezione delle dipendenze. Lo scopo è fornire un'alternativa all'iniezione del costruttore per quando è necessario un servizio solo se viene chiamato un metodo specifico.
Se un'istanza del tipo non è registrata nel contenitore di inserimento delle dipendenze, l'app genera un'eccezione quando si tenta di associare il parametro. Per rendere facoltativo il parametro, usare uno degli approcci seguenti:
- Rendere il parametro nullable.
- Impostare un valore predefinito per il parametro .
Per i parametri nullable, assicurarsi che il parametro non sia null
prima di accedervi.
Questo articolo illustra cos'è l'associazione di modelli, come funziona e come personalizzarne il comportamento.
Controller e pagine Razor lavorano con dati provenienti da richieste HTTP. Ad esempio, i dati di route possono fornire una chiave del record e i campi modulo inviati possono fornire valori per le proprietà del modello. La scrittura di codice per recuperare tutti i valori e convertirli da stringhe in tipi .NET sarebbe noiosa e soggetta a errori. L'associazione di modelli consente di automatizzare questo processo. Il sistema di associazione di modelli:
- Recupera i dati da diverse origini, ad esempio i dati di route, i campi modulo e le stringhe di query.
- Fornisce i dati ai controller e alle pagine Razor nei parametri e nelle proprietà pubbliche del metodo.
- Converte dati stringa in tipi .NET.
- Aggiorna le proprietà dei tipi complessi.
Si supponga di avere il metodo di azione seguente:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
E l'app riceve una richiesta con questo URL:
https://contoso.com/api/pets/2?DogsOnly=true
L'associazione di modelli esegue i passaggi seguenti dopo che il sistema di routing seleziona il metodo di azione:
- Trova il primo parametro di
GetById
, un intero denominatoid
. - Esamina le origini disponibili nella richiesta HTTP e trova
id
= "2" nei dati di route. - Converte la stringa "2" nell'intero 2.
- Trova il parametro successivo di
GetById
, un valore booleano denominatodogsOnly
. - Esamina le origini e trova "DogsOnly=true" nella stringa di query. Per la corrispondenza dei nomi non viene applicata la distinzione tra maiuscole e minuscole.
- Converte la stringa "true" nel valore booleano
true
.
Il framework chiama quindi il metodo GetById
, passando 2 per il parametro id
e true
per il parametro dogsOnly
.
Nell'esempio precedente, le destinazioni di associazione del modello sono parametri di metodo che sono tipi semplici . Le destinazioni possono essere anche le proprietà di un tipo complesso. Dopo l'associazione di ogni proprietà, viene eseguita la convalida dei modelli per la proprietà. Il record dei dati associati al modello e di eventuali errori di associazione o convalida viene archiviato in ControllerBase.ModelState oppure PageModel.ModelState. Per scoprire se questo processo ha esito positivo, l'app controlla il flag ModelState.IsValid.
Il binding dei modelli cerca di trovare i valori per i seguenti tipi di target:
- Parametri del metodo di azione del controller a cui viene indirizzata una richiesta.
- Parametri del metodo del Razor gestore Pages a cui viene indirizzata una richiesta.
- Proprietà pubbliche di un controller o una classe
PageModel
, se specificate dagli attributi.
Può essere applicato a una proprietà pubblica di un controller o una classe PageModel
per fare in modo che l'associazione di modelli usi tale proprietà come destinazione:
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
Può essere applicato a un controller o una classe PageModel
per indicare all'associazione del modello di usare tutte le proprietà pubbliche della classe come destinazione:
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
Per impostazione predefinita, le proprietà non vengono associate per le richieste HTTP GET. In genere, per una richiesta GET è sufficiente un parametro ID record. L'ID record viene usato per cercare l'elemento nel database. Pertanto, non è necessario associare una proprietà che contiene un'istanza del modello. Negli scenari in cui si vogliono associare le proprietà ai dati dalle richieste GET, impostare la proprietà SupportsGet
su true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
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 sull'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 impostazione predefinita, l'associazione di modelli ottiene i dati sotto forma di coppie chiave-valore dalle origini seguenti in una richiesta HTTP:
- Campi del modulo
- Il corpo della richiesta (per i controller con l'attributo [ApiController].)
- Dati di itinerario
- Parametri della stringa di query
- File caricati
Per ogni parametro o proprietà di destinazione, le origini vengono analizzate nell'ordine indicato nell'elenco precedente. Esistono tuttavia alcune eccezioni:
- I dati di route e i valori delle stringhe di query vengono usati solo per i tipi semplici .
- I file caricati vengono associati solo ai tipi di destinazione che implementano
IFormFile
oIEnumerable<IFormFile>
.
Se l'origine predefinita non è corretta, usare uno degli attributi seguenti per specificare l'origine:
-
[FromQuery]
: ottiene i valori dalla stringa di query. -
[FromRoute]
- Ottiene i valori dai dati di route. -
[FromForm]
- Ottiene i valori dai campi modulo inseriti. -
[FromBody]
- Ottiene i valori dal corpo della richiesta. -
[FromHeader]
- Ottiene i valori dalle intestazioni HTTP.
Questi attributi:
Vengono aggiunti alle proprietà del modello singolarmente e non alla classe del modello, come nell'esempio seguente:
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }
Accetta facoltativamente un valore per il nome del modello nel costruttore. Questa opzione è disponibile nel caso in cui il nome della proprietà non corrisponda al valore nella richiesta. Il valore nella richiesta potrebbe essere, ad esempio, un'intestazione con un trattino nel nome, come nell'esempio seguente:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
Applicare l'attributo [FromBody]
a un parametro per popolare le relative proprietà dal corpo di una richiesta HTTP. Il runtime di ASP.NET Core delega la responsabilità di leggere il corpo a un formattatore di input. I formattatori di input sono descritti più avanti in questo articolo.
Quando [FromBody]
viene applicato a un parametro di tipo complesso, tutti gli attributi di origine dell'associazione applicati alle relative proprietà vengono ignorati. Ad esempio, l'azione seguente Create
specifica che il relativo pet
parametro viene popolato dal corpo:
public ActionResult<Pet> Create([FromBody] Pet pet)
La Pet
classe specifica che la relativa Breed
proprietà viene popolata da un parametro della stringa di query:
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
Nell'esempio precedente:
- L'attributo
[FromQuery]
viene ignorato. - La
Breed
proprietà non viene popolata da un parametro di stringa di query.
I formattatori di input leggono solo il corpo e non comprendono gli attributi dell'origine di associazione. Se nel corpo viene trovato un valore appropriato, tale valore viene usato per popolare la Breed
proprietà.
Non applicare [FromBody]
a più di un parametro per ogni metodo di azione. Quando il flusso di richiesta viene letto da un formattatore di input, non è più disponibile per la lettura per l'associazione di altri [FromBody]
parametri.
I dati di origine vengono forniti al sistema di associazione di modelli dai provider di valori. È possibile scrivere e registrare i provider di valori personalizzati che recuperano i dati per l'associazione di modelli da altre origini. Ad esempio, potrebbe essere necessario recuperare dati dai cookie o dallo stato della sessione. Per ottenere dati da una nuova origine:
- Creare una classe che implementi
IValueProvider
. - Creare una classe che implementi
IValueProviderFactory
. - Registrare la classe factory in
Program.cs
.
L'esempio include un provider di valori e un esempio di factory che ottiene i valori dai cookie. Registra le fabbriche dei provider di valori personalizzati in Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
Il codice precedente inserisce il provider di valori personalizzato dopo tutti i provider di valori predefiniti. Per renderlo il primo nell'elenco, chiamare Insert(0, new CookieValueProviderFactory())
invece di Add
.
Per impostazione predefinita, non viene creato un errore di stato del modello se non viene trovato alcun valore per una proprietà del modello. La proprietà viene impostata su Null o su un valore predefinito:
- I tipi semplici nullable sono impostati su
null
. - I tipi valore non nullable vengono impostati su
default(T)
. Ad esempio, un parametroint id
viene impostato su 0. - Per i tipi complessi, l'associazione di modelli crea un'istanza usando il costruttore predefinito, senza impostare proprietà.
- Le matrici vengono impostate su
Array.Empty<T>()
, ad eccezione delle matricibyte[]
che vengono impostate sunull
.
Se lo stato del modello deve essere invalidato quando non viene trovato nulla nei campi modulo per una proprietà del modello, usare l'attributo [BindRequired]
.
Si noti che questo comportamento [BindRequired]
si applica all'associazione di modelli dai dati di moduli inviati e non ai dati JSON o XML nel corpo di una richiesta. I dati del corpo della richiesta vengono gestiti dai formattatori di input.
Se viene trovata un'origine ma non può essere convertita nel tipo di destinazione, lo stato del modello viene contrassegnato come non valido. Il parametro o la proprietà di destinazione viene impostato su Null o su un valore predefinito, come indicato nella sezione precedente.
In un controller API con l'attributo [ApiController]
, uno stato del modello non valido genera una risposta HTTP 400 automatica.
In una Razor pagina riprodurre la pagina con un messaggio di errore:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
Quando la pagina viene riprodotta dal codice precedente, l'input non valido non viene visualizzato nel campo modulo. Questo avviene perché la proprietà del modello è stata impostata su Null o su un valore predefinito. L'input non valido viene visualizzato in un messaggio di errore. Se si desidera riprodurre nuovamente i dati non valido nel campo modulo, è consigliabile impostare la proprietà del modello su una stringa ed eseguire manualmente la conversione dei dati.
La stessa strategia è consigliata se si vuole evitare che gli errori di conversione del tipo causino errori di stato del modello. In tal caso, imposta la proprietà del modello su tipo stringa.
Per una spiegazione dei tipi semplici e complessi, vedere Associazione di modelli di tipi semplici e complessi.
I tipi semplici in cui lo strumento di associazione di modelli può convertire le stringhe di origine includono i seguenti:
- Booleano
- Byte, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Decimale
- Doppio
- Enum
- Guid
- Int16, Int32, Int64
- Singolo
- TimeOnly
- TimeSpan
- UInt16, UInt32, UInt64
- URI
- Versione
L'API IParsable<TSelf>.TryParse
supporta i valori dei parametri di azione del controller di associazione:
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
La classe seguente DateRange
implementa IParsable<TSelf>
per supportare l'associazione di un intervallo di date:
public class DateRange : IParsable<DateRange>
{
public DateOnly? From { get; init; }
public DateOnly? To { get; init; }
public static DateRange Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse(string? value,
IFormatProvider? provider, out DateRange dateRange)
{
var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries
| StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& DateOnly.TryParse(segments[0], provider, out var fromDate)
&& DateOnly.TryParse(segments[1], provider, out var toDate))
{
dateRange = new DateRange { From = fromDate, To = toDate };
return true;
}
dateRange = new DateRange { From = default, To = default };
return false;
}
}
Il codice precedente:
- Converte una stringa che rappresenta due date in un
DateRange
oggetto - Lo strumento di associazione di modelli usa il metodo
IParsable<TSelf>.TryParse
per associare ilDateRange
.
L'azione controller seguente usa la DateRange
classe per associare un intervallo di date:
// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
La classe seguente Locale
implementa IParsable<TSelf>
per supportare l'associazione a CultureInfo
:
public class Locale : CultureInfo, IParsable<Locale>
{
public Locale(string culture) : base(culture)
{
}
public static Locale Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse([NotNullWhen(true)] string? value,
IFormatProvider? provider, out Locale locale)
{
if (value is null)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
try
{
locale = new Locale(value);
return true;
}
catch (CultureNotFoundException)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
}
}
L'azione controller seguente usa la Locale
classe per associare una CultureInfo
stringa:
// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View(weatherForecasts);
}
L'azione controller seguente usa le DateRange
classi e Locale
per associare un intervallo di date a CultureInfo
:
// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
{
ModelState.TryAddModelError(nameof(range),
$"Invalid date range: {range} for locale {locale.DisplayName}");
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
}
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
&& DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
L'app di esempio per le API in GitHub mostra l'esempio precedente per un controller API.
L'API TryParse
supporta i valori dei parametri di azione del controller di associazione:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse
è l'approccio consigliato per l'associazione di parametri perché, a differenza di TryParse
, non dipende dalla Reflection.
La classe seguente DateRangeTP
implementa TryParse
:
public class DateRangeTP
{
public DateOnly? From { get; }
public DateOnly? To { get; }
public DateRangeTP(string from, string to)
{
if (string.IsNullOrEmpty(from))
throw new ArgumentNullException(nameof(from));
if (string.IsNullOrEmpty(to))
throw new ArgumentNullException(nameof(to));
From = DateOnly.Parse(from);
To = DateOnly.Parse(to);
}
public static bool TryParse(string? value, out DateRangeTP? result)
{
var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (range?.Length != 2)
{
result = default;
return false;
}
result = new DateRangeTP(range[0], range[1]);
return true;
}
}
L'azione controller seguente usa la DateRangeTP
classe per associare un intervallo di date:
// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
Un tipo complesso deve avere un costruttore predefinito pubblico e proprietà scrivibili pubbliche per il binding. Al momento dell'associazione dei modelli, la classe viene istanziata utilizzando il costruttore predefinito pubblico.
Per ogni proprietà del tipo complesso, il model binding esamina le origini alla ricerca del pattern di nomeprefix.property_name. Se non viene trovato, cerca semplicemente nome_proprietà senza il prefisso. La decisione di usare il prefisso non viene presa per ogni proprietà. Ad esempio, con una query contenente ?Instructor.Id=100&Name=foo
, associata al metodo OnGet(Instructor instructor)
, l'oggetto risultante di tipo Instructor
contiene:
-
Id
impostato su100
. -
Name
impostato sunull
. Il binding dei modelli si aspettaInstructor.Name
perchéInstructor.Id
è stato usato nel parametro di query precedente.
Per l'associazione a un parametro, il prefisso è il nome del parametro. Per l'associazione a una proprietà pubblica PageModel
, il prefisso è il nome della proprietà pubblica. Alcuni attributi hanno una proprietà Prefix
che consente di eseguire l'override dell'utilizzo predefinito del nome di un parametro o una proprietà.
Ad esempio, si supponga che il tipo complesso sia la classe Instructor
seguente:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Se il modello da associare è un parametro denominato instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
L'associazione di modelli inizia cercando la chiave instructorToUpdate.ID
nelle fonti. Se non viene trovata, cerca ID
senza prefisso.
Se il modello da associare è una proprietà denominata Instructor
del controller o della classe PageModel
:
[BindProperty]
public Instructor Instructor { get; set; }
L'associazione dei modelli inizia cercando la chiave Instructor.ID
nelle origini. Se non viene trovata, cerca ID
senza prefisso.
Se il modello da associare è un parametro denominato instructorToUpdate
e un attributo Bind
specifica Instructor
come prefisso:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
L'associazione di modelli cerca prima di tutto la chiave Instructor.ID
nelle origini. Se non viene trovata, cerca ID
senza prefisso.
Sono disponibili vari attributi predefiniti per controllare l'associazione di modelli di tipi complessi:
Avviso
Questi attributi influiscono sul modello di associazione quando i dati di modulo inviati sono l'origine dei valori. Non influiscono sui formattatori di input, che elaborano i corpi delle richieste JSON e XML pubblicati. I formattatori di input sono descritti più avanti in questo articolo.
Può essere applicato a una classe o a un parametro di metodo. Specifica quali proprietà di un modello devono essere incluse nell'associazione di modelli.
[Bind]
non influisce sui formattatori di input.
Nell'esempio seguente vengono associate solo le proprietà specificate del modello Instructor
quando viene chiamato qualsiasi gestore o metodo di azione:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
Nell'esempio seguente vengono associate solo le proprietà specificate del modello Instructor
quando viene chiamato il metodo OnPost
:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
L'attributo [Bind]
può essere usato per evitare l'overposting negli scenari di creazione. Non funziona bene negli scenari di modifica perché le proprietà escluse vengono impostate su Null o su un valore predefinito anziché rimanere inalterate. Per la protezione dall'overposting, è consigliabile usare i modelli di visualizzazione anziché l'attributo [Bind]
. Per ulteriori informazioni, consultare la Nota sulla sicurezza riguardante l'overposting.
ModelBinderAttribute può essere applicato a tipi, proprietà o parametri. Consente di specificare il tipo di binder del modello usato per vincolare l'istanza o il tipo specifico. Ad esempio:
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
L'attributo [ModelBinder]
può essere usato anche per modificare il nome di una proprietà o di un parametro quando è associato al modello:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
Con questo attributo l'associazione di modelli aggiunge un errore di stato del modello se non è possibile eseguire l'associazione per una proprietà del modello. Ecco un esempio:
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
Vedere anche la discussione relativa all'attributo [Required]
in Convalida del modello.
Può essere applicato a una proprietà o a un tipo. Impedisce all'associazione di modelli di impostare una proprietà del modello. Se applicato a un tipo, il sistema di associazione di modelli esclude tutte le proprietà definite dal tipo. Ecco un esempio:
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Per le destinazioni che sono raccolte di tipi semplici, l'associazione di modelli cerca le corrispondenze per nome_parametro oppure nome_proprietà. Se non viene trovata alcuna corrispondenza, viene cercato uno dei formati supportati senza il prefisso. Ad esempio:
Si supponga che il parametro da associare sia una matrice denominata
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
I dati di modulo o di stringhe di query possono essere in uno dei formati seguenti:
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Evitare l'associazione di un parametro o di una proprietà denominata
index
oIndex
se è adiacente a un valore della raccolta. L'associazione di modelli tenta di usareindex
come indice per la raccolta, che potrebbe comportare un'associazione errata. Si consideri ad esempio l'azione seguente:public IActionResult Post(string index, List<Product> products)
Nel codice precedente, il
index
parametro della stringa di query viene associato al parametro delindex
metodo e viene usato anche per associare la raccolta di prodotti. La ridenominazione del parametro o l'uso di un attributo di associazione di modelli per configurare l'associazioneindex
evita questo problema:public IActionResult Post(string productIndex, List<Product> products)
Il formato seguente è supportato solo nei dati di modulo:
selectedCourses[]=1050&selectedCourses[]=2000
Per tutti i formati di esempio precedenti, l'associazione di modelli passa una matrice di due elementi al parametro
selectedCourses
:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Per i formati di dati che usano numeri con indice (... [0]... [1]...) è necessario assicurarsi che la numerazione sia progressiva a partire da zero. Se sono presenti eventuali interruzioni nella numerazione con indice, tutti gli elementi dopo l'interruzione vengono ignorati. Ad esempio, se i pedici sono 0 e 2 anziché 0 e 1, il secondo elemento viene ignorato.
Per le destinazioni Dictionary
, l'associazione di modelli cerca le corrispondenze per nome_parametro oppure nome_proprietà. Se non viene trovata alcuna corrispondenza, viene cercato uno dei formati supportati senza il prefisso. Ad esempio:
Si supponga che il parametro di destinazione sia un elemento
Dictionary<int, string>
denominatoselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
I dati di modulo o di stringhe di query inviati possono essere simili a uno degli esempi seguenti:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
Per tutti i formati di esempio precedenti, l'associazione di modelli passa un dizionario di due elementi al parametro
selectedCourses
:- selectedCourses["1050"]="Chimica"
- selectedCourses["2000"]="Economia"
L'associazione di modelli richiede che i tipi complessi abbiano un costruttore senza parametri. I System.Text.Json
formattatori di input basati e Newtonsoft.Json
supportano la deserializzazione delle classi che non hanno un costruttore senza parametri.
I tipi di record sono un ottimo modo per rappresentare in modo conciso i dati in rete. ASP.NET Core supporta l'associazione di modelli e la convalida dei tipi di record con un singolo costruttore:
public record Person(
[Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Person/Index.cshtml
:
@model Person
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
Quando si convalidano i tipi di record, il runtime cerca i metadati di associazione e convalida in modo specifico sui parametri anziché sulle proprietà.
Il framework consente l'associazione ai tipi di record e la convalida:
public record Person([Required] string Name, [Range(0, 100)] int Age);
Per il corretto funzionamento dell'oggetto precedente, il tipo deve:
- Essere un tipo di record.
- Avere esattamente un costruttore pubblico.
- Contenere parametri con una proprietà con lo stesso nome e tipo. I nomi non devono essere diversi in base alle maiuscole e minuscole.
I POCO che non dispongono di costruttori senza parametri non possono essere associati.
Il codice seguente genera un'eccezione che indica che il tipo deve avere un costruttore senza parametri:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Tipi di record con costruttori creati manualmente che hanno un aspetto simile al funzionamento dei costruttori primari
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age)
=> (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
Per i tipi di record, vengono usati i metadati di convalida e associazione per i parametri. Tutti i metadati sulle proprietà vengono ignorati
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
La convalida usa i metadati nel parametro, ma usa la proprietà per leggere il valore. Nel caso comune con i costruttori primari, i due sarebbero identici. Tuttavia, ci sono modi per sconfiggerla:
public record Person([Required] string Name)
{
private readonly string _name;
// The following property is never null.
// However this object could have been constructed as "new Person(null)".
public string Name { get; init => _name = value ?? string.Empty; }
}
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
In questo caso, MVC non tenterà di eseguire di nuovo l'associazione Name
. Tuttavia, è consentito aggiornare Age
Comportamento di globalizzazione dell'associazione dei modelli con i dati di route e le stringhe di query
Il provider di valore di route di ASP.NET Core e il provider di valore di stringa di query:
- Trattare i valori come cultura invariante.
- Si prevede che gli URL siano invarianti delle impostazioni culturali.
Al contrario, i valori provenienti dai dati del modulo vengono sottoposti a una conversione sensibile al contesto culturale. Questa operazione è progettata in modo che gli URL siano condivisibili tra le impostazioni locali.
Per fare in modo che il provider di valori di route di ASP.NET Core e il provider di valori delle stringhe di query siano soggetti a una conversione sensibile alla cultura:
- Eredita da IValueProviderFactory
- Copiare il codice da QueryStringValueProviderFactory o RouteValueValueProviderFactory
- Sostituire il valore cultura passato al costruttore del provider di valori con CultureInfo.CurrentCulture
- Sostituire la factory del provider di valori predefinita nelle opzioni MVC con quella nuova:
public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var query = context.ActionContext.HttpContext.Request.Query;
if (query?.Count > 0)
{
context.ValueProviders.Add(
new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture));
}
return Task.CompletedTask;
}
}
builder.Services.AddControllers(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
.Single());
options.ValueProviderFactories[index] =
new CultureQueryStringValueProviderFactory();
});
Esistono alcuni tipi di dati speciali che l'associazione di modelli può gestire.
Un file caricato incluso nella richiesta HTTP. È anche supportato IEnumerable<IFormFile>
per più file.
Le azioni possono facoltativamente associare un oggetto CancellationToken
come parametro. L'operazione associa RequestAborted che segnala quando la connessione alla base della richiesta HTTP viene interrotta. Le azioni possono usare questo parametro per annullare operazioni asincrone a esecuzione prolungata eseguite come parte delle azioni del controller.
Usato per recuperare tutti i valori dai dati di modulo inviati.
I dati nel corpo della richiesta possono essere in formato JSON, XML o in altri formati. Per analizzare questi dati, l'associazione di modelli usa un formattatore di input configurato in modo da gestire un particolare tipo di contenuto. Per impostazione predefinita, ASP.NET Core include i formattatori di input basati su JSON per la gestione dei dati JSON. È possibile aggiungere altri formattatori per altri tipi di contenuto.
ASP.NET Core consente di selezionare i formattatori di input in base all'attributo Consumes. Se non è presente alcun attributo, viene usata l'intestazione Content-Type.
Per usare i formattatori di input XML predefiniti:
In
Program.cs
, chiama il AddXmlSerializerFormatters o il AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .AddXmlSerializerFormatters();
Applicare l'attributo
Consumes
alle classi controller o ai metodi di azione che devono aspettarsi XML nel corpo della richiesta.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
Per altre informazioni, vedere Introduzione alla serializzazione XML.
Un formattatore di input assume la piena responsabilità di leggere i dati dal corpo della richiesta. Per personalizzare questo processo, configurare le API usate dal formattatore di input. Questa sezione descrive come personalizzare il formattatore di input basato su System.Text.Json
per comprendere un tipo personalizzato denominato ObjectId
.
Si consideri il modello seguente, che contiene una proprietà personalizzata ObjectId
:
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
Per personalizzare il processo di associazione del modello quando si usa System.Text.Json
, creare una classe derivata da JsonConverter<T>:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<int>(ref reader, options));
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
=> writer.WriteNumberValue(value.Id);
}
Per usare un convertitore personalizzato, applicare l'attributo JsonConverterAttribute al tipo . Nell'esempio seguente il ObjectId
tipo è configurato con ObjectIdConverter
come convertitore personalizzato:
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
Per altre informazioni, vedere Come scrivere convertitori personalizzati.
Il comportamento dei sistemi di associazione e convalida del modello è basato su ModelMetadata. È possibile personalizzare ModelMetadata
mediante l'aggiunta di un provider di dettagli a MvcOptions.ModelMetadataDetailsProviders. Sono disponibili provider di dettagli predefiniti per la disabilitazione dell'associazione di modelli o la convalida per i tipi specificati.
Per disabilitare l'associazione di modelli per tutti i modelli di un tipo specificato, aggiungere un ExcludeBindingMetadataProvider in Program.cs
. Ad esempio, per disabilitare l'associazione di modelli per tutti i modelli di tipo System.Version
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Per disabilitare la convalida per le proprietà di un tipo specificato, aggiungere un SuppressChildValidationMetadataProvider in Program.cs
. Ad esempio, per disabilitare la convalida per le proprietà di tipo System.Guid
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
È possibile estendere l'associazione di modelli scrivendo uno strumento di associazione di modelli personalizzato e usando l'attributo [ModelBinder]
per selezionarlo per una determinata destinazione. Altre informazioni sull'associazione di modelli personalizzata.
L'associazione di modelli può essere richiamata manualmente usando il metodo TryUpdateModelAsync. Il metodo è definito in entrambe le classi ControllerBase
e PageModel
. I sovraccarichi del metodo consentono di specificare il prefisso e il fornitore di valori da usare. Il metodo restituisce false
se l'associazione di modelli non riesce. Ecco un esempio:
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync usa provider di valori per ottenere dati dal corpo del modulo, dalla stringa di query e dai dati di route.
TryUpdateModelAsync
è in genere:
- Usato con Razor le app Pages e MVC usando controller e visualizzazioni per evitare l'over-post.
- Non usato con un'API Web, a meno che non venga utilizzato dai dati del modulo, dalle stringhe di query e dai dati di route. Gli endpoint API Web che utilizzano JSON usano formattatori di input per deserializzare il corpo della richiesta in un oggetto .
Per altre informazioni, vedere TryUpdateModelAsync.
Il nome di questo attributo segue il modello degli attributi di associazione di modelli che specificano un'origine dati. Non si tratta però dell'associazione di dati da un provider di valori. Ottiene un'istanza di un tipo dal contenitore di iniezione delle dipendenze. Lo scopo è fornire un'alternativa all'iniezione tramite costruttore quando si ha bisogno di un servizio solo se viene chiamato un metodo specifico.
Se un'istanza del tipo non è registrata nel contenitore di inserimento delle dipendenze, l'app genera un'eccezione quando si tenta di associare il parametro. Per rendere facoltativo il parametro, usare uno degli approcci seguenti:
- Rendere il parametro nullable.
- Impostare un valore predefinito per il parametro .
Per i parametri nullable, assicurarsi che il parametro non null
sia prima di accedervi.
Questo articolo illustra cos'è l'associazione di modelli, come funziona e come personalizzarne il comportamento.
Le pagine e i controller funzionano con i dati che provengono dalle richieste HTTP. Ad esempio, i dati di route possono fornire una chiave del record e i campi modulo inviati possono fornire valori per le proprietà del modello. La scrittura di codice per recuperare tutti i valori e convertirli da stringhe in tipi .NET sarebbe noiosa e soggetta a errori. L'associazione di modelli consente di automatizzare questo processo. Il sistema di associazione di modelli:
- Recupera i dati da diverse origini, ad esempio i dati di route, i campi modulo e le stringhe di query.
- Fornisce i dati ai controller e Razor alle pagine nei parametri del metodo e nelle proprietà pubbliche.
- Converte dati stringa in tipi .NET.
- Aggiorna le proprietà dei tipi complessi.
Si supponga di avere il metodo di azione seguente:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
E l'app riceve una richiesta con questo URL:
https://contoso.com/api/pets/2?DogsOnly=true
L'associazione di modelli esegue i passaggi seguenti dopo che il sistema di routing seleziona il metodo di azione:
- Trova il primo parametro di
GetById
, un intero denominatoid
. - Esamina le origini disponibili nella richiesta HTTP e trova
id
= "2" nei dati di route. - Converte la stringa "2" nell'intero 2.
- Trova il parametro successivo di
GetById
, un valore booleano denominatodogsOnly
. - Esamina le origini e trova "DogsOnly=true" nella stringa di query. Per la corrispondenza dei nomi non viene applicata la distinzione tra maiuscole e minuscole.
- Converte la stringa "true" nel valore booleano
true
.
Il framework chiama quindi il metodo GetById
, passando 2 per il parametro id
e true
per il parametro dogsOnly
.
Nell'esempio precedente le destinazioni dell'associazione di modelli sono parametri di metodo che sono tipi semplici. Le destinazioni possono essere anche le proprietà di un tipo complesso. Dopo l'associazione di ogni proprietà, viene eseguita la convalida dei modelli per la proprietà. Il record dei dati associati al modello e di eventuali errori di associazione o convalida viene archiviato in ControllerBase.ModelState oppure PageModel.ModelState. Per scoprire se questo processo ha esito positivo, l'app controlla il flag ModelState.IsValid.
Il binding dei modelli cerca di trovare i valori per i tipi di target seguenti:
- Parametri del metodo di azione del controller a cui viene indirizzata una richiesta.
- Parametri del metodo del Razor gestore Pages a cui viene indirizzata una richiesta.
- Proprietà pubbliche di un controller o una classe
PageModel
, se specificate dagli attributi.
Può essere applicato a una proprietà pubblica di un controller o una classe PageModel
per fare in modo che l'associazione di modelli usi tale proprietà come destinazione:
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
Può essere applicato a un controller o una classe PageModel
per indicare all'associazione del modello di usare tutte le proprietà pubbliche della classe come destinazione:
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
Per impostazione predefinita, le proprietà non vengono associate per le richieste HTTP GET. In genere, per una richiesta GET è sufficiente un parametro ID record. L'ID record viene usato per cercare l'elemento nel database. Pertanto, non è necessario associare una proprietà che contiene un'istanza del modello. Negli scenari in cui si vogliono associare le proprietà ai dati dalle richieste GET, impostare la proprietà SupportsGet
su true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
Per impostazione predefinita, l'associazione di modelli ottiene i dati sotto forma di coppie chiave-valore dalle origini seguenti in una richiesta HTTP:
- Campi del modulo
- Il corpo della richiesta (per i controller con l'attributo [ApiController].)
- Dati del percorso
- Parametri della stringa di query
- File caricati
Per ogni parametro o proprietà di destinazione, le origini vengono analizzate nell'ordine indicato nell'elenco precedente. Esistono tuttavia alcune eccezioni:
- I dati di route e i valori delle stringhe di query vengono usati solo per i tipi semplici.
- I file caricati vengono associati solo ai tipi di destinazione che implementano
IFormFile
oIEnumerable<IFormFile>
.
Se l'origine predefinita non è corretta, usare uno degli attributi seguenti per specificare l'origine:
-
[FromQuery]
: ottiene i valori dalla stringa di query. -
[FromRoute]
- Ottiene i valori dai dati di route. -
[FromForm]
- Ottiene i valori dai campi modulo inseriti. -
[FromBody]
- Ottiene i valori dal corpo della richiesta. -
[FromHeader]
- Ottiene i valori dalle intestazioni HTTP.
Questi attributi:
Vengono aggiunti alle proprietà del modello singolarmente e non alla classe del modello, come nell'esempio seguente:
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }
Accettare facoltativamente un valore per il nome del modello nel costruttore. Questa opzione è disponibile nel caso in cui il nome della proprietà non corrisponda al valore nella richiesta. Il valore nella richiesta, ad esempio, potrebbe essere un'intestazione con un trattino nel nome, come nell'esempio seguente:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
Applicare l'attributo [FromBody]
a un parametro per popolare le relative proprietà dal corpo di una richiesta HTTP. Il runtime di ASP.NET Core delega la responsabilità di leggere il corpo a un formattatore di input. I formattatori di input sono descritti più avanti in questo articolo.
Quando [FromBody]
viene applicato a un parametro di tipo complesso, tutti gli attributi di origine dell'associazione applicati alle relative proprietà vengono ignorati. Ad esempio, l'azione seguente Create
specifica che il relativo pet
parametro viene popolato dal corpo:
public ActionResult<Pet> Create([FromBody] Pet pet)
La Pet
classe specifica che la relativa Breed
proprietà viene popolata da un parametro della stringa di query:
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
Nell'esempio precedente:
- L'attributo
[FromQuery]
viene ignorato. - La
Breed
proprietà non viene popolata da un parametro di stringa di query.
I formattatori di input leggono solo il corpo e non comprendono gli attributi dell'origine di associazione. Se nel corpo viene trovato un valore appropriato, tale valore viene utilizzato per popolare la proprietà Breed
.
Non applicare [FromBody]
a più di un parametro per ogni metodo di azione. Quando il flusso di richiesta viene letto da un formattatore di input, non è più disponibile per la lettura per l'associazione di altri [FromBody]
parametri.
I dati di origine vengono forniti al sistema di associazione di modelli dai provider di valori. È possibile scrivere e registrare i provider di valori personalizzati che recuperano i dati per l'associazione di modelli da altre origini. Ad esempio, potrebbe essere necessario recuperare dati dai cookie o dallo stato della sessione. Per ottenere dati da una nuova origine:
- Creare una classe che implementi
IValueProvider
. - Creare una classe che implementi
IValueProviderFactory
. - Registrate la classe factory in
Program.cs
.
L'esempio include un provider di valori e un esempio di factory che ottiene i valori dai cookie. Registrare le factory per provider di valori personalizzati in Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
Il codice precedente inserisce il provider di valori personalizzato dopo tutti i provider di valori predefiniti. Per renderlo il primo nell'elenco, chiamare Insert(0, new CookieValueProviderFactory())
invece di Add
.
Per impostazione predefinita, non viene creato un errore di stato del modello se non viene trovato alcun valore per una proprietà del modello. La proprietà viene impostata su Null o su un valore predefinito:
- I tipi semplici nullable sono impostati su
null
. - I tipi valore non-null sono impostati su
default(T)
. Ad esempio, un parametroint id
viene impostato su 0. - Per i tipi complessi, l'associazione di modelli crea un'istanza usando il costruttore predefinito, senza impostare proprietà.
- Le matrici vengono impostate su
Array.Empty<T>()
, ad eccezione delle matricibyte[]
che vengono impostate sunull
.
Se lo stato del modello deve essere invalidato quando non viene trovato nulla nei campi modulo per una proprietà del modello, usare l'attributo [BindRequired]
.
Si noti che questo comportamento [BindRequired]
si applica all'associazione di modelli dai dati di moduli inviati e non ai dati JSON o XML nel corpo di una richiesta. I dati del corpo della richiesta vengono gestiti dai formattatori di input.
Se viene trovata un'origine ma non può essere convertita nel tipo di destinazione, lo stato del modello viene contrassegnato come non valido. Il parametro o la proprietà di destinazione viene impostato su Null o su un valore predefinito, come indicato nella sezione precedente.
In un controller API con l'attributo [ApiController]
, uno stato del modello non valido genera una risposta HTTP 400 automatica.
Nella Razor pagina, visualizzare nuovamente la pagina con un messaggio di errore.
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
Quando la pagina viene riprodotta dal codice precedente, l'input non valido non viene visualizzato nel campo modulo. Questo avviene perché la proprietà del modello è stata impostata su Null o su un valore predefinito. L'input non valido viene visualizzato in un messaggio di errore. Se si desidera riprodurre nuovamente i dati non valido nel campo modulo, è consigliabile impostare la proprietà del modello su una stringa ed eseguire manualmente la conversione dei dati.
La stessa strategia è consigliata se si vuole evitare che gli errori di conversione del tipo causino errori di stato del modello. In tal caso, impostare la proprietà del modello come stringa.
I tipi semplici in cui lo strumento di associazione di modelli può convertire le stringhe di origine includono i seguenti:
- Booleano
- Byte, SByte
- Char
- DateTime
- DateTimeOffset
- Decimale
- Doppio
- Enum
- Guid
- Int16, Int32, Int64
- Singolo
- TimeSpan
- UInt16, UInt32, UInt64
- URI
- Versione
Un tipo complesso deve avere un costruttore predefinito pubblico e da associare proprietà scrivibili pubbliche. Quando si verifica l'associazione di modelli, vengono create istanze della classe usando il costruttore predefinito pubblico.
Per ogni proprietà del tipo complesso, il binding del modello cerca nelle fonti per trovare il pattern del nomeprefix.property_name. Se non viene trovato, cerca semplicemente nome_proprietà senza il prefisso. La decisione di usare il prefisso non viene presa per ogni proprietà. Ad esempio, con una query contenente ?Instructor.Id=100&Name=foo
, associata al metodo OnGet(Instructor instructor)
, l'oggetto risultante di tipo Instructor
contiene:
-
Id
impostato su100
. -
Name
impostato sunull
. Il model binding si aspettaInstructor.Name
poichéInstructor.Id
è stato usato nel parametro della query precedente.
Per l'associazione a un parametro, il prefisso è il nome del parametro. Per l'associazione a una proprietà pubblica PageModel
, il prefisso è il nome della proprietà pubblica. Alcuni attributi hanno una proprietà Prefix
che consente di eseguire l'override dell'utilizzo predefinito del nome di un parametro o una proprietà.
Ad esempio, si supponga che il tipo complesso sia la classe Instructor
seguente:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Se il modello da associare è un parametro denominato instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
L'associazione di modelli inizia cercando la chiave instructorToUpdate.ID
nelle fonti. Se non viene trovata, cerca ID
senza prefisso.
Se il modello da associare è una proprietà denominata Instructor
del controller o della classe PageModel
:
[BindProperty]
public Instructor Instructor { get; set; }
Il processo di binding dei modelli inizia guardando attraverso le sorgenti per trovare la chiave Instructor.ID
. Se non viene trovata, cerca ID
senza prefisso.
Se il modello da associare è un parametro denominato instructorToUpdate
e un attributo Bind
specifica Instructor
come prefisso:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
L'associazione di modelli inizia cercando la chiave Instructor.ID
nelle origini. Se non viene trovata, cerca ID
senza prefisso.
Sono disponibili vari attributi predefiniti per controllare l'associazione di modelli di tipi complessi:
Avviso
Questi attributi influiscono sul modello di associazione quando i dati di modulo inviati sono l'origine dei valori. Non influiscono sui formattatori di input, che elaborano i corpi delle richieste JSON e XML pubblicati. I formattatori di input sono descritti più avanti in questo articolo.
Può essere applicato a una classe o a un parametro di metodo. Specifica quali proprietà di un modello devono essere incluse nell'associazione di modelli.
[Bind]
non influisce sui formattatori di input.
Nell'esempio seguente vengono associate solo le proprietà specificate del modello Instructor
quando viene chiamato qualsiasi gestore o metodo di azione:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
Nell'esempio seguente vengono associate solo le proprietà specificate del modello Instructor
quando viene chiamato il metodo OnPost
:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
L'attributo [Bind]
può essere usato per evitare l'overposting negli scenari di creazione. Non funziona bene negli scenari di modifica perché le proprietà escluse vengono impostate su Null o su un valore predefinito anziché rimanere inalterate. Per la protezione dall'overposting, è consigliabile usare i modelli di visualizzazione anziché l'attributo [Bind]
. Per altre informazioni, vedere Nota sulla sicurezza relativa all'overposting.
ModelBinderAttribute può essere applicato a tipi, proprietà o parametri. Consente di specificare il tipo di binder di modello usato per associare un'istanza o un tipo specifico. Ad esempio:
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
L'attributo [ModelBinder]
può essere usato anche per modificare il nome di una proprietà o di un parametro quando è associato al modello:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
Con questo attributo il binding del modello aggiunge un errore nello stato del modello se non è possibile eseguire l'associazione per una proprietà del modello. Ecco un esempio:
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
Vedere anche la discussione relativa all'attributo [Required]
in Convalida del modello.
Può essere applicato a una proprietà o a un tipo. Impedisce al binding del modello di settare una proprietà del modello. Se applicato a un tipo, il sistema di associazione di modelli esclude tutte le proprietà definite dal tipo. Ecco un esempio:
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Per le destinazioni che sono raccolte di tipi semplici, l’associazione dei modelli cerca le corrispondenze per nome_parametro oppure nome_proprietà. Se non viene trovata alcuna corrispondenza, viene cercato uno dei formati supportati senza il prefisso. Ad esempio:
Si supponga che il parametro da associare sia una matrice denominata
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
I dati di modulo o di stringhe di query possono essere in uno dei formati seguenti:
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Evitare l'associazione di un parametro o di una proprietà denominata
index
oIndex
se è adiacente a un valore della raccolta. L'associazione di modelli tenta di usareindex
come indice per la raccolta, che potrebbe comportare un'associazione errata. Si consideri ad esempio l'azione seguente:public IActionResult Post(string index, List<Product> products)
Nel codice precedente, il
index
parametro della stringa di query viene associato al parametro delindex
metodo e viene usato anche per associare la raccolta di prodotti. La ridenominazione del parametro o l'uso di un attributo di associazione di modelli per configurare l'associazioneindex
evita questo problema:public IActionResult Post(string productIndex, List<Product> products)
Il formato seguente è supportato solo nei dati di modulo:
selectedCourses[]=1050&selectedCourses[]=2000
Per tutti i formati di esempio precedenti, l'associazione di modelli passa una matrice di due elementi al parametro
selectedCourses
:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Per i formati di dati che usano numeri con indice (... [0]... [1]...) è necessario assicurarsi che la numerazione sia progressiva a partire da zero. Se sono presenti eventuali interruzioni nella numerazione con indice, tutti gli elementi dopo l'interruzione vengono ignorati. Ad esempio, se i pedici sono 0 e 2 anziché 0 e 1, il secondo elemento viene ignorato.
Per le destinazioni Dictionary
, l'associazione di modelli cerca le corrispondenze per nome_parametro oppure nome_proprietà. Se non viene trovata alcuna corrispondenza, viene cercato uno dei formati supportati senza il prefisso. Ad esempio:
Si supponga che il parametro di destinazione sia un elemento
Dictionary<int, string>
denominatoselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
I dati di modulo o di stringhe di query inviati possono essere simili a uno degli esempi seguenti:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
Per tutti i formati di esempio precedenti, l'associazione di modelli passa un dizionario di due elementi al parametro
selectedCourses
:- selectedCourses["1050"]="Chimica"
- selectedCourses["2000"]="Economia"
L'associazione di modelli richiede che i tipi complessi abbiano un costruttore senza parametri. I formattatori di input basati su System.Text.Json
e Newtonsoft.Json
supportano la deserializzazione delle classi che non hanno un costruttore senza parametri.
I tipi di record sono un ottimo modo per rappresentare in modo conciso i dati in rete. ASP.NET Core supporta l'associazione di modelli e la convalida dei tipi di record con un singolo costruttore:
public record Person(
[Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Person/Index.cshtml
:
@model Person
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
Quando si convalidano i tipi di record, il runtime cerca i metadati di associazione e convalida in modo specifico sui parametri anziché sulle proprietà.
Il framework consente l'associazione ai tipi di record e la convalida:
public record Person([Required] string Name, [Range(0, 100)] int Age);
Per il funzionamento di quanto precede, il tipo deve:
- Essere un tipo di record.
- Avere esattamente un costruttore pubblico.
- Contenere parametri con una proprietà con lo stesso nome e tipo. I nomi non devono essere diversi in base alle maiuscole e minuscole.
I POCO che non dispongono di costruttori senza parametri non possono essere associati.
Il codice seguente genera un'eccezione che indica che il tipo deve avere un costruttore senza parametri:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Tipi di record con costruttori creati manualmente che hanno un aspetto simile al funzionamento dei costruttori primari
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age)
=> (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
Per i tipi di record, vengono usati i metadati di convalida e associazione per i parametri. Tutti i metadati sulle proprietà vengono ignorati
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
La convalida usa i metadati nel parametro, ma usa la proprietà per leggere il valore. Nel caso comune con i costruttori primari, i due sarebbero identici. Tuttavia, ci sono modi per sconfiggerla:
public record Person([Required] string Name)
{
private readonly string _name;
// The following property is never null.
// However this object could have been constructed as "new Person(null)".
public string Name { get; init => _name = value ?? string.Empty; }
}
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
In questo caso, MVC non tenterà di eseguire di nuovo l'associazione Name
. Tuttavia, Age
può essere aggiornato
Comportamento di globalizzazione dei dati di route dell'associazione dei modelli e delle stringhe di query
Il provider di parametri di route di ASP.NET Core e il provider di parametri della stringa di query:
- Trattare i valori come cultura invariante.
- Ci si aspetta che gli URL siano invarianti rispetto alla cultura.
Al contrario, i valori provenienti dai dati del modulo vengono sottoposti a una conversione sensibile alla cultura. Questa operazione è progettata in modo che gli URL siano condivisibili tra le località.
Per fare in modo che il provider di valori di route core ASP.NET e il provider di valori stringa di query subisca una conversione sensibile alle impostazioni cultura:
- Ereditano da IValueProviderFactory
- Copiare il codice da QueryStringValueProviderFactory o RouteValueValueProviderFactory
- Sostituire il valore cultura passato al costruttore del provider di valore con CultureInfo.CurrentCulture
- Sostituisci la factory del provider di valori predefinita nelle opzioni MVC con la tua nuova.
public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var query = context.ActionContext.HttpContext.Request.Query;
if (query?.Count > 0)
{
context.ValueProviders.Add(
new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture));
}
return Task.CompletedTask;
}
}
builder.Services.AddControllers(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
.Single());
options.ValueProviderFactories[index] =
new CultureQueryStringValueProviderFactory();
});
Esistono alcuni tipi di dati speciali che l'associazione di modelli può gestire.
Un file caricato incluso nella richiesta HTTP. È anche supportato IEnumerable<IFormFile>
per più file.
Le azioni possono facoltativamente associare un oggetto CancellationToken
come parametro. Questo associa RequestAborted che segnala quando la connessione sottostante alla richiesta HTTP viene interrotta. Le azioni possono usare questo parametro per annullare operazioni asincrone a esecuzione prolungata eseguite come parte delle azioni del controller.
Usato per recuperare tutti i valori dai dati di modulo inviati.
I dati nel corpo della richiesta possono essere in formato JSON, XML o in altri formati. Per analizzare questi dati, l'associazione di modelli usa un formattatore di input configurato in modo da gestire un particolare tipo di contenuto. Per impostazione predefinita, ASP.NET Core include i formattatori di input basati su JSON per la gestione dei dati JSON. È possibile aggiungere altri formattatori per altri tipi di contenuto.
ASP.NET Core consente di selezionare i formattatori di input in base all'attributo Consumes. Se non è presente alcun attributo, viene usata l'intestazione Content-Type.
Per usare i formattatori di input XML predefiniti:
In
Program.cs
chiamare AddXmlSerializerFormatters o AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .AddXmlSerializerFormatters();
Applicare l'attributo
Consumes
alle classi controller o ai metodi di azione che devono aspettarsi XML nel corpo della richiesta.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
Per altre informazioni, vedere Introduzione alla serializzazione XML.
Un formattatore di input assume la piena responsabilità di leggere i dati dal corpo della richiesta. Per personalizzare questo processo, configurare le API usate dal formattatore di input. Questa sezione descrive come personalizzare il formattatore di input basato su System.Text.Json
per comprendere un tipo personalizzato denominato ObjectId
.
Si consideri il modello seguente, che contiene una proprietà personalizzata ObjectId
:
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
Per personalizzare il processo di associazione del modello quando si usa System.Text.Json
, creare una classe derivata da JsonConverter<T>:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<int>(ref reader, options));
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
=> writer.WriteNumberValue(value.Id);
}
Per usare un convertitore personalizzato, applicare l'attributo JsonConverterAttribute al tipo . Nell'esempio seguente il ObjectId
tipo è configurato con ObjectIdConverter
come convertitore personalizzato:
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
Per altre informazioni, vedere Come scrivere convertitori personalizzati.
Il comportamento dei sistemi di associazione e convalida del modello è basato su ModelMetadata. È possibile personalizzare ModelMetadata
mediante l'aggiunta di un provider di dettagli a MvcOptions.ModelMetadataDetailsProviders. Sono disponibili provider di dettagli integrati per disabilitare l'associazione di modelli o la convalida per tipi specificati.
Per disabilitare l'associazione di modelli per tutti i modelli di un tipo specificato, aggiungere un ExcludeBindingMetadataProvider in Program.cs
. Ad esempio, per disabilitare l'associazione di modelli per tutti i modelli di tipo System.Version
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Per disabilitare la convalida per le proprietà di un tipo specificato, aggiungere un SuppressChildValidationMetadataProvider in Program.cs
. Ad esempio, per disabilitare la convalida per le proprietà di tipo System.Guid
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
È possibile estendere l'associazione di modelli scrivendo uno strumento di associazione di modelli personalizzato e usando l'attributo [ModelBinder]
per selezionarlo per una determinata destinazione. Altre informazioni sull'associazione di modelli personalizzata.
L'associazione di modelli può essere richiamata manualmente usando il metodo TryUpdateModelAsync. Il metodo è definito in entrambe le classi ControllerBase
e PageModel
. I sovraccarichi del metodo consentono di specificare il prefisso e il fornitore di valori da usare. Il metodo restituisce false
se l'associazione di modelli non riesce. Ecco un esempio:
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync usa provider di valori per ottenere dati dal corpo del modulo, dalla stringa di query e dai dati di route.
TryUpdateModelAsync
è in genere:
- Usato con Razor le app Pages e MVC usando controller e visualizzazioni per evitare l'over-post.
- Non utilizzato con un'API Web, a meno che non venga consumato da dati di modulo, stringhe di query, e dati di percorso. Gli endpoint API Web che utilizzano JSON usano formattatori di input per deserializzare il corpo della richiesta in un oggetto .
Per altre informazioni, vedere TryUpdateModelAsync.
Il nome di questo attributo segue il modello degli attributi di associazione di modelli che specificano un'origine dati. Non si tratta però dell'associazione di dati da un provider di valori. Ottiene un'istanza di un tipo dal contenitore di iniezione delle dipendenze. Lo scopo è fornire un'alternativa all'iniezione del costruttore nel caso in cui sia necessario un servizio solo se viene chiamato un metodo specifico.
Se un'istanza del tipo non è registrata nel contenitore di inserimento delle dipendenze, l'app genera un'eccezione quando si tenta di associare il parametro. Per rendere facoltativo il parametro, usare uno degli approcci seguenti:
- Rendere il parametro nullable.
- Impostare un valore predefinito per il parametro .
Per i parametri nullable, assicurarsi che il parametro non sia null
prima di accedervi.
Questo articolo illustra cos'è l'associazione di modelli, come funziona e come personalizzarne il comportamento.
Visualizzare o scaricare il codice di esempio (procedura per il download).
I controller e Razor le pagine funzionano con i dati provenienti da richieste HTTP. Ad esempio, i dati del percorso possono fornire una chiave del record e i campi modulo inviati possono fornire valori per le proprietà del modello. La scrittura di codice per recuperare tutti i valori e convertirli da stringhe in tipi .NET sarebbe noiosa e soggetta a errori. L'associazione di modelli consente di automatizzare questo processo. Il sistema di associazione di modelli:
- Recupera i dati da diverse origini, ad esempio i dati di route, i campi modulo e le stringhe di query.
- Fornisce i dati ai controller e alle pagine Razor nei parametri e nelle proprietà pubbliche del metodo.
- Converte dati stringa in tipi .NET.
- Aggiorna le proprietà dei tipi complessi.
Si supponga di avere il metodo di azione seguente:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
E l'app riceve una richiesta con questo URL:
http://contoso.com/api/pets/2?DogsOnly=true
L'associazione di modelli esegue i passaggi seguenti dopo che il sistema di routing seleziona il metodo di azione:
- Trova il primo parametro di
GetById
, un intero denominatoid
. - Esamina le origini disponibili nella richiesta HTTP e trova
id
= "2" nei dati di route. - Converte la stringa "2" nell'intero 2.
- Trova il parametro successivo di
GetById
, un valore booleano denominatodogsOnly
. - Esamina le origini e trova "DogsOnly=true" nella stringa di query. Per la corrispondenza dei nomi non viene applicata la distinzione tra maiuscole e minuscole.
- Converte la stringa "true" nel valore booleano
true
.
Il framework chiama quindi il metodo GetById
, passando 2 per il parametro id
e true
per il parametro dogsOnly
.
Nell'esempio precedente le destinazioni dell'associazione di modelli sono parametri di metodo che sono tipi semplici. Le destinazioni possono essere anche le proprietà di un tipo complesso. Dopo l'associazione di ogni proprietà, viene eseguita la convalida dei modelli per la proprietà. Il record dei dati associati al modello e di eventuali errori di associazione o convalida viene archiviato in ControllerBase.ModelState oppure PageModel.ModelState. Per scoprire se questo processo ha esito positivo, l'app controlla il flag ModelState.IsValid.
Il binding dei modelli cerca di trovare i valori per i tipi di target seguenti:
- Parametri del metodo di azione del controller a cui viene indirizzata una richiesta.
- Parametri del metodo del Razor gestore Pages a cui viene indirizzata una richiesta.
- Proprietà pubbliche di un controller o una classe
PageModel
, se specificate dagli attributi.
Può essere applicato a una proprietà pubblica di un controller o una classe PageModel
per fare in modo che l'associazione di modelli usi tale proprietà come destinazione:
public class EditModel : InstructorsPageModel
{
[BindProperty]
public Instructor Instructor { get; set; }
Disponibile in ASP.NET Core 2.1 e versioni successive. Può essere applicato a un controller o una classe PageModel
per indicare all'associazione del modello di usare tutte le proprietà pubbliche della classe come destinazione:
[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
public Instructor Instructor { get; set; }
Per impostazione predefinita, le proprietà non vengono associate per le richieste HTTP GET. In genere, per una richiesta GET è sufficiente un parametro ID record. L'ID record viene usato per cercare l'elemento nel database. Pertanto, non è necessario associare una proprietà che contiene un'istanza del modello. Negli scenari in cui si vogliono associare le proprietà ai dati dalle richieste GET, impostare la proprietà SupportsGet
su true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }
Per impostazione predefinita, l'associazione di modelli ottiene i dati sotto forma di coppie chiave-valore dalle origini seguenti in una richiesta HTTP:
- Campi del modulo
- Il corpo della richiesta (per i controller con l'attributo [ApiController].)
- Dati percorso
- Parametri della stringa di query
- File caricati
Per ogni parametro o proprietà di destinazione, le origini vengono analizzate nell'ordine indicato nell'elenco precedente. Esistono tuttavia alcune eccezioni:
- I dati di route e i valori delle stringhe di query vengono usati solo per i tipi semplici.
- I file caricati vengono associati solo ai tipi di destinazione che implementano
IFormFile
oIEnumerable<IFormFile>
.
Se l'origine predefinita non è corretta, usare uno degli attributi seguenti per specificare l'origine:
-
[FromQuery]
: ottiene i valori dalla stringa di query. -
[FromRoute]
- Ottiene i valori dai dati di route. -
[FromForm]
- Ottiene i valori dai campi modulo inseriti. -
[FromBody]
- Ottiene i valori dal corpo della richiesta. -
[FromHeader]
- Ottiene i valori dalle intestazioni HTTP.
Questi attributi:
Vengono aggiunti singolarmente alle proprietà del modello (non alla classe del modello), come nell'esempio seguente:
public class Instructor { public int ID { get; set; } [FromQuery(Name = "Note")] public string NoteFromQueryString { get; set; }
Accettano facoltativamente un valore di nome di modello nel costruttore. Questa opzione è disponibile nel caso in cui il nome della proprietà non corrisponda al valore nella richiesta. Il valore nella richiesta potrebbe essere, ad esempio, un'intestazione con un trattino nel suo nome, come nell'esempio seguente:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
Applicare l'attributo [FromBody]
a un parametro per popolare le relative proprietà dal corpo di una richiesta HTTP. Il runtime di ASP.NET Core delega la responsabilità di leggere il corpo a un formattatore di input. I formattatori di input sono descritti più avanti in questo articolo.
Quando [FromBody]
viene applicato a un parametro di tipo complesso, tutti gli attributi di origine dell'associazione applicati alle relative proprietà vengono ignorati. Ad esempio, l'azione seguente Create
specifica che il relativo pet
parametro viene popolato dal corpo:
public ActionResult<Pet> Create([FromBody] Pet pet)
La Pet
classe specifica che la relativa Breed
proprietà viene popolata da un parametro della stringa di query:
public class Pet
{
public string Name { get; set; }
[FromQuery] // Attribute is ignored.
public string Breed { get; set; }
}
Nell'esempio precedente:
- L'attributo
[FromQuery]
viene ignorato. - La
Breed
proprietà non viene popolata da un parametro di stringa di query.
I formattatori di input leggono solo il corpo e non comprendono gli attributi dell'origine di associazione. Se nel corpo viene trovato un valore appropriato, tale valore viene usato per popolare la Breed
proprietà.
Non applicare [FromBody]
a più di un parametro per ogni metodo di azione. Quando il flusso di richiesta viene letto da un formattatore di input, non è più disponibile per la lettura per l'associazione di altri [FromBody]
parametri.
I dati di origine vengono forniti al sistema di associazione di modelli dai provider di valori. È possibile scrivere e registrare i provider di valori personalizzati che recuperano i dati per l'associazione di modelli da altre origini. Ad esempio, potrebbe essere necessario recuperare dati dai cookie o dallo stato della sessione. Per ottenere dati da una nuova origine:
- Creare una classe che implementi
IValueProvider
. - Creare una classe che implementi
IValueProviderFactory
. - Registrare la classe factory in
Startup.ConfigureServices
.
L'app di esempio include un esempio di provider di valori e factory che ottiene i valori dai cookie. Ecco il codice di registrazione in Startup.ConfigureServices
:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();
Il codice illustrato posiziona il provider di valori personalizzati dopo tutti i provider di valori predefiniti. Per renderlo il primo nell'elenco, chiamare Insert(0, new CookieValueProviderFactory())
invece di Add
.
Per impostazione predefinita, non viene creato un errore di stato del modello se non viene trovato alcun valore per una proprietà del modello. La proprietà viene impostata su Null o su un valore predefinito:
- I tipi semplici nullable vengono impostati su
null
. - I tipi valore non nullable vengono impostati su
default(T)
. Ad esempio, un parametroint id
viene impostato su 0. - Per i tipi complessi, l'associazione di modelli crea un'istanza usando il costruttore predefinito, senza impostare proprietà.
- Le matrici vengono impostate su
Array.Empty<T>()
, ad eccezione delle matricibyte[]
che vengono impostate sunull
.
Se lo stato del modello deve essere invalidato quando non viene trovato nulla nei campi modulo per una proprietà del modello, usare l'attributo [BindRequired]
.
Si noti che questo comportamento [BindRequired]
si applica all'associazione di modelli dai dati di moduli inviati e non ai dati JSON o XML nel corpo di una richiesta. I dati del corpo della richiesta vengono gestiti dai formattatori di input.
Se viene trovata un'origine ma non può essere convertita nel tipo di destinazione, lo stato del modello viene contrassegnato come non valido. Il parametro o la proprietà di destinazione viene impostato su Null o su un valore predefinito, come indicato nella sezione precedente.
In un controller API con l'attributo [ApiController]
, uno stato del modello non valido genera una risposta HTTP 400 automatica.
In una Razor pagina, visualizzare di nuovo la pagina con un messaggio di errore:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
_instructorsInMemoryStore.Add(Instructor);
return RedirectToPage("./Index");
}
La convalida lato client rileva la maggior parte dei dati non valido che altrimenti verrebbero inviati a un Razor modulo Pages. Questa convalida rende difficile attivare il codice sopra evidenziato. L'app di esempio include un pulsante Submit with Invalid Date (Invia con data non valida) che inserisce dati non validi nel campo Hire Date (Data assunzione) e invia il modulo. Questo pulsante illustra il funzionamento del codice per visualizzare di nuovo la pagina quando si verificano errori di conversione dei dati.
Quando la pagina viene nuovamente visualizzata dal codice precedente, l'input non valido non viene visualizzato nel campo modulo. Questo avviene perché la proprietà del modello è stata impostata su Null o su un valore predefinito. L'input non valido viene visualizzato in un messaggio di errore. Ma se si vogliono visualizzare di nuovo i dati non validi nel campo modulo, è consigliabile impostare la proprietà del modello come stringa ed eseguire manualmente la conversione dei dati.
La stessa strategia è consigliata se si vuole evitare che gli errori di conversione del tipo causino errori di stato del modello. In tal caso, impostare la proprietà del modello a stringa.
I tipi semplici in cui lo strumento di associazione di modelli può convertire le stringhe di origine includono i seguenti:
- Booleano
- Byte, SByte
- Char
- DateTime
- DateTimeOffset
- Decimale
- Doppio
- Enum
- Guid
- Int16, Int32, Int64
- Singolo
- TimeSpan
- UInt16, UInt32, UInt64
- URI
- Versione
Un tipo complesso deve avere un costruttore predefinito pubblico e proprietà scrivibili pubbliche da associare. Quando si verifica l'associazione di modelli, vengono create istanze della classe usando il costruttore predefinito pubblico.
Per ogni proprietà del tipo complesso, l'associazione di modelli cerca il pattern del nome prefisso.nome_proprietà nelle origini. Se non viene trovato, cerca semplicemente nome_proprietà senza il prefisso.
Per l'associazione a un parametro, il prefisso è il nome del parametro. Per l'associazione a una proprietà pubblica PageModel
, il prefisso è il nome della proprietà pubblica. Alcuni attributi hanno una proprietà Prefix
che consente di eseguire l'override dell'utilizzo predefinito del nome di un parametro o una proprietà.
Ad esempio, si supponga che il tipo complesso sia la classe Instructor
seguente:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Se il modello da associare è un parametro denominato instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
L'associazione di modelli inizia cercando la chiave instructorToUpdate.ID
nelle origini. Se non viene trovata, cerca ID
senza prefisso.
Se il modello da associare è una proprietà denominata Instructor
del controller o della classe PageModel
:
[BindProperty]
public Instructor Instructor { get; set; }
L'associazione dei modelli inizia cercando la chiave Instructor.ID
tra le fonti. Se non viene trovata, cerca ID
senza prefisso.
Se il modello da associare è un parametro denominato instructorToUpdate
e un attributo Bind
specifica Instructor
come prefisso:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
L'associazione di modelli inizia cercando la chiave Instructor.ID
all'interno delle fonti. Se non viene trovata, cerca ID
senza prefisso.
Sono disponibili vari attributi predefiniti per controllare l'associazione di modelli di tipi complessi:
[Bind]
[BindRequired]
[BindNever]
Avviso
Questi attributi influiscono sul modello di associazione quando i dati di modulo inviati sono l'origine dei valori. Non influiscono sui formattatori di input, che elaborano i corpi delle richieste JSON e XML pubblicati. I formattatori di input sono descritti più avanti in questo articolo.
Può essere applicato a una classe o a un parametro di metodo. Specifica quali proprietà di un modello devono essere incluse nell'associazione di modelli.
[Bind]
non influisce sui formattatori di input.
Nell'esempio seguente vengono associate solo le proprietà specificate del modello Instructor
quando viene chiamato qualsiasi gestore o metodo di azione:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
Nell'esempio seguente vengono associate solo le proprietà specificate del modello Instructor
quando viene chiamato il metodo OnPost
:
[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
L'attributo [Bind]
può essere usato per evitare l'overposting negli scenari di creazione. Non funziona bene negli scenari di modifica perché le proprietà escluse vengono impostate su Null o su un valore predefinito anziché rimanere inalterate. Per la protezione dall'overposting, è consigliabile usare i modelli di visualizzazione anziché l'attributo [Bind]
. Per altre informazioni, vedere Nota sulla sicurezza relativa all'overposting.
ModelBinderAttribute può essere applicato a tipi, proprietà o parametri. Consente di specificare il tipo di gestore di associazione di modelli usato per associare l'istanza o il tipo specifico. Ad esempio:
[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
L'attributo [ModelBinder]
può essere usato anche per modificare il nome di una proprietà o di un parametro quando è associato al modello:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
public string Name { get; set; }
}
Può essere applicato solo alle proprietà del modello e non ai parametri di metodo. Con questo attributo l'associazione di modelli aggiunge un errore di stato del modello se non è possibile eseguire l'associazione per una proprietà del modello. Ecco un esempio:
public class InstructorWithCollection
{
public int ID { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
[BindRequired]
public DateTime HireDate { get; set; }
Vedere anche la discussione relativa all'attributo [Required]
in Convalida del modello.
Può essere applicato solo alle proprietà del modello e non ai parametri di metodo. Impedisce all'associazione di modelli di impostare una proprietà del modello. Ecco un esempio:
public class InstructorWithDictionary
{
[BindNever]
public int ID { get; set; }
Per le destinazioni che sono raccolte di tipi semplici, l'associazione di modelli cerca le corrispondenze per nome_parametro oppure nome_proprietà. Se non viene trovata alcuna corrispondenza, viene cercato uno dei formati supportati senza il prefisso. Ad esempio:
Si supponga che il parametro da associare sia una matrice denominata
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
I dati di modulo o di stringhe di query possono essere in uno dei formati seguenti:
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Evitare l'associazione di un parametro o di una proprietà denominata
index
oIndex
se è adiacente a un valore della raccolta. L'associazione di modelli tenta di usareindex
come indice per la raccolta, che potrebbe comportare un'associazione errata. Si consideri ad esempio l'azione seguente:public IActionResult Post(string index, List<Product> products)
Nel codice precedente, il
index
parametro della stringa di query viene associato al parametro delindex
metodo e viene usato anche per associare la raccolta di prodotti. La ridenominazione del parametro o l'uso di un attributo di associazione di modelli per configurare l'associazioneindex
evita questo problema:public IActionResult Post(string productIndex, List<Product> products)
Il formato seguente è supportato solo nei dati di modulo:
selectedCourses[]=1050&selectedCourses[]=2000
Per tutti i formati di esempio precedenti, l'associazione di modelli passa una matrice di due elementi al parametro
selectedCourses
:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Per i formati di dati che usano numeri con indice (... [0]... [1]...) è necessario assicurarsi che la numerazione sia progressiva a partire da zero. Se sono presenti eventuali interruzioni nella numerazione con indice, tutti gli elementi dopo l'interruzione vengono ignorati. Ad esempio, se i pedici sono 0 e 2 anziché 0 e 1, il secondo elemento viene ignorato.
Per le destinazioni Dictionary
, l'associazione di modelli cerca le corrispondenze per nome_parametro oppure nome_proprietà. Se non viene trovata alcuna corrispondenza, viene cercato uno dei formati supportati senza il prefisso. Ad esempio:
Si supponga che il parametro di destinazione sia un elemento
Dictionary<int, string>
denominatoselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
I dati di modulo o di stringhe di query inviati possono essere simili a uno degli esempi seguenti:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
Per tutti i formati di esempio precedenti, l'associazione di modelli passa un dizionario di due elementi al parametro
selectedCourses
:- selectedCourses["1050"]="Chimica"
- selectedCourses["2000"]="Economia"
L'associazione di modelli richiede che i tipi complessi abbiano un costruttore senza parametri. Sia i formattatori di input basati su System.Text.Json
sia su Newtonsoft.Json
supportano la deserializzazione delle classi che non hanno un costruttore senza parametri.
C# 9 introduce i tipi di record, un ottimo modo per rappresentare in modo conciso i dati in rete. ASP.NET Core aggiunge il supporto per l'associazione di modelli e la convalida dei tipi di record con un singolo costruttore:
public record Person([Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
...
}
}
Person/Index.cshtml
:
@model Person
<label>Name: <input asp-for="Name" /></label>
...
<label>Age: <input asp-for="Age" /></label>
Quando si convalidano i tipi di record, il runtime cerca i metadati di associazione e convalida in modo specifico sui parametri anziché sulle proprietà.
Il framework consente l'associazione ai tipi di record e la convalida:
public record Person([Required] string Name, [Range(0, 100)] int Age);
Affinché quanto sopra possa funzionare, il tipo deve:
- Essere un tipo di record.
- Avere esattamente un costruttore pubblico.
- Contenere parametri con una proprietà con lo stesso nome e tipo. I nomi non devono essere diversi in base alle maiuscole e minuscole.
I POCO che non dispongono di costruttori senza parametri non possono essere associati.
Il codice seguente genera un'eccezione che indica che il tipo deve avere un costruttore senza parametri:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Tipi di record con costruttori creati manualmente che hanno un aspetto simile al funzionamento dei costruttori primari
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age) => (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
Per i tipi di record, vengono usati i metadati di convalida e associazione per i parametri. Tutti i metadati sulle proprietà vengono ignorati
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
La convalida usa i metadati nel parametro, ma usa la proprietà per leggere il valore. Nel caso comune con i costruttori primari, i due sarebbero identici. Tuttavia, ci sono modi per sconfiggerla:
public record Person([Required] string Name)
{
private readonly string _name;
public Name { get; init => _name = value ?? string.Empty; } // Now this property is never null. However this object could have been constructed as `new Person(null);`
}
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
In questo caso, MVC non tenterà di eseguire di nuovo l'associazione Name
. Tuttavia, Age
è consentito aggiornare
Provider di valori di route core ASP.NET e provider di valori stringa di query:
- Considerare i valori come cultura invariante.
- Ci si aspetta che gli URL siano invarianti rispetto alle impostazioni culturali.
Al contrario, i valori provenienti dai dati del modulo vengono sottoposti a una conversione sensibile alle impostazioni cultura. Questa operazione è progettata in modo che gli URL siano condivisibili tra le impostazioni locali.
Per fare in modo che il provider di valori di route core ASP.NET e il provider di valori stringa di query subisca una conversione sensibile alle impostazioni cultura:
- Ereditano da IValueProviderFactory
- Copiare il codice da QueryStringValueProviderFactory o RouteValueValueProviderFactory
- Sostituire il valore delle impostazioni cultura passato al costruttore del provider di valori con CultureInfo.CurrentCulture
- Sostituire la factory del provider di valori predefinita nelle opzioni MVC con quella nuova:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
options.ValueProviderFactories[index] = new CulturedQueryStringValueProviderFactory();
});
}
public class CulturedQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var query = context.ActionContext.HttpContext.Request.Query;
if (query != null && query.Count > 0)
{
var valueProvider = new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture);
context.ValueProviders.Add(valueProvider);
}
return Task.CompletedTask;
}
}
Esistono alcuni tipi di dati speciali che l'associazione di modelli può gestire.
Un file caricato incluso nella richiesta HTTP. È anche supportato IEnumerable<IFormFile>
per più file.
Le azioni possono facoltativamente associare un oggetto CancellationToken
come parametro. Associa RequestAborted che segnala quando la connessione sottostante alla richiesta HTTP viene interrotta. Le azioni possono usare questo parametro per annullare operazioni asincrone a esecuzione prolungata eseguite come parte delle azioni del controller.
Usato per recuperare tutti i valori dai dati di modulo inviati.
I dati nel corpo della richiesta possono essere in formato JSON, XML o in altri formati. Per analizzare questi dati, l'associazione di modelli usa un formattatore di input configurato in modo da gestire un particolare tipo di contenuto. Per impostazione predefinita, ASP.NET Core include i formattatori di input basati su JSON per la gestione dei dati JSON. È possibile aggiungere altri formattatori per altri tipi di contenuto.
ASP.NET Core consente di selezionare i formattatori di input in base all'attributo Consumes. Se non è presente alcun attributo, viene usata l'intestazione Content-Type.
Per usare i formattatori di input XML predefiniti:
Installare il pacchetto NuGet
Microsoft.AspNetCore.Mvc.Formatters.Xml
.In
Startup.ConfigureServices
, chiama AddXmlSerializerFormatters o AddXmlDataContractSerializerFormatters.services.AddRazorPages() .AddMvcOptions(options => { options.ValueProviderFactories.Add(new CookieValueProviderFactory()); options.ModelMetadataDetailsProviders.Add( new ExcludeBindingMetadataProvider(typeof(System.Version))); options.ModelMetadataDetailsProviders.Add( new SuppressChildValidationMetadataProvider(typeof(System.Guid))); }) .AddXmlSerializerFormatters();
Applicare l'attributo
Consumes
alle classi controller o ai metodi di azione che devono aspettarsi XML nel corpo della richiesta.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
Per altre informazioni, vedere Introduzione alla serializzazione XML.
Un formattatore di input assume la piena responsabilità di leggere i dati dal corpo della richiesta. Per personalizzare questo processo, configurare le API usate dal formattatore di input. Questa sezione descrive come personalizzare il formattatore di input basato su System.Text.Json
per gestire un tipo personalizzato denominato ObjectId
.
Si consideri il modello seguente, che contiene una proprietà personalizzata denominata ObjectId
Id
:
public class ModelWithObjectId
{
public ObjectId Id { get; set; }
}
Per personalizzare il processo di associazione del modello quando si usa System.Text.Json
, creare una classe derivata da JsonConverter<T>:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new ObjectId(JsonSerializer.Deserialize<int>(ref reader, options));
}
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Id);
}
}
Per usare un convertitore personalizzato, applicare l'attributo JsonConverterAttribute al tipo . Nell'esempio seguente il ObjectId
tipo è configurato con ObjectIdConverter
come convertitore personalizzato:
[JsonConverter(typeof(ObjectIdConverter))]
public struct ObjectId
{
public ObjectId(int id) =>
Id = id;
public int Id { get; }
}
Per altre informazioni, vedere Come scrivere convertitori personalizzati.
Il comportamento dei sistemi di associazione e convalida del modello è basato su ModelMetadata. È possibile personalizzare ModelMetadata
mediante l'aggiunta di un provider di dettagli a MvcOptions.ModelMetadataDetailsProviders. Provider di dettagli integrati sono disponibili per disabilitare l'associazione dei modelli o la convalida per i tipi specificati.
Per disabilitare l'associazione di modelli per tutti i modelli di un tipo specificato, aggiungere un ExcludeBindingMetadataProvider in Startup.ConfigureServices
. Ad esempio, per disabilitare l'associazione di modelli per tutti i modelli di tipo System.Version
:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();
Per disabilitare la convalida per le proprietà di un tipo specificato, aggiungere un SuppressChildValidationMetadataProvider in Startup.ConfigureServices
. Ad esempio, per disabilitare la convalida per le proprietà di tipo System.Guid
:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();
È possibile estendere l'associazione di modelli scrivendo uno strumento di associazione di modelli personalizzato e usando l'attributo [ModelBinder]
per selezionarlo per una determinata destinazione. Altre informazioni sull'associazione di modelli personalizzata.
L'associazione di modelli può essere richiamata manualmente usando il metodo TryUpdateModelAsync. Il metodo è definito in entrambe le classi ControllerBase
e PageModel
. I sovraccarichi del metodo consentono di specificare il prefisso e il provider di valori da usare. Il metodo restituisce false
se l'associazione di modelli non riesce. Ecco un esempio:
if (await TryUpdateModelAsync<InstructorWithCollection>(
newInstructor,
"Instructor",
i => i.FirstMidName, i => i.LastName, i => i.HireDate))
{
_instructorsInMemoryStore.Add(newInstructor);
return RedirectToPage("./Index");
}
PopulateAssignedCourseData(newInstructor);
return Page();
TryUpdateModelAsync usa provider di valori per ottenere dati dal corpo del modulo, dalla stringa di query e dai dati di route.
TryUpdateModelAsync
è in genere:
- Usato con Razor le app Pages e MVC usando controller e visualizzazioni per evitare l'over-post.
- Non usato con un'API Web, a meno che non venga utilizzato dai dati del modulo, dalle stringhe di query e dai dati di route. Gli endpoint API Web che utilizzano JSON usano formattatori di input per deserializzare il corpo della richiesta in un oggetto .
Per altre informazioni, vedere TryUpdateModelAsync.
Il nome di questo attributo segue il modello degli attributi di associazione di modelli che specificano un'origine dati. Non si tratta però del collegamento di dati da un fornitore di valori. Ottiene un'istanza di un tipo dal contenitore di inserimento delle dipendenze. Lo scopo è fornire un'alternativa all'iniezione del costruttore quando hai bisogno di un servizio solo se viene chiamato un metodo specifico.
Feedback su ASP.NET Core
ASP.NET Core è un progetto di open source. Selezionare un collegamento per fornire feedback: