Modellbindung in ASP.NET Core

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

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

Was ist Modellbindung?

Controller und Razor Pages arbeiten mit Daten, die aus 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 diese Daten Controllern und Razor Pages in Methodenparametern und öffentlichen Eigenschaften bereit.
  • 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:

https://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.

Targets

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, zu der eine Anforderung weitergeleitet 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 : PageModel
{
    [BindProperty]
    public Instructor? Instructor { get; set; }

    // ...
}

[BindProperties]-Attribut

Kann auf einen Controller oder eine PageModel-Klasse angewendet werden, um die Modellbindung anzuweisen, alle öffentlichen Eigenschaften dieser Klasse als Ziel zu verwenden:

[BindProperties]
public class CreateModel : PageModel
{
    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; }

Einfache und komplexe Typen der Modellbindung

Die Modellbindung verwendet spezifische Definitionen für die Typen, die sie verwendet. Ein einfacher Typ wird aus einer einzigen Zeichenfolge mithilfe von TypeConverter oder einer TryParse-Methode konvertiert. Ein komplexer Typ wird aus mehreren Eingabewerten konvertiert. Das Framework bestimmt den Unterschied auf Grundlage des Vorhandenseins eines TypeConverter- oder TryParse-Objekts. Es wird empfohlen, einen Typkonverter zu erstellen oder die TryParse-Funktion für eine string- oder SomeType-Konvertierung zu verwenden, für die keine externen Ressourcen oder mehrere Eingaben erforderlich sind.

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:

Diese Attribute:

  • Werden Modelleigenschaften wie im folgenden Beispiel einzeln und nicht zur Modellklasse hinzugefügt:

    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; } = null!;

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

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. Beispielsweise können Sie Daten aus cookies oder Sitzungszuständen abrufen. 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 Program.cs.

Das Beispiel umfasst ein Beispiel für einen Wertanbieter und eine Factory, das Werte aus cookies abruft. Registrieren Sie benutzerdefinierte Wertanbieterfactorys in Program.cs:

builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});

Der vorherige 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 Typen, die Nullwerte zulassen, 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.

Um den Modellzustand ungültig zu machen, 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 auf einer Razor Page die Seite mit einer Fehlermeldung erneut an:

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

    // ...

    return RedirectToPage("./Index");
}

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

Erklärungen zu einfachen und komplexen Typen finden Sie unter Modellbindung für einfache und komplexe Typen.

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

Binden mit der IParsable<T>.TryParse-Methode

Die IParsable<TSelf>.TryParse-API unterstützt das Binden von Parameterwerten für Controlleraktionen:

public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);

Die folgende DateRange-Klasse implementiert die IParsable<TSelf>-Methode, um die Bindung eines Datumsbereichs zu unterstützen:

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

Der vorangehende Code:

  • Konvertiert eine Zeichenfolge, die zwei Datumsangaben darstellt, in ein DateRange-Objekt
  • Die Modellbindung bindet mithilfe der IParsable<TSelf>.TryParse-Methode das DateRange-Objekt.

Die folgende Controlleraktion verwendet die DateRange-Klasse, um einen Datumsbereich zu binden:

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

Die folgende Locale-Klasse implementiert die IParsable<TSelf>-Methode, um die Bindung eines Datumsbereichs zum CultureInfo-Objekt zu unterstützen:

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

Die folgende Controlleraktion verwendet die Locale-Klasse, um eine CultureInfo-Zeichenfolge zu binden:

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

Die folgende Controlleraktion verwendet die Klassen DateRange und Locale, um einen Datumsbereich mit dem CultureInfo-Objekt zu binden:

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

Die API-Beispiel-App auf GitHub zeigt das vorherige Beispiel für einen API-Controller.

Binden mit der TryParse-Methode

Die TryParse-API unterstützt das Binden von Parameterwerten für Controlleraktionen:

public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);

Die IParsable<T>.TryParse-Schnittstelle ist der empfohlene Ansatz für die Parameterbindung, da sie im Gegensatz zur TryParse-Methode nicht von Reflexionen abhängig ist.

Die folgende DateRangeTP-Klasse implementiert die TryParse-Methode:

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

