Modellbindung in ASP.NET Core

In diesem Artikel wird erläutert, was Modellbindung ist, wie sie funktioniert, und wie Sie ihr Verhalten anpassen können.

Zeigen Sie Beispielcode an, oder laden Sie diesen herunter (Vorgehensweise zum Herunterladen).

Was ist Modellbindung?

Controller und Razor Seiten arbeiten mit Daten, die von HTTP-Anforderungen stammen. Routendaten können beispielsweise einen Datensatzschlüssel enthalten, und bereitgestellte Formularfelder können Werte für die Eigenschaften des Modells bereitstellen. Das Schreiben von Code zum Abrufen jedes dieser Werte und deren Konvertierung aus Zeichenfolgen in .NET-Datentypen wäre mühsam und fehleranfällig. Modellbindung automatisiert diesen Vorgang. Das Modellbindungssystem:

  • Ruft Daten aus verschiedenen Quellen ab, z. B. Routendaten, Formularfelder und Abfragezeichenfolgen.
  • Stellt die Daten für Controller und Razor Seiten in Methodenparametern und öffentlichen Eigenschaften zur Verfügung.
  • Konvertiert Zeichenfolgendaten in .NET-Typen.
  • Aktualisiert Eigenschaften komplexer Typen.

Beispiel

Angenommen Sie haben die folgende Aktionsmethode:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

Und die App empfängt eine Anforderung mit dieser URL:

http://contoso.com/api/pets/2?DogsOnly=true

Die Modellbindung durchläuft die folgenden Schritte, nachdem das Routingsystem die Aktionsmethode ausgewählt hat:

  • Findet den ersten Parameters von GetById, eine ganze Zahl namens id.
  • Durchsucht die verfügbaren Quellen in der HTTP-Anforderung und findet id = „2“ in den Routendaten.
  • Konvertiert der Zeichenfolge „2“ in die ganze Zahl 2.
  • Findet den nächsten Parameter von GetById, einen booleschen Wert namens dogsOnly.
  • Durchsucht die Quellen und findet „DogsOnly=True“ in der Abfragezeichenfolge. Beim Abgleich von Namen wird die Groß- und Kleinschreibung nicht berücksichtigt.
  • Konvertiert die Zeichenfolge „true“ in den booleschen Wert true.

Das Framework ruft dann die GetById-Methode auf, und übergibt dabei als Eingabe „2“ für den id-Parameter und true für den dogsOnly-Parameter.

Im vorherigen Beispiel sind die Ziele der Modellbindung Methodenparameter, die einfache Typen sind. Ziele können aber auch die Eigenschaften eines komplexen Typs sein. Nachdem jede Eigenschaft erfolgreich gebunden wurde, erfolgt die Modellvalidierung für diese Eigenschaft. Der Datensatz darüber, welche Daten an das Modell gebunden sind, sowie mit allen Bindungs- oder Validierungsfehlern wird in ControllerBase.ModelState oder PageModel.ModelState gespeichert. Um herauszufinden, ob dieser Vorgang erfolgreich war, überprüft die App das Flag ModelState.IsValid.

Ziele

Die Modellbindung versucht, Werte für die folgenden Arten von Zielen zu finden:

  • Parameter der Controlleraktionsmethode, zu der eine Anforderung weitergeleitet wird.
  • Parameter der Razor Pages-Handlermethode, an die eine Anforderung geroutet wird.
  • Öffentliche Eigenschaften eines Controllers oder einer PageModel-Klasse, falls durch Attribute angegeben.

[BindProperty]-Attribut

Kann auf eine öffentliche Eigenschaft eines Controllers oder einer PageModel-Klasse angewendet werden, um die Modellbindung anzuweisen, diese Eigenschaft als Ziel zu verwenden:

public class EditModel : InstructorsPageModel
{
    [BindProperty]
    public Instructor Instructor { get; set; }

[BindProperties]-Attribut

Verfügbar in ASP.NET Core 2.1 und höher. Kann auf einen Controller oder eine PageModel-Klasse angewendet werden, um die Modellbindung anzuweisen, alle öffentlichen Eigenschaften dieser Klasse als Ziel zu verwenden:

[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
    public Instructor Instructor { get; set; }

Modellbindung für HTTP-GET-Anforderungen

Standardmäßig sind Eigenschaften für HTTP GET-Anforderungen nicht gebunden. In der Regel ist alles, was Sie für eine GET-Anforderung benötigen, ein Datensatz-ID-Parameter. Die Datensatz-ID wird verwendet, um das Element in der Datenbank zu suchen. Daher besteht keine Notwendigkeit, eine Eigenschaft zu binden, die eine Instanz des Modells enthält. In Szenarien, in denen Sie Eigenschaften an Daten aus GET-Anforderungen binden möchten, legen Sie die Eigenschaft SupportsGet auf true fest:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }

Quellen

Standardmäßig ruft die Modellbindung Daten in Form von Schlüssel-Wert-Paaren aus den folgenden Quellen in einer HTTP-Anforderung ab:

  1. Formularfelder
  2. Der Anforderungstext (für Controller mit dem [ApiController]-Attribut)
  3. Routendaten
  4. Abfragezeichenfolge-Parameter
  5. Hochgeladene Dateien

Für jeden Zielparameter oder jede Zieleigenschaft werden die Quellen nach der oben aufgeführten Reihenfolge überprüft. Es gibt ein paar Ausnahmen:

  • Routendaten und Abfragezeichenfolgenwerte werden nur für einfache Typen verwendet.
  • Hochgeladene Dateien werden nur an Zieltypen gebunden, die IFormFile oder IEnumerable<IFormFile> implementieren.

Wenn die Standardquelle nicht richtig ist, verwenden Sie eines der folgenden Attribute zum Festlegen der Quelle:

  • [FromQuery] – Ruft Werte aus der Abfragezeichenfolge ab.
  • [FromRoute] : Ruft Werte aus Routendaten ab.
  • [FromForm] – Ruft Werte aus den gesendeten Formularfeldern ab.
  • [FromBody] – Ruft Werte aus dem Anforderungskörper ab.
  • [FromHeader] : Ruft Werte aus HTTP-Headern ab.

Diese Attribute:

  • Werden Modelleigenschaften einzeln hinzugefügt (nicht zur Modellklasse), wie im folgenden Beispiel gezeigt:

    public class Instructor
    {
        public int ID { get; set; }
    
        [FromQuery(Name = "Note")]
        public string NoteFromQueryString { get; set; }
    
