Dela via


Modellbindning i ASP.NET Core

Note

Det här är inte den senaste versionen av den här artikeln. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Warning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Important

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Den här artikeln förklarar vad modellbindning är, hur den fungerar och hur du anpassar dess beteende.

Vad är modellbindning

Kontrollanter och Razor sidor fungerar med data som kommer från HTTP-begäranden. Routningsdata kan till exempel ge en postnyckel, och publicerade formulärfält kan ge värden för modellens egenskaper. Att skriva kod för att hämta vart och ett av dessa värden och konvertera dem från strängar till .NET-typer skulle vara omständligt och felbenäget. Modellbindning automatiserar den här processen. Modellbindningssystemet:

  • Hämtar data från olika källor, till exempel routningsdata, formulärfält och frågesträngar.
  • Tillhandahåller data till kontroller och Razor sidor i metodparametrar och publika egenskaper.
  • Konverterar strängdata till .NET-typer.
  • Uppdaterar egenskaper för komplexa typer.

Example

Anta att du har följande åtgärdsmetod:

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

Och appen får en begäran med den här URL:en:

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

Modellbindningen går igenom följande steg när routningssystemet har valt åtgärdsmetoden:

  • Hittar den första parametern i GetById, ett heltal med namnet id.
  • Söker igenom de tillgängliga källorna i HTTP-begäran och hittar id = "2" i routningsdata.
  • Konverterar strängen "2" till heltal 2.
  • Hittar nästa parameter i GetById, ett booleskt värde med namnet dogsOnly.
  • Söker igenom källorna och hittar "DogsOnly=true" i frågesträngen. Namnmatchning är inte skiftlägeskänsligt.
  • Konverterar strängen "true" till boolesk true.

Ramverket anropar GetById sedan metoden och skickar in 2 för parametern id och true för parametern dogsOnly .

I föregående exempel är modellbindningsmålen metodparametrar som är enkla typer. Mål kan också vara egenskaper för en komplex typ. När varje egenskap har bundits sker modellverifiering för den egenskapen. Posten för vilka data som är bundna till modellen och eventuella bindnings- eller valideringsfel lagras i ControllerBase.ModelState eller PageModel.ModelState. För att ta reda på om den här processen lyckades kontrollerar appen flaggan ModelState.IsValid .

Targets

Modellbindning försöker hitta värden för följande typer av mål:

  • Parametrar för den kontrollantåtgärdsmetod som en begäran dirigeras till.
  • Parametrar för den Razor sidhanterarmetod som en begäran dirigeras till.
  • Offentliga egenskaper för en kontrollant eller PageModel klass, om de anges av attribut.

[BindProperty]-attribut

Kan tillämpas på en offentlig egenskap hos en kontroller eller PageModel klass för att modellbindning ska rikta sig mot den egenskapen.

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

    // ...
}

[BindProperties]-attribut

Kan tillämpas på en kontroller eller PageModel klass för att ange modellbindning att rikta in sig på alla offentliga egenskaper i klassen.

[BindProperties]
public class CreateModel : PageModel
{
    public Instructor? Instructor { get; set; }

    // ...
}

Modellbindning för HTTP GET-begäranden

Som standard är egenskaperna inte bundna till HTTP GET-begäranden. Vanligtvis är allt du behöver för en GET-begäran en post-ID-parameter. Post-ID:t används för att leta upp objektet i databasen. Därför behöver du inte binda en egenskap som innehåller en instans av modellen. I scenarier där du vill att egenskaper ska bindas till data från GET-begäranden anger du SupportsGet egenskapen till true:

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

Modellbindning av enkla och komplexa typer

Modellbindning använder specifika definitioner för de typer som den fungerar på. En enkel typ konverteras från en enda sträng med hjälp av TypeConverter eller en TryParse metod. En komplex typ konverteras från flera indatavärden. Ramverket avgör skillnaden baserat på förekomsten av en TypeConverter eller TryParse. Vi rekommenderar att du skapar en typkonverterare eller använder TryParse för en string till-konvertering SomeType som inte kräver externa resurser eller flera indata.

Sources

Som standard hämtar modellbindningen data i form av nyckel/värde-par från följande källor i en HTTP-begäran:

  1. Formulärfält
  2. Begärandetexten (för kontrollanter som har attributet [ApiController].)
  3. Ruttdata
  4. Frågesträngsparametrar
  5. Ladda upp filer

För varje målparameter eller egenskap genomsöks källorna i den ordning som anges i föregående lista. Det finns några undantag:

  • Routningsdata och frågesträngsvärden används endast för enkla typer.
  • Uppladdade filer är endast bundna till måltyper som implementerar IFormFile eller IEnumerable<IFormFile>.

Om standardkällan inte är korrekt använder du något av följande attribut för att ange källan:

  • [FromQuery] – Hämtar värden från frågesträngen.
  • [FromRoute] – Hämtar värden från vägdata.
  • [FromForm] – Hämtar värden från publicerade formulärfält.
  • [FromBody] – Hämtar värden från begärandetexten.
  • [FromHeader] – Hämtar värden från HTTP-huvuden.

Följande attribut:

  • Läggs till i modellegenskaper individuellt och inte i modellklassen, som i följande exempel:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Det är valfritt att acceptera ett modellnamnsvärde i konstruktorn. Det här alternativet anges om egenskapsnamnet inte matchar värdet i begäran. Till exempel kan värdet i begäran vara en rubrik med ett bindestreck i namnet, som i följande exempel:

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

[FromBody]-attribut

[FromBody] Använd attributet för en parameter för att fylla i dess egenskaper från brödtexten i en HTTP-begäran. ASP.NET Core-körningen delegerar ansvaret för att läsa brödtexten till en indataformaterare. Indataformaterare beskrivs senare i den här artikeln.

När [FromBody] tillämpas på en komplex typparameter ignoreras alla bindningskällaattribut som tillämpas på dess egenskaper. Följande åtgärd anger till exempel Create att parametern pet är ifylld från brödtexten:

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

Klassen Pet anger att dess Breed egenskap fylls i från en frågesträngsparameter:

public class Pet
{
    public string Name { get; set; } = null!;

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

I föregående exempel:

  • Attributet [FromQuery] ignoreras.
  • Egenskapen Breed fylls inte i från en frågesträngsparameter.

Indataformaterare läser endast brödtexten och förstår inte bindningskällans attribut. Om ett lämpligt värde hittas i brödtexten används det värdet för att fylla i Breed egenskapen.

Gäller [FromBody] inte för fler än en parameter per åtgärdsmetod. När begärandeströmmen har lästs av en indataformaterare är den inte längre tillgänglig för att läsas igen för bindning av andra [FromBody] parametrar.

Ytterligare källor

Källdata tillhandahålls till modellbindningssystemet av värdeprovidrar. Du kan skriva och registrera anpassade värdeprovidrar som hämtar data för modellbindning från andra källor. Du kanske till exempel vill ha data från cookies eller sessionstillstånd. Så här hämtar du data från en ny källa:

  • Skapa en klass som implementerar IValueProvider.
  • Skapa en klass som implementerar IValueProviderFactory.
  • Registrera fabriksklassen i Program.cs.

Exemplet innehåller en värdeprovider och ett fabriksexempel som hämtar värden från cookies. Registrera anpassade värdeproviderfabriker i Program.cs:

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

Föregående kod placerar den anpassade värdeprovidern efter alla inbyggda värdeprovidrar. Om du vill göra den till den första i listan anropar du Insert(0, new CookieValueProviderFactory()) i stället Addför .

Ingen källa för en modells egenskap

Som standard skapas inte ett modelltillståndsfel om inget värde hittas för en modellegenskap. Egenskapen är inställd på null eller ett standardvärde:

  • Nullbara enkla typer är inställda på null.
  • Värdetyper som inte kan vara null är inställda på default(T). En parameter int id är till exempel inställd på 0.
  • För komplexa typer skapar modellbindning en instans med hjälp av standardkonstruktorn, utan att ange egenskaper.
  • Matriser är inställda på Array.Empty<T>(), förutom att byte[] matriser är inställda på null.

Om modelltillståndet ska ogiltigförklaras när inget hittas i formulärfält för en modellegenskap använder du attributet [BindRequired] .

Observera att det här [BindRequired] beteendet gäller för modellbindning från publicerade formulärdata, inte från JSON- eller XML-data i en begärandetext. Brödtextdata för begäran hanteras av indataformaterare.

Typkonverteringsfel

Om en källa hittas men inte kan konverteras till måltypen flaggas modelltillståndet som ogiltigt. Målparametern eller -egenskapen är inställd på null eller ett standardvärde, enligt beskrivningen i föregående avsnitt.

I en API-kontrollant som har [ApiController] attributet resulterar ogiltigt modelltillstånd i ett automatiskt HTTP 400-svar.

Visa sidan igen med ett felmeddelande på en Razor sida.

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

    // ...

    return RedirectToPage("./Index");
}

När sidan spelas upp av föregående kod visas inte ogiltiga indata i formulärfältet. Det beror på att modellegenskapen har angetts till null eller ett standardvärde. Ogiltiga indata visas i ett felmeddelande. Om du vill redigera om felaktiga data i formulärfältet kan du överväga att göra modellegenskapen till en sträng och göra datakonverteringen manuellt.

Samma strategi rekommenderas om du inte vill att typkonverteringsfel ska resultera i modelltillståndsfel. I så fall gör du modellegenskapen till en sträng.

Enkla typer

Se Modellbindning av enkla och komplexa typer för förklaring av enkla och komplexa typer.

De enkla typer som modellbindningen kan konvertera källsträngar till innehåller följande:

Bind med IParsable<T>.TryParse

API:et IParsable<TSelf>.TryParse stöder parametervärden för bindningskontrollantåtgärd:

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

Följande DateRange-klass implementerar IParsable<TSelf> för att binda ett datumintervall.

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

Föregående kod:

  • Konverterar en sträng som representerar två datum till ett DateRange objekt
  • Modellbindningen använder IParsable<TSelf>.TryParse metoden för att binda DateRange.

Följande kontrollantåtgärd använder DateRange klassen för att binda ett datumintervall:

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

Följande Locale klass implementerar IParsable<TSelf> för att stödja bindning till 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;
        }
    }
}

Följande kontrollantåtgärd använder Locale klassen för att binda en CultureInfo sträng:

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

Följande kontrollantåtgärd använder klasserna DateRange och Locale för att binda ett datumintervall med 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);
}

API-exempelappen på GitHub visar föregående exempel för en API-kontrollant.

Bind med TryParse

API:et TryParse stöder parametervärden för bindningskontrollantåtgärd:

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

IParsable<T>.TryParse är den rekommenderade metoden för parameterbindning eftersom den till skillnad från TryParse, inte beror på reflektion.

Följande DateRangeTP klass implementerar TryParse:

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

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

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

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

        result = new DateRangeTP(range[0], range[1]);
        return true;
    }
}

Följande kontrollantåtgärd använder DateRangeTP klassen för att binda ett datumintervall:

// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

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

    return View("Index", weatherForecasts);
}

Komplexa typer

En komplex typ måste ha en offentlig standardkonstruktor och offentliga skrivbara egenskaper för bindning. När modellbindningen inträffar instansieras klassen med hjälp av den offentliga standardkonstruktorn.

För varje egenskap av den komplexa typen söker modellbindningen igenom källorna efter namnmönstretprefix.property_name. Om inget hittas letar den efter bara property_name utan prefixet. Beslutet att använda prefixet fattas inte per egenskap. Med till exempel en fråga som innehåller ?Instructor.Id=100&Name=foo, bunden till -metoden OnGet(Instructor instructor), innehåller det resulterande objektet av typen Instructor :

  • Id inställt på 100.
  • Name inställt på null. Modellbindningen förväntar sig Instructor.Name eftersom Instructor.Id användes i föregående frågeparameter.

Note