Die folgende Controlleraktion verwendet die DateRangeTP-Klasse, um einen Datumsbereich zu binden:

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

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.

Die Modellbindung durchsucht die Quellen für das Namensmuster für jede Eigenschaft des komplexen Typs nach prefix.property_name. Wenn nichts gefunden wird, sucht sie nur nach property_name ohne das Präfix. Die Entscheidung für die Verwendung des Präfix wird nicht für jede Eigenschaft getroffen. Wenn beispielsweise eine Eigenschaft mit einer Abfrage ?Instructor.Id=100&Name=foo enthält, was an die OnGet(Instructor instructor)-Methode gebunden ist, enthält das resultierende Objekt vom Typ Instructor Folgendes:

  • Legen Sie Id auf 100 fest.
  • Legen Sie Name auf null fest. Die Modellbindung erwartet Instructor.Name, weil Instructor.Id im vorherigen Abfrageparameter verwendet wurde.

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:

Warnung

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.

[Bind]-Attribut

Kann auf eine Klasse oder einen Methodenparameter angewendet werden. Gibt an, welche Eigenschaften eines Modells in die Modellbindung aufgenommen werden sollen. Das [Bind]-Attribut hat keine Auswirkungen auf Eingabeformatierer.

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 Erstellungsszenarien (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

Das ModelBinderAttribute kann auf Typen, Eigenschaften oder Parameter angewendet werden. Das Attribut ermöglicht das Angeben des Modellbindungtyps, der zum Binden der bestimmten Instanz oder des bestimmten Typs verwendet wird. Beispiel:

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

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

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

    // ...
}

[BindRequired]-Attribut

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 InstructorBindRequired
{
    // ...

    [BindRequired]
    public DateTime HireDate { get; set; }
}

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

[BindNever]-Attribut

Das Attribut kann auf eine Eigenschaft oder einen Typ angewendet werden. Verhindert, dass die Modellbindung die Eigenschaft eines Modells festlegt. Wenn es auf einen Typ angewendet wird, schließt das Modellbindungssystem alle Eigenschaften aus, die der Typ definiert. Hier sehen Sie ein Beispiel:

public class InstructorBindNever
{
    [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
    

    Vermeiden Sie die Bindung eines Parameters oder einer Eigenschaft mit dem Namen index oder Index, wenn diese an einen Sammlungswert angrenzen. Die Modellbindung versucht, index als Index für die Sammlung zu verwenden, was möglicherweise zu einer falschen Bindung führen kann. Sehen Sie sich beispielsweise die folgende Aktion an:

    public IActionResult Post(string index, List<Product> products)
    

    Im vorherigen Code wird der index-Abfragezeichenfolgenparameter an den index-Methodenparameter gebunden und auch zum Binden der Produktsammlung verwendet. Wenn Sie den index-Parameter umbenennen oder ein Modellbindungsattribut zum Konfigurieren der Bindung verwenden, wird dieses Problem vermieden:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • 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. System.Text.Json- und Newtonsoft.Json-basierte Eingabeformatierer unterstützen die Deserialisierung von Klassen ohne einen parameterlosen Konstruktor.

Datensatztypen eignen sich besonders gut für die prägnante Darstellung von Daten über das Netzwerk. 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" />
<br />
Age: <input asp-for="Age" />

Bei der Validierung von Datensatztypen sucht die Runtime nur in Parametern, nicht jedoch in Eigenschaften, nach Bindungs- und Validierungsmetadaten.

Das Framework ermöglicht die Bindung an und Validierung von Datensatztypen:

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

Damit der vorherige Code funktioniert, muss der Typ:

  • ein Datensatztyp sein,
  • genau einen öffentlichen Konstruktor haben,
  • Parameter enthalten, die über eine Eigenschaft mit demselben Namen und Typ verfügen. Die Groß-/Kleinschreibung der Namen darf sich nicht unterscheiden.

POCOs ohne parameterlose Konstruktoren

POCOs (Plain Old CLR Objects), die keine parameterlosen Konstruktoren haben, können nicht gebunden werden.

Der folgende Code führt zu einer Ausnahme, die besagt, dass der Typ über einen parameterlosen Konstruktor verfügen 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

Der folgende Code enthält Datensatztypen mit manuell erstellten Konstruktoren, die so aussehen, wie 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; }
}