  • Akzeptieren optional einen Modellnamenswert im Konstruktor. Diese Option wird für den Fall bereitgestellt, dass der Eigenschaftenname nicht mit dem Wert in der Anforderung übereinstimmt. Beispielsweise könnte der Wert in der Anforderung ein Header mit einem Bindestrich in seinem Namen sein, wie im folgenden Beispiel gezeigt:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

[FromBody]-Attribut

Wenden Sie das [FromBody]-Attribut auf einen Parameter an, um dessen Eigenschaften über den Text einer HTTP-Anforderung aufzufüllen. Die ASP.NET Core-Runtime delegiert die Verantwortung, für das Lesen des Texts an einen Eingabeformatierer. Eingabeformatierer werden später in diesem Artikel erklärt.

Wenn [FromBody] auf einen komplexen Typparameter angewendet wird, werden alle Bindungsquellenattribute ignoriert, die auf die Eigenschaften angewendet werden. Die folgende Create-Aktion legt beispielsweise fest, dass der pet-Parameter mithilfe des Texts aufgefüllt wird:

public ActionResult<Pet> Create([FromBody] Pet pet)

Die Pet-Klasse legt fest, dass ihre Breed-Eigenschaft mithilfe eines Abfragezeichenfolgenparameters aufgefüllt wird:

public class Pet
{
    public string Name { get; set; }

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; }
}

Im vorherigen Beispiel:

  • Das [FromQuery]-Attribut wird ignoriert.
  • Die Breed-Eigenschaft wird nicht mithilfe eines Abfragezeichenfolgenparameters aufgefüllt.

Eingabeformatierer lesen nur den Text und verstehen Bindungsquellenattribute nicht. Wenn ein geeigneter Wert im Text gefunden wird, wird dieser Wert zum Auffüllen der Breed-Eigenschaft verwendet.

Wenden Sie [FromBody] auf nicht mehr als einen Parameter pro Aktionsmethode an. Sobald der Anforderungsdatenstrom von einem Eingabeformatierer gelesen wurde, ist er nicht mehr verfügbar, um für die Bindung anderer [FromBody]-Parameter nochmal gelesen zu werden.

Zusätzliche Quellen

Quelldaten werden dem Modellbindungssystem durch Wertanbieter bereitgestellt. Sie können benutzerdefinierte Wertanbieter schreiben und registrieren, die Daten für die Modellbindung aus anderen Quellen abrufen. Sie können z. B. Daten aus cookie s oder dem Sitzungszustand wünschen. So rufen Sie Daten aus einer neuen Quelle ab

  • Erstellen Sie eine Klasse, die das IValueProvider implementiert.
  • Erstellen Sie eine Klasse, die das IValueProviderFactory implementiert.
  • Registrieren Sie die Factoryklasse in Startup.ConfigureServices.

Die Beispiel-App enthält einen Wertanbieter und ein Factorybeispiel, das Werte aus cookie s ruft. Dies ist der Registrierungscode 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();

Der angezeigte Code fügt den benutzerdefinierten Wertanbieter hinter allen integrierten Wertanbietern ein. Damit er der erste in der Liste wird, rufen Sie Insert(0, new CookieValueProviderFactory()) anstelle von Add auf.

Keine Quelle für eine Modelleigenschaft

Standardmäßig wird kein Modellzustandsfehler erstellt, wenn kein Wert für eine Modelleigenschaft gefunden wird. Die Eigenschaft wird auf „null“ oder einen Standardwert festgelegt:

  • Einfache Nullable-Typen werden auf null festgelegt.
  • Nicht-Nullable-Werttypen werden auf default(T) festgelegt. Beispiel: Ein Parameter int id wird auf „0“ festgelegt.
  • Für komplexe Typen erstellt die Modellbindung eine Instanz, indem der Standardkonstruktor verwendet wird, ohne Eigenschaften festzulegen.
  • Arrays werden auf Array.Empty<T>() festgelegt, mit der Ausnahme, dass byte[]-Arrays auf null festgelegt werden.

Wenn der Modellzustand ungültig gemacht werden soll, wenn in Formularfeldern für eine Modelleigenschaft nichts gefunden wird, verwenden Sie das [BindRequired] -Attribut.

Beachten Sie, dass dieses [BindRequired]-Verhalten für die Modellbindung von Daten aus bereitgestellten Formulardaten gilt, nicht für JSON- oder XML-Daten in einem Anforderungstext. Anforderungstextdaten werden von Eingabeformatierern verarbeitet.

Typkonvertierungsfehler

Wenn eine Quelle gefunden wird, aber nicht in den Zieltyp konvertiert werden kann, wird der Modellzustand als „ungültig“ gekennzeichnet. Der Zielparameter oder die Zieleigenschaft wird auf „null“ oder einen Standardwert festgelegt, wie bereits im vorherigen Abschnitt erwähnt.

In einem API-Controller, der über das [ApiController]-Attribut verfügt, führt ein ungültiger Modellzustand zu einer automatischen „HTTP 400“-Antwort.

Auf einer Razor Seite wird die Seite erneut mit einer Fehlermeldung angezeigt:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _instructorsInMemoryStore.Add(Instructor);
    return RedirectToPage("./Index");
}

Die clientseitige Validierung fängt die meisten fehlerhaften Daten ab, die andernfalls an ein Razor Pages-Formular übermittelt würden. Diese Validierung erschwert es, den voranstehenden, hervorgehobenen Code auszulösen. Die Beispiel-App umfasst eine Schaltfläche Submit with Invalid Date (Mit ungültigem Datum absenden), die ungültige Daten in das Feld Hire Date (Einstellungsdatum) einfügt und das Formular absendet. Diese Schaltfläche zeigt, wie der Code zum erneuten Anzeigen der Seite funktioniert, wenn Datenkonvertierungsfehler auftreten.

Wenn die Seite von dem vorangehenden Code erneut angezeigt wird, wird die ungültige Eingabe nicht im Formularfeld angezeigt. Dies liegt daran, dass die Modelleigenschaft auf „null“ oder einen Standardwert festgelegt wurde. Die ungültige Eingabe wird jedoch in einer Fehlermeldung angezeigt. Wenn Sie aber die ungültigen Daten im Formularfeld erneut anzeigen möchten, sollten Sie aus der Modelleigenschaft eine Zeichenfolge machen und die Datenkonvertierung manuell ausführen.

Dieselbe Strategie empfiehlt sich, wenn Sie nicht möchten, dass Typkonvertierungsfehler zu Modellzustandsfehlern führen. In diesem Fall machen Sie aus der Modelleigenschaft eine Zeichenfolge.

Einfache Typen

Die einfachen Typen, in die die Modellbindung Quellzeichenfolgen konvertieren kann, sind unter anderem:

Komplexe Typen

Ein komplexer Typ muss einen öffentlichen Standardkonstruktor und öffentliche schreibbare Eigenschaften besitzen, die gebunden werden können. Wenn die Modellbindung erfolgt, wird die Klasse mit dem öffentlichen Standardkonstruktor instanziiert.

Für jede Eigenschaft des komplexen Typs durchsucht die Modellbindung die Quellen für das Namensmuster prefix.property_name. Wenn nichts gefunden wird, sucht sie nur nach property_name ohne das Präfix.

