Modellbindung in ASP.NET Core

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

Was ist Modellbindung?

Controller und Razor Seiten 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 die Daten für Controller und Razor Seiten 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.

Ziele

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

  • Parameter der Controlleraktionsmethode, zu der eine Anforderung weitergeleitet wird.
  • Parameter der Razor Pages-Handlermethode, an die eine Anforderung 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 zu Modelleigenschaften einzeln und nicht zur Modellklasse hinzugefügt, wie im folgenden Beispiel:

    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. Sie können beispielsweise Daten aus dem Sitzungszustand oder aus cookiedem Sitzungszustand verwenden. 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 enthält einen Wertanbieter und ein Factorybeispiel , das Werte von cookies abruft. Registrieren von benutzerdefinierten Wertanbieter-Fabriken in Program.cs:

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

Der vorherige Code platziert den benutzerdefinierten Wertanbieter nach allen integrierten Wertanbietern. 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 auf die Modellbindung aus geposteten Formulardaten angewendet wird, nicht auf 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 Seite die Seite mit einer Razor Fehlermeldung erneut an:

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

    // ...

    return RedirectToPage("./Index");
}

Wenn die Seite durch den vorherigen 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 schlechten Daten im Formularfeld erneut anzeigen möchten, sollten Sie die Modelleigenschaft als Zeichenfolge erstellen 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:

Binden mit IParsable<T>.TryParse

Die IParsable<TSelf>.TryParse API unterstützt Bindungscontroller-Aktionsparameterwerte:

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

Die folgende DateRange Klasse implementiert die Unterstützung der Bindung eines Datumsbereichs IParsable<TSelf> :

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 obige Code:

  • Konvertiert eine Zeichenfolge, die zwei Datumsangaben in ein DateRange Objekt darstellt.
  • Der Modellbinder verwendet die Methode, um die IParsable<TSelf>.TryParse Bindung zu DateRangebinden.

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 IParsable<TSelf> die Unterstützung der Bindung an CultureInfo:

public class Locale : CultureInfo, IParsable<Locale>
{
    public Locale(string culture) : base(culture)
    {
    }

    public static Locale Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
           throw new ArgumentException("Could not parse supplied value.", nameof(value));
        }

        return result;
    }

    public static bool TryParse([NotNullWhen(true)] string? value,
                                IFormatProvider? provider, out Locale locale)
    {
        if (value is null)
        {
            locale = new Locale(CurrentCulture.Name);
            return false;
        }
        
        try
        {
            locale = new Locale(value);
            return true;
        }
        catch (CultureNotFoundException)
        {
            locale = new Locale(CurrentCulture.Name);
            return false;
        }
    }
}

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 DateRange und Locale Klassen zum Binden eines Datumsbereichs mit CultureInfo:

// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
    {
        ModelState.TryAddModelError(nameof(range),
            $"Invalid date range: {range} for locale {locale.DisplayName}");

        return View("Error", ModelState.Values.SelectMany(v => v.Errors));
    }

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
                     && DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d", locale),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

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

Binden mit TryParse

Die TryParse API unterstützt Bindungscontroller-Aktionsparameterwerte:

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

IParsable<T>.TryParse ist der empfohlene Ansatz für die Parameterbindung, da es im Gegensatz TryParsedazu nicht vom Spiegelung abhängig ist.

Die folgende DateRangeTP Klasse implementiert TryParse:

public class DateRangeTP
{
    public DateOnly? From { get; }
    public DateOnly? To { get; }

    public DateRangeTP(string from, string to)
    {
        if (string.IsNullOrEmpty(from))
            throw new ArgumentNullException(nameof(from));
        if (string.IsNullOrEmpty(to))
            throw new ArgumentNullException(nameof(to));

        From = DateOnly.Parse(from);
        To = DateOnly.Parse(to);
    }

    public static bool TryParse(string? value, out DateRangeTP? result)
    {
        if (string.IsNullOrEmpty(value) || value.Split('-').Length != 2)
        {
            result = default;
            return false;
        }

        var range = value.Split(',');
        result = new DateRangeTP(range[0], range[1]);
        return true;
    }
}

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

// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([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);
}

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 wird die Modellbindung durch die Quellen für das Namemusterprefix.property_name gesucht. Wenn nichts gefunden wird, sucht sie nur nach property_name ohne das Präfix. Die Entscheidung zur Verwendung des Präfixes erfolgt nicht pro Eigenschaft. Beispielsweise enthält eine Abfrage, ?Instructor.Id=100&Name=foodie an die Methode OnGet(Instructor instructor)gebunden ist, das resultierende Objekt des Typs Instructor enthält:

  • Id festgelegt auf 100.
  • Name festgelegt auf null. Die Modellbindung erwartet Instructor.Name , weil Instructor.Id sie 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 wirken sich nicht auf Eingabeformatierer aus, die ON- und XML-Anforderungstexte verarbeiten JS. Eingabeformatierer werden später in diesem Artikel erklärt.

[Bind]-Attribut

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

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

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

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

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

Das [Bind]-Attribut kann zum Schutz vor Overposting in 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

ModelBinderAttribute kann auf Typen, Eigenschaften oder Parameter angewendet werden. Es ermöglicht das Angeben des Typs des Modellordners, der zum Binden der bestimmten Instanz oder des 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

Kann auf eine Eigenschaft oder einen Typ angewendet werden. Verhindert, dass die Modellbindung die Eigenschaft eines Modells festlegt. Beim Anwenden auf einen Typ 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 namens index oder Index wenn sie sich neben einem Auflistungswert befindet. Modellbindung versucht, als Index für die Auflistung zu verwenden index , was zu einer falschen Bindung führen kann. Ziehen Sie beispielsweise die folgende Aktion in Betracht:

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

Im vorherigen Code bindet der index Abfragezeichenfolgenparameter an den index Methodenparameter und wird auch verwendet, um die Produktauflistung zu binden. Wenn Sie den index Parameter umbenennen oder ein Modellbindungsattribute verwenden, um die Bindung zu konfigurieren, wird dieses Problem vermieden:

public IActionResult Post(string productIndex, List<Product> products)
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"

Konstruktorbindungs- und Datensatztypen

Die Modellbindung erfordert, dass komplexe Typen einen parameterlosen Konstruktor aufweisen. Newtonsoft.Json Sowohl System.Text.Json als auch basierende Eingabeformatierer unterstützen die Deserialisierung von Klassen, die keinen parameterlosen Konstruktor aufweisen.

Datensatztypen sind eine großartige Möglichkeit, Daten im Netzwerk prägnant darzustellen. ASP.NET Core unterstützt Modellbindung und Überprüfung 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" />

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

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

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

Damit die vorherige Arbeit funktioniert, muss der Typ Folgendes ausführen:

  • Ein Datensatztyp sein.
  • Haben Sie genau einen öffentlichen Konstruktor.
  • Enthält Parameter, die über eine Eigenschaft mit demselben Namen und typ verfügen. Die Namen dürfen sich nicht nach Groß-/Kleinschreibung unterscheiden.

POCOs ohne parameterlose Konstruktoren

POCOs, die keine parameterlosen Konstruktoren haben, können nicht gebunden werden.

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

public class Person(string Name)

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

Datensatztypen mit manuell erstellten Konstruktoren

Datensatztypen mit manuell erstellten Konstruktoren, die wie primäre Konstruktoren aussehen

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, Überprüfungs- und Bindungsmetadaten

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

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Überprüfung und Metadaten

Validierung verwendet Metadaten für den Parameter, verwendet jedoch die Eigenschaft, um den Wert zu lesen. Im gewöhnlichen Fall mit primären Konstruktoren wäre die beiden identisch. Es gibt jedoch Möglichkeiten, sie zu besiegen:

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 zu binden Name . Es ist jedoch zulässig, Age aktualisiert zu 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 einen CancellationToken Parameter binden. Dadurch wird das Signal gebunden RequestAborted , wenn die Verbindung, die der HTTP-Anforderung zugrunde liegt, abgebrochen wird. Aktionen können diesen Parameter verwenden, um lange ausgeführte 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 in 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. Standardmäßig enthält JSASP.NET Core ON-basierte Eingabeformatierer zum Behandeln 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 mit dem Namen Id 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 Modellbindungs- und Validierungssysteme wird von ModelMetadata. 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, abfragezeichenfolgen und Routingdaten abzurufen. TryUpdateModelAsync wird normalerweise so verwendet:

  • Wird mit Razor Seiten und MVC-Apps mithilfe von Controllern und Ansichten verwendet, um die Übersendung zu verhindern.
  • Nicht zusammen mit einer Web-API, es sei denn, sie wird von Formulardaten, Abfragezeichenfolgen und Routendaten konsumiert. Web-API-Endpunkte, die ON verwenden JS, 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 Abhängigkeitsinjektionscontainer registriert ist, löst die App beim Versuch, den Parameter zu binden, eine Ausnahme aus. Verwenden Sie eine der folgenden Ansätze, um den Parameter optional zu gestalten:

  • Machen Sie den Parameter nullwert.
  • Legen Sie einen Standardwert für den Parameter fest.