Validierung und Metadaten

Bei der Validierung werden Metadaten für den Parameter verwendet. Sie verwendet jedoch die Eigenschaft, um den Wert zu lesen. Normalerweise wären bei zwei primären Konstruktoren beide identisch. Es gibt jedoch Möglichkeiten, damit umzugehen:

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

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, den Parameter Name zu binden. Der Parameter Age darf 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 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();
});

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 ein CancellationToken als Parameter binden. Dadurch wird die Eigenschaft RequestAborted gebunden, die anzeigt, dass die der HTTP-Anforderung zugrunde liegende Verbindung abgebrochen wird. Aktionen können diesen Parameter verwenden, um zeitintensive asynchrone Vorgänge abzubrechen, 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 vorliegen. 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

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 enthält:

public class InstructorObjectId
{
    [Required]
    public ObjectId ObjectId { get; set; } = null!;
}

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)
        => new(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 record ObjectId(int Id);

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 Program.cs einen ExcludeBindingMetadataProvider hinzu. Beispielsweise können Sie die Modellbindung für alle Modelle vom Typ System.Version wie folgt deaktivieren:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

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

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

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(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync verwendet Wertanbieter, um Daten aus dem Formulartext, der Abfragezeichenfolge und den Routendaten abzurufen. TryUpdateModelAsync wird normalerweise so verwendet:

  • Mit Razor Pages und MVC-Apps, die Controller und Ansichten verwenden, um übermäßiges Veröffentlichen 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.

Wenn eine Instanz des Typs nicht im Dependency-Injection-Container registriert ist, löst die App beim Versuch, einen Parameter zu binden, eine Ausnahme aus. Verwenden Sie einen der folgenden Ansätze, um den Parameter optional zu machen:

  • Stellen Sie den Parameter so ein, dass er Nullwerte zulässt.
  • Legen Sie einen Standardwert für den Parameter fest.

Stellen Sie für Parameter, die Nullwerte zulassen, sicher, dass der Parameter vor dem Zugriff nicht null ist.

Zusätzliche Ressourcen

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

Was ist Modellbindung?

Controller und Razor Pages arbeiten mit Daten, die aus 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 diese Daten Controllern und Razor Pages in Methodenparametern und öffentlichen Eigenschaften bereit.
  • 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:

https://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.

Targets

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, zu der eine Anforderung weitergeleitet 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 : PageModel
{
    [BindProperty]
    public Instructor? Instructor { get; set; }

    // ...
}

[BindProperties]-Attribut

Kann auf einen Controller oder eine PageModel-Klasse angewendet werden, um die Modellbindung anzuweisen, alle öffentlichen Eigenschaften dieser Klasse als Ziel zu verwenden:

[BindProperties]
public class CreateModel : PageModel
{
    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; }

Einfache und komplexe Typen der Modellbindung

Die Modellbindung verwendet spezifische Definitionen für die Typen, die sie verwendet. Ein einfacher Typ wird aus einer einzigen Zeichenfolge mithilfe von TypeConverter oder einer TryParse-Methode konvertiert. Ein komplexer Typ wird aus mehreren Eingabewerten konvertiert. Das Framework bestimmt den Unterschied auf Grundlage des Vorhandenseins eines TypeConverter- oder TryParse-Objekts. Es wird empfohlen, einen Typkonverter zu erstellen oder die TryParse-Funktion für eine string- oder SomeType-Konvertierung zu verwenden, für die keine externen Ressourcen oder mehrere Eingaben erforderlich sind.

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:

Diese Attribute:

  • Werden Modelleigenschaften wie im folgenden Beispiel einzeln und nicht zur Modellklasse hinzugefügt:

    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; } = null!;

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

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. Beispielsweise können Sie Daten aus cookies oder Sitzungszuständen abrufen. 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 Program.cs.

Das Beispiel umfasst ein Beispiel für einen Wertanbieter und eine Factory, das Werte aus cookies abruft. Registrieren Sie benutzerdefinierte Wertanbieterfactorys in Program.cs:

builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});