Dokumentationslänkar till .NET-referenskällan läser vanligtvis in lagringsplatsens standardgren, vilket representerar den aktuella utvecklingen för nästa version av .NET. Om du vill välja en tagg för en specifik version använder du listrutan Välj bland grenar eller taggar. Mer information finns i Så här väljer du en versionstagg för ASP.NET Core-källkod (dotnet/AspNetCore.Docs #26205).

För bindning till en parameter är prefixet parameternamnet. För bindning till en PageModel offentlig egenskap är prefixet namnet på den offentliga egenskapen. Vissa attribut har en Prefix egenskap som gör att du kan åsidosätta standardanvändningen av parametern eller egenskapsnamnet.

Anta till exempel att den komplexa typen är följande Instructor klass:

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

Prefix = parameternamn

Om modellen som ska bindas är en parameter med namnet instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Modellbindningen börjar med att titta igenom källorna för nyckeln instructorToUpdate.ID. Om det inte hittas söker den efter ID utan ett prefix.

Prefix = egenskapsnamn

Om modellen som ska bindas är en egenskap med namnet Instructor på kontrollanten eller PageModel klassen:

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

Modellbindningen börjar med att titta igenom källorna för nyckeln Instructor.ID. Om det inte hittas söker den efter ID utan ett prefix.

Anpassat prefix

Om modellen som ska bindas är en parameter med namnet instructorToUpdate och ett Bind attribut anger Instructor som prefix:

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

Modellbindningen börjar med att titta igenom källorna för nyckeln Instructor.ID. Om det inte hittas söker den efter ID utan ett prefix.

Attribut för komplexa typmål

Flera inbyggda attribut är tillgängliga för att styra modellbindning av komplexa typer:

Warning

Dessa attribut påverkar modellbindningen när publicerade formulärdata är källan till värden. De påverkar inte indataformaterare, som bearbetar publicerade JSON- och XML-begärandeorgan. Indataformaterare beskrivs senare i den här artikeln.

Attributet [Bind]

Kan tillämpas på en klass eller en metodparameter. Anger vilka egenskaper för en modell som ska ingå i modellbindningen. [Bind] påverkar inte indataformaterare.

I följande exempel är endast de angivna egenskaperna för Instructor modellen bundna när någon hanterare eller åtgärdsmetod anropas:

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

I följande exempel är endast de angivna egenskaperna för Instructor modellen bundna när OnPost metoden anropas:

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

Attributet [Bind] kan användas för att skydda mot överpublicering i skapa scenarier. Det fungerar inte bra i redigeringsscenarier eftersom exkluderade egenskaper är inställda på null eller ett standardvärde i stället för att lämnas oförändrade. För skydd mot överpublicering rekommenderas visningsmodeller i stället för attributet [Bind] . Mer information finns i Säkerhetsanteckning om överpublicering.

[ModelBinder]-attribut

ModelBinderAttribute kan tillämpas på typer, egenskaper eller parametrar. Det gör det möjligt att ange vilken typ av modellbindning som används för att binda den specifika instansen eller typen. Till exempel:

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

Attributet [ModelBinder] kan också användas för att ändra namnet på en egenskap eller parameter när det är modellbundet:

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

    // ...
}

[BindRequired]-attribut

Orsakar modellbindning för att lägga till ett modelltillståndsfel om bindningen inte kan ske för en modells egenskap. Här är ett exempel:

public class InstructorBindRequired
{
    // ...

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

Se även diskussionen om attributet [Required] i Modellverifiering.

[BindNever]-attribut

Kan tillämpas på en egenskap eller en typ. Förhindrar modellbindning från att bestämma en modells egenskap. När det tillämpas på en typ exkluderar modellbindningssystemet alla egenskaper som typen definierar. Här är ett exempel:

public class InstructorBindNever
{
    [BindNever]
    public int Id { get; set; }

    // ...
}

Collections

För mål som är samlingar av enkla typer letar modellbindningen efter matchningar till parameter_name eller property_name. Om ingen matchning hittas letar den efter ett av de format som stöds utan prefixet. Till exempel:

  • Anta att parametern som ska bindas är en matris med namnet selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Formulär- eller frågesträngsdata kan vara i något av följande format:

    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
    

    Undvik att binda en parameter eller en egenskap med namnet index eller Index om den ligger i anslutning till ett samlingsvärde. Modellbindning försöker använda index som index för samlingen, vilket kan leda till felaktig bindning. Tänk till exempel på följande åtgärd:

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

    I föregående kod binder frågesträngsparametern index till index metodparametern och används även för att binda produktsamlingen. Om du byter namn på parametern index eller använder ett modellbindningsattribut för att konfigurera bindning undviks det här problemet:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Följande format stöds endast i formulärdata:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • För alla föregående exempelformat skickar modellbindningen en matris med två objekt till parametern selectedCourses :

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

    Dataformat som använder nedsänkta siffror (... [0] ... [1] ...) måste se till att de numreras sekventiellt med början på noll. Om det finns några luckor i siffernummer ignoreras alla objekt efter luckan. Om de till exempel är 0 och 2 i stället för 0 och 1 ignoreras det andra objektet.

Dictionaries

För Dictionary mål söker modellbindning efter matchningar till parameter_name eller property_name. Om ingen matchning hittas letar den efter ett av de format som stöds utan prefixet. Till exempel:

  • Anta att målparametern heter Dictionary<int, string>selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Publicerade formulär- eller frågesträngsdata kan se ut som något av följande exempel:

    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
    
  • För alla föregående exempelformat skickar modellbindningen en ordlista med två objekt till parametern selectedCourses :

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

Konstruktorbindning och rekordtyper

Modellbindning kräver att komplexa typer har en parameterlös konstruktor. Både System.Text.Json och Newtonsoft.Json baserade indataformaterare stöder deserialisering av klasser som inte har en parameterlös konstruktor.

Posttyper är ett bra sätt att kortfattat representera data på nätverket. ASP.NET Core stöder modellbindning och validering av posttyper med en enda 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

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

Vid validering av posttyper söker körningsmiljön efter bindnings- och valideringsmetadata specifikt på parametrar i stället för på egenskaper.

Ramverket tillåter bindning till och validering av posttyper:

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

För att det föregående ska fungera, måste typen:

  • Vara en posttyp.
  • Ha exakt en offentlig konstruktor.
  • Innehåller parametrar som har en egenskap med samma namn och typ. Namnen får inte skilja sig åt från fall till fall.

POCOs utan parameterfria konstruktorer

POCO:er som inte har parameterlösa konstruktorer kan inte bindas.

Följande kod resulterar i ett undantag som säger att typen måste ha en parameterlös konstruktor:

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

Registrera typer med manuellt skapade konstruktorer

Posttyper med manuellt skapade konstruktorer som ser ut som primära konstruktorer fungerar

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

Posttyper, validering och bindningsmetadata

För posttyper används validering och bindning av metadata för parametrar. Metadata för egenskaper ignoreras

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

Validering och metadata

Validering använder metadata för parametern men använder egenskapen för att läsa värdet. I vanliga fall med primära konstruktorer skulle de två vara identiska. Det finns dock sätt att besegra det:

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 uppdaterar inte parametrar för en posttyp

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

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

I det här fallet försöker MVC inte binda Name igen. Kan dock Age uppdateras

Globaliseringsbeteende för modellbindningsvägsdata och frågesträngar

ASP.NET Core leverantör för routvärden och leverantör för frågesträngsvärden:

  • Behandla värden som invariant kultur.
  • Förvänta dig att URL:er är kulturinvarianta.

Däremot genomgår värden som kommer från formulärdata en kulturkänslig konvertering. Detta är avsiktligt så att URL:er kan delas oberoende av språkversion.

Så här gör du så att ASP.NET Core-routningsvärdeprovidern och frågesträngsvärdeprovidern genomgår en kulturkänslig konvertering:

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

Särskilda datatyper

Det finns vissa särskilda datatyper som modellbindning kan hantera.

IFormFile och IFormFileCollection

En uppladdad fil som ingår i HTTP-begäran. IEnumerable<IFormFile> stöds också för flera filer.

CancellationToken

Åtgärder kan valfritt binda en CancellationToken som en parameter. Detta binder RequestAborted som signalerar när anslutningen som ligger till grund för HTTP-begäran avbryts. Åtgärder kan använda den här parametern för att avbryta långvariga asynkrona åtgärder som körs som en del av kontrollantåtgärderna.

FormCollection

Används för att hämta alla värden från publicerade formulärdata.

Indataformaterare

Data i begärandetexten kan vara i JSON, XML eller något annat format. För att parsa dessa data använder modellbindningen en indataformaterare som är konfigurerad för att hantera en viss innehållstyp. Som standard innehåller ASP.NET Core JSON-baserade indataformaterare för hantering av JSON-data med .System.Text.Json Du kan lägga till andra formaterare för andra innehållstyper.

Standardformaterare för JSON-indata kan konfigureras med hjälp av AddJsonOptions metoden:

builder.Services.AddControllers().AddJsonOptions(options =>
{
    // Configure property naming policy (camelCase)
    options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

    // Add enum converter to serialize enums as strings
    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());

    // Configure other JSON options
    options.JsonSerializerOptions.WriteIndented = true;
    options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});

Vanliga konfigurationsalternativ är:

  • Namngivningsprincip för egenskaper – Konfigurera camelCase eller andra namngivningskonventioner
  • Enum-konverterare – Hantera enum-serialisering som strängar
  • Anpassade konverterare – Lägg till typspecifik serialiseringslogik

ASP.NET Core väljer indataformaterare baserat på attributet Förbrukar . Om det inte finns något attribut används rubriken Innehållstyp.

Så här använder du de inbyggda XML-indataformatrarna:

Anpassa modellbindning med indataformaterare

En indataformaterare tar fullt ansvar för att läsa data från begärandetexten. Om du vill anpassa den här processen konfigurerar du de API:er som används av indataformaterare. I det här avsnittet beskrivs hur du anpassar den System.Text.Json-baserade indataformateren för att förstå en anpassad typ med namnet ObjectId.

Tänk på följande modell, som innehåller en anpassad ObjectId egenskap:

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

Om du vill anpassa modellbindningsprocessen när du använder System.Text.Jsonskapar du en klass som härletts från JsonConverter<T>:

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

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

Använd attributet JsonConverterAttribute för typen för att använda en anpassad konverterare. I följande exempel konfigureras ObjectId-typen med ObjectIdConverter som sin egen anpassade konverterare.

[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);

Mer information finns i Skriva anpassade konverterare.

Undanta angivna typer från modellbindning

Modellens bindnings- och valideringssystems beteende styrs av ModelMetadata. Du kan anpassa ModelMetadata genom att lägga till en informationsprovider i MvcOptions.ModelMetadataDetailsProviders. Inbyggda informationsprovidrar är tillgängliga för inaktivering av modellbindning eller validering för angivna typer.

Om du vill inaktivera modellbindning för alla modeller av en angiven typ lägger du till en ExcludeBindingMetadataProvider i Program.cs. Om du till exempel vill inaktivera modellbindning för alla modeller av typen System.Version:

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

Om du vill inaktivera validering av egenskaper för en angiven typ lägger du till en SuppressChildValidationMetadataProvider i Program.cs. Om du till exempel vill inaktivera validering av egenskaper av typen System.Guid:

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

Anpassade modellbindare

Du kan utöka modellbindningen genom att skriva en anpassad modellbindning och använda [ModelBinder] attributet för att välja den för ett visst mål. Läs mer om bindning av anpassade modeller.

Manuell modellbindning

Modellbindning kan anropas manuellt genom att använda metoden TryUpdateModelAsync. Metoden definieras för båda ControllerBase klasserna och PageModel . Med metodöverlagringar kan du ange prefixet och värdeprovidern som ska användas. Metoden returnerar false om modellbindningen misslyckas. Här är ett exempel:

if (await TryUpdateModelAsync(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync använder värdeleverantörer för att hämta data från formulärets data, frågesträngen och routningsdata. TryUpdateModelAsync är vanligtvis:

  • Används med Razor Pages- och MVC-appar med hjälp av kontrollanter och vyer för att förhindra överpublicering.
  • Används inte med ett webb-API förutom när det hämtas från formulärdata, frågesträngar och routdata. Webb-API-slutpunkter som använder JSON använder indataformatrar för att deserialisera begärandetexten till ett objekt.

Mer information finns i TryUpdateModelAsync.

[FromServices]-attribut

Det här attributets namn följer mönstret för modellbindningsattribut som anger en datakälla. Men det handlar inte om att binda data från en värdeprovider. Den hämtar en instans av en typ från containern för beroendeinmatning . Syftet är att tillhandahålla ett alternativ till konstruktorinmatning för när du behöver en tjänst endast om en viss metod anropas.

Om en instans av typen inte är registrerad i containern för beroendeinmatning genererar appen ett undantag när parametern försöker bindas. Om du vill göra parametern valfri använder du någon av följande metoder:

  • Gör parametern nullbar.
  • Ange ett standardvärde för parametern.

För null-parametrar kontrollerar du att parametern inte null är innan du kommer åt den.

Json+PipeReader-deserialisering i MVC

Från och med .NET 10 använder följande funktionella områden i ASP.NET Core överbelastningar av JsonSerializer.DeserializeAsync som är baserade på PipeReader istället för Stream.

  • Minimala API:er (parameterbindning, brödtext för läsbegäran)
  • MVC (indataformaterare, modell)
  • Tilläggsmetoderna HttpRequestJsonExtensions för att läsa begärandetexten som JSON.

För de flesta program ger en övergång från Stream till PipeReader bättre prestanda utan att kräva ändringar i programkoden. Men om ditt program har en anpassad konverterare kanske konverteraren inte hanterar Utf8JsonReader.HasValueSequence korrekt. Om den inte gör det kan resultatet bli fel som ArgumentOutOfRangeException eller saknade data vid deserialisering. Du har följande alternativ för att få konverteraren att fungera utan PipeReader-relaterade fel.

Alternativ 1: Tillfällig lösning

Den snabba lösningen är att gå tillbaka till att använda Stream utan PipeReader-stöd. Om du vill implementera det här alternativet anger du "Microsoft.AspNetCore.UseStreamBasedJsonParsing" AppContext-växeln till "true". Vi rekommenderar att du endast gör detta som en tillfällig lösning och uppdaterar konverteraren så att den stöder HasValueSequence det så snart som möjligt. Växeln kan tas bort i .NET 11. Det enda syftet var att ge utvecklare tid att få sina konverterare uppdaterade.

Alternativ 2: En snabbkorrigering för JsonConverter implementeringar

För den här korrigeringen allokerar du en matris från ReadOnlySequence. Det här exemplet visar hur koden skulle se ut:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
    // previous code
}

Alternativ 3: En mer komplicerad men bättre presterande korrigering

Den här korrigeringen innebär att du konfigurerar en separat kodsökväg för ReadOnlySequence hanteringen:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.HasValueSequence)
    {
        reader.ValueSequence;
        // ReadOnlySequence optimized path
    }
    else
    {
        reader.ValueSpan;
        // ReadOnlySpan optimized path
    }
}

Mer information finns i

Ytterligare resurser

Den här artikeln förklarar vad modellbindning är, hur den fungerar och hur du anpassar dess beteende.

Vad är modellbindning

Kontrollanter och Razor sidor fungerar med data som kommer från HTTP-begäranden. Routningsdata kan till exempel ge en postnyckel, och publicerade formulärfält kan ge värden för modellens egenskaper. Att skriva kod för att hämta vart och ett av dessa värden och konvertera dem från strängar till .NET-typer skulle vara omständligt och felbenäget. Modellbindning automatiserar den här processen. Modellbindningssystemet:

  • Hämtar data från olika källor, till exempel routningsdata, formulärfält och frågesträngar.
  • Tillhandahåller data till kontroller och Razor sidor i metodparametrar och publika egenskaper.
  • Konverterar strängdata till .NET-typer.
  • Uppdaterar egenskaper för komplexa typer.

Example

Anta att du har följande åtgärdsmetod:

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

Och appen får en begäran med den här URL:en:

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

Modellbindningen går igenom följande steg när routningssystemet har valt åtgärdsmetoden:

  • Hittar den första parametern i GetById, ett heltal med namnet id.
  • Söker igenom de tillgängliga källorna i HTTP-begäran och hittar id = "2" i routningsdata.
  • Konverterar strängen "2" till heltal 2.
  • Hittar nästa parameter i GetById, ett booleskt värde med namnet dogsOnly.
  • Söker igenom källorna och hittar "DogsOnly=true" i frågesträngen. Namnmatchning är inte skiftlägeskänsligt.
  • Konverterar strängen "true" till boolesk true.

Ramverket anropar GetById sedan metoden och skickar in 2 för parametern id och true för parametern dogsOnly .

I föregående exempel är modellbindningsmålen metodparametrar som är enkla typer. Mål kan också vara egenskaper för en komplex typ. När varje egenskap har bundits sker modellverifiering för den egenskapen. Posten för vilka data som är bundna till modellen och eventuella bindnings- eller valideringsfel lagras i ControllerBase.ModelState eller PageModel.ModelState. För att ta reda på om den här processen lyckades kontrollerar appen flaggan ModelState.IsValid .

Targets

Modellbindning försöker hitta värden för följande typer av mål:

  • Parametrar för den kontrollantåtgärdsmetod som en begäran dirigeras till.
  • Parametrar för den Razor sidhanterarmetod som en begäran dirigeras till.
  • Offentliga egenskaper för en kontrollant eller PageModel klass, om de anges av attribut.

[BindProperty]-attribut

Kan tillämpas på en offentlig egenskap hos en kontroller eller PageModel klass för att modellbindning ska rikta sig mot den egenskapen.

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

    // ...
}

[BindProperties]-attribut

Kan tillämpas på en kontroller eller PageModel klass för att ange modellbindning att rikta in sig på alla offentliga egenskaper i klassen.

[BindProperties]
public class CreateModel : PageModel
{
    public Instructor? Instructor { get; set; }

    // ...
}

Modellbindning för HTTP GET-begäranden

Som standard är egenskaperna inte bundna till HTTP GET-begäranden. Vanligtvis är allt du behöver för en GET-begäran en post-ID-parameter. Post-ID:t används för att leta upp objektet i databasen. Därför behöver du inte binda en egenskap som innehåller en instans av modellen. I scenarier där du vill att egenskaper ska bindas till data från GET-begäranden anger du SupportsGet egenskapen till true:

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

Modellbindning av enkla och komplexa typer

Modellbindning använder specifika definitioner för de typer som den fungerar på. En enkel typ konverteras från en enda sträng med hjälp av TypeConverter eller en TryParse metod. En komplex typ konverteras från flera indatavärden. Ramverket avgör skillnaden baserat på förekomsten av en TypeConverter eller TryParse. Vi rekommenderar att du skapar en typkonverterare eller använder TryParse för en string till-konvertering SomeType som inte kräver externa resurser eller flera indata.

Sources

Som standard hämtar modellbindningen data i form av nyckel/värde-par från följande källor i en HTTP-begäran:

  1. Formulärfält
  2. Begärandetexten (för kontrollanter som har attributet [ApiController].)
  3. Ruttdata
  4. Frågesträngsparametrar
  5. Ladda upp filer

För varje målparameter eller egenskap genomsöks källorna i den ordning som anges i föregående lista. Det finns några undantag:

  • Routningsdata och frågesträngsvärden används endast för enkla typer.
  • Uppladdade filer är endast bundna till måltyper som implementerar IFormFile eller IEnumerable<IFormFile>.

Om standardkällan inte är korrekt använder du något av följande attribut för att ange källan:

  • [FromQuery] – Hämtar värden från frågesträngen.
  • [FromRoute] – Hämtar värden från vägdata.
  • [FromForm] – Hämtar värden från publicerade formulärfält.
  • [FromBody] – Hämtar värden från begärandetexten.
  • [FromHeader] – Hämtar värden från HTTP-huvuden.

Följande attribut:

  • Läggs till i modellegenskaper individuellt och inte i modellklassen, som i följande exempel:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Det är valfritt att acceptera ett modellnamnsvärde i konstruktorn. Det här alternativet anges om egenskapsnamnet inte matchar värdet i begäran. Till exempel kan värdet i begäran vara en rubrik med ett bindestreck i namnet, som i följande exempel:

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

[FromBody]-attribut

[FromBody] Använd attributet för en parameter för att fylla i dess egenskaper från brödtexten i en HTTP-begäran. ASP.NET Core-körningen delegerar ansvaret för att läsa brödtexten till en indataformaterare. Indataformaterare beskrivs senare i den här artikeln.

När [FromBody] tillämpas på en komplex typparameter ignoreras alla bindningskällaattribut som tillämpas på dess egenskaper. Följande åtgärd anger till exempel Create att parametern pet är ifylld från brödtexten:

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

Klassen Pet anger att dess Breed egenskap fylls i från en frågesträngsparameter:

public class Pet
{
    public string Name { get; set; } = null!;

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

I föregående exempel:

  • Attributet [FromQuery] ignoreras.
  • Egenskapen Breed fylls inte i från en frågesträngsparameter.

Indataformaterare läser endast brödtexten och förstår inte bindningskällans attribut. Om ett lämpligt värde hittas i brödtexten används det värdet för att fylla i Breed egenskapen.

Gäller [FromBody] inte för fler än en parameter per åtgärdsmetod. När begärandeströmmen har lästs av en indataformaterare är den inte längre tillgänglig för att läsas igen för bindning av andra [FromBody] parametrar.

Ytterligare källor

Källdata tillhandahålls till modellbindningssystemet av värdeprovidrar. Du kan skriva och registrera anpassade värdeprovidrar som hämtar data för modellbindning från andra källor. Du kanske till exempel vill ha data från cookies eller sessionstillstånd. Så här hämtar du data från en ny källa:

  • Skapa en klass som implementerar IValueProvider.
  • Skapa en klass som implementerar IValueProviderFactory.
  • Registrera fabriksklassen i Program.cs.

Exemplet innehåller en värdeprovider och ett fabriksexempel som hämtar värden från cookies. Registrera anpassade värdeproviderfabriker i Program.cs:

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

Föregående kod placerar den anpassade värdeprovidern efter alla inbyggda värdeprovidrar. Om du vill göra den till den första i listan anropar du Insert(0, new CookieValueProviderFactory()) i stället Addför .

Ingen källa för en modells egenskap

Som standard skapas inte ett modelltillståndsfel om inget värde hittas för en modellegenskap. Egenskapen är inställd på null eller ett standardvärde:

  • Nullbara enkla typer är inställda på null.
  • Värdetyper som inte kan vara null är inställda på default(T). En parameter int id är till exempel inställd på 0.
  • För komplexa typer skapar modellbindning en instans med hjälp av standardkonstruktorn, utan att ange egenskaper.
  • Matriser är inställda på Array.Empty<T>(), förutom att byte[] matriser är inställda på null.

Om modelltillståndet ska ogiltigförklaras när inget hittas i formulärfält för en modellegenskap använder du attributet [BindRequired] .

Observera att det här [BindRequired] beteendet gäller för modellbindning från publicerade formulärdata, inte för JSON- eller XML-data i en begärandetext. Brödtextdata för begäran hanteras av indataformaterare.

Typkonverteringsfel

Om en källa hittas men inte kan konverteras till måltypen flaggas modelltillståndet som ogiltigt. Målparametern eller -egenskapen är inställd på null eller ett standardvärde, enligt beskrivningen i föregående avsnitt.

I en API-kontrollant som har [ApiController] attributet resulterar ogiltigt modelltillstånd i ett automatiskt HTTP 400-svar.

Visa sidan igen med ett felmeddelande på en Razor sida.

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

    // ...

    return RedirectToPage("./Index");
}