Stellen Sie für nullfähige Parameter sicher, dass der Parameter nicht null vor dem Zugriff darauf erfolgt.

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 Seiten 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 die Daten für Controller und Razor Seiten 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.

Ziele

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

  • Parameter der Controlleraktionsmethode, zu der eine Anforderung weitergeleitet wird.
  • Parameter der Razor Pages-Handlermethode, an die eine Anforderung 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 zu Modelleigenschaften einzeln und nicht zur Modellklasse hinzugefügt, wie im folgenden Beispiel:

    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. Sie können beispielsweise Daten aus dem Sitzungszustand oder aus cookiedem Sitzungszustand verwenden. 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 enthält einen Wertanbieter und ein Factorybeispiel , das Werte von cookies abruft. Registrieren von benutzerdefinierten Wertanbieter-Fabriken in Program.cs:

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

Der vorherige Code platziert den benutzerdefinierten Wertanbieter nach allen integrierten Wertanbietern. 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 auf Modellbindung aus geposteten Formulardaten, nicht auf JSON- oder XML-Daten in einem Anforderungstext angewendet wird. 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 Seite die Seite mit einer Fehlermeldung erneut an:

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

    // ...

    return RedirectToPage("./Index");
}

Wenn die Seite durch den vorherigen 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 fehlerhaften Daten im Formularfeld erneut anzeigen möchten, sollten Sie die Modelleigenschaft als Zeichenfolge erstellen 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.

Bei jeder Eigenschaft des komplexen Typs durchsieht die Modellbindung die Quellen für das Namensmusterprefix.property_name. Wenn nichts gefunden wird, sucht sie nur nach property_name ohne das Präfix. Die Entscheidung zur Verwendung des Präfixes erfolgt nicht pro Eigenschaft. Beispielsweise enthält eine Abfrage, ?Instructor.Id=100&Name=foodie an die Methode OnGet(Instructor instructor)gebunden ist, das resultierende Objekt des Typs Instructor enthält:

  • Id100auf .
  • Namenullauf . Die Modellbindung erwartet, Instructor.Name weil Instructor.Id sie 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 wirken sich nicht auf Eingabeformatierer aus, die on- und XML-Anforderungstexte verarbeiten JS. Eingabeformatierer werden später in diesem Artikel erklärt.

[Bind]-Attribut

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

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

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

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

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

Das [Bind]-Attribut kann zum Schutz vor Overposting in 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

ModelBinderAttribute kann auf Typen, Eigenschaften oder Parameter angewendet werden. Er ermöglicht das Angeben des Typs des Modellordners, der zum Binden der jeweiligen Instanz oder des jeweiligen 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

Kann auf eine Eigenschaft oder einen Typ angewendet werden. Verhindert, dass die Modellbindung die Eigenschaft eines Modells festlegt. Beim Anwenden auf einen Typ 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 namens index oder Index wenn sie sich neben einem Auflistungswert befindet. Modellbindung versucht, als Index für die Auflistung zu verwenden index , was zu einer falschen Bindung führen kann. Ziehen Sie beispielsweise die folgende Aktion in Betracht:

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

Im vorherigen Code bindet der index Abfragezeichenfolgenparameter an den index Methodenparameter und wird auch verwendet, um die Produktauflistung zu binden. Wenn Sie den index Parameter umbenennen oder ein Modellbindungsattribute verwenden, um die Bindung zu konfigurieren, wird dieses Problem vermieden:

public IActionResult Post(string productIndex, List<Product> products)
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"

Konstruktorbindungs- und Datensatztypen

Die Modellbindung erfordert, dass komplexe Typen einen parameterlosen Konstruktor aufweisen. Newtonsoft.Json Sowohl System.Text.Json als auch basierende Eingabeformatierer unterstützen die Deserialisierung von Klassen, die keinen parameterlosen Konstruktor aufweisen.

Datensatztypen sind eine großartige Möglichkeit, Daten im Netzwerk prägnant darzustellen. ASP.NET Core unterstützt Modellbindung und Überprüfung 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" />

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

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

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