Beim Binden an einen Parameter ist das Präfix der Name des Parameters. Beim Binden an eine öffentliche Eigenschaft PageModel ist das Präfix der Name der öffentlichen Eigenschaft. Einige Attribute besitzen eine Eigenschaft Prefix, die es Ihnen gestattet, die Standardverwendung des Parameter- oder Eigenschaftennamens außer Kraft zu setzen.

Nehmen Sie beispielsweise an, der komplexe Typ ist die folgende Instructor-Klasse:

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Präfix = Parametername

Wenn das zu bindende Modell ein Parameter namens instructorToUpdate ist:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Die Modellbindung beginnt, indem sie die Quellen nach dem Schlüssel instructorToUpdate.ID durchsucht. Wenn dieser nicht gefunden wird, sucht sie nach ID ohne Präfix.

Präfix = Name der Eigenschaft

Wenn das zu bindende Modell eine Eigenschaft des Controllers oder der PageModel-Klasse namens Instructor ist:

[BindProperty]
public Instructor Instructor { get; set; }

Die Modellbindung beginnt, indem sie die Quellen nach dem Schlüssel Instructor.ID durchsucht. Wenn dieser nicht gefunden wird, sucht sie nach ID ohne Präfix.

Benutzerdefiniertes Präfix

Wenn das zu bindende Modell ein Parameter namens instructorToUpdate ist, und ein Bind-Attribut Instructor als Präfix angibt:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

Die Modellbindung beginnt, indem sie die Quellen nach dem Schlüssel Instructor.ID durchsucht. Wenn dieser nicht gefunden wird, sucht sie nach ID ohne Präfix.

Attribute für Ziele komplexen Typs

Mehrere integrierte Attribute stehen für die Kontrolle der Modellbindung komplexer Typen zur Verfügung:

  • [Bind]
  • [BindRequired]
  • [BindNever]

Warnung

Diese Attribute wirken sich auf die Modellbindung aus, wenn bereitgestellte Formulardaten die Quelle der Wert sind. Sie wirken sich nicht auf Eingabeformatierer aus, die die gesendeten JSON- und XML-Anforderungskörper verarbeiten. Eingabeformatierer werden später in diesem Artikel erklärt.

[Bind]-Attribut

Kann auf eine Klasse oder einen Methodenparameter angewendet werden. Gibt an, welche Eigenschaften eines Modells in die Modellbindung aufgenommen werden sollen. [Bind] wirkt sich nicht auf Eingabeformatierer aus.

Im folgenden Beispiel werden nur die angegebenen Eigenschaften des Instructor-Modells gebunden, wenn ein Ereignishandler oder eine Aktionsmethode aufgerufen wird:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

Im folgenden Beispiel werden nur die angegebenen Eigenschaften des Instructor-Modells gebunden, wenn die OnPost-Methode aufgerufen wird:

[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

Das [Bind]-Attribut kann zum Schutz vor Overposting in Erstellungs szenarien (create) verwendet werden. Es funktioniert nicht gut in Bearbeitungsszenarien (edit), weil ausgeschlossene Eigenschaften auf „null“ oder einen Standardwert festgelegt werden, anstatt unverändert zu bleiben. Zum Schutz vor Overposting werden Ansichtsmodelle empfohlen, anstelle des [Bind]-Attributs. Weitere Informationen finden Sie unter Sicherheitshinweis zum Overposting.

[ModelBinder]-Attribut

ModelBinderAttribute kann auf Typen, Eigenschaften oder Parameter angewendet werden. Sie ermöglicht die Angabe des Modellbindungstyps, der zum Binden der spezifischen Instanz oder des Typs verwendet wird. Beispiel:

[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)

Das -Attribut kann auch verwendet werden, um den Namen einer Eigenschaft oder eines Parameters zu ändern, wenn sie [ModelBinder] modellgebunden ist:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    public string Name { get; set; }
}

[BindRequired]-Attribut

Kann nur auf Modelleigenschaften angewendet werden, nicht auf Methodenparameter. Bewirkt, dass die Modellbindung einen Modellzustandsfehler hinzufügt, wenn die Bindung für die Eigenschaft eines Modells nicht erfolgen kann. Hier sehen Sie ein Beispiel:

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

Lesen Sie auch die Diskussion des [Required]-Attributs in der Modellvalidierung.

[BindNever]-Attribut

Kann nur auf Modelleigenschaften angewendet werden, nicht auf Methodenparameter. Verhindert, dass die Modellbindung die Eigenschaft eines Modells festlegt. Hier sehen Sie ein Beispiel:

public class InstructorWithDictionary
{
    [BindNever]
    public int ID { get; set; }

Sammlungen

Bei Zielen, die Sammlungen einfacher Typen sind, sucht die Modellbindung nach Übereinstimmungen mit parameter_name oder property_name. Wird keine Übereinstimmung gefunden, sucht sie nach einem der unterstützten Formate ohne Präfix. Beispiel:

  • Angenommen, der zu bindende Parameter ist ein Array namens selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Formular- oder Abfragezeichenfolgendaten können eins der folgenden Formate haben:

    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
    
  • Das folgende Format wird nur für Formulardaten unterstützt:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Bei allen der vorangehenden Beispielformate übergibt die Modellbindung ein Array von zwei Elementen an den selectedCourses-Parameter:

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Datenformate, die Indexnummern verwenden(... [0]... [1] ...), müssen sicherstellen, dass sie fortlaufend nummeriert sind, beginnend mit 0 (null). Treten bei der Indexnummerierung Lücken auf, werden alle Elemente, die auf die Lücke folgen, ignoriert. Wenn die Indizes beispielsweise 0 und 2 anstelle von 0 und 1 sind, wird beispielsweise das zweite Element ignoriert.

Wörterbücher

Bei Dictionary-Zielen sucht die Modellbindung nach Übereinstimmungen mit parameter_name oder property_name. Wird keine Übereinstimmung gefunden, sucht sie nach einem der unterstützten Formate ohne Präfix. Beispiel:

  • Angenommen, der Zielparameter ist eine Dictionary<int, string> mit dem Namen selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Die bereitgestellten Formular- oder Abfragezeichenfolgendaten können wie eins der folgenden Beispiele aussehen:

    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
    
  • Bei allen der vorangehenden Beispielformate übergibt die Modellbindung ein Wörterbuch aus zwei Elementen an den selectedCourses-Parameter:

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Konstruktorbindung und Datensatztypen

Die Modellbindung erfordert, dass komplexe Typen über einen parameterlosen Konstruktor verfügen. Sowohl System.Text.Json als auch basierte Eingabeformatierer unterstützen die Deserialisierung von Klassen, die keinen Newtonsoft.Json parameterlosen Konstruktor haben.

C# 9 führt Datensatztypen ein, die eine hervorragende Möglichkeit darstellen, Daten über das Netzwerk kurz zu darstellen. ASP.NET Core unterstützt die Modellbindung und validierung von Datensatztypen mit einem einzelnen Konstruktor:

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

Name: <input asp-for="Name" />
...
Age: <input asp-for="Age" />

Beim Überprüfen von Datensatztypen sucht die Laufzeit nach Bindungs- und Validierungsmetadaten, die sich speziell auf Parameter und nicht auf Eigenschaften belassen.

Das Framework ermöglicht das Binden an und Überprüfen von Datensatztypen:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Damit das vorherige -Beispiel funktioniert, muss der Typ:

  • Ein Datensatztyp sein.
  • Sie verfügen über genau einen öffentlichen Konstruktor.
  • Enthalten Parameter, die über eine Eigenschaft mit dem gleichen Namen und Typ verfügen. Die Namen dürfen sich nicht nach Fall unterscheiden.

POCOs ohne parameterlose Konstruktoren

POCOs ohne parameterlose Konstruktoren können nicht gebunden werden.

Der folgende Code führt zu einer Ausnahme, die angibt, dass der Typ einen parameterlosen Konstruktor haben muss:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
   public Person(string Name) : this (Name, 0);
}

Datensatztypen mit manuell erstellten Konstruktoren

Datensatztypen mit manuell erstellten Konstruktoren, die so aussehen, als ob primäre Konstruktoren funktionieren

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

Datensatztypen, Validierungs- und Bindungsmetadaten

Für Datensatztypen werden Validierungs- und Bindungsmetadaten für Parameter verwendet. Alle Metadaten für Eigenschaften werden ignoriert.

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

Überprüfung und Metadaten

Die Validierung verwendet Metadaten für den Parameter, verwendet jedoch die -Eigenschaft, um den Wert zu lesen. Im normalen Fall mit primären Konstruktoren wären die beiden identisch. Es gibt jedoch Möglichkeiten, dies zu verhindern:

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

TryUpdateModel aktualisiert keine Parameter für einen Datensatztyp.

public record Person(string Name)
{
   public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

In diesem Fall versucht MVC nicht erneut, eine Bindung Name zu verwenden. darf Age jedoch aktualisiert werden.

Globalisierungsverhalten der Routendaten und Abfragezeichenfolgen für die Modellbindung

Der ASP.NET Core-Routenwertanbieter und der Abfragezeichenfolgenwert-Anbieter:

  • behandeln Werte als invariante Kulturen.
  • erwarten, dass URLs kulturinvariant sind.

Im Gegensatz dazu durchlaufen Werte, die aus Formulardaten stammen, eine kulturabhängige Konvertierung. Dies ist beabsichtigt, damit URLs zwischen Gebietsschemas freigegeben werden können.

So lassen Sie den ASP.NET Core-Routenwertanbieter und den Abfragezeichenfolgenwert-Anbieter eine kulturabhängige Konvertierung durchlaufen:

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

Spezielle Datentypen

Es gibt einige spezielle Datentypen, die die Modellbindung verarbeiten kann.

„IFormFile“ und „IFormFileCollection“

Eine in der HTTP-Anforderung enthaltene, hochgeladenen Datei. Außerdem wird IEnumerable<IFormFile> für mehrere Dateien unterstützt.

CancellationToken

Aktionen können optional eine als CancellationToken Parameter binden. Dadurch wird eine RequestAborted Bindung hergestellt, die signalisiert, wenn die verbindung, die der HTTP-Anforderung zugrunde liegt, abgebrochen wird. Aktionen können diesen Parameter verwenden, um asynchrone Vorgänge mit langer Laufzeit abzubricht, die als Teil der Controlleraktionen ausgeführt werden.

„FormCollection“

Wird verwendet, um alle Werte aus bereitgestellten Formulardaten abzurufen.

Eingabeformatierer

Daten im Anforderungstext können im JSON-, XML- oder einem anderen Format sein. Um diese Daten zu analysieren, verwendet die Modellbindung einen Eingabeformatierer, der für die Verarbeitung eines bestimmten Inhaltstyps konfiguriert ist. ASP.NET Core enthält standardmäßig JSON-basierte Eingabeformatierer für die Verarbeitung von JSON-Daten. Sie können andere Formatierer für andere Inhaltstypen hinzufügen.

ASP.NET Core wählt Eingabeformatierer auf Grundlage des Consumes-Attributs aus. Wenn kein Attribut vorhanden ist, verwendet es den Content-Type-Header.

So verwenden Sie die integrierte XML-Eingabeformatierer

  • Installieren Sie das NuGet-Paket Microsoft.AspNetCore.Mvc.Formatters.Xml.

  • In Startup.ConfigureServices, rufen Sie AddXmlSerializerFormatters oder AddXmlDataContractSerializerFormatters auf.

    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();
    
  • Wenden Sie das Consumes-Attribut auf Controllerklassen oder Aktionsmethoden an, die XML im Anforderungstext erwarten sollten.

    [HttpPost]
    [Consumes("application/xml")]
    public ActionResult<Pet> Create(Pet pet)
    

    Weitere Informationen finden Sie unter Einführung der XML-Serialisierung.

Anpassen der Modellbindung mit Eingabeformatierern

Ein Eingabeformatierer ist in vollem Umfang für das Lesen von Daten aus dem Anforderungstext verantwortlich. Um diesen Prozess anzupassen, konfigurieren Sie die APIs, die vom Eingabeformatierer verwendet werden. In diesem Abschnitt wird beschrieben, wie Sie den auf System.Text.Json basierenden Eingabeformatierer so anpassen, dass er einen benutzerdefinierten Typ mit dem Namen ObjectId versteht.

Betrachten Sie das folgende Modell, das eine benutzerdefinierte ObjectId-Eigenschaft mit dem Namen Id enthält:

public class ModelWithObjectId
{
    public ObjectId Id { get; set; }
}

Um den Modellbindungsprozess bei der Verwendung von System.Text.Jsonanzupassen, erstellen Sie eine aus JsonConverter<T> abgeleitete Klasse:

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

Um einen benutzerdefinierten Konverter zu verwenden, wenden Sie das JsonConverterAttribute-Attribut auf den Typ an. Im folgenden Beispiel wird der Typ ObjectId mit ObjectIdConverter als seinem benutzerdefinierten Konverter konfiguriert:

[JsonConverter(typeof(ObjectIdConverter))]
public struct ObjectId
{
    public ObjectId(int id) =>
        Id = id;