När sidan spelas upp av föregående kod visas inte ogiltiga indata i formulärfältet. Det beror på att modellegenskapen har angetts till null eller ett standardvärde. Ogiltiga indata visas i ett felmeddelande. Om du vill redigera om felaktiga data i formulärfältet kan du överväga att göra modellegenskapen till en sträng och göra datakonverteringen manuellt.

Samma strategi rekommenderas om du inte vill att typkonverteringsfel ska resultera i modelltillståndsfel. I så fall gör du modellegenskapen till en sträng.

Enkla typer

Se Modellbindning av enkla och komplexa typer för förklaring av enkla och komplexa typer.

De enkla typer som modellbindningen kan konvertera källsträngar till innehåller följande:

Bind med IParsable<T>.TryParse

API:et IParsable<TSelf>.TryParse stöder parametervärden för bindningskontrollantåtgärd:

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

Följande DateRange-klass implementerar IParsable<TSelf> för att binda ett datumintervall.

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

Föregående kod:

  • Konverterar en sträng som representerar två datum till ett DateRange objekt
  • Modellbindningen använder IParsable<TSelf>.TryParse metoden för att binda DateRange.

Följande kontrollantåtgärd använder DateRange klassen för att binda ett datumintervall:

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

Följande Locale klass implementerar IParsable<TSelf> för att stödja bindning till 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;
        }
    }
}

Följande kontrollantåtgärd använder Locale klassen för att binda en CultureInfo sträng:

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

Följande kontrollantåtgärd använder klasserna DateRange och Locale för att binda ett datumintervall med 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);
}

API-exempelappen på GitHub visar föregående exempel för en API-kontrollant.

Bind med TryParse

API:et TryParse stöder parametervärden för bindningskontrollantåtgärd:

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

IParsable<T>.TryParse är den rekommenderade metoden för parameterbindning eftersom den till skillnad från TryParse, inte beror på reflektion.

Följande DateRangeTP klass implementerar TryParse:

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

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

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

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

        result = new DateRangeTP(range[0], range[1]);
        return true;
    }
}

Följande kontrollantåtgärd använder DateRangeTP klassen för att binda ett datumintervall:

// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

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

    return View("Index", weatherForecasts);
}

Komplexa typer

En komplex typ måste ha en offentlig standardkonstruktor och offentliga skrivbara egenskaper för bindning. När modellbindningen inträffar instansieras klassen med hjälp av den offentliga standardkonstruktorn.

För varje egenskap av den komplexa typen söker modellbindningen igenom källorna efter namnmönstretprefix.property_name. Om inget hittas letar den efter bara property_name utan prefixet. Beslutet att använda prefixet fattas inte per egenskap. Med till exempel en fråga som innehåller ?Instructor.Id=100&Name=foo, bunden till -metoden OnGet(Instructor instructor), innehåller det resulterande objektet av typen Instructor :

  • Id inställt på 100.
  • Name inställt på null. Modellbindningen förväntar sig Instructor.Name eftersom Instructor.Id användes i föregående frågeparameter.

Note

Dokumentationslänkar till .NET-referenskällan läser vanligtvis in lagringsplatsens standardgren, vilket representerar den aktuella utvecklingen för nästa version av .NET. Om du vill välja en tagg för en specifik version använder du listrutan Välj bland grenar eller taggar. Mer information finns i Så här väljer du en versionstagg för ASP.NET Core-källkod (dotnet/AspNetCore.Docs #26205).

För bindning till en parameter är prefixet parameternamnet. För bindning till en PageModel offentlig egenskap är prefixet namnet på den offentliga egenskapen. Vissa attribut har en Prefix egenskap som gör att du kan åsidosätta standardanvändningen av parametern eller egenskapsnamnet.

Anta till exempel att den komplexa typen är följande Instructor klass:

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

Prefix = parameternamn

Om modellen som ska bindas är en parameter med namnet instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Modellbindningen börjar med att titta igenom källorna för nyckeln instructorToUpdate.ID. Om det inte hittas söker den efter ID utan ett prefix.

Prefix = egenskapsnamn

Om modellen som ska bindas är en egenskap med namnet Instructor på kontrollanten eller PageModel klassen:

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

Modellbindningen börjar med att titta igenom källorna för nyckeln Instructor.ID. Om det inte hittas söker den efter ID utan ett prefix.

Anpassat prefix

Om modellen som ska bindas är en parameter med namnet instructorToUpdate och ett Bind attribut anger Instructor som prefix:

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

Modellbindningen börjar med att titta igenom källorna för nyckeln Instructor.ID. Om det inte hittas söker den efter ID utan ett prefix.

Attribut för komplexa typmål

Flera inbyggda attribut är tillgängliga för att styra modellbindning av komplexa typer:

Warning

Dessa attribut påverkar modellbindningen när publicerade formulärdata är källan till värden. De påverkar inte indataformaterare, som bearbetar publicerade JSON- och XML-begärandeorgan. Indataformaterare beskrivs senare i den här artikeln.

Attributet [Bind]

Kan tillämpas på en klass eller en metodparameter. Anger vilka egenskaper för en modell som ska ingå i modellbindningen. [Bind] påverkar inte indataformaterare.

I följande exempel är endast de angivna egenskaperna för Instructor modellen bundna när någon hanterare eller åtgärdsmetod anropas:

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

I följande exempel är endast de angivna egenskaperna för Instructor modellen bundna när OnPost metoden anropas:

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

Attributet [Bind] kan användas för att skydda mot överpublicering i skapa scenarier. Det fungerar inte bra i redigeringsscenarier eftersom exkluderade egenskaper är inställda på null eller ett standardvärde i stället för att lämnas oförändrade. För skydd mot överpublicering rekommenderas visningsmodeller i stället för attributet [Bind] . Mer information finns i Säkerhetsanteckning om överpublicering.

[ModelBinder]-attribut

ModelBinderAttribute kan tillämpas på typer, egenskaper eller parametrar. Det gör det möjligt att ange vilken typ av modellbindning som används för att binda den specifika instansen eller typen. Till exempel:

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

Attributet [ModelBinder] kan också användas för att ändra namnet på en egenskap eller parameter när det är modellbundet:

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

    // ...
}

[BindRequired]-attribut

Orsakar modellbindning för att lägga till ett modelltillståndsfel om bindningen inte kan ske för en modells egenskap. Här är ett exempel:

public class InstructorBindRequired
{
    // ...

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

Se även diskussionen om attributet [Required] i Modellverifiering.

[BindNever]-attribut

Kan tillämpas på en egenskap eller en typ. Förhindrar modellbindning från att bestämma en modells egenskap. När det tillämpas på en typ exkluderar modellbindningssystemet alla egenskaper som typen definierar. Här är ett exempel:

public class InstructorBindNever
{
    [BindNever]
    public int Id { get; set; }

    // ...
}

Collections

För mål som är samlingar av enkla typer letar modellbindningen efter matchningar till parameter_name eller property_name. Om ingen matchning hittas letar den efter ett av de format som stöds utan prefixet. Till exempel:

  • Anta att parametern som ska bindas är en matris med namnet selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Formulär- eller frågesträngsdata kan vara i något av följande format:

    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
    

    Undvik att binda en parameter eller en egenskap med namnet index eller Index om den ligger i anslutning till ett samlingsvärde. Modellbindning försöker använda index som index för samlingen, vilket kan leda till felaktig bindning. Tänk till exempel på följande åtgärd:

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

    I föregående kod binder frågesträngsparametern index till index metodparametern och används även för att binda produktsamlingen. Om du byter namn på parametern index eller använder ett modellbindningsattribut för att konfigurera bindning undviks det här problemet:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Följande format stöds endast i formulärdata:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • För alla föregående exempelformat skickar modellbindningen en matris med två objekt till parametern selectedCourses :

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

    Dataformat som använder nedsänkta siffror (... [0] ... [1] ...) måste se till att de numreras sekventiellt med början på noll. Om det finns några luckor i siffernummer ignoreras alla objekt efter luckan. Om de till exempel är 0 och 2 i stället för 0 och 1 ignoreras det andra objektet.

Dictionaries

För Dictionary mål söker modellbindning efter matchningar till parameter_name eller property_name. Om ingen matchning hittas letar den efter ett av de format som stöds utan prefixet. Till exempel:

  • Anta att målparametern heter Dictionary<int, string>selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Publicerade formulär- eller frågesträngsdata kan se ut som något av följande exempel:

    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
    
  • För alla föregående exempelformat skickar modellbindningen en ordlista med två objekt till parametern selectedCourses :

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

Konstruktorbindning och rekordtyper

Modellbindning kräver att komplexa typer har en parameterlös konstruktor. Både System.Text.Json och Newtonsoft.Json baserade indataformaterare stöder deserialisering av klasser som inte har en parameterlös konstruktor.

Posttyper är ett bra sätt att kortfattat representera data på nätverket. ASP.NET Core stöder modellbindning och validering av posttyper med en enda 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

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

Vid validering av posttyper söker körningsmiljön efter bindnings- och valideringsmetadata specifikt på parametrar i stället för på egenskaper.

Ramverket tillåter bindning till och validering av posttyper:

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

För att det föregående ska fungera, måste typen:

  • Vara en posttyp.
  • Ha exakt en offentlig konstruktor.
  • Innehåller parametrar som har en egenskap med samma namn och typ. Namnen får inte skilja sig åt från fall till fall.

POCOs utan parameterfria konstruktorer

POCO:er som inte har parameterlösa konstruktorer kan inte bindas.

Följande kod resulterar i ett undantag som säger att typen måste ha en parameterlös konstruktor:

public class Person(string Name)

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

Registrera typer med manuellt skapade konstruktorer

Posttyper med manuellt skapade konstruktorer som ser ut som primära konstruktorer fungerar

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

Posttyper, validering och bindningsmetadata

För posttyper används validering och bindning av metadata för parametrar. Metadata för egenskaper ignoreras

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

Validering och metadata

Validering använder metadata för parametern men använder egenskapen för att läsa värdet. I vanliga fall med primära konstruktorer skulle de två vara identiska. Det finns dock sätt att besegra det:

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 uppdaterar inte parametrar för en posttyp

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

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

I det här fallet försöker MVC inte binda Name igen. Kan dock Age uppdateras

Globaliseringsbeteende för modellbindningsvägsdata och frågesträngar

ASP.NET Core leverantör för routvärden och leverantör för frågesträngsvärden:

  • Behandla värden som invariant kultur.
  • Förvänta dig att URL:er är kulturinvarianta.

Däremot genomgår värden som kommer från formulärdata en kulturkänslig konvertering. Detta är avsiktligt så att URL:er kan delas oberoende av språkversion.

Så här gör du så att ASP.NET Core-routningsvärdeprovidern och frågesträngsvärdeprovidern genomgår en kulturkänslig konvertering:

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

Särskilda datatyper

Det finns vissa särskilda datatyper som modellbindning kan hantera.

IFormFile och IFormFileCollection

En uppladdad fil som ingår i HTTP-begäran. IEnumerable<IFormFile> stöds också för flera filer.

CancellationToken

Åtgärder kan valfritt binda en CancellationToken som en parameter. Detta binder RequestAborted som signalerar när anslutningen som ligger till grund för HTTP-begäran avbryts. Åtgärder kan använda den här parametern för att avbryta långvariga asynkrona åtgärder som körs som en del av kontrollantåtgärderna.

FormCollection

Används för att hämta alla värden från publicerade formulärdata.

Indataformaterare

Data i begärandetexten kan vara i JSON, XML eller något annat format. För att parsa dessa data använder modellbindningen en indataformaterare som är konfigurerad för att hantera en viss innehållstyp. Som standard innehåller ASP.NET Core JSON-baserade indataformaterare för hantering av JSON-data. Du kan lägga till andra formaterare för andra innehållstyper.

ASP.NET Core väljer indataformaterare baserat på attributet Förbrukar . Om det inte finns något attribut används rubriken Innehållstyp.

Så här använder du de inbyggda XML-indataformatrarna:

Anpassa modellbindning med indataformaterare

En indataformaterare tar fullt ansvar för att läsa data från begärandetexten. Om du vill anpassa den här processen konfigurerar du de API:er som används av indataformaterare. I det här avsnittet beskrivs hur du anpassar den System.Text.Json-baserade indataformateren för att förstå en anpassad typ med namnet ObjectId.

Tänk på följande modell, som innehåller en anpassad ObjectId egenskap:

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

Om du vill anpassa modellbindningsprocessen när du använder System.Text.Jsonskapar du en klass som härletts från JsonConverter<T>:

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

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

Använd attributet JsonConverterAttribute för typen för att använda en anpassad konverterare. I följande exempel konfigureras ObjectId-typen med ObjectIdConverter som sin egen anpassade konverterare.

[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);

Mer information finns i Skriva anpassade konverterare.

Undanta angivna typer från modellbindning

Modellens bindnings- och valideringssystems beteende styrs av ModelMetadata. Du kan anpassa ModelMetadata genom att lägga till en informationsprovider i MvcOptions.ModelMetadataDetailsProviders. Inbyggda informationsprovidrar är tillgängliga för inaktivering av modellbindning eller validering för angivna typer.

Om du vill inaktivera modellbindning för alla modeller av en angiven typ lägger du till en ExcludeBindingMetadataProvider i Program.cs. Om du till exempel vill inaktivera modellbindning för alla modeller av typen System.Version:

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

Om du vill inaktivera validering av egenskaper för en angiven typ lägger du till en SuppressChildValidationMetadataProvider i Program.cs. Om du till exempel vill inaktivera validering av egenskaper av typen System.Guid:

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

Anpassade modellbindare

Du kan utöka modellbindningen genom att skriva en anpassad modellbindning och använda [ModelBinder] attributet för att välja den för ett visst mål. Läs mer om bindning av anpassade modeller.

Manuell modellbindning

Modellbindning kan anropas manuellt genom att använda metoden TryUpdateModelAsync. Metoden definieras för båda ControllerBase klasserna och PageModel . Med metodöverlagringar kan du ange prefixet och värdeprovidern som ska användas. Metoden returnerar false om modellbindningen misslyckas. Här är ett exempel:

if (await TryUpdateModelAsync(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync använder värdeleverantörer för att hämta data från formulärets data, frågesträngen och routningsdata. TryUpdateModelAsync är vanligtvis:

  • Används med Razor Pages- och MVC-appar med hjälp av kontrollanter och vyer för att förhindra överpublicering.
  • Används inte med ett webb-API förutom när det hämtas från formulärdata, frågesträngar och routdata. Webb-API-slutpunkter som använder JSON använder indataformatrar för att deserialisera begärandetexten till ett objekt.

Mer information finns i TryUpdateModelAsync.

[FromServices]-attribut

Det här attributets namn följer mönstret för modellbindningsattribut som anger en datakälla. Men det handlar inte om att binda data från en värdeprovider. Den hämtar en instans av en typ från containern för beroendeinmatning . Syftet är att tillhandahålla ett alternativ till konstruktorinmatning för när du behöver en tjänst endast om en viss metod anropas.

Om en instans av typen inte är registrerad i containern för beroendeinmatning genererar appen ett undantag när parametern försöker bindas. Om du vill göra parametern valfri använder du någon av följande metoder:

  • Gör parametern nullbar.
  • Ange ett standardvärde för parametern.

För null-parametrar kontrollerar du att parametern inte null är innan du kommer åt den.

Ytterligare resurser

Den här artikeln förklarar vad modellbindning är, hur den fungerar och hur du anpassar dess beteende.

Vad är modellbindning

Kontrollanter och Razor sidor fungerar med data som kommer från HTTP-begäranden. Routningsdata kan till exempel ge en postnyckel, och publicerade formulärfält kan ge värden för modellens egenskaper. Att skriva kod för att hämta vart och ett av dessa värden och konvertera dem från strängar till .NET-typer skulle vara omständligt och felbenäget. Modellbindning automatiserar den här processen. Modellbindningssystemet:

  • Hämtar data från olika källor, till exempel routningsdata, formulärfält och frågesträngar.
  • Tillhandahåller data till kontroller och Razor sidor i metodparametrar och publika egenskaper.
  • Konverterar strängdata till .NET-typer.
  • Uppdaterar egenskaper för komplexa typer.

Example

Anta att du har följande åtgärdsmetod:

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

Och appen får en begäran med den här URL:en:

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

Modellbindningen går igenom följande steg när routningssystemet har valt åtgärdsmetoden:

  • Hittar den första parametern i GetById, ett heltal med namnet id.
  • Söker igenom de tillgängliga källorna i HTTP-begäran och hittar id = "2" i routningsdata.
  • Konverterar strängen "2" till heltal 2.
  • Hittar nästa parameter i GetById, ett booleskt värde med namnet dogsOnly.
  • Söker igenom källorna och hittar "DogsOnly=true" i frågesträngen. Namnmatchning är inte skiftlägeskänsligt.
  • Konverterar strängen "true" till boolesk true.

Ramverket anropar GetById sedan metoden och skickar in 2 för parametern id och true för parametern dogsOnly .

I föregående exempel är modellbindningsmålen metodparametrar som är enkla typer. Mål kan också vara egenskaper för en komplex typ. När varje egenskap har bundits sker modellverifiering för den egenskapen. Posten för vilka data som är bundna till modellen och eventuella bindnings- eller valideringsfel lagras i ControllerBase.ModelState eller PageModel.ModelState. För att ta reda på om den här processen lyckades kontrollerar appen flaggan ModelState.IsValid .

Targets

Modellbindning försöker hitta värden för följande typer av mål:

  • Parametrar för den kontrollantåtgärdsmetod som en begäran dirigeras till.
  • Parametrar för den Razor sidhanterarmetod som en begäran dirigeras till.
  • Offentliga egenskaper för en kontrollant eller PageModel klass, om de anges av attribut.

[BindProperty]-attribut

Kan tillämpas på en offentlig egenskap hos en kontroller eller PageModel klass för att modellbindning ska rikta sig mot den egenskapen.

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

    // ...
}

[BindProperties]-attribut

Kan tillämpas på en kontroller eller PageModel klass för att ange modellbindning att rikta in sig på alla offentliga egenskaper i klassen.

[BindProperties]
public class CreateModel : PageModel
{
    public Instructor? Instructor { get; set; }

    // ...
}

Modellbindning för HTTP GET-begäranden

Som standard är egenskaperna inte bundna till HTTP GET-begäranden. Vanligtvis är allt du behöver för en GET-begäran en post-ID-parameter. Post-ID:t används för att leta upp objektet i databasen. Därför behöver du inte binda en egenskap som innehåller en instans av modellen. I scenarier där du vill att egenskaper ska bindas till data från GET-begäranden anger du SupportsGet egenskapen till true:

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

Sources

Som standard hämtar modellbindningen data i form av nyckel/värde-par från följande källor i en HTTP-begäran:

  1. Formulärfält
  2. Begärandetexten (för kontrollanter som har attributet [ApiController].)
  3. Ruttdata
  4. Frågesträngsparametrar
  5. Ladda upp filer

För varje målparameter eller egenskap genomsöks källorna i den ordning som anges i föregående lista. Det finns några undantag:

  • Routningsdata och frågesträngsvärden används endast för enkla typer.
  • Uppladdade filer är endast bundna till måltyper som implementerar IFormFile eller IEnumerable<IFormFile>.

Om standardkällan inte är korrekt använder du något av följande attribut för att ange källan:

  • [FromQuery] – Hämtar värden från frågesträngen.
  • [FromRoute] – Hämtar värden från vägdata.
  • [FromForm] – Hämtar värden från publicerade formulärfält.
  • [FromBody] – Hämtar värden från begärandetexten.
  • [FromHeader] – Hämtar värden från HTTP-huvuden.

Följande attribut:

  • Läggs till i modellegenskaper individuellt och inte i modellklassen, som i följande exempel:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Det är valfritt att acceptera ett modellnamnsvärde i konstruktorn. Det här alternativet anges om egenskapsnamnet inte matchar värdet i begäran. Till exempel kan värdet i begäran vara en rubrik med ett bindestreck i namnet, som i följande exempel:

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

[FromBody]-attribut

[FromBody] Använd attributet för en parameter för att fylla i dess egenskaper från brödtexten i en HTTP-begäran. ASP.NET Core-körningen delegerar ansvaret för att läsa brödtexten till en indataformaterare. Indataformaterare beskrivs senare i den här artikeln.

När [FromBody] tillämpas på en komplex typparameter ignoreras alla bindningskällaattribut som tillämpas på dess egenskaper. Följande åtgärd anger till exempel Create att parametern pet är ifylld från brödtexten:

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

Klassen Pet anger att dess Breed egenskap fylls i från en frågesträngsparameter:

public class Pet
{
    public string Name { get; set; } = null!;

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

I föregående exempel:

  • Attributet [FromQuery] ignoreras.
  • Egenskapen Breed fylls inte i från en frågesträngsparameter.

Indataformaterare läser endast brödtexten och förstår inte bindningskällans attribut. Om ett lämpligt värde hittas i brödtexten används det värdet för att fylla i Breed egenskapen.

Gäller [FromBody] inte för fler än en parameter per åtgärdsmetod. När begärandeströmmen har lästs av en indataformaterare är den inte längre tillgänglig för att läsas igen för bindning av andra [FromBody] parametrar.

Ytterligare källor

Källdata tillhandahålls till modellbindningssystemet av värdeprovidrar. Du kan skriva och registrera anpassade värdeprovidrar som hämtar data för modellbindning från andra källor. Du kanske till exempel vill ha data från cookies eller sessionstillstånd. Så här hämtar du data från en ny källa:

  • Skapa en klass som implementerar IValueProvider.
  • Skapa en klass som implementerar IValueProviderFactory.
  • Registrera fabriksklassen i Program.cs.

Exemplet innehåller en värdeprovider och ett fabriksexempel som hämtar värden från cookies. Registrera anpassade värdeproviderfabriker i Program.cs:

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

Föregående kod placerar den anpassade värdeprovidern efter alla inbyggda värdeprovidrar. Om du vill göra den till den första i listan anropar du Insert(0, new CookieValueProviderFactory()) i stället Addför .

Ingen källa för en modells egenskap

Som standard skapas inte ett modelltillståndsfel om inget värde hittas för en modellegenskap. Egenskapen är inställd på null eller ett standardvärde:

  • Enkla typer som kan ogiltigförklaras är inställda på null.
  • Värdetyper som inte kan vara null är inställda på default(T). En parameter int id är till exempel inställd på 0.
  • För komplexa typer skapar modellbindning en instans med hjälp av standardkonstruktorn, utan att ange egenskaper.
  • Matriser är inställda på Array.Empty<T>(), förutom att byte[] matriser är inställda på null.

Om modelltillståndet ska ogiltigförklaras när inget hittas i formulärfält för en modellegenskap använder du attributet [BindRequired] .

Observera att det här [BindRequired] beteendet gäller för modellbindning från publicerade formulärdata, inte för JSON- eller XML-data i en begärandetext. Brödtextdata för begäran hanteras av indataformaterare.

Typkonverteringsfel

Om en källa hittas men inte kan konverteras till måltypen flaggas modelltillståndet som ogiltigt. Målparametern eller -egenskapen är inställd på null eller ett standardvärde, enligt beskrivningen i föregående avsnitt.

I en API-kontrollant som har [ApiController] attributet resulterar ogiltigt modelltillstånd i ett automatiskt HTTP 400-svar.

Visa sidan igen med ett felmeddelande på en Razor sida.

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

    // ...

    return RedirectToPage("./Index");
}

När sidan spelas upp av föregående kod visas inte ogiltiga indata i formulärfältet. Det beror på att modellegenskapen har angetts till null eller ett standardvärde. Ogiltiga indata visas i ett felmeddelande. Om du vill redigera om felaktiga data i formulärfältet kan du överväga att göra modellegenskapen till en sträng och göra datakonverteringen manuellt.

Samma strategi rekommenderas om du inte vill att typkonverteringsfel ska resultera i modelltillståndsfel. I så fall gör du modellegenskapen till en sträng.

Enkla typer

De enkla typer som modellbindningen kan konvertera källsträngar till innehåller följande:

Komplexa typer

En komplex typ måste ha en offentlig standardkonstruktor och offentliga skrivbara egenskaper för bindning. När modellbindningen inträffar instansieras klassen med hjälp av den offentliga standardkonstruktorn.

För varje egenskap av den komplexa typen söker modellbindningen igenom källorna efter namnmönstretprefix.property_name. Om inget hittas letar den efter bara property_name utan prefixet. Beslutet att använda prefixet fattas inte per egenskap. Med till exempel en fråga som innehåller ?Instructor.Id=100&Name=foo, bunden till -metoden OnGet(Instructor instructor), innehåller det resulterande objektet av typen Instructor :

  • Id inställt på 100.
  • Name inställt på null. Modellbindningen förväntar sig Instructor.Name eftersom Instructor.Id användes i föregående frågeparameter.

Note

Dokumentationslänkar till .NET-referenskällan läser vanligtvis in lagringsplatsens standardgren, vilket representerar den aktuella utvecklingen för nästa version av .NET. Om du vill välja en tagg för en specifik version använder du listrutan Välj bland grenar eller taggar. Mer information finns i Så här väljer du en versionstagg för ASP.NET Core-källkod (dotnet/AspNetCore.Docs #26205).

För bindning till en parameter är prefixet parameternamnet. För bindning till en PageModel offentlig egenskap är prefixet namnet på den offentliga egenskapen. Vissa attribut har en Prefix egenskap som gör att du kan åsidosätta standardanvändningen av parametern eller egenskapsnamnet.

Anta till exempel att den komplexa typen är följande Instructor klass:

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

Prefix = parameternamn

Om modellen som ska bindas är en parameter med namnet instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Modellbindningen börjar med att titta igenom källorna för nyckeln instructorToUpdate.ID. Om det inte hittas söker den efter ID utan ett prefix.

Prefix = egenskapsnamn

Om modellen som ska bindas är en egenskap med namnet Instructor på kontrollanten eller PageModel klassen:

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

Modellbindningen börjar med att titta igenom källorna för nyckeln Instructor.ID. Om det inte hittas söker den efter ID utan ett prefix.

Anpassat prefix

Om modellen som ska bindas är en parameter med namnet instructorToUpdate och ett Bind attribut anger Instructor som prefix:

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

Modellbindningen börjar med att titta igenom källorna för nyckeln Instructor.ID. Om det inte hittas söker den efter ID utan ett prefix.

Attribut för komplexa typmål

Flera inbyggda attribut är tillgängliga för att styra modellbindning av komplexa typer:

Warning

Dessa attribut påverkar modellbindningen när publicerade formulärdata är källan till värden. De påverkar inte indataformaterare, som bearbetar publicerade JSON- och XML-begärandeorgan. Indataformaterare beskrivs senare i den här artikeln.

Attributet [Bind]

Kan tillämpas på en klass eller en metodparameter. Anger vilka egenskaper för en modell som ska ingå i modellbindningen. [Bind] påverkar inte indataformaterare.

I följande exempel är endast de angivna egenskaperna för Instructor modellen bundna när någon hanterare eller åtgärdsmetod anropas:

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

I följande exempel är endast de angivna egenskaperna för Instructor modellen bundna när OnPost metoden anropas:

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

Attributet [Bind] kan användas för att skydda mot överpublicering i skapa scenarier. Det fungerar inte bra i redigeringsscenarier eftersom exkluderade egenskaper är inställda på null eller ett standardvärde i stället för att lämnas oförändrade. För skydd mot överpublicering rekommenderas visningsmodeller i stället för attributet [Bind] . Mer information finns i Säkerhetsanteckning om överpublicering.

[ModelBinder]-attribut

ModelBinderAttribute kan tillämpas på typer, egenskaper eller parametrar. Det gör det möjligt att ange vilken typ av modellbindning som används för att binda den specifika instansen eller typen. Till exempel:

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

Attributet [ModelBinder] kan också användas för att ändra namnet på en egenskap eller parameter när det är modellbundet:

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

    // ...
}

[BindRequired]-attribut

Orsakar modellbindning för att lägga till ett modelltillståndsfel om bindningen inte kan ske för en modells egenskap. Här är ett exempel:

public class InstructorBindRequired
{
    // ...

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

Se även diskussionen om attributet [Required] i Modellverifiering.

[BindNever]-attribut

Kan tillämpas på en egenskap eller en typ. Förhindrar modellbindning från att bestämma en modells egenskap. När det tillämpas på en typ exkluderar modellbindningssystemet alla egenskaper som typen definierar. Här är ett exempel:

public class InstructorBindNever
{
    [BindNever]
    public int Id { get; set; }

    // ...
}

Collections

För mål som är samlingar av enkla typer letar modellbindningen efter matchningar till parameter_name eller property_name. Om ingen matchning hittas letar den efter ett av de format som stöds utan prefixet. Till exempel:

  • Anta att parametern som ska bindas är en matris med namnet selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Formulär- eller frågesträngsdata kan vara i något av följande format:

    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
    

    Undvik att binda en parameter eller en egenskap med namnet index eller Index om den ligger i anslutning till ett samlingsvärde. Modellbindning försöker använda index som index för samlingen, vilket kan leda till felaktig bindning. Tänk till exempel på följande åtgärd:

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

    I föregående kod binder frågesträngsparametern index till index metodparametern och används även för att binda produktsamlingen. Om du byter namn på parametern index eller använder ett modellbindningsattribut för att konfigurera bindning undviks det här problemet:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Följande format stöds endast i formulärdata:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • För alla föregående exempelformat skickar modellbindningen en matris med två objekt till parametern selectedCourses :

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

    Dataformat som använder nedsänkta siffror (... [0] ... [1] ...) måste se till att de numreras sekventiellt med början på noll. Om det finns några luckor i siffernummer ignoreras alla objekt efter luckan. Om de till exempel är 0 och 2 i stället för 0 och 1 ignoreras det andra objektet.

Dictionaries

För Dictionary mål söker modellbindning efter matchningar till parameter_name eller property_name. Om ingen matchning hittas letar den efter ett av de format som stöds utan prefixet. Till exempel:

  • Anta att målparametern heter Dictionary<int, string>selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Publicerade formulär- eller frågesträngsdata kan se ut som något av följande exempel:

    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
    
  • För alla föregående exempelformat skickar modellbindningen en ordlista med två objekt till parametern selectedCourses :

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

Konstruktorbindning och rekordtyper

Modellbindning kräver att komplexa typer har en parameterlös konstruktor. Både System.Text.Json och Newtonsoft.Json baserade indataformaterare stöder deserialisering av klasser som inte har en parameterlös konstruktor.

Posttyper är ett bra sätt att kortfattat representera data på nätverket. ASP.NET Core stöder modellbindning och validering av posttyper med en enda 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

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

Vid validering av posttyper söker körningsmiljön efter bindnings- och valideringsmetadata specifikt på parametrar i stället för på egenskaper.

Ramverket tillåter bindning till och validering av posttyper:

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

För att det föregående ska fungera, måste typen:

  • Vara en posttyp.
  • Ha exakt en offentlig konstruktor.
  • Innehåller parametrar som har en egenskap med samma namn och typ. Namnen får inte skilja sig åt från fall till fall.

POCOs utan parameterfria konstruktorer

POCO:er som inte har parameterlösa konstruktorer kan inte bindas.

Följande kod resulterar i ett undantag som säger att typen måste ha en parameterlös konstruktor:

public class Person(string Name)

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

Registrera typer med manuellt skapade konstruktorer

Posttyper med manuellt skapade konstruktorer som ser ut som primära konstruktorer fungerar

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

Posttyper, validering och bindningsmetadata

För posttyper används validering och bindning av metadata för parametrar. Metadata för egenskaper ignoreras

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

Validering och metadata

Validering använder metadata för parametern men använder egenskapen för att läsa värdet. I vanliga fall med primära konstruktorer skulle de två vara identiska. Det finns dock sätt att besegra det:

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 uppdaterar inte parametrar för en posttyp

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

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

I det här fallet försöker MVC inte binda Name igen. Kan dock Age uppdateras

Globaliseringsbeteende för modellbindningsvägsdata och frågesträngar

ASP.NET Core leverantör för routvärden och leverantör för frågesträngsvärden:

  • Behandla värden som invariant kultur.
  • Förvänta dig att URL:er är kulturinvarianta.

Däremot genomgår värden som kommer från formulärdata en kulturkänslig konvertering. Detta är avsiktligt så att URL:er kan delas oberoende av språkversion.

Så här gör du så att ASP.NET Core-routningsvärdeprovidern och frågesträngsvärdeprovidern genomgår en kulturkänslig konvertering:

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

Särskilda datatyper

Det finns vissa särskilda datatyper som modellbindning kan hantera.

IFormFile och IFormFileCollection

En uppladdad fil som ingår i HTTP-begäran. IEnumerable<IFormFile> stöds också för flera filer.

CancellationToken

Åtgärder kan valfritt binda en CancellationToken som en parameter. Detta binder RequestAborted som signalerar när anslutningen som ligger till grund för HTTP-begäran avbryts. Åtgärder kan använda den här parametern för att avbryta långvariga asynkrona åtgärder som körs som en del av kontrollantåtgärderna.

FormCollection

Används för att hämta alla värden från publicerade formulärdata.

Indataformaterare

Data i begärandetexten kan vara i JSON, XML eller något annat format. För att parsa dessa data använder modellbindningen en indataformaterare som är konfigurerad för att hantera en viss innehållstyp. Som standard innehåller ASP.NET Core JSON-baserade indataformaterare för hantering av JSON-data. Du kan lägga till andra formaterare för andra innehållstyper.

ASP.NET Core väljer indataformaterare baserat på attributet Förbrukar . Om det inte finns något attribut används rubriken Innehållstyp.

Så här använder du de inbyggda XML-indataformatrarna:

Anpassa modellbindning med indataformaterare

En indataformaterare tar fullt ansvar för att läsa data från begärandetexten. Om du vill anpassa den här processen konfigurerar du de API:er som används av indataformaterare. I det här avsnittet beskrivs hur du anpassar den System.Text.Json-baserade indataformateren för att förstå en anpassad typ med namnet ObjectId.

Tänk på följande modell, som innehåller en anpassad ObjectId egenskap:

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

Om du vill anpassa modellbindningsprocessen när du använder System.Text.Jsonskapar du en klass som härletts från JsonConverter<T>:

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

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

Använd attributet JsonConverterAttribute för typen för att använda en anpassad konverterare. I följande exempel konfigureras ObjectId-typen med ObjectIdConverter som sin egen anpassade konverterare.

[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);

Mer information finns i Skriva anpassade konverterare.

Undanta angivna typer från modellbindning

Modellens bindnings- och valideringssystems beteende styrs av ModelMetadata. Du kan anpassa ModelMetadata genom att lägga till en informationsprovider i MvcOptions.ModelMetadataDetailsProviders. Inbyggda informationsprovidrar är tillgängliga för inaktivering av modellbindning eller validering för angivna typer.

Om du vill inaktivera modellbindning för alla modeller av en angiven typ lägger du till en ExcludeBindingMetadataProvider i Program.cs. Om du till exempel vill inaktivera modellbindning för alla modeller av typen System.Version:

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

Om du vill inaktivera validering av egenskaper för en angiven typ lägger du till en SuppressChildValidationMetadataProvider i Program.cs. Om du till exempel vill inaktivera validering av egenskaper av typen System.Guid:

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

Anpassade modellbindare

Du kan utöka modellbindningen genom att skriva en anpassad modellbindning och använda [ModelBinder] attributet för att välja den för ett visst mål. Läs mer om bindning av anpassade modeller.

Manuell modellbindning

Modellbindning kan anropas manuellt genom att använda metoden TryUpdateModelAsync. Metoden definieras för båda ControllerBase klasserna och PageModel . Med metodöverlagringar kan du ange prefixet och värdeprovidern som ska användas. Metoden returnerar false om modellbindningen misslyckas. Här är ett exempel:

if (await TryUpdateModelAsync(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync använder värdeleverantörer för att hämta data från formulärets data, frågesträngen och routningsdata. TryUpdateModelAsync är vanligtvis:

  • Används med Razor Pages- och MVC-appar med hjälp av kontrollanter och vyer för att förhindra överpublicering.
  • Används inte med ett webb-API förutom när det hämtas från formulärdata, frågesträngar och routdata. Webb-API-slutpunkter som använder JSON använder indataformatrar för att deserialisera begärandetexten till ett objekt.

Mer information finns i TryUpdateModelAsync.

[FromServices]-attribut

Det här attributets namn följer mönstret för modellbindningsattribut som anger en datakälla. Men det handlar inte om att binda data från en värdeprovider. Den hämtar en instans av en typ från containern för beroendeinmatning . Syftet är att tillhandahålla ett alternativ till konstruktorinmatning för när du behöver en tjänst endast om en viss metod anropas.

Om en instans av typen inte är registrerad i containern för beroendeinmatning genererar appen ett undantag när parametern försöker bindas. Om du vill göra parametern valfri använder du någon av följande metoder:

  • Gör parametern nullbar.
  • Ange ett standardvärde för parametern.

För null-parametrar kontrollerar du att parametern inte null är innan du kommer åt den.

Ytterligare resurser

Den här artikeln förklarar vad modellbindning är, hur den fungerar och hur du anpassar dess beteende.

Visa eller ladda ned exempelkod (hur du laddar ned).

Vad är modellbindning

Kontrollanter och Razor sidor fungerar med data som kommer från HTTP-begäranden. Routningsdata kan till exempel ge en postnyckel, och publicerade formulärfält kan ge värden för modellens egenskaper. Att skriva kod för att hämta vart och ett av dessa värden och konvertera dem från strängar till .NET-typer skulle vara omständligt och felbenäget. Modellbindning automatiserar den här processen. Modellbindningssystemet:

  • Hämtar data från olika källor, till exempel routningsdata, formulärfält och frågesträngar.
  • Tillhandahåller data till kontroller och Razor sidor i metodparametrar och publika egenskaper.
  • Konverterar strängdata till .NET-typer.
  • Uppdaterar egenskaper för komplexa typer.

Example

Anta att du har följande åtgärdsmetod:

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

Och appen får en begäran med den här URL:en:

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

Modellbindningen går igenom följande steg när routningssystemet har valt åtgärdsmetoden:

  • Hittar den första parametern i GetById, ett heltal med namnet id.
  • Söker igenom de tillgängliga källorna i HTTP-begäran och hittar id = "2" i routningsdata.
  • Konverterar strängen "2" till heltal 2.
  • Hittar nästa parameter i GetById, ett booleskt värde med namnet dogsOnly.
  • Söker igenom källorna och hittar "DogsOnly=true" i frågesträngen. Namnmatchning är inte skiftlägeskänsligt.
  • Konverterar strängen "true" till boolesk true.

Ramverket anropar GetById sedan metoden och skickar in 2 för parametern id och true för parametern dogsOnly .

I föregående exempel är modellbindningsmålen metodparametrar som är enkla typer. Mål kan också vara egenskaper för en komplex typ. När varje egenskap har bundits sker modellverifiering för den egenskapen. Posten för vilka data som är bundna till modellen och eventuella bindnings- eller valideringsfel lagras i ControllerBase.ModelState eller PageModel.ModelState. För att ta reda på om den här processen lyckades kontrollerar appen flaggan ModelState.IsValid .

Targets

Modellbindning försöker hitta värden för följande typer av mål:

  • Parametrar för den kontrollantåtgärdsmetod som en begäran dirigeras till.
  • Parametrar för den Razor sidhanterarmetod som en begäran dirigeras till.
  • Offentliga egenskaper för en kontrollant eller PageModel klass, om de anges av attribut.

[BindProperty]-attribut

Kan tillämpas på en offentlig egenskap hos en kontroller eller PageModel klass för att modellbindning ska rikta sig mot den egenskapen.

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

[BindProperties]-attribut

Finns i ASP.NET Core 2.1 eller senare. Kan tillämpas på en kontroller eller PageModel klass för att ange modellbindning att rikta in sig på alla offentliga egenskaper i klassen.

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

Modellbindning för HTTP GET-begäranden

Som standard är egenskaperna inte bundna till HTTP GET-begäranden. Vanligtvis är allt du behöver för en GET-begäran en post-ID-parameter. Post-ID:t används för att leta upp objektet i databasen. Därför behöver du inte binda en egenskap som innehåller en instans av modellen. I scenarier där du vill att egenskaper ska bindas till data från GET-begäranden anger du SupportsGet egenskapen till true:

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

Sources

Som standard hämtar modellbindningen data i form av nyckel/värde-par från följande källor i en HTTP-begäran:

  1. Formulärfält
  2. Begärandetexten (för kontrollanter som har attributet [ApiController].)
  3. Ruttdata
  4. Frågesträngsparametrar
  5. Ladda upp filer

För varje målparameter eller egenskap genomsöks källorna i den ordning som anges i föregående lista. Det finns några undantag:

  • Routningsdata och frågesträngsvärden används endast för enkla typer.
  • Uppladdade filer är endast bundna till måltyper som implementerar IFormFile eller IEnumerable<IFormFile>.

Om standardkällan inte är korrekt använder du något av följande attribut för att ange källan:

  • [FromQuery] – Hämtar värden från frågesträngen.
  • [FromRoute] – Hämtar värden från vägdata.
  • [FromForm] – Hämtar värden från publicerade formulärfält.
  • [FromBody] – Hämtar värden från begärandetexten.
  • [FromHeader] – Hämtar värden från HTTP-huvuden.

Följande attribut:

  • Läggs till i modellegenskaper individuellt (inte i modellklassen), som i följande exempel:

    public class Instructor
    {
        public int ID { get; set; }
    
        [FromQuery(Name = "Note")]
        public string NoteFromQueryString { get; set; }
    
  • Det är valfritt att acceptera ett modellnamnsvärde i konstruktorn. Det här alternativet anges om egenskapsnamnet inte matchar värdet i begäran. Till exempel kan värdet i begäran vara en rubrik med ett bindestreck i namnet, som i följande exempel:

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

[FromBody]-attribut

[FromBody] Använd attributet för en parameter för att fylla i dess egenskaper från brödtexten i en HTTP-begäran. ASP.NET Core-körningen delegerar ansvaret för att läsa brödtexten till en indataformaterare. Indataformaterare beskrivs senare i den här artikeln.

När [FromBody] tillämpas på en komplex typparameter ignoreras alla bindningskällaattribut som tillämpas på dess egenskaper. Följande åtgärd anger till exempel Create att parametern pet är ifylld från brödtexten:

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

Klassen Pet anger att dess Breed egenskap fylls i från en frågesträngsparameter:

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

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

I föregående exempel:

  • Attributet [FromQuery] ignoreras.
  • Egenskapen Breed fylls inte i från en frågesträngsparameter.

Indataformaterare läser endast brödtexten och förstår inte bindningskällans attribut. Om ett lämpligt värde hittas i brödtexten används det värdet för att fylla i Breed egenskapen.

Gäller [FromBody] inte för fler än en parameter per åtgärdsmetod. När begärandeströmmen har lästs av en indataformaterare är den inte längre tillgänglig för att läsas igen för bindning av andra [FromBody] parametrar.

Ytterligare källor

Källdata tillhandahålls till modellbindningssystemet av värdeprovidrar. Du kan skriva och registrera anpassade värdeprovidrar som hämtar data för modellbindning från andra källor. Du kanske till exempel vill ha data från cookies eller sessionstillstånd. Så här hämtar du data från en ny källa:

  • Skapa en klass som implementerar IValueProvider.
  • Skapa en klass som implementerar IValueProviderFactory.
  • Registrera fabriksklassen i Startup.ConfigureServices.

Exempelappen innehåller en värdeprovider och ett fabriksexempel som hämtar värden från cookies. Här är registreringskoden i 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();

Koden som visas placerar den anpassade värdeprovidern efter alla inbyggda värdeprovidrar. Om du vill göra den till den första i listan anropar du Insert(0, new CookieValueProviderFactory()) i stället Addför .

Ingen källa för en modells egenskap

Som standard skapas inte ett modelltillståndsfel om inget värde hittas för en modellegenskap. Egenskapen är inställd på null eller ett standardvärde:

  • Enkla typer som kan ogiltigförklaras är inställda på null.
  • Värdetyper som inte kan vara null är inställda på default(T). En parameter int id är till exempel inställd på 0.
  • För komplexa typer skapar modellbindning en instans med hjälp av standardkonstruktorn, utan att ange egenskaper.
  • Matriser är inställda på Array.Empty<T>(), förutom att byte[] matriser är inställda på null.

Om modelltillståndet ska ogiltigförklaras när inget hittas i formulärfält för en modellegenskap använder du attributet [BindRequired] .

Observera att det här [BindRequired] beteendet gäller för modellbindning från publicerade formulärdata, inte för JSON- eller XML-data i en begärandetext. Brödtextdata för begäran hanteras av indataformaterare.

Typkonverteringsfel

Om en källa hittas men inte kan konverteras till måltypen flaggas modelltillståndet som ogiltigt. Målparametern eller -egenskapen är inställd på null eller ett standardvärde, enligt beskrivningen i föregående avsnitt.

I en API-kontrollant som har [ApiController] attributet resulterar ogiltigt modelltillstånd i ett automatiskt HTTP 400-svar.

Visa sidan igen med ett felmeddelande på en Razor sida.

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

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

Validering på klientsidan fångar upp de flesta felaktiga data som annars skulle skickas till ett Razor sidformulär. Den här valideringen gör det svårt att utlösa den ovan markerade koden. Exempelappen innehåller knappen Skicka med ogiltigt datum som placerar felaktiga data i fältet Anställningsdatum och skickar formuläret. Den här knappen visar hur koden för att redigera sidan fungerar när datakonverteringsfel inträffar.

När sidan spelas upp av föregående kod visas inte ogiltiga indata i formulärfältet. Det beror på att modellegenskapen har angetts till null eller ett standardvärde. Ogiltiga indata visas i ett felmeddelande. Men om du vill redigera om felaktiga data i formulärfältet kan du överväga att göra modellegenskapen till en sträng och göra datakonverteringen manuellt.

Samma strategi rekommenderas om du inte vill att typkonverteringsfel ska resultera i modelltillståndsfel. I så fall gör du modellegenskapen till en sträng.

Enkla typer

De enkla typer som modellbindningen kan konvertera källsträngar till innehåller följande:

Komplexa typer

En komplex typ måste ha en offentlig standardkonstruktor och offentliga skrivbara egenskaper för bindning. När modellbindningen inträffar instansieras klassen med hjälp av den offentliga standardkonstruktorn.

För varje egenskap av den komplexa typen söker modellbindningen igenom källorna efter namnmönstret prefix.property_name. Om inget hittas letar den efter bara property_name utan prefixet.

För bindning till en parameter är prefixet parameternamnet. För bindning till en PageModel offentlig egenskap är prefixet namnet på den offentliga egenskapen. Vissa attribut har en Prefix egenskap som gör att du kan åsidosätta standardanvändningen av parametern eller egenskapsnamnet.

Anta till exempel att den komplexa typen är följande Instructor klass:

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

Prefix = parameternamn

Om modellen som ska bindas är en parameter med namnet instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Modellbindningen börjar med att titta igenom källorna för nyckeln instructorToUpdate.ID. Om det inte hittas söker den efter ID utan ett prefix.

Prefix = egenskapsnamn

Om modellen som ska bindas är en egenskap med namnet Instructor på kontrollanten eller PageModel klassen:

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

Modellbindningen börjar med att titta igenom källorna för nyckeln Instructor.ID. Om det inte hittas söker den efter ID utan ett prefix.

Anpassat prefix

Om modellen som ska bindas är en parameter med namnet instructorToUpdate och ett Bind attribut anger Instructor som prefix:

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

Modellbindningen börjar med att titta igenom källorna för nyckeln Instructor.ID. Om det inte hittas söker den efter ID utan ett prefix.

Attribut för komplexa typmål

Flera inbyggda attribut är tillgängliga för att styra modellbindning av komplexa typer:

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

Warning

Dessa attribut påverkar modellbindningen när publicerade formulärdata är källan till värden. De påverkar inte indataformaterare, som bearbetar publicerade JSON- och XML-begärandeorgan. Indataformaterare beskrivs senare i den här artikeln.

Attributet [Bind]

Kan tillämpas på en klass eller en metodparameter. Anger vilka egenskaper för en modell som ska ingå i modellbindningen. [Bind] påverkar inte indataformaterare.

I följande exempel är endast de angivna egenskaperna för Instructor modellen bundna när någon hanterare eller åtgärdsmetod anropas:

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

I följande exempel är endast de angivna egenskaperna för Instructor modellen bundna när OnPost metoden anropas:

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

Attributet [Bind] kan användas för att skydda mot överpublicering i skapa scenarier. Det fungerar inte bra i redigeringsscenarier eftersom exkluderade egenskaper är inställda på null eller ett standardvärde i stället för att lämnas oförändrade. För skydd mot överpublicering rekommenderas visningsmodeller i stället för attributet [Bind] . Mer information finns i Säkerhetsanteckning om överpublicering.

[ModelBinder]-attribut

ModelBinderAttribute kan tillämpas på typer, egenskaper eller parametrar. Det gör det möjligt att ange vilken typ av modellbindning som används för att binda den specifika instansen eller typen. Till exempel:

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

Attributet [ModelBinder] kan också användas för att ändra namnet på en egenskap eller parameter när det är modellbundet:

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

    public string Name { get; set; }
}

[BindRequired]-attribut

Kan endast tillämpas på modellegenskaper, inte på metodparametrar. Orsakar modellbindning för att lägga till ett modelltillståndsfel om bindningen inte kan ske för en modells egenskap. Här är ett exempel:

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

Se även diskussionen om attributet [Required] i Modellverifiering.

[BindNever]-attribut

Kan endast tillämpas på modellegenskaper, inte på metodparametrar. Förhindrar modellbindning från att bestämma en modells egenskap. Här är ett exempel:

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

Collections

För mål som är samlingar av enkla typer letar modellbindningen efter matchningar till parameter_name eller property_name. Om ingen matchning hittas letar den efter ett av de format som stöds utan prefixet. Till exempel:

  • Anta att parametern som ska bindas är en matris med namnet selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Formulär- eller frågesträngsdata kan vara i något av följande format:

    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
    

    Undvik att binda en parameter eller en egenskap med namnet index eller Index om den ligger i anslutning till ett samlingsvärde. Modellbindning försöker använda index som index för samlingen, vilket kan leda till felaktig bindning. Tänk till exempel på följande åtgärd:

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

    I föregående kod binder frågesträngsparametern index till index metodparametern och används även för att binda produktsamlingen. Om du byter namn på parametern index eller använder ett modellbindningsattribut för att konfigurera bindning undviks det här problemet:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Följande format stöds endast i formulärdata:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • För alla föregående exempelformat skickar modellbindningen en matris med två objekt till parametern selectedCourses :

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

    Dataformat som använder nedsänkta siffror (... [0] ... [1] ...) måste se till att de numreras sekventiellt med början på noll. Om det finns några luckor i siffernummer ignoreras alla objekt efter luckan. Om de till exempel är 0 och 2 i stället för 0 och 1 ignoreras det andra objektet.

Dictionaries

För Dictionary mål söker modellbindning efter matchningar till parameter_name eller property_name. Om ingen matchning hittas letar den efter ett av de format som stöds utan prefixet. Till exempel:

  • Anta att målparametern heter Dictionary<int, string>selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Publicerade formulär- eller frågesträngsdata kan se ut som något av följande exempel:

    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
    
  • För alla föregående exempelformat skickar modellbindningen en ordlista med två objekt till parametern selectedCourses :

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

Konstruktorbindning och rekordtyper

Modellbindning kräver att komplexa typer har en parameterlös konstruktor. Både System.Text.Json och Newtonsoft.Json baserade indataformaterare stöder deserialisering av klasser som inte har en parameterlös konstruktor.

C# 9 introducerar recordtyper, som är ett utmärkt sätt att kortfattat representera data över nätverket. ASP.NET Core lägger till stöd för modellbindning och validering av posttyper med en enda 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

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

Vid validering av posttyper söker körningsmiljön efter bindnings- och valideringsmetadata specifikt på parametrar i stället för på egenskaper.

Ramverket tillåter bindning till och validering av posttyper:

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

För att det föregående ska fungera, måste typen:

  • Vara en posttyp.
  • Ha exakt en offentlig konstruktor.
  • Innehåller parametrar som har en egenskap med samma namn och typ. Namnen får inte skilja sig åt från fall till fall.

POCOs utan parameterfria konstruktorer

POCO:er som inte har parameterlösa konstruktorer kan inte bindas.

Följande kod resulterar i ett undantag som säger att typen måste ha en parameterlös konstruktor:

public class Person(string Name)

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

Registrera typer med manuellt skapade konstruktorer

Posttyper med manuellt skapade konstruktorer som ser ut som primära konstruktorer fungerar

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

Posttyper, validering och bindningsmetadata

För posttyper används validering och bindning av metadata för parametrar. Metadata för egenskaper ignoreras

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

Validering och metadata

Validering använder metadata för parametern men använder egenskapen för att läsa värdet. I vanliga fall med primära konstruktorer skulle de två vara identiska. Det finns dock sätt att besegra det:

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 uppdaterar inte parametrar för en posttyp

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

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

I det här fallet försöker MVC inte binda Name igen. Kan dock Age uppdateras

Globaliseringsbeteende för modellbindningsvägsdata och frågesträngar

ASP.NET Core leverantör för routvärden och leverantör för frågesträngsvärden:

  • Behandla värden som invariant kultur.
  • Förvänta dig att URL:er är kulturinvarianta.

Däremot genomgår värden som kommer från formulärdata en kulturkänslig konvertering. Detta är avsiktligt så att URL:er kan delas oberoende av språkversion.

Så här gör du så att ASP.NET Core-routningsvärdeprovidern och frågesträngsvärdeprovidern genomgår en kulturkänslig konvertering:

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

Särskilda datatyper

Det finns vissa särskilda datatyper som modellbindning kan hantera.

IFormFile och IFormFileCollection

En uppladdad fil som ingår i HTTP-begäran. IEnumerable<IFormFile> stöds också för flera filer.

CancellationToken

Åtgärder kan valfritt binda en CancellationToken som en parameter. Detta binder RequestAborted som signalerar när anslutningen som ligger till grund för HTTP-begäran avbryts. Åtgärder kan använda den här parametern för att avbryta långvariga asynkrona åtgärder som körs som en del av kontrollantåtgärderna.

FormCollection

Används för att hämta alla värden från publicerade formulärdata.

Indataformaterare

Data i begärandetexten kan vara i JSON, XML eller något annat format. För att parsa dessa data använder modellbindningen en indataformaterare som är konfigurerad för att hantera en viss innehållstyp. Som standard innehåller ASP.NET Core JSON-baserade indataformaterare för hantering av JSON-data. Du kan lägga till andra formaterare för andra innehållstyper.

ASP.NET Core väljer indataformaterare baserat på attributet Förbrukar . Om det inte finns något attribut används rubriken Innehållstyp.

Så här använder du de inbyggda XML-indataformatrarna:

  • Installera NuGet-paketet Microsoft.AspNetCore.Mvc.Formatters.Xml .

  • I Startup.ConfigureServicesanropar AddXmlSerializerFormatters eller AddXmlDataContractSerializerFormatters.

    services.AddRazorPages()
        .AddMvcOptions(options =>
    {
        options.ValueProviderFactories.Add(new CookieValueProviderFactory());
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(System.Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
    })
    .AddXmlSerializerFormatters();
    
  • Consumes Använd attributet för kontrollantklasser eller åtgärdsmetoder som bör förvänta sig XML i begärandetexten.

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

    Mer information finns i Introduktion till XML-serialisering.

Anpassa modellbindning med indataformaterare

En indataformaterare tar fullt ansvar för att läsa data från begärandetexten. Om du vill anpassa den här processen konfigurerar du de API:er som används av indataformaterare. I det här avsnittet beskrivs hur du anpassar den System.Text.Json-baserade indataformateren för att förstå en anpassad typ med namnet ObjectId.

Tänk på följande modell, som innehåller en anpassad ObjectId egenskap med namnet Id:

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

Om du vill anpassa modellbindningsprocessen när du använder System.Text.Jsonskapar du en klass som härletts från JsonConverter<T>:

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

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

Använd attributet JsonConverterAttribute för typen för att använda en anpassad konverterare. I följande exempel konfigureras ObjectId-typen med ObjectIdConverter som sin egen anpassade konverterare.

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

    public int Id { get; }
}

Mer information finns i Skriva anpassade konverterare.

Undanta angivna typer från modellbindning

Modellens bindnings- och valideringssystems beteende styrs av ModelMetadata. Du kan anpassa ModelMetadata genom att lägga till en informationsprovider i MvcOptions.ModelMetadataDetailsProviders. Inbyggda informationsprovidrar är tillgängliga för inaktivering av modellbindning eller validering för angivna typer.

Om du vill inaktivera modellbindning för alla modeller av en angiven typ lägger du till en ExcludeBindingMetadataProvider i Startup.ConfigureServices. Om du till exempel vill inaktivera modellbindning för alla modeller av typen System.Version:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

Om du vill inaktivera validering av egenskaper för en angiven typ lägger du till en SuppressChildValidationMetadataProvider i Startup.ConfigureServices. Om du till exempel vill inaktivera validering av egenskaper av typen System.Guid:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

Anpassade modellbindare

Du kan utöka modellbindningen genom att skriva en anpassad modellbindning och använda [ModelBinder] attributet för att välja den för ett visst mål. Läs mer om bindning av anpassade modeller.

Manuell modellbindning

Modellbindning kan anropas manuellt genom att använda metoden TryUpdateModelAsync. Metoden definieras för båda ControllerBase klasserna och PageModel . Med metodöverlagringar kan du ange prefixet och värdeprovidern som ska användas. Metoden returnerar false om modellbindningen misslyckas. Här är ett exempel:

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 använder värdeleverantörer för att hämta data från formulärets data, frågesträngen och routningsdata. TryUpdateModelAsync är vanligtvis:

  • Används med Razor Pages- och MVC-appar med hjälp av kontrollanter och vyer för att förhindra överpublicering.
  • Används inte med ett webb-API förutom när det hämtas från formulärdata, frågesträngar och routdata. Webb-API-slutpunkter som använder JSON använder indataformatrar för att deserialisera begärandetexten till ett objekt.

Mer information finns i TryUpdateModelAsync.

[FromServices]-attribut

Det här attributets namn följer mönstret för modellbindningsattribut som anger en datakälla. Men det handlar inte om att binda data från en värdeprovider. Den hämtar en instans av en typ från containern för beroendeinmatning . Syftet är att tillhandahålla ett alternativ till konstruktorinmatning för när du behöver en tjänst endast om en viss metod anropas.

Ytterligare resurser