Damit die vorherige Arbeit funktioniert, muss der Typ Folgendes ausführen:

  • Ein Datensatztyp sein.
  • Haben Sie genau einen öffentlichen Konstruktor.
  • Enthält Parameter, die über eine Eigenschaft mit demselben Namen und typ verfügen. Die Namen dürfen sich nicht nach Groß-/Kleinschreibung unterscheiden.

POCOs ohne parameterlose Konstruktoren

POCOs, die keine parameterlosen Konstruktoren haben, können nicht gebunden werden.

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

public class Person(string Name)

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

Datensatztypen mit manuell erstellten Konstruktoren

Datensatztypen mit manuell erstellten Konstruktoren, die wie primäre Konstruktoren aussehen

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, Überprüfungs- und Bindungsmetadaten

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

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Überprüfung und Metadaten

Validierung verwendet Metadaten für den Parameter, verwendet jedoch die Eigenschaft, um den Wert zu lesen. Im gewöhnlichen Fall mit primären Konstruktoren wäre die beiden identisch. Es gibt jedoch Möglichkeiten, sie zu besiegen:

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 zu binden Name . Es ist jedoch zulässig, Age aktualisiert zu 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 einen CancellationToken Parameter binden. Dadurch wird das Signal gebunden RequestAborted , wenn die Verbindung, die der HTTP-Anforderung zugrunde liegt, abgebrochen wird. Aktionen können diesen Parameter verwenden, um lange ausgeführte 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 in JSON, XML oder einem anderen Format enthalten sein. Um diese Daten zu analysieren, verwendet die Modellbindung einen Eingabeformatierer, der für die Verarbeitung eines bestimmten Inhaltstyps konfiguriert ist. Standardmäßig enthält JSASP.NET Core ON-basierte Eingabeformatierer zum Behandeln 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 mit dem Namen Id 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 des Modellbindungs- und Validierungssystemes wird von ModelMetadata. 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 der Weiterleitung von Daten abzurufen. TryUpdateModelAsync wird normalerweise so verwendet:

  • Wird mit Seiten und MVC-Apps mit Razor Controllern und Ansichten verwendet, um die Übersendung zu verhindern.
  • Nicht zusammen mit einer Web-API, es sei denn, sie wird von Formulardaten, Abfragezeichenfolgen und Routendaten konsumiert. Web-API-Endpunkte, die ON verwenden JS, verwenden Eingabeformatierer , um den Anforderungstext in ein Objekt zu enterialisieren.

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 Abhängigkeits-Injektionscontainer registriert ist, löst die App beim Versuch, den Parameter zu binden, eine Ausnahme aus. Um den Parameter optional zu machen, verwenden Sie einen der folgenden Ansätze:

  • Machen Sie den Parameter nullable.
  • Legen Sie einen Standardwert für den Parameter fest.

Stellen Sie für nullable Parameter sicher, dass der Parameter nicht null vor dem Zugriff darauf 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 Seiten 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 die Daten für Controller und Seiten in Methodenparametern und Razor ö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.

Ziele

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

  • Parameter der Controlleraktionsmethode, zu der eine Anforderung weitergeleitet wird.
  • Parameter der Pages-Handlermethode, an die Razor 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. Sie können beispielsweise Daten aus dem Sitzungszustand oder aus cookiedem Sitzungszustand verwenden. So rufen Sie Daten aus einer neuen Quelle ab

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

Die Beispiel-App enthält einen Wertanbieter und ein Factorybeispiel , das Werte von 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 auf Modellbindung aus geposteten Formulardaten, nicht auf JSON- oder XML-Daten in einem Anforderungstext angewendet wird. 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 Seite die Seite mit einer Fehlermeldung erneut an:

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

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

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

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

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

Einfache Typen

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

Komplexe Typen

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

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

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

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

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

Präfix = Parametername

Wenn das zu bindende Modell ein Parameter namens instructorToUpdate ist:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

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

Präfix = Name der Eigenschaft

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

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

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

Benutzerdefiniertes Präfix

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

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

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

Attribute für Ziele komplexen Typs

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

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

Warnung

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

[Bind]-Attribut

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

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

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

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

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

Das [Bind]-Attribut kann zum Schutz vor Overposting in 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