Der vorherige 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 Typen, die Nullwerte zulassen, 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.

Um den Modellzustand ungültig zu machen, 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 auf einer Razor Page die Seite mit einer Fehlermeldung erneut an:

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

    // ...

    return RedirectToPage("./Index");
}

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

Erklärungen zu einfachen und komplexen Typen finden Sie unter Modellbindung für einfache und komplexe Typen.

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

Binden mit der IParsable<T>.TryParse-Methode

Die IParsable<TSelf>.TryParse-API unterstützt das Binden von Parameterwerten für Controlleraktionen:

public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);

Die folgende DateRange-Klasse implementiert die IParsable<TSelf>-Methode, um die Bindung eines Datumsbereichs zu unterstützen:

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

Der vorangehende Code:

  • Konvertiert eine Zeichenfolge, die zwei Datumsangaben darstellt, in ein DateRange-Objekt
  • Die Modellbindung bindet mithilfe der IParsable<TSelf>.TryParse-Methode das DateRange-Objekt.

Die folgende Controlleraktion verwendet die DateRange-Klasse, um einen Datumsbereich zu binden:

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

Die folgende Locale-Klasse implementiert die IParsable<TSelf>-Methode, um die Bindung eines Datumsbereichs zum CultureInfo-Objekt zu unterstützen:

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

Die folgende Controlleraktion verwendet die Locale-Klasse, um eine CultureInfo-Zeichenfolge zu binden:

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

Die folgende Controlleraktion verwendet die Klassen DateRange und Locale, um einen Datumsbereich mit dem CultureInfo-Objekt zu binden:

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

Die API-Beispiel-App auf GitHub zeigt das vorherige Beispiel für einen API-Controller.

Binden mit der TryParse-Methode

Die TryParse-API unterstützt das Binden von Parameterwerten für Controlleraktionen:

public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);

Die IParsable<T>.TryParse-Schnittstelle ist der empfohlene Ansatz für die Parameterbindung, da sie im Gegensatz zur TryParse-Methode nicht von Reflexionen abhängig ist.

Die folgende DateRangeTP-Klasse implementiert die TryParse-Methode:

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

Die folgende Controlleraktion verwendet die DateRangeTP-Klasse, um einen Datumsbereich zu binden:

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

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.

Die Modellbindung durchsucht die Quellen für das Namensmuster für jede Eigenschaft des komplexen Typs nach prefix.property_name. Wenn nichts gefunden wird, sucht sie nur nach property_name ohne das Präfix. Die Entscheidung für die Verwendung des Präfix wird nicht für jede Eigenschaft getroffen. Wenn beispielsweise eine Eigenschaft mit einer Abfrage ?Instructor.Id=100&Name=foo enthält, was an die OnGet(Instructor instructor)-Methode gebunden ist, enthält das resultierende Objekt vom Typ Instructor Folgendes:

  • Legen Sie Id auf 100 fest.
  • Legen Sie Name auf null fest. Die Modellbindung erwartet Instructor.Name, weil Instructor.Id im vorherigen Abfrageparameter verwendet wurde.

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:

Warnung

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.

[Bind]-Attribut

Kann auf eine Klasse oder einen Methodenparameter angewendet werden. Gibt an, welche Eigenschaften eines Modells in die Modellbindung aufgenommen werden sollen. Das [Bind]-Attribut hat keine Auswirkungen auf Eingabeformatierer.

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 Erstellungsszenarien (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

Das ModelBinderAttribute kann auf Typen, Eigenschaften oder Parameter angewendet werden. Das Attribut ermöglicht das Angeben des Modellbindungtyps, der zum Binden der bestimmten Instanz oder des bestimmten Typs verwendet wird. Beispiel:

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

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

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

    // ...
}

[BindRequired]-Attribut

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 InstructorBindRequired
{
    // ...

    [BindRequired]
    public DateTime HireDate { get; set; }
}

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

[BindNever]-Attribut

Das Attribut kann auf eine Eigenschaft oder einen Typ angewendet werden. Verhindert, dass die Modellbindung die Eigenschaft eines Modells festlegt. Wenn es auf einen Typ angewendet wird, schließt das Modellbindungssystem alle Eigenschaften aus, die der Typ definiert. Hier sehen Sie ein Beispiel:

public class InstructorBindNever
{
    [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
    

    Vermeiden Sie die Bindung eines Parameters oder einer Eigenschaft mit dem Namen index oder Index, wenn diese an einen Sammlungswert angrenzen. Die Modellbindung versucht, index als Index für die Sammlung zu verwenden, was möglicherweise zu einer falschen Bindung führen kann. Sehen Sie sich beispielsweise die folgende Aktion an:

    public IActionResult Post(string index, List<Product> products)
    

    Im vorherigen Code wird der index-Abfragezeichenfolgenparameter an den index-Methodenparameter gebunden und auch zum Binden der Produktsammlung verwendet. Wenn Sie den index-Parameter umbenennen oder ein Modellbindungsattribut zum Konfigurieren der Bindung verwenden, wird dieses Problem vermieden:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • 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. System.Text.Json- und Newtonsoft.Json-basierte Eingabeformatierer unterstützen die Deserialisierung von Klassen ohne einen parameterlosen Konstruktor.

Datensatztypen eignen sich besonders gut für die prägnante Darstellung von Daten über das Netzwerk. 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" />
<br />
Age: <input asp-for="Age" />

Bei der Validierung von Datensatztypen sucht die Runtime nur in Parametern, nicht jedoch in Eigenschaften, nach Bindungs- und Validierungsmetadaten.

Das Framework ermöglicht die Bindung an und Validierung von Datensatztypen:

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

Damit der vorherige Code funktioniert, muss der Typ:

  • ein Datensatztyp sein,
  • genau einen öffentlichen Konstruktor haben,
  • Parameter enthalten, die über eine Eigenschaft mit demselben Namen und Typ verfügen. Die Groß-/Kleinschreibung der Namen darf sich nicht unterscheiden.

POCOs ohne parameterlose Konstruktoren

POCOs (Plain Old CLR Objects), die keine parameterlosen Konstruktoren haben, können nicht gebunden werden.

Der folgende Code führt zu einer Ausnahme, die besagt, dass der Typ über einen parameterlosen Konstruktor verfügen 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

Der folgende Code enthält Datensatztypen mit manuell erstellten Konstruktoren, die so aussehen, wie 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; }
}

Validierung und Metadaten

Bei der Validierung werden Metadaten für den Parameter verwendet. Sie verwendet jedoch die Eigenschaft, um den Wert zu lesen. Normalerweise wären bei zwei primären Konstruktoren beide identisch. Es gibt jedoch Möglichkeiten, damit umzugehen:

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

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, den Parameter Name zu binden. Der Parameter Age darf 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 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();
});

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 ein CancellationToken als Parameter binden. Dadurch wird die Eigenschaft RequestAborted gebunden, die anzeigt, dass die der HTTP-Anforderung zugrunde liegende Verbindung abgebrochen wird. Aktionen können diesen Parameter verwenden, um zeitintensive asynchrone Vorgänge abzubrechen, 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 vorliegen. 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

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 enthält:

public class InstructorObjectId
{
    [Required]
    public ObjectId ObjectId { get; set; } = null!;
}

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)
        => new(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 record ObjectId(int Id);

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 Program.cs einen ExcludeBindingMetadataProvider hinzu. Beispielsweise können Sie die Modellbindung für alle Modelle vom Typ System.Version wie folgt deaktivieren:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

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

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

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(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync verwendet Wertanbieter, um Daten aus dem Formulartext, der Abfragezeichenfolge und den Routendaten abzurufen. TryUpdateModelAsync wird normalerweise so verwendet:

  • Mit Razor Pages und MVC-Apps, die Controller und Ansichten verwenden, um übermäßiges Veröffentlichen 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.

Wenn eine Instanz des Typs nicht im Dependency-Injection-Container registriert ist, löst die App beim Versuch, einen Parameter zu binden, eine Ausnahme aus. Verwenden Sie einen der folgenden Ansätze, um den Parameter optional zu machen:

  • Stellen Sie den Parameter so ein, dass er Nullwerte zulässt.
  • Legen Sie einen Standardwert für den Parameter fest.

Stellen Sie für Parameter, die Nullwerte zulassen, sicher, dass der Parameter vor dem Zugriff nicht null ist.

Zusätzliche Ressourcen

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

Was ist Modellbindung?

Controller und Razor Pages arbeiten mit Daten, die aus 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 diese Daten Controllern und Razor Pages in Methodenparametern und öffentlichen Eigenschaften bereit.
  • 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:

https://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.

Targets

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, zu der eine Anforderung weitergeleitet 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 : PageModel
{
    [BindProperty]
    public Instructor? Instructor { get; set; }

    // ...
}

[BindProperties]-Attribut

Kann auf einen Controller oder eine PageModel-Klasse angewendet werden, um die Modellbindung anzuweisen, alle öffentlichen Eigenschaften dieser Klasse als Ziel zu verwenden:

[BindProperties]
public class CreateModel : PageModel
{
    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:

Diese Attribute:

  • Werden Modelleigenschaften wie im folgenden Beispiel einzeln und nicht zur Modellklasse hinzugefügt:

    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; } = null!;

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

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. Beispielsweise können Sie Daten aus cookies oder Sitzungszuständen abrufen. 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 Program.cs.

Das Beispiel umfasst ein Beispiel für einen Wertanbieter und eine Factory, das Werte aus cookies abruft. Registrieren Sie benutzerdefinierte Wertanbieterfactorys in Program.cs:

builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});

Der vorherige 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.

Um den Modellzustand ungültig zu machen, 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 auf einer Razor Page die Seite mit einer Fehlermeldung erneut an:

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

    // ...

    return RedirectToPage("./Index");
}

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 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.

Die Modellbindung durchsucht die Quellen für das Namensmuster für jede Eigenschaft des komplexen Typs nach prefix.property_name. Wenn nichts gefunden wird, sucht sie nur nach property_name ohne das Präfix. Die Entscheidung für die Verwendung des Präfix wird nicht für jede Eigenschaft getroffen. Wenn beispielsweise eine Eigenschaft mit einer Abfrage ?Instructor.Id=100&Name=foo enthält, was an die OnGet(Instructor instructor)-Methode gebunden ist, enthält das resultierende Objekt vom Typ Instructor Folgendes:

  • Legen Sie Id auf 100 fest.
  • Legen Sie Name auf null fest. Die Modellbindung erwartet Instructor.Name, weil Instructor.Id im vorherigen Abfrageparameter verwendet wurde.

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:

Warnung

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.

[Bind]-Attribut

Kann auf eine Klasse oder einen Methodenparameter angewendet werden. Gibt an, welche Eigenschaften eines Modells in die Modellbindung aufgenommen werden sollen. Das [Bind]-Attribut hat keine Auswirkungen auf Eingabeformatierer.

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 Erstellungsszenarien (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

Das ModelBinderAttribute kann auf Typen, Eigenschaften oder Parameter angewendet werden. Das Attribut ermöglicht das Angeben des Modellbindungtyps, der zum Binden der bestimmten Instanz oder des bestimmten Typs verwendet wird. Beispiel:

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

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

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

    // ...
}

[BindRequired]-Attribut

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 InstructorBindRequired
{
    // ...

    [BindRequired]
    public DateTime HireDate { get; set; }
}

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

[BindNever]-Attribut

Das Attribut kann auf eine Eigenschaft oder einen Typ angewendet werden. Verhindert, dass die Modellbindung die Eigenschaft eines Modells festlegt. Wenn es auf einen Typ angewendet wird, schließt das Modellbindungssystem alle Eigenschaften aus, die der Typ definiert. Hier sehen Sie ein Beispiel:

public class InstructorBindNever
{
    [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
    

    Vermeiden Sie die Bindung eines Parameters oder einer Eigenschaft mit dem Namen index oder Index, wenn diese an einen Sammlungswert angrenzen. Die Modellbindung versucht, index als Index für die Sammlung zu verwenden, was möglicherweise zu einer falschen Bindung führen kann. Sehen Sie sich beispielsweise die folgende Aktion an:

    public IActionResult Post(string index, List<Product> products)
    

    Im vorherigen Code wird der index-Abfragezeichenfolgenparameter an den index-Methodenparameter gebunden und auch zum Binden der Produktsammlung verwendet. Wenn Sie den index-Parameter umbenennen oder ein Modellbindungsattribut zum Konfigurieren der Bindung verwenden, wird dieses Problem vermieden:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • 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. System.Text.Json- und Newtonsoft.Json-basierte Eingabeformatierer unterstützen die Deserialisierung von Klassen ohne einen parameterlosen Konstruktor.

Datensatztypen eignen sich besonders gut für die prägnante Darstellung von Daten über das Netzwerk. 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" />
<br />
Age: <input asp-for="Age" />

Bei der Validierung von Datensatztypen sucht die Runtime nur in Parametern, nicht jedoch in Eigenschaften, nach Bindungs- und Validierungsmetadaten.

Das Framework ermöglicht die Bindung an und Validierung von Datensatztypen:

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

Damit der vorherige Code funktioniert, muss der Typ:

  • ein Datensatztyp sein,
  • genau einen öffentlichen Konstruktor haben,
  • Parameter enthalten, die über eine Eigenschaft mit demselben Namen und Typ verfügen. Die Groß-/Kleinschreibung der Namen darf sich nicht unterscheiden.

POCOs ohne parameterlose Konstruktoren

POCOs (Plain Old CLR Objects), die keine parameterlosen Konstruktoren haben, können nicht gebunden werden.

Der folgende Code führt zu einer Ausnahme, die besagt, dass der Typ über einen parameterlosen Konstruktor verfügen 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

Der folgende Code enthält Datensatztypen mit manuell erstellten Konstruktoren, die so aussehen, wie 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; }
}

Validierung und Metadaten

Bei der Validierung werden Metadaten für den Parameter verwendet. Sie verwendet jedoch die Eigenschaft, um den Wert zu lesen. Normalerweise wären bei zwei primären Konstruktoren beide identisch. Es gibt jedoch Möglichkeiten, damit umzugehen:

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

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, den Parameter Name zu binden. Der Parameter Age darf 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 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();
});

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 ein CancellationToken als Parameter binden. Dadurch wird die Eigenschaft RequestAborted gebunden, die anzeigt, dass die der HTTP-Anforderung zugrunde liegende Verbindung abgebrochen wird. Aktionen können diesen Parameter verwenden, um zeitintensive asynchrone Vorgänge abzubrechen, 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 vorliegen. 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

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 enthält:

public class InstructorObjectId
{
    [Required]
    public ObjectId ObjectId { get; set; } = null!;
}

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)
        => new(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 record ObjectId(int Id);

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 Program.cs einen ExcludeBindingMetadataProvider hinzu. Beispielsweise können Sie die Modellbindung für alle Modelle vom Typ System.Version wie folgt deaktivieren:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

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

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

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(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync verwendet Wertanbieter, um Daten aus dem Formulartext, der Abfragezeichenfolge und den Routendaten abzurufen. TryUpdateModelAsync wird normalerweise so verwendet:

  • Mit Razor Pages und MVC-Apps, die Controller und Ansichten verwenden, um übermäßiges Veröffentlichen 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.

Wenn eine Instanz des Typs nicht im Dependency-Injection-Container registriert ist, löst die App beim Versuch, einen Parameter zu binden, eine Ausnahme aus. Verwenden Sie einen der folgenden Ansätze, um den Parameter optional zu machen:

  • Stellen Sie den Parameter so ein, dass er Nullwerte zulässt.
  • Legen Sie einen Standardwert für den Parameter fest.

Stellen Sie für Parameter, die Nullwerte zulassen, sicher, dass der Parameter vor dem Zugriff nicht null ist.

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 Pages arbeiten mit Daten, die aus 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 diese Daten Controllern und Razor Pages in Methodenparametern und öffentlichen Eigenschaften bereit.
  • 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.

Targets

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, zu der eine Anforderung weitergeleitet 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:

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. Beispielsweise können Sie Daten aus cookies oder Sitzungszuständen abrufen. 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 umfasst ein Beispiel für einen Wertanbieter und eine Factory, das Werte aus cookies abruft. 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.

Um den Modellzustand ungültig zu machen, 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 auf einer Razor Page die Seite mit einer Fehlermeldung erneut an:

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

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

Die clientseitige Validierung fängt die meisten ungültigen 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 haben keine Auswirkungen auf Eingabeformatierer, die bereitgestellte JSON- und XML-Anforderungstexte 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. Das [Bind]-Attribut hat keine Auswirkungen auf Eingabeformatierer.

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 Erstellungsszenarien (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

Das ModelBinderAttribute kann auf Typen, Eigenschaften oder Parameter angewendet werden. Das Attribut ermöglicht das Angeben des Modellbindungtyps, der zum Binden der bestimmten Instanz oder des bestimmten Typs verwendet wird. Beispiel:

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

Das [ModelBinder]-Attribut kann auch verwendet werden, um den Namen einer Eigenschaft oder eines Parameters zu ändern, wenn es 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
    

    Vermeiden Sie die Bindung eines Parameters oder einer Eigenschaft mit dem Namen index oder Index, wenn diese an einen Sammlungswert angrenzen. Die Modellbindung versucht, index als Index für die Sammlung zu verwenden, was möglicherweise zu einer falschen Bindung führen kann. Sehen Sie sich beispielsweise die folgende Aktion an:

    public IActionResult Post(string index, List<Product> products)
    

    Im vorherigen Code wird der index-Abfragezeichenfolgenparameter an den index-Methodenparameter gebunden und auch zum Binden der Produktsammlung verwendet. Wenn Sie den index-Parameter umbenennen oder ein Modellbindungsattribut zum Konfigurieren der Bindung verwenden, wird dieses Problem vermieden:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • 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. System.Text.Json- und Newtonsoft.Json-basierte Eingabeformatierer unterstützen die Deserialisierung von Klassen ohne einen parameterlosen Konstruktor.

C# 9 führt Datensatztypen ein, die sich besonders gut dafür eigenen, Daten über das Netzwerk prägnant darzustellen. ASP.NET Core fügt die Unterstützung für die Modellbindung und Validierung von Datensatztypen mit einem einzelnen Konstruktor hinzu:

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" />

Bei der Validierung von Datensatztypen sucht die Runtime nur in Parametern, nicht jedoch in Eigenschaften, nach Bindungs- und Validierungsmetadaten.

Das Framework ermöglicht die Bindung an und Validierung von Datensatztypen:

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

Damit der vorherige Code funktioniert, muss der Typ:

  • ein Datensatztyp sein,
  • genau einen öffentlichen Konstruktor haben,
  • Parameter enthalten, die über eine Eigenschaft mit demselben Namen und Typ verfügen. Die Groß-/Kleinschreibung der Namen darf sich nicht unterscheiden.

POCOs ohne parameterlose Konstruktoren

POCOs (Plain Old CLR Objects), die keine parameterlosen Konstruktoren haben, können nicht gebunden werden.

Der folgende Code führt zu einer Ausnahme, die besagt, dass der Typ über einen parameterlosen Konstruktor verfügen 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

Der folgende Code enthält Datensatztypen mit manuell erstellten Konstruktoren, die so aussehen, wie 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; }
}

Validierung und Metadaten

Bei der Validierung werden Metadaten für den Parameter verwendet. Sie verwendet jedoch die Eigenschaft, um den Wert zu lesen. Normalerweise wären bei zwei primären Konstruktoren beide identisch. Es gibt jedoch Möglichkeiten, damit umzugehen:

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, den Parameter Name zu binden. Der Parameter Age darf 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 ein CancellationToken als Parameter binden. Dadurch wird die Eigenschaft RequestAborted gebunden, die anzeigt, dass die der HTTP-Anforderung zugrunde liegende Verbindung abgebrochen wird. Aktionen können diesen Parameter verwenden, um zeitintensive asynchrone Vorgänge abzubrechen, 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 vorliegen. 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 Microsoft.AspNetCore.Mvc.Formatters.Xml NuGet-Paket.

  • 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:

  • Mit Razor Pages und MVC-Apps, die Controller und Ansichten verwenden, um übermäßiges Veröffentlichen 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