    public int Id { get; }
}

Weitere Informationen finden Sie unter Vorgehensweise: Schreiben benutzerdefinierter Konverter.

Ausschließen angegebener Typen aus der Modellbindung

Das Verhalten der Modellbindung und des Validierungssystems wird von der Klasse ModelMetadata gesteuert. Sie können ModelMetadata anpassen, indem Sie MvcOptions.ModelMetadataDetailsProviders einen Detailanbieter hinzufügen. Integrierte Detailanbieter sind verfügbar, um die Modellbindung oder Validierung für angegebene Typen zu deaktivieren.

Um die Modellbindung für alle Modelle eines angegebenen Typs zu deaktivieren, fügen Sie in Startup.ConfigureServices einen ExcludeBindingMetadataProvider hinzu. Beispielsweise können Sie die Modellbindung für alle Modelle vom Typ System.Version wie folgt deaktivieren:

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

Um die Validierung für Eigenschaften eines angegebenen Typs zu deaktivieren, fügen Sie in Startup.ConfigureServices einen SuppressChildValidationMetadataProvider hinzu. Beispielsweise können Sie die Überprüfung von Eigenschaften vom Typ System.Guid wie folgt deaktivieren:

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

Benutzerdefinierte Modellbindungen

Sie können die Modellbindung erweitern, indem Sie eine benutzerdefinierte Modellbindung schreiben und das [ModelBinder]-Attribut verwenden, um diese für ein bestimmtes Ziel auszuwählen. Erfahren Sie mehr über die benutzerdefinierte Modellbindung.

Manuelle Modellbindung

Die Modellbindung kann mithilfe der TryUpdateModelAsync-Methode manuell aufgerufen werden. Die Methode ist für die beiden Klassen ControllerBase und PageModel definiert. Mithilfe von Methodenüberladungen können Sie das Präfix und den Wertanbieter festlegen, die verwendet werden sollen. Die Methode gibt false zurück, wenn die Modellbindung fehlschlägt. Hier sehen Sie ein Beispiel:

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 verwendet Wertanbieter, um Daten aus dem Formulartext, der Abfragezeichenfolge und den Routendaten abzurufen. TryUpdateModelAsync wird normalerweise so verwendet:

  • Wird mit Razor Pages- und MVC-Apps verwendet, die Controller und Ansichten verwenden, um zu viele Veröffentlichungen zu verhindern.
  • Nicht zusammen mit einer Web-API, es sei denn, sie wird von Formulardaten, Abfragezeichenfolgen und Routendaten konsumiert. Web-API-Endpunkte, die JSON nutzen, verwenden Eingabeformatierer, um den Anforderungstext in ein-Objekt zu deserialisieren.

Weitere Informationen finden Sie unter TryUpdateModelAsync.

[FromServices]-Attribut

Der Name dieses Attributs folgt dem Muster von Modellbindungsattributen, die eine Datenquelle angeben. Es ist aber nicht zum Binden von Daten aus einem Wertanbieter gedacht. Es ruft eine Instanz eines Typs aus dem Dependency Injection-Container (Abhängigkeitsinjektion) ab. Sein Zweck besteht darin, eine Alternative zur „Constructor Injection“ (Konstruktorinjektion) bereitzustellen, wenn Sie einen Dienst nur dann benötigen, wenn eine bestimmte Methode aufgerufen wird.

Zusätzliche Ressourcen

In diesem Artikel wird erläutert, was Modellbindung ist, wie sie funktioniert, und wie Sie ihr Verhalten anpassen können.

Zeigen Sie Beispielcode an, oder laden Sie diesen herunter (Vorgehensweise zum Herunterladen).

Was ist Modellbindung?

Controller und Razor Seiten arbeiten mit Daten, die von HTTP-Anforderungen stammen. Routendaten können beispielsweise einen Datensatzschlüssel enthalten, und bereitgestellte Formularfelder können Werte für die Eigenschaften des Modells bereitstellen. Das Schreiben von Code zum Abrufen jedes dieser Werte und deren Konvertierung aus Zeichenfolgen in .NET-Datentypen wäre mühsam und fehleranfällig. Modellbindung automatisiert diesen Vorgang. Das Modellbindungssystem:

  • Ruft Daten aus verschiedenen Quellen ab, z. B. Routendaten, Formularfelder und Abfragezeichenfolgen.
  • Stellt die Daten für Controller und Razor Seiten in Methodenparametern und öffentlichen Eigenschaften zur Verfügung.
  • Konvertiert Zeichenfolgendaten in .NET-Typen.
  • Aktualisiert Eigenschaften komplexer Typen.

Beispiel

Angenommen Sie haben die folgende Aktionsmethode:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

Und die App empfängt eine Anforderung mit dieser URL:

http://contoso.com/api/pets/2?DogsOnly=true

Die Modellbindung durchläuft die folgenden Schritte, nachdem das Routingsystem die Aktionsmethode ausgewählt hat:

  • Findet den ersten Parameters von GetByID, eine ganze Zahl namens id.
  • Durchsucht die verfügbaren Quellen in der HTTP-Anforderung und findet id = „2“ in den Routendaten.
  • Konvertiert der Zeichenfolge „2“ in die ganze Zahl 2.
  • Findet den nächsten Parameter von GetByID, einen booleschen Wert namens dogsOnly.
  • Durchsucht die Quellen und findet „DogsOnly=True“ in der Abfragezeichenfolge. Beim Abgleich von Namen wird die Groß- und Kleinschreibung nicht berücksichtigt.
  • Konvertiert die Zeichenfolge „true“ in den booleschen Wert true.

Das Framework ruft dann die GetById-Methode auf, und übergibt dabei als Eingabe „2“ für den id-Parameter und true für den dogsOnly-Parameter.

Im vorherigen Beispiel sind die Ziele der Modellbindung Methodenparameter, die einfache Typen sind. Ziele können aber auch die Eigenschaften eines komplexen Typs sein. Nachdem jede Eigenschaft erfolgreich gebunden wurde, erfolgt die Modellvalidierung für diese Eigenschaft. Der Datensatz darüber, welche Daten an das Modell gebunden sind, sowie mit allen Bindungs- oder Validierungsfehlern wird in ControllerBase.ModelState oder PageModel.ModelState gespeichert. Um herauszufinden, ob dieser Vorgang erfolgreich war, überprüft die App das Flag ModelState.IsValid.

Ziele

Die Modellbindung versucht, Werte für die folgenden Arten von Zielen zu finden:

  • Parameter der Controlleraktionsmethode, zu der eine Anforderung weitergeleitet wird.
  • Parameter der Razor Pages-Handlermethode, an die eine Anforderung geroutet wird.
  • Öffentliche Eigenschaften eines Controllers oder einer PageModel-Klasse, falls durch Attribute angegeben.

[BindProperty]-Attribut

Kann auf eine öffentliche Eigenschaft eines Controllers oder einer PageModel-Klasse angewendet werden, um die Modellbindung anzuweisen, diese Eigenschaft als Ziel zu verwenden:

public class EditModel : InstructorsPageModel
{
    [BindProperty]
    public Instructor Instructor { get; set; }

[BindProperties]-Attribut

Verfügbar in ASP.NET Core 2.1 und höher. Kann auf einen Controller oder eine PageModel-Klasse angewendet werden, um die Modellbindung anzuweisen, alle öffentlichen Eigenschaften dieser Klasse als Ziel zu verwenden:

[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
    public Instructor Instructor { get; set; }

Modellbindung für HTTP-GET-Anforderungen

Standardmäßig sind Eigenschaften für HTTP GET-Anforderungen nicht gebunden. In der Regel ist alles, was Sie für eine GET-Anforderung benötigen, ein Datensatz-ID-Parameter. Die Datensatz-ID wird verwendet, um das Element in der Datenbank zu suchen. Daher besteht keine Notwendigkeit, eine Eigenschaft zu binden, die eine Instanz des Modells enthält. In Szenarien, in denen Sie Eigenschaften an Daten aus GET-Anforderungen binden möchten, legen Sie die Eigenschaft SupportsGet auf true fest:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }

Quellen

Standardmäßig ruft die Modellbindung Daten in Form von Schlüssel-Wert-Paaren aus den folgenden Quellen in einer HTTP-Anforderung ab:

  1. Formularfelder
  2. Der Anforderungstext (für Controller mit dem [ApiController]-Attribut)
  3. Routendaten
  4. Abfragezeichenfolge-Parameter
  5. Hochgeladene Dateien

Für jeden Zielparameter oder jede Zieleigenschaft werden die Quellen nach der oben aufgeführten Reihenfolge überprüft. Es gibt ein paar Ausnahmen:

  • Routendaten und Abfragezeichenfolgenwerte werden nur für einfache Typen verwendet.
  • Hochgeladene Dateien werden nur an Zieltypen gebunden, die IFormFile oder IEnumerable<IFormFile> implementieren.

Wenn die Standardquelle nicht richtig ist, verwenden Sie eines der folgenden Attribute zum Festlegen der Quelle:

  • [FromQuery] – Ruft Werte aus der Abfragezeichenfolge ab.
  • [FromRoute] : Ruft Werte aus Routendaten ab.
  • [FromForm] – Ruft Werte aus den gesendeten Formularfeldern ab.
  • [FromBody] – Ruft Werte aus dem Anforderungskörper ab.
  • [FromHeader] : Ruft Werte aus HTTP-Headern ab.

Diese Attribute:

  • Werden Modelleigenschaften einzeln hinzugefügt (nicht zur Modellklasse), wie im folgenden Beispiel gezeigt:

    public class Instructor
    {
        public int ID { get; set; }
    
        [FromQuery(Name = "Note")]
        public string NoteFromQueryString { get; set; }
    
  • Akzeptieren optional einen Modellnamenswert im Konstruktor. Diese Option wird für den Fall bereitgestellt, dass der Eigenschaftenname nicht mit dem Wert in der Anforderung übereinstimmt. Beispielsweise könnte der Wert in der Anforderung ein Header mit einem Bindestrich in seinem Namen sein, wie im folgenden Beispiel gezeigt:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

[FromBody]-Attribut

Wenden Sie das [FromBody]-Attribut auf einen Parameter an, um dessen Eigenschaften über den Text einer HTTP-Anforderung aufzufüllen. Die ASP.NET Core-Runtime delegiert die Verantwortung, für das Lesen des Texts an einen Eingabeformatierer. Eingabeformatierer werden später in diesem Artikel erklärt.

Wenn [FromBody] auf einen komplexen Typparameter angewendet wird, werden alle Bindungsquellenattribute ignoriert, die auf die Eigenschaften angewendet werden. Die folgende Create-Aktion legt beispielsweise fest, dass der pet-Parameter mithilfe des Texts aufgefüllt wird:

public ActionResult<Pet> Create([FromBody] Pet pet)

Die Pet-Klasse legt fest, dass ihre Breed-Eigenschaft mithilfe eines Abfragezeichenfolgenparameters aufgefüllt wird:

public class Pet
{
    public string Name { get; set; }

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; }
}

Im vorherigen Beispiel:

  • Das [FromQuery]-Attribut wird ignoriert.
  • Die Breed-Eigenschaft wird nicht mithilfe eines Abfragezeichenfolgenparameters aufgefüllt.

Eingabeformatierer lesen nur den Text und verstehen Bindungsquellenattribute nicht. Wenn ein geeigneter Wert im Text gefunden wird, wird dieser Wert zum Auffüllen der Breed-Eigenschaft verwendet.

Wenden Sie [FromBody] auf nicht mehr als einen Parameter pro Aktionsmethode an. Sobald der Anforderungsdatenstrom von einem Eingabeformatierer gelesen wurde, ist er nicht mehr verfügbar, um für die Bindung anderer [FromBody]-Parameter nochmal gelesen zu werden.

Zusätzliche Quellen

Quelldaten werden dem Modellbindungssystem durch Wertanbieter bereitgestellt. Sie können benutzerdefinierte Wertanbieter schreiben und registrieren, die Daten für die Modellbindung aus anderen Quellen abrufen. Sie können z. B. Daten aus cookie s oder dem Sitzungszustand wünschen. So rufen Sie Daten aus einer neuen Quelle ab