ModelBinderAttribute kann auf Typen, Eigenschaften oder Parameter angewendet werden. Es ermöglicht das Angeben des Typs des Modellordners, der zum Binden der bestimmten Instanz oder des 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 namens index oder Index wenn sie neben einem Auflistungswert liegt. Modellbindung versucht, als Index für die Auflistung zu verwenden index , die möglicherweise zu einer falschen Bindung führen kann. Berücksichtigen Sie beispielsweise die folgende Aktion:

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

    Im vorherigen Code bindet der index Abfragezeichenfolgenparameter an den index Methodenparameter und wird auch zum Binden der Produktsammlung verwendet. Wenn Sie den Parameter umbenennen oder ein Modellbindungsattribute verwenden, um die index Bindung zu konfigurieren, 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"

Konstruktorbindungs- und Datensatztypen

Die Modellbindung erfordert, dass komplexe Typen einen parameterlosen Konstruktor aufweisen. Newtonsoft.Json Sowohl System.Text.Json als auch basierende Eingabeformatierer unterstützen die Deerialisierung von Klassen, die keinen parameterlosen Konstruktor haben.

C# 9 führt Datensatztypen ein, die eine hervorragende Möglichkeit sind, Daten über das Netzwerk kurz darzustellen. ASP.NET Core fügt Unterstützung für die Modellbindung und Überprüfung 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" />

Beim Überprüfen von Datensatztypen sucht die Laufzeit speziell auf Parametern und eigenschaften nach Bindungs- und Validierungsmetadaten.

Das Framework ermöglicht die Bindung und Überprüfung von Datensatztypen:

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

Für die vorherige Arbeit muss der Typ Folgendes ausführen:

  • Ein Datensatztyp sein.
  • Haben Sie genau einen öffentlichen Konstruktor.
  • Enthalten Parameter, die über eine Eigenschaft mit demselben Namen und demselben Typ verfügen. Die Namen dürfen sich nicht nach Groß-/Kleinschreibung unterscheiden.

POCOs ohne parameterlose Konstruktoren

POCOs, die keine parameterlosen Konstruktoren haben, können nicht gebunden werden.

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

public class Person(string Name)

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

Datensatztypen mit manuell erstellten Konstruktoren

Datensatztypen mit manuell erstellten Konstruktoren, die wie primäre Konstruktoren aussehen, 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, Überprüfungs- und Bindungsmetadaten

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

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Überprüfung und Metadaten

Die Überprüfung verwendet Metadaten für den Parameter, verwendet jedoch die Eigenschaft, um den Wert zu lesen. Im normalen Fall mit primären Konstruktoren würde die beiden identisch sein. Es gibt jedoch Möglichkeiten, sie zu besiegen:

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 auf einem 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 zu binden Name . Es ist jedoch zulässig, Age aktualisiert zu 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 einen CancellationToken Parameter binden. Dadurch wird RequestAborted gebunden, dass signalisiert wird, wenn die Verbindung, die der HTTP-Anforderung zugrunde liegt, abgebrochen wird. Aktionen können diesen Parameter verwenden, um lange ausgeführte 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 in JSON, XML oder einem anderen Format enthalten sein. Um diese Daten zu analysieren, verwendet die Modellbindung einen Eingabeformatierer, der für die Verarbeitung eines bestimmten Inhaltstyps konfiguriert ist. Standardmäßig enthält JSASP.NET Core ON-basierte Eingabeformatierer zum Behandeln von JSON-Daten. Sie können andere Formatierer für andere Inhaltstypen hinzufügen.

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

So verwenden Sie die integrierte XML-Eingabeformatierer

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

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

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

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

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

Anpassen der Modellbindung mit Eingabeformatierern

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

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

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

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

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return new ObjectId(JsonSerializer.Deserialize<int>(ref reader, options));
    }

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
    {
        writer.WriteNumberValue(value.Id);
    }
}

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

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

    public int Id { get; }
}

Weitere Informationen finden Sie unter Vorgehensweise: Schreiben benutzerdefinierter Konverter.

Ausschließen angegebener Typen aus der Modellbindung

Das Verhalten des Modellbindungs- und Validierungssystemes wird von ModelMetadata. 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 der Weiterleitung von Daten abzurufen. TryUpdateModelAsync wird normalerweise so verwendet:

  • Wird mit Seiten und MVC-Apps mit Razor Controllern und Ansichten verwendet, um die Übersendung zu verhindern.
  • Nicht zusammen mit einer Web-API, es sei denn, sie wird von Formulardaten, Abfragezeichenfolgen und Routendaten konsumiert. Web-API-Endpunkte, die ON verwenden JS, verwenden Eingabeformatierer , um den Anforderungstext in ein Objekt zu enterialisieren.

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