  • Erstellen Sie eine Klasse, die das IValueProvider implementiert.
  • Erstellen Sie eine Klasse, die das IValueProviderFactory implementiert.
  • Registrieren Sie die Factoryklasse in Startup.ConfigureServices.

Die Beispiel-App enthält einen Wertanbieter und ein Factorybeispiel, das Werte aus cookie s ruft. Dies ist der Registrierungscode in Startup.ConfigureServices:

services.AddMvc(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Der angezeigte Code fügt den benutzerdefinierten Wertanbieter hinter allen integrierten Wertanbietern ein. Damit er der erste in der Liste wird, rufen Sie Insert(0, new CookieValueProviderFactory()) anstelle von Add auf.

Keine Quelle für eine Modelleigenschaft

Standardmäßig wird kein Modellzustandsfehler erstellt, wenn kein Wert für eine Modelleigenschaft gefunden wird. Die Eigenschaft wird auf „null“ oder einen Standardwert festgelegt:

  • Einfache Nullable-Typen werden auf null festgelegt.
  • Nicht-Nullable-Werttypen werden auf default(T) festgelegt. Beispiel: Ein Parameter int id wird auf „0“ festgelegt.
  • Für komplexe Typen erstellt die Modellbindung eine Instanz, indem der Standardkonstruktor verwendet wird, ohne Eigenschaften festzulegen.
  • Arrays werden auf Array.Empty<T>() festgelegt, mit der Ausnahme, dass byte[]-Arrays auf null festgelegt werden.

Wenn der Modellzustand ungültig werden soll, wenn in Formularfeldern für eine Modelleigenschaft nichts gefunden wird, verwenden Sie das [BindRequired] -Attribut.

Beachten Sie, dass dieses [BindRequired]-Verhalten für die Modellbindung von Daten aus bereitgestellten Formulardaten gilt, nicht für JSON- oder XML-Daten in einem Anforderungstext. Anforderungstextdaten werden von Eingabeformatierern verarbeitet.

Typkonvertierungsfehler

Wenn eine Quelle gefunden wird, aber nicht in den Zieltyp konvertiert werden kann, wird der Modellzustand als „ungültig“ gekennzeichnet. Der Zielparameter oder die Zieleigenschaft wird auf „null“ oder einen Standardwert festgelegt, wie bereits im vorherigen Abschnitt erwähnt.

In einem API-Controller, der über das [ApiController]-Attribut verfügt, führt ein ungültiger Modellzustand zu einer automatischen „HTTP 400“-Antwort.

Zeigen Sie die Seite auf einer Razor Seite erneut mit einer Fehlermeldung an:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _instructorsInMemoryStore.Add(Instructor);
    return RedirectToPage("./Index");
}

Die clientseitige Validierung fängt die meisten fehlerhaften Daten ab, die andernfalls an ein Razor Pages-Formular übermittelt würden. Diese Validierung erschwert es, den voranstehenden, hervorgehobenen Code auszulösen. Die Beispiel-App umfasst eine Schaltfläche Submit with Invalid Date (Mit ungültigem Datum absenden), die ungültige Daten in das Feld Hire Date (Einstellungsdatum) einfügt und das Formular absendet. Diese Schaltfläche zeigt, wie der Code zum erneuten Anzeigen der Seite funktioniert, wenn Datenkonvertierungsfehler auftreten.

Wenn die Seite von dem vorangehenden Code erneut angezeigt wird, wird die ungültige Eingabe nicht im Formularfeld angezeigt. Dies liegt daran, dass die Modelleigenschaft auf „null“ oder einen Standardwert festgelegt wurde. Die ungültige Eingabe wird jedoch in einer Fehlermeldung angezeigt. Wenn Sie aber die ungültigen Daten im Formularfeld erneut anzeigen möchten, sollten Sie aus der Modelleigenschaft eine Zeichenfolge machen und die Datenkonvertierung manuell ausführen.

Dieselbe Strategie empfiehlt sich, wenn Sie nicht möchten, dass Typkonvertierungsfehler zu Modellzustandsfehlern führen. In diesem Fall machen Sie aus der Modelleigenschaft eine Zeichenfolge.

Einfache Typen

Die einfachen Typen, in die die Modellbindung Quellzeichenfolgen konvertieren kann, sind unter anderem:

Komplexe Typen

Ein komplexer Typ muss einen öffentlichen Standardkonstruktor und öffentliche schreibbare Eigenschaften besitzen, die gebunden werden können. Wenn die Modellbindung erfolgt, wird die Klasse mit dem öffentlichen Standardkonstruktor instanziiert.

Für jede Eigenschaft des komplexen Typs durchsucht die Modellbindung die Quellen für das Namensmuster prefix.property_name. Wenn nichts gefunden wird, sucht sie nur nach property_name ohne das Präfix.

Beim Binden an einen Parameter ist das Präfix der Name des Parameters. Beim Binden an eine öffentliche Eigenschaft PageModel ist das Präfix der Name der öffentlichen Eigenschaft. Einige Attribute besitzen eine Eigenschaft Prefix, die es Ihnen gestattet, die Standardverwendung des Parameter- oder Eigenschaftennamens außer Kraft zu setzen.

Nehmen Sie beispielsweise an, der komplexe Typ ist die folgende Instructor-Klasse:

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Präfix = Parametername

Wenn das zu bindende Modell ein Parameter namens instructorToUpdate ist:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Die Modellbindung beginnt, indem sie die Quellen nach dem Schlüssel instructorToUpdate.ID durchsucht. Wenn dieser nicht gefunden wird, sucht sie nach ID ohne Präfix.

Präfix = Name der Eigenschaft

Wenn das zu bindende Modell eine Eigenschaft des Controllers oder der PageModel-Klasse namens Instructor ist:

[BindProperty]
public Instructor Instructor { get; set; }

Die Modellbindung beginnt, indem sie die Quellen nach dem Schlüssel Instructor.ID durchsucht. Wenn dieser nicht gefunden wird, sucht sie nach ID ohne Präfix.

Benutzerdefiniertes Präfix

Wenn das zu bindende Modell ein Parameter namens instructorToUpdate ist, und ein Bind-Attribut Instructor als Präfix angibt:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

Die Modellbindung beginnt, indem sie die Quellen nach dem Schlüssel Instructor.ID durchsucht. Wenn dieser nicht gefunden wird, sucht sie nach ID ohne Präfix.

Attribute für Ziele komplexen Typs

Mehrere integrierte Attribute stehen für die Kontrolle der Modellbindung komplexer Typen zur Verfügung:

  • [BindRequired]
  • [BindNever]
  • [Bind]

Hinweis

Diese Attribute wirken sich auf die Modellbindung aus, wenn bereitgestellte Formulardaten die Quelle der Wert sind. Sie haben keine Auswirkungen auf Eingabeformatierer, die bereitgestellte JSON- und XML-Anforderungstexte verarbeiten. Eingabeformatierer werden später in diesem Artikel erklärt.

Lesen Sie auch die Diskussion des [Required]-Attributs in der Modellvalidierung.

[BindRequired]-Attribut

Kann nur auf Modelleigenschaften angewendet werden, nicht auf Methodenparameter. Bewirkt, dass die Modellbindung einen Modellzustandsfehler hinzufügt, wenn die Bindung für die Eigenschaft eines Modells nicht erfolgen kann. Hier sehen Sie ein Beispiel:

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

[BindNever]-Attribut

Kann nur auf Modelleigenschaften angewendet werden, nicht auf Methodenparameter. Verhindert, dass die Modellbindung die Eigenschaft eines Modells festlegt. Hier sehen Sie ein Beispiel:

public class InstructorWithDictionary
{
    [BindNever]
    public int ID { get; set; }

[Bind]-Attribut

Kann auf eine Klasse oder einen Methodenparameter angewendet werden. Gibt an, welche Eigenschaften eines Modells in die Modellbindung aufgenommen werden sollen.

Im folgenden Beispiel werden nur die angegebenen Eigenschaften des Instructor-Modells gebunden, wenn ein Ereignishandler oder eine Aktionsmethode aufgerufen wird:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

Im folgenden Beispiel werden nur die angegebenen Eigenschaften des Instructor-Modells gebunden, wenn die OnPost-Methode aufgerufen wird:

[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

Das [Bind]-Attribut kann zum Schutz vor Overposting in Erstellungs szenarien (create) verwendet werden. Es funktioniert nicht gut in Bearbeitungsszenarien (edit), weil ausgeschlossene Eigenschaften auf „null“ oder einen Standardwert festgelegt werden, anstatt unverändert zu bleiben. Zum Schutz vor Overposting werden Ansichtsmodelle empfohlen, anstelle des [Bind]-Attributs. Weitere Informationen finden Sie unter Sicherheitshinweis zum Overposting.

Sammlungen

Bei Zielen, die Sammlungen einfacher Typen sind, sucht die Modellbindung nach Übereinstimmungen mit parameter_name oder property_name. Wird keine Übereinstimmung gefunden, sucht sie nach einem der unterstützten Formate ohne Präfix. Beispiel:

  • Angenommen, der zu bindende Parameter ist ein Array namens selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Formular- oder Abfragezeichenfolgendaten können eins der folgenden Formate haben:

    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
    
  • Das folgende Format wird nur für Formulardaten unterstützt:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Bei allen der vorangehenden Beispielformate übergibt die Modellbindung ein Array von zwei Elementen an den selectedCourses-Parameter:

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Datenformate, die Indexnummern verwenden(... [0]... [1] ...), müssen sicherstellen, dass sie fortlaufend nummeriert sind, beginnend mit 0 (null). Treten bei der Indexnummerierung Lücken auf, werden alle Elemente, die auf die Lücke folgen, ignoriert. Wenn die Indizes beispielsweise 0 und 2 anstelle von 0 und 1 sind, wird beispielsweise das zweite Element ignoriert.

Wörterbücher

Bei Dictionary-Zielen sucht die Modellbindung nach Übereinstimmungen mit parameter_name oder property_name. Wird keine Übereinstimmung gefunden, sucht sie nach einem der unterstützten Formate ohne Präfix. Beispiel:

  • Angenommen, der Zielparameter ist eine Dictionary<int, string> mit dem Namen selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Die bereitgestellten Formular- oder Abfragezeichenfolgendaten können wie eins der folgenden Beispiele aussehen:

    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
    
  • Bei allen der vorangehenden Beispielformate übergibt die Modellbindung ein Wörterbuch aus zwei Elementen an den selectedCourses-Parameter:

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Globalisierungsverhalten der Routendaten und Abfragezeichenfolgen für die Modellbindung

Der ASP.NET Core-Routenwertanbieter und der Abfragezeichenfolgenwert-Anbieter:

  • behandeln Werte als invariante Kulturen.
  • erwarten, dass URLs kulturinvariant sind.

Im Gegensatz dazu durchlaufen Werte, die aus Formulardaten stammen, eine kulturabhängige Konvertierung. Dies ist beabsichtigt, damit URLs zwischen Gebietsschemas freigegeben werden können.

So lassen Sie den ASP.NET Core-Routenwertanbieter und den Abfragezeichenfolgenwert-Anbieter eine kulturabhängige Konvertierung durchlaufen:

services.AddMvc(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;
    }
}

Spezielle Datentypen

Es gibt einige spezielle Datentypen, die die Modellbindung verarbeiten kann.

„IFormFile“ und „IFormFileCollection“

Eine in der HTTP-Anforderung enthaltene, hochgeladenen Datei. Außerdem wird IEnumerable<IFormFile> für mehrere Dateien unterstützt.

CancellationToken

Wird verwendet, um die Aktivität in asynchronen Controllern zu beenden.

„FormCollection“

Wird verwendet, um alle Werte aus bereitgestellten Formulardaten abzurufen.

Eingabeformatierer

Daten im Anforderungstext können im JSON-, XML- oder einem anderen Format sein. Um diese Daten zu analysieren, verwendet die Modellbindung einen Eingabeformatierer, der für die Verarbeitung eines bestimmten Inhaltstyps konfiguriert ist. ASP.NET Core enthält standardmäßig JSON-basierte Eingabeformatierer für die Verarbeitung von JSON-Daten. Sie können andere Formatierer für andere Inhaltstypen hinzufügen.

ASP.NET Core wählt Eingabeformatierer auf Grundlage des Consumes-Attributs aus. Wenn kein Attribut vorhanden ist, verwendet es den Content-Type-Header.

So verwenden Sie die integrierte XML-Eingabeformatierer

  • Installieren Sie das NuGet-Paket Microsoft.AspNetCore.Mvc.Formatters.Xml.

  • In Startup.ConfigureServices, rufen Sie AddXmlSerializerFormatters oder AddXmlDataContractSerializerFormatters auf.

    services.AddMvc(options =>
    {
        options.ValueProviderFactories.Add(new CookieValueProviderFactory());
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(System.Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
    })
    .AddXmlSerializerFormatters()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
  • Wenden Sie das Consumes-Attribut auf Controllerklassen oder Aktionsmethoden an, die XML im Anforderungstext erwarten sollten.

    [HttpPost]
    [Consumes("application/xml")]
    public ActionResult<Pet> Create(Pet pet)
    

    Weitere Informationen finden Sie unter Einführung der XML-Serialisierung.

Ausschließen angegebener Typen aus der Modellbindung

Das Verhalten der Modellbindung und des Validierungssystems wird von der Klasse ModelMetadata gesteuert. Sie können ModelMetadata anpassen, indem Sie MvcOptions.ModelMetadataDetailsProviders einen Detailanbieter hinzufügen. Integrierte Detailanbieter sind verfügbar, um die Modellbindung oder Validierung für angegebene Typen zu deaktivieren.

Um die Modellbindung für alle Modelle eines angegebenen Typs zu deaktivieren, fügen Sie in Startup.ConfigureServices einen ExcludeBindingMetadataProvider hinzu. Beispielsweise können Sie die Modellbindung für alle Modelle vom Typ System.Version wie folgt deaktivieren:

services.AddMvc(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Um die Validierung für Eigenschaften eines angegebenen Typs zu deaktivieren, fügen Sie in Startup.ConfigureServices einen SuppressChildValidationMetadataProvider hinzu. Beispielsweise können Sie die Überprüfung von Eigenschaften vom Typ System.Guid wie folgt deaktivieren:

services.AddMvc(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Benutzerdefinierte Modellbindungen

Sie können die Modellbindung erweitern, indem Sie eine benutzerdefinierte Modellbindung schreiben und das [ModelBinder]-Attribut verwenden, um diese für ein bestimmtes Ziel auszuwählen. Erfahren Sie mehr über die benutzerdefinierte Modellbindung.

Manuelle Modellbindung

Die Modellbindung kann mithilfe der TryUpdateModelAsync-Methode manuell aufgerufen werden. Die Methode ist für die beiden Klassen ControllerBase und PageModel definiert. Mithilfe von Methodenüberladungen können Sie das Präfix und den Wertanbieter festlegen, die verwendet werden sollen. Die Methode gibt false zurück, wenn die Modellbindung fehlschlägt. Hier sehen Sie ein Beispiel:

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

[FromServices]-Attribut

Der Name dieses Attributs folgt dem Muster von Modellbindungsattributen, die eine Datenquelle angeben. Es ist aber nicht zum Binden von Daten aus einem Wertanbieter gedacht. Es ruft eine Instanz eines Typs aus dem Dependency Injection-Container (Abhängigkeitsinjektion) ab. Sein Zweck besteht darin, eine Alternative zur „Constructor Injection“ (Konstruktorinjektion) bereitzustellen, wenn Sie einen Dienst nur dann benötigen, wenn eine bestimmte Methode aufgerufen wird.

Zusätzliche Ressourcen