Powiązanie modelu w ASP.NET Core

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

W tym artykule wyjaśniono, czym jest powiązanie modelu, jak działa i jak dostosować jego zachowanie.

Co to jest powiązanie modelu

Kontrolery i Razor strony współpracują z danymi pochodzącymi z żądań HTTP. Na przykład dane trasy mogą zawierać klucz rekordu, a opublikowane pola formularza mogą zawierać wartości właściwości modelu. Pisanie kodu w celu pobrania każdej z tych wartości i przekonwertowanie ich z ciągów na typy platformy .NET byłoby żmudne i podatne na błędy. Powiązanie modelu automatyzuje ten proces. System powiązania modelu:

  • Pobiera dane z różnych źródeł, takich jak dane trasy, pola formularza i ciągi zapytań.
  • Udostępnia dane kontrolerom i Razor stronom w parametrach metody i właściwościach publicznych.
  • Konwertuje dane ciągu na typy platformy .NET.
  • Aktualizacje właściwości typów złożonych.

Przykład

Załóżmy, że masz następującą metodę akcji:

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

Aplikacja otrzymuje żądanie z następującym adresem URL:

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

Powiązanie modelu wykonuje następujące kroki po wybraniu przez system routingu metody akcji:

  • Znajduje pierwszy parametr , GetByIdliczba całkowita o nazwie id.
  • Przegląda dostępne źródła w żądaniu HTTP i znajduje id = "2" w danych trasy.
  • Konwertuje ciąg "2" na liczbę całkowitą 2.
  • Znajduje następny parametr , GetByIdwartość logiczną o nazwie dogsOnly.
  • Przegląda źródła i znajduje ciąg zapytania "DogsOnly=true". Dopasowywanie nazw nie jest uwzględniane wielkości liter.
  • Konwertuje ciąg "true" na wartość logiczną true.

Następnie struktura wywołuje metodę GetById , przekazując wartość 2 dla parametru id i true parametru dogsOnly .

W poprzednim przykładzie docelowe powiązania modelu to parametry metody, które są prostymi typami. Obiekty docelowe mogą być również właściwościami typu złożonego. Po pomyślnym powiązaniu każdej właściwości walidacja modelu jest wykonywana dla tej właściwości. Rekord danych powiązanych z modelem oraz wszelkich błędów powiązań lub walidacji jest przechowywany w pliku ControllerBase.ModelState lub PageModel.ModelState. Aby dowiedzieć się, czy ten proces zakończył się pomyślnie, aplikacja sprawdza flagę ModelState.IsValid .

Elementy docelowe

Powiązanie modelu próbuje znaleźć wartości dla następujących rodzajów obiektów docelowych:

  • Parametry metody akcji kontrolera, do którego jest kierowane żądanie.
  • Razor Parametry metody obsługi stron, do których jest kierowane żądanie.
  • Publiczne właściwości kontrolera lub PageModel klasy, jeśli są określone przez atrybuty.

[BindProperty] , atrybut

Można zastosować do właściwości publicznej kontrolera lub PageModel klasy, aby spowodować powiązanie modelu z tą właściwością docelową:

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

    // ...
}

[BindProperties] , atrybut

Można zastosować do kontrolera lub PageModel klasy, aby poinformować powiązanie modelu w celu kierowania wszystkich właściwości publicznych klasy:

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

    // ...
}

Powiązanie modelu dla żądań HTTP GET

Domyślnie właściwości nie są powiązane z żądaniami HTTP GET. Zazwyczaj wszystko, czego potrzebujesz do żądania GET, to parametr identyfikatora rekordu. Identyfikator rekordu służy do wyszukiwania elementu w bazie danych. W związku z tym nie ma potrzeby wiązania właściwości, która zawiera wystąpienie modelu. W scenariuszach, w których chcesz, aby właściwości powiązane z danymi z żądań GET ustawiły SupportsGet właściwość na true:

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

Tworzenie prostych i złożonych typów powiązania modelu

Powiązanie modelu używa określonych definicji dla typów, na których działa. Prosty typ jest konwertowany z jednego ciągu przy użyciu TypeConverter metody lub TryParse metody. Typ złożony jest konwertowany z wielu wartości wejściowych. Struktura określa różnicę na podstawie istnienia obiektu TypeConverter lub TryParse. Zalecamy utworzenie konwertera typów lub użycie TryParse metody do stringSomeType konwersji, która nie wymaga zasobów zewnętrznych ani wielu danych wejściowych.

Źródła

Domyślnie powiązanie modelu pobiera dane w postaci par klucz-wartość z następujących źródeł w żądaniu HTTP:

  1. Pola formularza
  2. Treść żądania (dla kontrolerów, które mają atrybut [ApiController].
  3. Dane trasy
  4. Parametry ciągu zapytania
  5. Przekazane pliki

Dla każdego parametru docelowego lub właściwości źródła są skanowane w kolejności wskazanej na powyższej liście. Istnieje kilka wyjątków:

  • Dane trasy i wartości ciągu zapytania są używane tylko dla prostych typów.
  • Przekazane pliki są powiązane tylko z typami docelowymi, które implementują IFormFile program lub IEnumerable<IFormFile>.

Jeśli domyślne źródło nie jest poprawne, użyj jednego z następujących atrybutów, aby określić źródło:

  • [FromQuery] — Pobiera wartości z ciągu zapytania.
  • [FromRoute] - Pobiera wartości z danych trasy.
  • [FromForm] - Pobiera wartości z opublikowanych pól formularza.
  • [FromBody] — Pobiera wartości z treści żądania.
  • [FromHeader] - Pobiera wartości z nagłówków HTTP.

Te atrybuty:

  • Są dodawane do właściwości modelu indywidualnie, a nie do klasy modelu, jak w poniższym przykładzie:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Opcjonalnie zaakceptuj wartość nazwy modelu w konstruktorze. Ta opcja jest dostępna w przypadku, gdy nazwa właściwości nie jest zgodna z wartością w żądaniu. Na przykład wartość w żądaniu może być nagłówkiem z łącznikiem w jego nazwie, jak w poniższym przykładzie:

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

[FromBody] , atrybut

Zastosuj atrybut do parametru, [FromBody] aby wypełnić jego właściwości z treści żądania HTTP. Środowisko uruchomieniowe ASP.NET Core deleguje odpowiedzialność za odczytywanie treści do elementu formatującego dane wejściowe. Formatery danych wejściowych zostały wyjaśnione w dalszej części tego artykułu.

Po [FromBody] zastosowaniu do parametru typu złożonego wszystkie atrybuty źródła powiązania zastosowane do jego właściwości są ignorowane. Na przykład następująca Create akcja określa, że jego pet parametr jest wypełniany z treści:

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

Klasa Pet określa, że jego Breed właściwość jest wypełniana z parametru ciągu zapytania:

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

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

W powyższym przykładzie:

  • Atrybut [FromQuery] jest ignorowany.
  • Właściwość Breed nie jest wypełniana z parametru ciągu zapytania.

Formatery wejściowe odczytują tylko treść i nie rozumieją atrybutów źródła powiązania. Jeśli w treści znajduje się odpowiednia wartość, ta wartość jest używana do wypełnienia Breed właściwości.

Nie należy stosować [FromBody] do więcej niż jednego parametru dla metody akcji. Gdy strumień żądania zostanie odczytany przez program formatujący dane wejściowe, nie będzie już dostępny do ponownego odczytania w celu powiązania innych [FromBody] parametrów.

Dodatkowe źródła

Dane źródłowe są dostarczane do systemu powiązania modelu przez dostawców wartości. Można zapisywać i rejestrować niestandardowych dostawców wartości, którzy pobierają dane na potrzeby powiązania modelu z innych źródeł. Na przykład możesz chcieć uzyskać dane ze cookiestanu s lub sesji. Aby pobrać dane z nowego źródła:

  • Utwórz klasę, która implementuje IValueProvider.
  • Utwórz klasę, która implementuje IValueProviderFactory.
  • Zarejestruj klasę fabryki w pliku Program.cs.

Przykład zawiera dostawcę wartości i przykład fabryki, który pobiera wartości z cookies. Zarejestruj fabryki dostawców wartości niestandardowych w programie Program.cs:

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

Powyższy kod umieszcza niestandardowego dostawcę wartości po wszystkich wbudowanych dostawcach wartości. Aby ustawić ją jako pierwszą na liście, wywołaj metodę Insert(0, new CookieValueProviderFactory()) zamiast Add.

Brak źródła dla właściwości modelu

Domyślnie błąd stanu modelu nie jest tworzony, jeśli dla właściwości modelu nie zostanie znaleziona żadna wartość. Właściwość jest ustawiona na wartość null lub wartość domyślną:

  • Proste typy dopuszczane do wartości null są ustawione na nullwartość .
  • Typy wartości innych niż null są ustawione na default(T)wartość . Na przykład parametr int id ma wartość 0.
  • W przypadku złożonych typów powiązanie modelu tworzy wystąpienie przy użyciu konstruktora domyślnego bez ustawiania właściwości.
  • Tablice są ustawione na Array.Empty<T>(), z tą różnicą, że byte[] tablice są ustawione na null.

Jeśli stan modelu powinien zostać unieważniony, jeśli nic nie zostanie znalezione w polach formularza dla właściwości modelu, użyj atrybutu [BindRequired] .

Należy pamiętać, że to [BindRequired] zachowanie dotyczy powiązania modelu z opublikowanych danych formularza, a nie danych JSON lub XML w treści żądania. Dane treści żądania są obsługiwane przez osoby formatujące dane wejściowe.

Błędy konwersji typów

Jeśli źródło zostanie znalezione, ale nie można go przekonwertować na typ docelowy, stan modelu jest oznaczony jako nieprawidłowy. Docelowy parametr lub właściwość jest ustawiona na wartość null lub wartość domyślną, jak wspomniano w poprzedniej sekcji.

W kontrolerze interfejsu [ApiController] API, który ma atrybut, nieprawidłowy stan modelu powoduje automatyczną odpowiedź HTTP 400.

Razor Na stronie redisplay strony z komunikatem o błędzie:

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

    // ...

    return RedirectToPage("./Index");
}

Gdy strona jest redisplayed przez poprzedni kod, nieprawidłowe dane wejściowe nie są wyświetlane w polu formularza. Jest to spowodowane tym, że właściwość modelu została ustawiona na wartość null lub wartość domyślną. Nieprawidłowe dane wejściowe są wyświetlane w komunikacie o błędzie. Jeśli chcesz ponownie odtworzyć nieprawidłowe dane w polu formularza, rozważ utworzenie właściwości modelu jako ciągu i ręczne przeprowadzenie konwersji danych.

Ta sama strategia jest zalecana, jeśli nie chcesz, aby błędy konwersji typów powodowały błędy stanu modelu. W takim przypadku utwórz właściwość modelu jako ciąg.

Typy proste

Aby uzyskać wyjaśnienie prostych i złożonych typów, zobacz Powiązania modelu proste i złożone typy.

Proste typy, które binder modelu mogą konwertować ciągi źródłowe na następujące:

Wiązanie z IParsable<T>.TryParse

Interfejs IParsable<TSelf>.TryParse API obsługuje wartości parametrów akcji kontrolera powiązania:

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

DateRange Następująca klasa implementuje IParsable<TSelf> obsługę powiązania zakresu dat:

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

Powyższy kod ma następujące działanie:

  • Konwertuje ciąg reprezentujący dwie daty na DateRange obiekt
  • Powiązanie modelu używa IParsable<TSelf>.TryParse metody , aby powiązać DateRangeelement .

Następująca akcja kontrolera używa DateRange klasy do powiązania zakresu dat:

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

Następująca Locale klasa implementuje IParsable<TSelf> obsługę powiązania z elementem 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;
        }
    }
}

Następująca akcja kontrolera używa Locale klasy do powiązania CultureInfo ciągu:

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

Następująca akcja kontrolera używa DateRange klas i Locale do powiązania zakresu dat za pomocą polecenia 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);
}

Przykładowa aplikacja interfejsu API w usłudze GitHub przedstawia poprzedni przykład dla kontrolera interfejsu API.

Wiązanie z TryParse

Interfejs TryParse API obsługuje wartości parametrów akcji kontrolera powiązania:

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

IParsable<T>.TryParse jest zalecanym podejściem do powiązania parametrów, ponieważ w przeciwieństwie do TryParsemetody , nie zależy od odbicia.

DateRangeTP Następująca klasa implementuje TryParseelement :

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

Następująca akcja kontrolera używa DateRangeTP klasy do powiązania zakresu dat:

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

Typy złożone

Typ złożony musi mieć publiczny konstruktor domyślny i publiczne właściwości zapisywalne do powiązania. Po utworzeniu powiązania modelu klasę tworzy się przy użyciu publicznego konstruktora domyślnego.

Dla każdej właściwości typu złożonego powiązanie modelu analizuje źródła wzorcanazw prefix.property_name. Jeśli nic nie zostanie znalezione, szuka tylko property_name bez prefiksu. Decyzja o użyciu prefiksu nie jest podjęta na właściwość. Na przykład z zapytaniem zawierającym ?Instructor.Id=100&Name=foometodę , powiązana z metodą OnGet(Instructor instructor), wynikowy obiekt typu Instructor zawiera:

  • Id ustaw wartość 100.
  • Name ustaw wartość null. Powiązanie modelu oczekuje, Instructor.Name ponieważ Instructor.Id zostało użyte w poprzednim parametrze zapytania.

W przypadku powiązania z parametrem prefiks jest nazwą parametru. W przypadku powiązania z właściwością publiczną PageModel prefiks jest nazwą właściwości publicznej. Niektóre atrybuty mają Prefix właściwość, która umożliwia zastąpienie domyślnego użycia parametru lub nazwy właściwości.

Załóżmy na przykład, że typ złożony to następująca Instructor klasa:

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

Prefiks = nazwa parametru

Jeśli model, który ma być powiązany, jest parametrem o nazwie instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza instructorToUpdate.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Prefiks = nazwa właściwości

Jeśli model, który ma być powiązany, jest właściwością o nazwie Instructor kontrolera lub PageModel klasy:

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

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza Instructor.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Prefiks niestandardowy

Jeśli model, który ma być powiązany, jest parametrem o nazwie instructorToUpdate , a Bind atrybut określa Instructor jako prefiks:

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

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza Instructor.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Atrybuty dla obiektów docelowych typu złożonego

Dostępnych jest kilka wbudowanych atrybutów do kontrolowania powiązania modelu typów złożonych:

Ostrzeżenie

Te atrybuty wpływają na powiązanie modelu, gdy opublikowane dane formularza są źródłem wartości. Nie mają one wpływu na formatery danych wejściowych, które przetwarzają treści JSżądań ON i XML. Formatery danych wejściowych zostały wyjaśnione w dalszej części tego artykułu.

[Bind] , atrybut

Można zastosować do klasy lub parametru metody. Określa, które właściwości modelu powinny być uwzględnione w powiązaniu modelu. [Bind]nie ma wpływu na formatery wejściowe.

W poniższym przykładzie tylko określone właściwości Instructor modelu są powiązane, gdy wywoływana jest dowolna metoda obsługi lub akcji:

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

W poniższym przykładzie po wywołaniu OnPost metody są powiązane tylko określone właściwości Instructor modelu:

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

Atrybut [Bind] może służyć do ochrony przed przesłanianiem w scenariuszach tworzenia . Nie działa dobrze w scenariuszach edycji, ponieważ wykluczone właściwości są ustawione na wartość null lub wartość domyślną, a nie pozostawione bez zmian. W przypadku ochrony przed przesłanianiem zalecane są modele wyświetlania, a nie atrybutu [Bind] . Aby uzyskać więcej informacji, zobacz Uwaga dotycząca zabezpieczeń dotycząca zastępowania.

[ModelBinder] , atrybut

ModelBinderAttribute można stosować do typów, właściwości lub parametrów. Umożliwia określenie typu powiązania modelu używanego do powiązania określonego wystąpienia lub typu. Na przykład:

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

Atrybut [ModelBinder] może również służyć do zmiany nazwy właściwości lub parametru, gdy jest powiązany model:

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

    // ...
}

[BindRequired] , atrybut

Powoduje, że powiązanie modelu powoduje dodanie błędu stanu modelu, jeśli powiązanie nie może wystąpić dla właściwości modelu. Oto przykład:

public class InstructorBindRequired
{
    // ...

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

Zobacz również omówienie atrybutu [Required] w temacie Walidacja modelu.

[BindNever] , atrybut

Można zastosować do właściwości lub typu. Uniemożliwia ustawienie właściwości modelu przez powiązanie modelu. Po zastosowaniu do typu system powiązań modelu wyklucza wszystkie właściwości zdefiniowane przez typ. Oto przykład:

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

    // ...
}

Kolekcje

W przypadku obiektów docelowych, które są kolekcjami prostych typów, powiązanie modelu wyszukuje dopasowania do parameter_name lub property_name. Jeśli nie zostanie znalezione dopasowanie, szuka jednego z obsługiwanych formatów bez prefiksu. Na przykład:

  • Załóżmy, że parametr, który ma być powiązany, to tablica o nazwie selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Dane formularza lub ciągu zapytania mogą być w jednym z następujących formatów:

    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
    

    Unikaj powiązania parametru lub właściwości o nazwie index lub Index , jeśli sąsiaduje z wartością kolekcji. Powiązanie modelu próbuje użyć index jako indeksu kolekcji, co może spowodować nieprawidłowe powiązanie. Rozważmy na przykład następującą akcję:

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

    W poprzednim kodzie index parametr ciągu zapytania jest powiązany z parametrem index metody, a także jest używany do powiązania kolekcji produktów. Zmiana nazwy parametru index lub użycie atrybutu powiązania modelu w celu skonfigurowania powiązania pozwala uniknąć tego problemu:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Następujący format jest obsługiwany tylko w danych formularza:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • We wszystkich poprzednich przykładowych formatach powiązanie modelu przekazuje tablicę dwóch elementów do parametru selectedCourses :

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

    Formaty danych używające liczb w indeksie dolnym (... [0] ... [1] ...) musi upewnić się, że są one numerowane sekwencyjnie rozpoczynające się od zera. Jeśli występują luki w numerowaniu indeksu dolnego, wszystkie elementy po przerwie są ignorowane. Jeśli na przykład indeksy dolny to 0 i 2 zamiast 0 i 1, drugi element jest ignorowany.

Słowniki

W przypadku Dictionary obiektów docelowych powiązanie modelu wyszukuje dopasowania do parameter_name lub property_name. Jeśli nie zostanie znalezione dopasowanie, szuka jednego z obsługiwanych formatów bez prefiksu. Na przykład:

  • Załóżmy, że parametr docelowy Dictionary<int, string> ma nazwę selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Dane opublikowanego formularza lub ciągu zapytania mogą wyglądać podobnie do jednego z następujących przykładów:

    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
    
  • We wszystkich poprzednich przykładowych formatach powiązanie modelu przekazuje słownik dwóch elementów do parametru selectedCourses :

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

Powiązanie konstruktora i typy rekordów

Powiązanie modelu wymaga, aby złożone typy miały konstruktor bez parametrów. Zarówno System.Text.Json metody formatowania danych wejściowych, jak i Newtonsoft.Json obsługują deserializację klas, które nie mają konstruktora bez parametrów.

Typy rekordów to doskonały sposób, aby zwięźle reprezentować dane za pośrednictwem sieci. ASP.NET Core obsługuje wiązanie modelu i weryfikowanie typów rekordów za pomocą jednego konstruktora:

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

public class PersonController
{
    public IActionResult Index() => View();

    [HttpPost]
    public IActionResult Index(Person person)
    {
        // ...
    }
}

Person/Index.cshtml:

@model Person

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

Podczas sprawdzania poprawności typów rekordów środowisko uruchomieniowe wyszukuje metadane powiązania i walidacji w szczególności na parametrach, a nie we właściwościach.

Platforma umożliwia wiązanie i weryfikowanie typów rekordów:

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

Aby poprzedni element działał, typ musi:

  • Być typem rekordu.
  • Mieć dokładnie jeden publiczny konstruktor.
  • Zawierają parametry, które mają właściwość o tej samej nazwie i typie. Nazwy nie mogą się różnić literami.

Obiekty POCO bez konstruktorów bez parametrów

Obiekty POC, które nie mają konstruktorów bez parametrów, nie mogą być powiązane.

Poniższy kod powoduje wyjątek z informacją, że typ musi mieć konstruktor bez parametrów:

public class Person(string Name)

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

Typy rekordów z ręcznie utworzonymi konstruktorami

Typy rekordów z ręcznie utworzonymi konstruktorami, które wyglądają jak konstruktory podstawowe działają

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

Typy rekordów, walidacja i metadane powiązania

W przypadku typów rekordów używane są metadane walidacji i powiązania parametrów. Wszystkie metadane właściwości są ignorowane

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

Walidacja i metadane

Walidacja używa metadanych w parametrze, ale używa właściwości do odczytania wartości. W zwykłych przypadkach z konstruktorami podstawowymi te dwa byłyby identyczne. Istnieją jednak sposoby, aby go pokonać:

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

Funkcja TryUpdateModel nie aktualizuje parametrów typu rekordu

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

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

W takim przypadku usługa MVC nie podejmie ponownej próby powiązania Name . Age Można jednak zaktualizować

Zachowanie globalizacji danych trasy powiązania modelu i ciągów zapytań

Dostawca wartości trasy ASP.NET Core i dostawca wartości ciągu zapytania:

  • Traktuj wartości jako niezmienną kulturę.
  • Spodziewaj się, że adresy URL są niezmienne dla kultury.

Natomiast wartości pochodzące z danych formularza są poddawane konwersji wrażliwej na kulturę. Jest to zaprojektowane tak, aby adresy URL można udostępniać między ustawieniami regionalnymi.

Aby dostawca wartości trasy ASP.NET Core i dostawca wartości ciągu zapytania przeszedł konwersję wrażliwą na kulturę:

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

Specjalne typy danych

Istnieją pewne specjalne typy danych, które mogą obsłużyć powiązanie modelu.

IFormFile i IFormFileCollection

Przekazany plik uwzględniony w żądaniu HTTP. Obsługiwane jest IEnumerable<IFormFile> również w przypadku wielu plików.

Cancellationtoken

Akcje mogą opcjonalnie powiązać element CancellationToken jako parametr. RequestAborted Wiąże się to z sygnałem, gdy połączenie bazowe żądania HTTP zostanie przerwane. Akcje mogą używać tego parametru do anulowania długotrwałych operacji asynchronicznych wykonywanych w ramach akcji kontrolera.

FormCollection

Służy do pobierania wszystkich wartości z opublikowanych danych formularza.

Formatery danych wejściowych

Dane w treści żądania mogą być w JSformacie ON, XML lub innym. Aby przeanalizować te dane, powiązanie modelu używa formatującego danych wejściowych skonfigurowanego do obsługi określonego typu zawartości. Domyślnie ASP.NET Core zawiera JSoparte na danych formatery wejściowe do obsługi JSdanych WŁ. Możesz dodać inne formatery dla innych typów zawartości.

ASP.NET Core wybiera formatery wejściowe na podstawie atrybutu Consumes . Jeśli atrybut nie jest obecny, używa nagłówka Content-Type.

Aby użyć wbudowanych formatów wejściowych XML:

Dostosowywanie powiązania modelu za pomocą formaterów wejściowych

Moduł formatujący dane wejściowe ponosi pełną odpowiedzialność za odczytywanie danych z treści żądania. Aby dostosować ten proces, skonfiguruj interfejsy API używane przez program formatujący dane wejściowe. W tej sekcji opisano sposób dostosowywania opartego na formatatorze System.Text.Jsondanych wejściowych w celu zrozumienia niestandardowego typu o nazwie ObjectId.

Rozważmy następujący model, który zawiera właściwość niestandardową ObjectId :

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

Aby dostosować proces powiązania modelu podczas używania metody System.Text.Json, utwórz klasę pochodzącą z JsonConverter<T>klasy :

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

Aby użyć konwertera niestandardowego, zastosuj JsonConverterAttribute atrybut do typu. W poniższym przykładzie ObjectId typ jest skonfigurowany ObjectIdConverter jako konwerter niestandardowy:

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

Aby uzyskać więcej informacji, zobacz Jak pisać konwertery niestandardowe.

Wykluczanie określonych typów z powiązania modelu

Zachowanie systemów powiązań i walidacji modelu jest sterowane przez ModelMetadataprogram . Możesz dostosować ModelMetadata , dodając dostawcę szczegółów do elementu MvcOptions.ModelMetadataDetailsProviders. Wbudowani dostawcy szczegółów są dostępni do wyłączania powiązania modelu lub walidacji dla określonych typów.

Aby wyłączyć powiązanie modelu dla wszystkich modeli określonego typu, dodaj element w elemExcludeBindingMetadataProvider.Program.cs Aby na przykład wyłączyć powiązanie modelu dla wszystkich modeli typu System.Version:

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

Aby wyłączyć walidację właściwości określonego typu, dodaj element SuppressChildValidationMetadataProvider w Program.cspliku . Aby na przykład wyłączyć walidację właściwości typu System.Guid:

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

Niestandardowe powiązania modelu

Powiązanie modelu można rozszerzyć, pisząc powiązanie modelu niestandardowego i używając atrybutu [ModelBinder] , aby wybrać go dla danego obiektu docelowego. Dowiedz się więcej o powiązaniu modelu niestandardowego.

Ręczne powiązanie modelu

Powiązanie modelu można wywołać ręcznie przy użyciu TryUpdateModelAsync metody . Metoda jest definiowana w klasach ControllerBase i PageModel . Przeciążenia metody umożliwiają określenie prefiksu i dostawcy wartości do użycia. Metoda zwraca false wartość , jeśli powiązanie modelu nie powiedzie się. Oto przykład:

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

return Page();

TryUpdateModelAsync używa dostawców wartości do pobierania danych z treści formularza, ciągu zapytania i kierowania danych. TryUpdateModelAsync jest zwykle:

  • Używane z aplikacjami Razor Pages i MVC przy użyciu kontrolerów i widoków, aby zapobiec nadmiernemu delegowaniu.
  • Nie jest używany z internetowym interfejsem API, chyba że są używane z danych formularza, ciągów zapytań i danych trasy. Punkty końcowe internetowego interfejsu API, które używają JSfunkcji ON, używają formatatorów danych wejściowych do deserializacji treści żądania do obiektu.

Aby uzyskać więcej informacji, zobacz TryUpdateModelAsync.

[FromServices] , atrybut

Nazwa tego atrybutu jest zgodna ze wzorcem atrybutów powiązania modelu, które określają źródło danych. Nie chodzi jednak o powiązanie danych od dostawcy wartości. Pobiera wystąpienie typu z kontenera wstrzykiwania zależności. Jego celem jest zapewnienie alternatywy dla iniekcji konstruktora, jeśli potrzebujesz usługi tylko wtedy, gdy wywoływana jest określona metoda.

Jeśli wystąpienie typu nie jest zarejestrowane w kontenerze wstrzykiwania zależności, aplikacja zgłasza wyjątek podczas próby powiązania parametru. Aby ustawić parametr jako opcjonalny, użyj jednego z następujących metod:

  • Ustaw parametr na wartość null.
  • Ustaw wartość domyślną parametru.

W przypadku parametrów dopuszczających wartość null upewnij się, że parametr nie null jest używany przed uzyskaniem do niego dostępu.

Dodatkowe zasoby

W tym artykule wyjaśniono, czym jest powiązanie modelu, jak działa i jak dostosować jego zachowanie.

Co to jest powiązanie modelu

Kontrolery i Razor strony współpracują z danymi pochodzącymi z żądań HTTP. Na przykład dane trasy mogą zawierać klucz rekordu, a opublikowane pola formularza mogą zawierać wartości właściwości modelu. Pisanie kodu w celu pobrania każdej z tych wartości i przekonwertowanie ich z ciągów na typy platformy .NET byłoby żmudne i podatne na błędy. Powiązanie modelu automatyzuje ten proces. System powiązania modelu:

  • Pobiera dane z różnych źródeł, takich jak dane trasy, pola formularza i ciągi zapytań.
  • Udostępnia dane kontrolerom i Razor stronom w parametrach metody i właściwościach publicznych.
  • Konwertuje dane ciągu na typy platformy .NET.
  • Aktualizacje właściwości typów złożonych.

Przykład

Załóżmy, że masz następującą metodę akcji:

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

Aplikacja otrzymuje żądanie z następującym adresem URL:

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

Powiązanie modelu wykonuje następujące kroki po wybraniu przez system routingu metody akcji:

  • Znajduje pierwszy parametr , GetByIdliczba całkowita o nazwie id.
  • Przegląda dostępne źródła w żądaniu HTTP i znajduje id = "2" w danych trasy.
  • Konwertuje ciąg "2" na liczbę całkowitą 2.
  • Znajduje następny parametr , GetByIdwartość logiczną o nazwie dogsOnly.
  • Przegląda źródła i znajduje ciąg zapytania "DogsOnly=true". Dopasowywanie nazw nie jest uwzględniane wielkości liter.
  • Konwertuje ciąg "true" na wartość logiczną true.

Następnie struktura wywołuje metodę GetById , przekazując wartość 2 dla parametru id i true parametru dogsOnly .

W poprzednim przykładzie docelowe powiązania modelu to parametry metody, które są prostymi typami. Obiekty docelowe mogą być również właściwościami typu złożonego. Po pomyślnym powiązaniu każdej właściwości walidacja modelu jest wykonywana dla tej właściwości. Rekord danych powiązanych z modelem oraz wszelkich błędów powiązań lub walidacji jest przechowywany w pliku ControllerBase.ModelState lub PageModel.ModelState. Aby dowiedzieć się, czy ten proces zakończył się pomyślnie, aplikacja sprawdza flagę ModelState.IsValid .

Elementy docelowe

Powiązanie modelu próbuje znaleźć wartości dla następujących rodzajów obiektów docelowych:

  • Parametry metody akcji kontrolera, do którego jest kierowane żądanie.
  • Razor Parametry metody obsługi stron, do których jest kierowane żądanie.
  • Publiczne właściwości kontrolera lub PageModel klasy, jeśli są określone przez atrybuty.

[BindProperty] , atrybut

Można zastosować do właściwości publicznej kontrolera lub PageModel klasy, aby spowodować powiązanie modelu z tą właściwością docelową:

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

    // ...
}

[BindProperties] , atrybut

Można zastosować do kontrolera lub PageModel klasy, aby poinformować powiązanie modelu w celu kierowania wszystkich właściwości publicznych klasy:

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

    // ...
}

Powiązanie modelu dla żądań HTTP GET

Domyślnie właściwości nie są powiązane z żądaniami HTTP GET. Zazwyczaj wszystko, czego potrzebujesz do żądania GET, to parametr identyfikatora rekordu. Identyfikator rekordu służy do wyszukiwania elementu w bazie danych. W związku z tym nie ma potrzeby wiązania właściwości, która zawiera wystąpienie modelu. W scenariuszach, w których chcesz, aby właściwości powiązane z danymi z żądań GET ustawiły SupportsGet właściwość na true:

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

Tworzenie prostych i złożonych typów powiązania modelu

Powiązanie modelu używa określonych definicji dla typów, na których działa. Prosty typ jest konwertowany z jednego ciągu przy użyciu TypeConverter metody lub TryParse metody. Typ złożony jest konwertowany z wielu wartości wejściowych. Struktura określa różnicę na podstawie istnienia obiektu TypeConverter lub TryParse. Zalecamy utworzenie konwertera typów lub użycie TryParse metody do stringSomeType konwersji, która nie wymaga zasobów zewnętrznych ani wielu danych wejściowych.

Źródła

Domyślnie powiązanie modelu pobiera dane w postaci par klucz-wartość z następujących źródeł w żądaniu HTTP:

  1. Pola formularza
  2. Treść żądania (dla kontrolerów, które mają atrybut [ApiController].
  3. Dane trasy
  4. Parametry ciągu zapytania
  5. Przekazane pliki

Dla każdego parametru docelowego lub właściwości źródła są skanowane w kolejności wskazanej na powyższej liście. Istnieje kilka wyjątków:

  • Dane trasy i wartości ciągu zapytania są używane tylko dla prostych typów.
  • Przekazane pliki są powiązane tylko z typami docelowymi, które implementują IFormFile program lub IEnumerable<IFormFile>.

Jeśli domyślne źródło nie jest poprawne, użyj jednego z następujących atrybutów, aby określić źródło:

  • [FromQuery] — Pobiera wartości z ciągu zapytania.
  • [FromRoute] - Pobiera wartości z danych trasy.
  • [FromForm] - Pobiera wartości z opublikowanych pól formularza.
  • [FromBody] — Pobiera wartości z treści żądania.
  • [FromHeader] - Pobiera wartości z nagłówków HTTP.

Te atrybuty:

  • Są dodawane do właściwości modelu indywidualnie, a nie do klasy modelu, jak w poniższym przykładzie:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Opcjonalnie zaakceptuj wartość nazwy modelu w konstruktorze. Ta opcja jest dostępna w przypadku, gdy nazwa właściwości nie jest zgodna z wartością w żądaniu. Na przykład wartość w żądaniu może być nagłówkiem z łącznikiem w jego nazwie, jak w poniższym przykładzie:

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

[FromBody] , atrybut

Zastosuj atrybut do parametru, [FromBody] aby wypełnić jego właściwości z treści żądania HTTP. Środowisko uruchomieniowe ASP.NET Core deleguje odpowiedzialność za odczytywanie treści do elementu formatującego dane wejściowe. Formatery danych wejściowych zostały wyjaśnione w dalszej części tego artykułu.

Po [FromBody] zastosowaniu do parametru typu złożonego wszystkie atrybuty źródła powiązania zastosowane do jego właściwości są ignorowane. Na przykład następująca Create akcja określa, że jego pet parametr jest wypełniany z treści:

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

Klasa Pet określa, że jego Breed właściwość jest wypełniana z parametru ciągu zapytania:

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

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

W powyższym przykładzie:

  • Atrybut [FromQuery] jest ignorowany.
  • Właściwość Breed nie jest wypełniana z parametru ciągu zapytania.

Formatery wejściowe odczytują tylko treść i nie rozumieją atrybutów źródła powiązania. Jeśli w treści znajduje się odpowiednia wartość, ta wartość jest używana do wypełnienia Breed właściwości.

Nie należy stosować [FromBody] do więcej niż jednego parametru dla metody akcji. Gdy strumień żądania zostanie odczytany przez program formatujący dane wejściowe, nie będzie już dostępny do ponownego odczytania w celu powiązania innych [FromBody] parametrów.

Dodatkowe źródła

Dane źródłowe są dostarczane do systemu powiązania modelu przez dostawców wartości. Można zapisywać i rejestrować niestandardowych dostawców wartości, którzy pobierają dane na potrzeby powiązania modelu z innych źródeł. Na przykład możesz chcieć uzyskać dane ze cookiestanu s lub sesji. Aby pobrać dane z nowego źródła:

  • Utwórz klasę, która implementuje IValueProvider.
  • Utwórz klasę, która implementuje IValueProviderFactory.
  • Zarejestruj klasę fabryki w pliku Program.cs.

Przykład zawiera dostawcę wartości i przykład fabryki, który pobiera wartości z cookies. Zarejestruj fabryki dostawców wartości niestandardowych w programie Program.cs:

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

Powyższy kod umieszcza niestandardowego dostawcę wartości po wszystkich wbudowanych dostawcach wartości. Aby ustawić ją jako pierwszą na liście, wywołaj metodę Insert(0, new CookieValueProviderFactory()) zamiast Add.

Brak źródła dla właściwości modelu

Domyślnie błąd stanu modelu nie jest tworzony, jeśli dla właściwości modelu nie zostanie znaleziona żadna wartość. Właściwość jest ustawiona na wartość null lub wartość domyślną:

  • Proste typy dopuszczane do wartości null są ustawione na nullwartość .
  • Typy wartości innych niż null są ustawione na default(T)wartość . Na przykład parametr int id ma wartość 0.
  • W przypadku złożonych typów powiązanie modelu tworzy wystąpienie przy użyciu konstruktora domyślnego bez ustawiania właściwości.
  • Tablice są ustawione na Array.Empty<T>(), z tą różnicą, że byte[] tablice są ustawione na null.

Jeśli stan modelu powinien zostać unieważniony, jeśli nic nie zostanie znalezione w polach formularza dla właściwości modelu, użyj atrybutu [BindRequired] .

Należy pamiętać, że to [BindRequired] zachowanie dotyczy powiązania modelu z opublikowanych danych formularza, a nie danych JSON lub XML w treści żądania. Dane treści żądania są obsługiwane przez osoby formatujące dane wejściowe.

Błędy konwersji typów

Jeśli źródło zostanie znalezione, ale nie można go przekonwertować na typ docelowy, stan modelu jest oznaczony jako nieprawidłowy. Docelowy parametr lub właściwość jest ustawiona na wartość null lub wartość domyślną, jak wspomniano w poprzedniej sekcji.

W kontrolerze interfejsu [ApiController] API, który ma atrybut, nieprawidłowy stan modelu powoduje automatyczną odpowiedź HTTP 400.

Razor Na stronie redisplay strony z komunikatem o błędzie:

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

    // ...

    return RedirectToPage("./Index");
}

Gdy strona jest redisplayed przez poprzedni kod, nieprawidłowe dane wejściowe nie są wyświetlane w polu formularza. Jest to spowodowane tym, że właściwość modelu została ustawiona na wartość null lub wartość domyślną. Nieprawidłowe dane wejściowe są wyświetlane w komunikacie o błędzie. Jeśli chcesz ponownie odtworzyć nieprawidłowe dane w polu formularza, rozważ utworzenie właściwości modelu jako ciągu i ręczne przeprowadzenie konwersji danych.

Ta sama strategia jest zalecana, jeśli nie chcesz, aby błędy konwersji typów powodowały błędy stanu modelu. W takim przypadku utwórz właściwość modelu jako ciąg.

Typy proste

Aby uzyskać wyjaśnienie prostych i złożonych typów, zobacz Powiązania modelu proste i złożone typy.

Proste typy, które binder modelu mogą konwertować ciągi źródłowe na następujące:

Wiązanie z IParsable<T>.TryParse

Interfejs IParsable<TSelf>.TryParse API obsługuje wartości parametrów akcji kontrolera powiązania:

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

DateRange Następująca klasa implementuje IParsable<TSelf> obsługę powiązania zakresu dat:

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

Powyższy kod ma następujące działanie:

  • Konwertuje ciąg reprezentujący dwie daty na DateRange obiekt
  • Powiązanie modelu używa IParsable<TSelf>.TryParse metody , aby powiązać DateRangeelement .

Następująca akcja kontrolera używa DateRange klasy do powiązania zakresu dat:

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

Następująca Locale klasa implementuje IParsable<TSelf> obsługę powiązania z elementem 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;
        }
    }
}

Następująca akcja kontrolera używa Locale klasy do powiązania CultureInfo ciągu:

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

Następująca akcja kontrolera używa DateRange klas i Locale do powiązania zakresu dat za pomocą polecenia 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);
}

Przykładowa aplikacja interfejsu API w usłudze GitHub przedstawia poprzedni przykład dla kontrolera interfejsu API.

Wiązanie z TryParse

Interfejs TryParse API obsługuje wartości parametrów akcji kontrolera powiązania:

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

IParsable<T>.TryParse jest zalecanym podejściem do powiązania parametrów, ponieważ w przeciwieństwie do TryParsemetody , nie zależy od odbicia.

DateRangeTP Następująca klasa implementuje TryParseelement :

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

Następująca akcja kontrolera używa DateRangeTP klasy do powiązania zakresu dat:

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

Typy złożone

Typ złożony musi mieć publiczny konstruktor domyślny i publiczne właściwości zapisywalne do powiązania. Po utworzeniu powiązania modelu klasę tworzy się przy użyciu publicznego konstruktora domyślnego.

Dla każdej właściwości typu złożonego powiązanie modelu analizuje źródła wzorcanazw prefix.property_name. Jeśli nic nie zostanie znalezione, szuka tylko property_name bez prefiksu. Decyzja o użyciu prefiksu nie jest podjęta na właściwość. Na przykład z zapytaniem zawierającym ?Instructor.Id=100&Name=foometodę , powiązana z metodą OnGet(Instructor instructor), wynikowy obiekt typu Instructor zawiera:

  • Id ustaw wartość 100.
  • Name ustaw wartość null. Powiązanie modelu oczekuje, Instructor.Name ponieważ Instructor.Id zostało użyte w poprzednim parametrze zapytania.

W przypadku powiązania z parametrem prefiks jest nazwą parametru. W przypadku powiązania z właściwością publiczną PageModel prefiks jest nazwą właściwości publicznej. Niektóre atrybuty mają Prefix właściwość, która umożliwia zastąpienie domyślnego użycia parametru lub nazwy właściwości.

Załóżmy na przykład, że typ złożony to następująca Instructor klasa:

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

Prefiks = nazwa parametru

Jeśli model, który ma być powiązany, jest parametrem o nazwie instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza instructorToUpdate.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Prefiks = nazwa właściwości

Jeśli model, który ma być powiązany, jest właściwością o nazwie Instructor kontrolera lub PageModel klasy:

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

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza Instructor.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Prefiks niestandardowy

Jeśli model, który ma być powiązany, jest parametrem o nazwie instructorToUpdate , a Bind atrybut określa Instructor jako prefiks:

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

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza Instructor.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Atrybuty dla obiektów docelowych typu złożonego

Dostępnych jest kilka wbudowanych atrybutów do kontrolowania powiązania modelu typów złożonych:

Ostrzeżenie

Te atrybuty wpływają na powiązanie modelu, gdy opublikowane dane formularza są źródłem wartości. Nie mają one wpływu na formatery danych wejściowych, które przetwarzają treści JSżądań ON i XML. Formatery danych wejściowych zostały wyjaśnione w dalszej części tego artykułu.

[Bind] , atrybut

Można zastosować do klasy lub parametru metody. Określa, które właściwości modelu powinny być uwzględnione w powiązaniu modelu. [Bind]nie ma wpływu na formatery wejściowe.

W poniższym przykładzie tylko określone właściwości Instructor modelu są powiązane, gdy wywoływana jest dowolna metoda obsługi lub akcji:

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

W poniższym przykładzie po wywołaniu OnPost metody są powiązane tylko określone właściwości Instructor modelu:

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

Atrybut [Bind] może służyć do ochrony przed przesłanianiem w scenariuszach tworzenia . Nie działa dobrze w scenariuszach edycji, ponieważ wykluczone właściwości są ustawione na wartość null lub wartość domyślną, a nie pozostawione bez zmian. W przypadku ochrony przed przesłanianiem zalecane są modele wyświetlania, a nie atrybutu [Bind] . Aby uzyskać więcej informacji, zobacz Uwaga dotycząca zabezpieczeń dotycząca zastępowania.

[ModelBinder] , atrybut

ModelBinderAttribute można stosować do typów, właściwości lub parametrów. Umożliwia określenie typu powiązania modelu używanego do powiązania określonego wystąpienia lub typu. Na przykład:

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

Atrybut [ModelBinder] może również służyć do zmiany nazwy właściwości lub parametru, gdy jest powiązany model:

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

    // ...
}

[BindRequired] , atrybut

Powoduje, że powiązanie modelu powoduje dodanie błędu stanu modelu, jeśli powiązanie nie może wystąpić dla właściwości modelu. Oto przykład:

public class InstructorBindRequired
{
    // ...

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

Zobacz również omówienie atrybutu [Required] w temacie Walidacja modelu.

[BindNever] , atrybut

Można zastosować do właściwości lub typu. Uniemożliwia ustawienie właściwości modelu przez powiązanie modelu. Po zastosowaniu do typu system powiązań modelu wyklucza wszystkie właściwości zdefiniowane przez typ. Oto przykład:

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

    // ...
}

Kolekcje

W przypadku obiektów docelowych, które są kolekcjami prostych typów, powiązanie modelu wyszukuje dopasowania do parameter_name lub property_name. Jeśli nie zostanie znalezione dopasowanie, szuka jednego z obsługiwanych formatów bez prefiksu. Na przykład:

  • Załóżmy, że parametr, który ma być powiązany, to tablica o nazwie selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Dane formularza lub ciągu zapytania mogą być w jednym z następujących formatów:

    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
    

    Unikaj powiązania parametru lub właściwości o nazwie index lub Index , jeśli sąsiaduje z wartością kolekcji. Powiązanie modelu próbuje użyć index jako indeksu kolekcji, co może spowodować nieprawidłowe powiązanie. Rozważmy na przykład następującą akcję:

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

    W poprzednim kodzie index parametr ciągu zapytania jest powiązany z parametrem index metody, a także jest używany do powiązania kolekcji produktów. Zmiana nazwy parametru index lub użycie atrybutu powiązania modelu w celu skonfigurowania powiązania pozwala uniknąć tego problemu:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Następujący format jest obsługiwany tylko w danych formularza:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • We wszystkich poprzednich przykładowych formatach powiązanie modelu przekazuje tablicę dwóch elementów do parametru selectedCourses :

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

    Formaty danych używające liczb w indeksie dolnym (... [0] ... [1] ...) musi upewnić się, że są one numerowane sekwencyjnie rozpoczynające się od zera. Jeśli występują luki w numerowaniu indeksu dolnego, wszystkie elementy po przerwie są ignorowane. Jeśli na przykład indeksy dolny to 0 i 2 zamiast 0 i 1, drugi element jest ignorowany.

Słowniki

W przypadku Dictionary obiektów docelowych powiązanie modelu wyszukuje dopasowania do parameter_name lub property_name. Jeśli nie zostanie znalezione dopasowanie, szuka jednego z obsługiwanych formatów bez prefiksu. Na przykład:

  • Załóżmy, że parametr docelowy Dictionary<int, string> ma nazwę selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Dane opublikowanego formularza lub ciągu zapytania mogą wyglądać podobnie do jednego z następujących przykładów:

    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
    
  • We wszystkich poprzednich przykładowych formatach powiązanie modelu przekazuje słownik dwóch elementów do parametru selectedCourses :

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

Powiązanie konstruktora i typy rekordów

Powiązanie modelu wymaga, aby złożone typy miały konstruktor bez parametrów. Zarówno System.Text.Json metody formatowania danych wejściowych, jak i Newtonsoft.Json obsługują deserializację klas, które nie mają konstruktora bez parametrów.

Typy rekordów to doskonały sposób, aby zwięźle reprezentować dane za pośrednictwem sieci. ASP.NET Core obsługuje wiązanie modelu i weryfikowanie typów rekordów za pomocą jednego konstruktora:

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

public class PersonController
{
    public IActionResult Index() => View();

    [HttpPost]
    public IActionResult Index(Person person)
    {
        // ...
    }
}

Person/Index.cshtml:

@model Person

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

Podczas sprawdzania poprawności typów rekordów środowisko uruchomieniowe wyszukuje metadane powiązania i walidacji w szczególności na parametrach, a nie we właściwościach.

Platforma umożliwia wiązanie i weryfikowanie typów rekordów:

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

Aby poprzedni element działał, typ musi:

  • Być typem rekordu.
  • Mieć dokładnie jeden publiczny konstruktor.
  • Zawierają parametry, które mają właściwość o tej samej nazwie i typie. Nazwy nie mogą się różnić literami.

Obiekty POCO bez konstruktorów bez parametrów

Obiekty POC, które nie mają konstruktorów bez parametrów, nie mogą być powiązane.

Poniższy kod powoduje wyjątek z informacją, że typ musi mieć konstruktor bez parametrów:

public class Person(string Name)

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

Typy rekordów z ręcznie utworzonymi konstruktorami

Typy rekordów z ręcznie utworzonymi konstruktorami, które wyglądają jak konstruktory podstawowe działają

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

Typy rekordów, walidacja i metadane powiązania

W przypadku typów rekordów używane są metadane walidacji i powiązania parametrów. Wszystkie metadane właściwości są ignorowane

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

Walidacja i metadane

Walidacja używa metadanych w parametrze, ale używa właściwości do odczytania wartości. W zwykłych przypadkach z konstruktorami podstawowymi te dwa byłyby identyczne. Istnieją jednak sposoby, aby go pokonać:

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

Funkcja TryUpdateModel nie aktualizuje parametrów typu rekordu

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

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

W takim przypadku usługa MVC nie podejmie ponownej próby powiązania Name . Age Można jednak zaktualizować

Zachowanie globalizacji danych trasy powiązania modelu i ciągów zapytań

Dostawca wartości trasy ASP.NET Core i dostawca wartości ciągu zapytania:

  • Traktuj wartości jako niezmienną kulturę.
  • Spodziewaj się, że adresy URL są niezmienne dla kultury.

Natomiast wartości pochodzące z danych formularza są poddawane konwersji wrażliwej na kulturę. Jest to zaprojektowane tak, aby adresy URL można udostępniać między ustawieniami regionalnymi.

Aby dostawca wartości trasy ASP.NET Core i dostawca wartości ciągu zapytania przeszedł konwersję wrażliwą na kulturę:

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

Specjalne typy danych

Istnieją pewne specjalne typy danych, które mogą obsłużyć powiązanie modelu.

IFormFile i IFormFileCollection

Przekazany plik uwzględniony w żądaniu HTTP. Obsługiwane jest IEnumerable<IFormFile> również w przypadku wielu plików.

Cancellationtoken

Akcje mogą opcjonalnie powiązać element CancellationToken jako parametr. RequestAborted Wiąże się to z sygnałem, gdy połączenie bazowe żądania HTTP zostanie przerwane. Akcje mogą używać tego parametru do anulowania długotrwałych operacji asynchronicznych wykonywanych w ramach akcji kontrolera.

FormCollection

Służy do pobierania wszystkich wartości z opublikowanych danych formularza.

Formatery danych wejściowych

Dane w treści żądania mogą być w JSformacie ON, XML lub innym. Aby przeanalizować te dane, powiązanie modelu używa formatującego danych wejściowych skonfigurowanego do obsługi określonego typu zawartości. Domyślnie ASP.NET Core zawiera JSoparte na danych formatery wejściowe do obsługi JSdanych WŁ. Możesz dodać inne formatery dla innych typów zawartości.

ASP.NET Core wybiera formatery wejściowe na podstawie atrybutu Consumes . Jeśli atrybut nie jest obecny, używa nagłówka Content-Type.

Aby użyć wbudowanych formatów wejściowych XML:

Dostosowywanie powiązania modelu za pomocą formaterów wejściowych

Moduł formatujący dane wejściowe ponosi pełną odpowiedzialność za odczytywanie danych z treści żądania. Aby dostosować ten proces, skonfiguruj interfejsy API używane przez program formatujący dane wejściowe. W tej sekcji opisano sposób dostosowywania opartego na formatatorze System.Text.Jsondanych wejściowych w celu zrozumienia niestandardowego typu o nazwie ObjectId.

Rozważmy następujący model, który zawiera właściwość niestandardową ObjectId :

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

Aby dostosować proces powiązania modelu podczas używania metody System.Text.Json, utwórz klasę pochodzącą z JsonConverter<T>klasy :

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

Aby użyć konwertera niestandardowego, zastosuj JsonConverterAttribute atrybut do typu. W poniższym przykładzie ObjectId typ jest skonfigurowany ObjectIdConverter jako konwerter niestandardowy:

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

Aby uzyskać więcej informacji, zobacz Jak pisać konwertery niestandardowe.

Wykluczanie określonych typów z powiązania modelu

Zachowanie systemów powiązań i walidacji modelu jest sterowane przez ModelMetadataprogram . Możesz dostosować ModelMetadata , dodając dostawcę szczegółów do elementu MvcOptions.ModelMetadataDetailsProviders. Wbudowani dostawcy szczegółów są dostępni do wyłączania powiązania modelu lub walidacji dla określonych typów.

Aby wyłączyć powiązanie modelu dla wszystkich modeli określonego typu, dodaj element w elemExcludeBindingMetadataProvider.Program.cs Aby na przykład wyłączyć powiązanie modelu dla wszystkich modeli typu System.Version:

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

Aby wyłączyć walidację właściwości określonego typu, dodaj element SuppressChildValidationMetadataProvider w Program.cspliku . Aby na przykład wyłączyć walidację właściwości typu System.Guid:

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

Niestandardowe powiązania modelu

Powiązanie modelu można rozszerzyć, pisząc powiązanie modelu niestandardowego i używając atrybutu [ModelBinder] , aby wybrać go dla danego obiektu docelowego. Dowiedz się więcej o powiązaniu modelu niestandardowego.

Ręczne powiązanie modelu

Powiązanie modelu można wywołać ręcznie przy użyciu TryUpdateModelAsync metody . Metoda jest definiowana w klasach ControllerBase i PageModel . Przeciążenia metody umożliwiają określenie prefiksu i dostawcy wartości do użycia. Metoda zwraca false wartość , jeśli powiązanie modelu nie powiedzie się. Oto przykład:

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

return Page();

TryUpdateModelAsync używa dostawców wartości do pobierania danych z treści formularza, ciągu zapytania i kierowania danych. TryUpdateModelAsync jest zwykle:

  • Używane z aplikacjami Razor Pages i MVC przy użyciu kontrolerów i widoków, aby zapobiec nadmiernemu delegowaniu.
  • Nie jest używany z internetowym interfejsem API, chyba że są używane z danych formularza, ciągów zapytań i danych trasy. Punkty końcowe internetowego interfejsu API, które używają JSfunkcji ON, używają formatatorów danych wejściowych do deserializacji treści żądania do obiektu.

Aby uzyskać więcej informacji, zobacz TryUpdateModelAsync.

[FromServices] , atrybut

Nazwa tego atrybutu jest zgodna ze wzorcem atrybutów powiązania modelu, które określają źródło danych. Nie chodzi jednak o powiązanie danych od dostawcy wartości. Pobiera wystąpienie typu z kontenera wstrzykiwania zależności. Jego celem jest zapewnienie alternatywy dla iniekcji konstruktora, jeśli potrzebujesz usługi tylko wtedy, gdy wywoływana jest określona metoda.

Jeśli wystąpienie typu nie jest zarejestrowane w kontenerze wstrzykiwania zależności, aplikacja zgłasza wyjątek podczas próby powiązania parametru. Aby ustawić parametr jako opcjonalny, użyj jednego z następujących metod:

  • Ustaw parametr na wartość null.
  • Ustaw wartość domyślną parametru.

W przypadku parametrów dopuszczających wartość null upewnij się, że parametr nie null jest używany przed uzyskaniem do niego dostępu.

Dodatkowe zasoby

W tym artykule wyjaśniono, czym jest powiązanie modelu, jak działa i jak dostosować jego zachowanie.

Co to jest powiązanie modelu

Kontrolery i Razor strony współpracują z danymi pochodzącymi z żądań HTTP. Na przykład dane trasy mogą zawierać klucz rekordu, a opublikowane pola formularza mogą zawierać wartości właściwości modelu. Pisanie kodu w celu pobrania każdej z tych wartości i przekonwertowanie ich z ciągów na typy platformy .NET byłoby żmudne i podatne na błędy. Powiązanie modelu automatyzuje ten proces. System powiązania modelu:

  • Pobiera dane z różnych źródeł, takich jak dane trasy, pola formularza i ciągi zapytań.
  • Udostępnia dane kontrolerom i Razor stronom w parametrach metody i właściwościach publicznych.
  • Konwertuje dane ciągu na typy platformy .NET.
  • Aktualizacje właściwości typów złożonych.

Przykład

Załóżmy, że masz następującą metodę akcji:

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

Aplikacja otrzymuje żądanie z następującym adresem URL:

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

Powiązanie modelu wykonuje następujące kroki po wybraniu przez system routingu metody akcji:

  • Znajduje pierwszy parametr , GetByIdliczba całkowita o nazwie id.
  • Przegląda dostępne źródła w żądaniu HTTP i znajduje id = "2" w danych trasy.
  • Konwertuje ciąg "2" na liczbę całkowitą 2.
  • Znajduje następny parametr , GetByIdwartość logiczną o nazwie dogsOnly.
  • Przegląda źródła i znajduje ciąg zapytania "DogsOnly=true". Dopasowywanie nazw nie jest uwzględniane wielkości liter.
  • Konwertuje ciąg "true" na wartość logiczną true.

Następnie struktura wywołuje metodę GetById , przekazując wartość 2 dla parametru id i true parametru dogsOnly .

W poprzednim przykładzie docelowe powiązania modelu to parametry metody, które są prostymi typami. Obiekty docelowe mogą być również właściwościami typu złożonego. Po pomyślnym powiązaniu każdej właściwości walidacja modelu jest wykonywana dla tej właściwości. Rekord danych powiązanych z modelem oraz wszelkich błędów powiązań lub walidacji jest przechowywany w pliku ControllerBase.ModelState lub PageModel.ModelState. Aby dowiedzieć się, czy ten proces zakończył się pomyślnie, aplikacja sprawdza flagę ModelState.IsValid .

Elementy docelowe

Powiązanie modelu próbuje znaleźć wartości dla następujących rodzajów obiektów docelowych:

  • Parametry metody akcji kontrolera, do którego jest kierowane żądanie.
  • Razor Parametry metody obsługi stron, do których jest kierowane żądanie.
  • Publiczne właściwości kontrolera lub PageModel klasy, jeśli są określone przez atrybuty.

[BindProperty] , atrybut

Można zastosować do właściwości publicznej kontrolera lub PageModel klasy, aby spowodować powiązanie modelu z tą właściwością docelową:

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

    // ...
}

[BindProperties] , atrybut

Można zastosować do kontrolera lub PageModel klasy, aby poinformować powiązanie modelu w celu kierowania wszystkich właściwości publicznych klasy:

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

    // ...
}

Powiązanie modelu dla żądań HTTP GET

Domyślnie właściwości nie są powiązane z żądaniami HTTP GET. Zazwyczaj wszystko, czego potrzebujesz do żądania GET, to parametr identyfikatora rekordu. Identyfikator rekordu służy do wyszukiwania elementu w bazie danych. W związku z tym nie ma potrzeby wiązania właściwości, która zawiera wystąpienie modelu. W scenariuszach, w których chcesz, aby właściwości powiązane z danymi z żądań GET ustawiły SupportsGet właściwość na true:

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

Źródła

Domyślnie powiązanie modelu pobiera dane w postaci par klucz-wartość z następujących źródeł w żądaniu HTTP:

  1. Pola formularza
  2. Treść żądania (dla kontrolerów, które mają atrybut [ApiController].
  3. Dane trasy
  4. Parametry ciągu zapytania
  5. Przekazane pliki

Dla każdego parametru docelowego lub właściwości źródła są skanowane w kolejności wskazanej na powyższej liście. Istnieje kilka wyjątków:

  • Dane trasy i wartości ciągu zapytania są używane tylko dla prostych typów.
  • Przekazane pliki są powiązane tylko z typami docelowymi, które implementują IFormFile program lub IEnumerable<IFormFile>.

Jeśli domyślne źródło nie jest poprawne, użyj jednego z następujących atrybutów, aby określić źródło:

  • [FromQuery] — Pobiera wartości z ciągu zapytania.
  • [FromRoute] - Pobiera wartości z danych trasy.
  • [FromForm] - Pobiera wartości z opublikowanych pól formularza.
  • [FromBody] — Pobiera wartości z treści żądania.
  • [FromHeader] - Pobiera wartości z nagłówków HTTP.

Te atrybuty:

  • Są dodawane do właściwości modelu indywidualnie, a nie do klasy modelu, jak w poniższym przykładzie:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Opcjonalnie zaakceptuj wartość nazwy modelu w konstruktorze. Ta opcja jest dostępna w przypadku, gdy nazwa właściwości nie jest zgodna z wartością w żądaniu. Na przykład wartość w żądaniu może być nagłówkiem z łącznikiem w jego nazwie, jak w poniższym przykładzie:

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

[FromBody] , atrybut

Zastosuj atrybut do parametru, [FromBody] aby wypełnić jego właściwości z treści żądania HTTP. Środowisko uruchomieniowe ASP.NET Core deleguje odpowiedzialność za odczytywanie treści do elementu formatującego dane wejściowe. Formatery danych wejściowych zostały wyjaśnione w dalszej części tego artykułu.

Po [FromBody] zastosowaniu do parametru typu złożonego wszystkie atrybuty źródła powiązania zastosowane do jego właściwości są ignorowane. Na przykład następująca Create akcja określa, że jego pet parametr jest wypełniany z treści:

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

Klasa Pet określa, że jego Breed właściwość jest wypełniana z parametru ciągu zapytania:

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

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

W powyższym przykładzie:

  • Atrybut [FromQuery] jest ignorowany.
  • Właściwość Breed nie jest wypełniana z parametru ciągu zapytania.

Formatery wejściowe odczytują tylko treść i nie rozumieją atrybutów źródła powiązania. Jeśli w treści znajduje się odpowiednia wartość, ta wartość jest używana do wypełnienia Breed właściwości.

Nie należy stosować [FromBody] do więcej niż jednego parametru dla metody akcji. Gdy strumień żądania zostanie odczytany przez program formatujący dane wejściowe, nie będzie już dostępny do ponownego odczytania w celu powiązania innych [FromBody] parametrów.

Dodatkowe źródła

Dane źródłowe są dostarczane do systemu powiązania modelu przez dostawców wartości. Można zapisywać i rejestrować niestandardowych dostawców wartości, którzy pobierają dane na potrzeby powiązania modelu z innych źródeł. Na przykład możesz chcieć uzyskać dane ze cookiestanu s lub sesji. Aby pobrać dane z nowego źródła:

  • Utwórz klasę, która implementuje IValueProvider.
  • Utwórz klasę, która implementuje IValueProviderFactory.
  • Zarejestruj klasę fabryki w pliku Program.cs.

Przykład zawiera dostawcę wartości i przykład fabryki, który pobiera wartości z cookies. Zarejestruj fabryki dostawców wartości niestandardowych w programie Program.cs:

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

Powyższy kod umieszcza niestandardowego dostawcę wartości po wszystkich wbudowanych dostawcach wartości. Aby ustawić ją jako pierwszą na liście, wywołaj metodę Insert(0, new CookieValueProviderFactory()) zamiast Add.

Brak źródła dla właściwości modelu

Domyślnie błąd stanu modelu nie jest tworzony, jeśli dla właściwości modelu nie zostanie znaleziona żadna wartość. Właściwość jest ustawiona na wartość null lub wartość domyślną:

  • Proste typy dopuszczane do wartości null są ustawione na nullwartość .
  • Typy wartości innych niż null są ustawione na default(T)wartość . Na przykład parametr int id ma wartość 0.
  • W przypadku złożonych typów powiązanie modelu tworzy wystąpienie przy użyciu konstruktora domyślnego bez ustawiania właściwości.
  • Tablice są ustawione na Array.Empty<T>(), z tą różnicą, że byte[] tablice są ustawione na null.

Jeśli stan modelu powinien zostać unieważniony, jeśli nic nie zostanie znalezione w polach formularza dla właściwości modelu, użyj atrybutu [BindRequired] .

Należy pamiętać, że to [BindRequired] zachowanie dotyczy powiązania modelu z opublikowanych danych formularza, a nie danych JSON lub XML w treści żądania. Dane treści żądania są obsługiwane przez osoby formatujące dane wejściowe.

Błędy konwersji typów

Jeśli źródło zostanie znalezione, ale nie można go przekonwertować na typ docelowy, stan modelu jest oznaczony jako nieprawidłowy. Docelowy parametr lub właściwość jest ustawiona na wartość null lub wartość domyślną, jak wspomniano w poprzedniej sekcji.

W kontrolerze interfejsu [ApiController] API, który ma atrybut, nieprawidłowy stan modelu powoduje automatyczną odpowiedź HTTP 400.

Razor Na stronie redisplay strony z komunikatem o błędzie:

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

    // ...

    return RedirectToPage("./Index");
}

Gdy strona jest redisplayed przez poprzedni kod, nieprawidłowe dane wejściowe nie są wyświetlane w polu formularza. Jest to spowodowane tym, że właściwość modelu została ustawiona na wartość null lub wartość domyślną. Nieprawidłowe dane wejściowe są wyświetlane w komunikacie o błędzie. Jeśli chcesz ponownie odtworzyć nieprawidłowe dane w polu formularza, rozważ utworzenie właściwości modelu jako ciągu i ręczne przeprowadzenie konwersji danych.

Ta sama strategia jest zalecana, jeśli nie chcesz, aby błędy konwersji typów powodowały błędy stanu modelu. W takim przypadku utwórz właściwość modelu jako ciąg.

Typy proste

Proste typy, które binder modelu mogą konwertować ciągi źródłowe na następujące:

Typy złożone

Typ złożony musi mieć publiczny konstruktor domyślny i publiczne właściwości zapisywalne do powiązania. Po utworzeniu powiązania modelu klasę tworzy się przy użyciu publicznego konstruktora domyślnego.

Dla każdej właściwości typu złożonego powiązanie modelu analizuje źródła wzorcanazw prefix.property_name. Jeśli nic nie zostanie znalezione, szuka tylko property_name bez prefiksu. Decyzja o użyciu prefiksu nie jest podjęta na właściwość. Na przykład z zapytaniem zawierającym ?Instructor.Id=100&Name=foometodę , powiązana z metodą OnGet(Instructor instructor), wynikowy obiekt typu Instructor zawiera:

  • Id ustaw wartość 100.
  • Name ustaw wartość null. Powiązanie modelu oczekuje, Instructor.Name ponieważ Instructor.Id zostało użyte w poprzednim parametrze zapytania.

W przypadku powiązania z parametrem prefiks jest nazwą parametru. W przypadku powiązania z właściwością publiczną PageModel prefiks jest nazwą właściwości publicznej. Niektóre atrybuty mają Prefix właściwość, która umożliwia zastąpienie domyślnego użycia parametru lub nazwy właściwości.

Załóżmy na przykład, że typ złożony to następująca Instructor klasa:

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

Prefiks = nazwa parametru

Jeśli model, który ma być powiązany, jest parametrem o nazwie instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza instructorToUpdate.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Prefiks = nazwa właściwości

Jeśli model, który ma być powiązany, jest właściwością o nazwie Instructor kontrolera lub PageModel klasy:

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

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza Instructor.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Prefiks niestandardowy

Jeśli model, który ma być powiązany, jest parametrem o nazwie instructorToUpdate , a Bind atrybut określa Instructor jako prefiks:

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

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza Instructor.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Atrybuty dla obiektów docelowych typu złożonego

Dostępnych jest kilka wbudowanych atrybutów do kontrolowania powiązania modelu typów złożonych:

Ostrzeżenie

Te atrybuty wpływają na powiązanie modelu, gdy opublikowane dane formularza są źródłem wartości. Nie mają one wpływu na formatery danych wejściowych, które przetwarzają treści JSżądań ON i XML. Formatery danych wejściowych zostały wyjaśnione w dalszej części tego artykułu.

[Bind] , atrybut

Można zastosować do klasy lub parametru metody. Określa, które właściwości modelu powinny być uwzględnione w powiązaniu modelu. [Bind]nie ma wpływu na formatery wejściowe.

W poniższym przykładzie tylko określone właściwości Instructor modelu są powiązane, gdy wywoływana jest dowolna metoda obsługi lub akcji:

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

W poniższym przykładzie po wywołaniu OnPost metody są powiązane tylko określone właściwości Instructor modelu:

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

Atrybut [Bind] może służyć do ochrony przed przesłanianiem w scenariuszach tworzenia . Nie działa dobrze w scenariuszach edycji, ponieważ wykluczone właściwości są ustawione na wartość null lub wartość domyślną, a nie pozostawione bez zmian. W przypadku ochrony przed przesłanianiem zalecane są modele wyświetlania, a nie atrybutu [Bind] . Aby uzyskać więcej informacji, zobacz Uwaga dotycząca zabezpieczeń dotycząca zastępowania.

[ModelBinder] , atrybut

ModelBinderAttribute można stosować do typów, właściwości lub parametrów. Umożliwia określenie typu powiązania modelu używanego do powiązania określonego wystąpienia lub typu. Na przykład:

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

Atrybut [ModelBinder] może również służyć do zmiany nazwy właściwości lub parametru, gdy jest powiązany model:

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

    // ...
}

[BindRequired] , atrybut

Powoduje, że powiązanie modelu powoduje dodanie błędu stanu modelu, jeśli powiązanie nie może wystąpić dla właściwości modelu. Oto przykład:

public class InstructorBindRequired
{
    // ...

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

Zobacz również omówienie atrybutu [Required] w temacie Walidacja modelu.

[BindNever] , atrybut

Można zastosować do właściwości lub typu. Uniemożliwia ustawienie właściwości modelu przez powiązanie modelu. Po zastosowaniu do typu system powiązań modelu wyklucza wszystkie właściwości zdefiniowane przez typ. Oto przykład:

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

    // ...
}

Kolekcje

W przypadku obiektów docelowych, które są kolekcjami prostych typów, powiązanie modelu wyszukuje dopasowania do parameter_name lub property_name. Jeśli nie zostanie znalezione dopasowanie, szuka jednego z obsługiwanych formatów bez prefiksu. Na przykład:

  • Załóżmy, że parametr, który ma być powiązany, to tablica o nazwie selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Dane formularza lub ciągu zapytania mogą być w jednym z następujących formatów:

    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
    

    Unikaj powiązania parametru lub właściwości o nazwie index lub Index , jeśli sąsiaduje z wartością kolekcji. Powiązanie modelu próbuje użyć index jako indeksu kolekcji, co może spowodować nieprawidłowe powiązanie. Rozważmy na przykład następującą akcję:

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

    W poprzednim kodzie index parametr ciągu zapytania jest powiązany z parametrem index metody, a także jest używany do powiązania kolekcji produktów. Zmiana nazwy parametru index lub użycie atrybutu powiązania modelu w celu skonfigurowania powiązania pozwala uniknąć tego problemu:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Następujący format jest obsługiwany tylko w danych formularza:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • We wszystkich poprzednich przykładowych formatach powiązanie modelu przekazuje tablicę dwóch elementów do parametru selectedCourses :

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

    Formaty danych używające liczb w indeksie dolnym (... [0] ... [1] ...) musi upewnić się, że są one numerowane sekwencyjnie rozpoczynające się od zera. Jeśli występują luki w numerowaniu indeksu dolnego, wszystkie elementy po przerwie są ignorowane. Jeśli na przykład indeksy dolny to 0 i 2 zamiast 0 i 1, drugi element jest ignorowany.

Słowniki

W przypadku Dictionary obiektów docelowych powiązanie modelu wyszukuje dopasowania do parameter_name lub property_name. Jeśli nie zostanie znalezione dopasowanie, szuka jednego z obsługiwanych formatów bez prefiksu. Na przykład:

  • Załóżmy, że parametr docelowy Dictionary<int, string> ma nazwę selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Dane opublikowanego formularza lub ciągu zapytania mogą wyglądać podobnie do jednego z następujących przykładów:

    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
    
  • We wszystkich poprzednich przykładowych formatach powiązanie modelu przekazuje słownik dwóch elementów do parametru selectedCourses :

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

Powiązanie konstruktora i typy rekordów

Powiązanie modelu wymaga, aby złożone typy miały konstruktor bez parametrów. Zarówno System.Text.Json metody formatowania danych wejściowych, jak i Newtonsoft.Json obsługują deserializację klas, które nie mają konstruktora bez parametrów.

Typy rekordów to doskonały sposób, aby zwięźle reprezentować dane za pośrednictwem sieci. ASP.NET Core obsługuje wiązanie modelu i weryfikowanie typów rekordów za pomocą jednego konstruktora:

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

public class PersonController
{
    public IActionResult Index() => View();

    [HttpPost]
    public IActionResult Index(Person person)
    {
        // ...
    }
}

Person/Index.cshtml:

@model Person

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

Podczas sprawdzania poprawności typów rekordów środowisko uruchomieniowe wyszukuje metadane powiązania i walidacji w szczególności na parametrach, a nie we właściwościach.

Platforma umożliwia wiązanie i weryfikowanie typów rekordów:

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

Aby poprzedni element działał, typ musi:

  • Być typem rekordu.
  • Mieć dokładnie jeden publiczny konstruktor.
  • Zawierają parametry, które mają właściwość o tej samej nazwie i typie. Nazwy nie mogą się różnić literami.

Obiekty POCO bez konstruktorów bez parametrów

Obiekty POC, które nie mają konstruktorów bez parametrów, nie mogą być powiązane.

Poniższy kod powoduje wyjątek z informacją, że typ musi mieć konstruktor bez parametrów:

public class Person(string Name)

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

Typy rekordów z ręcznie utworzonymi konstruktorami

Typy rekordów z ręcznie utworzonymi konstruktorami, które wyglądają jak konstruktory podstawowe działają

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

Typy rekordów, walidacja i metadane powiązania

W przypadku typów rekordów używane są metadane walidacji i powiązania parametrów. Wszystkie metadane właściwości są ignorowane

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

Walidacja i metadane

Walidacja używa metadanych w parametrze, ale używa właściwości do odczytania wartości. W zwykłych przypadkach z konstruktorami podstawowymi te dwa byłyby identyczne. Istnieją jednak sposoby, aby go pokonać:

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

Funkcja TryUpdateModel nie aktualizuje parametrów typu rekordu

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

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

W takim przypadku usługa MVC nie podejmie ponownej próby powiązania Name . Age Można jednak zaktualizować

Zachowanie globalizacji danych trasy powiązania modelu i ciągów zapytań

Dostawca wartości trasy ASP.NET Core i dostawca wartości ciągu zapytania:

  • Traktuj wartości jako niezmienną kulturę.
  • Spodziewaj się, że adresy URL są niezmienne dla kultury.

Natomiast wartości pochodzące z danych formularza są poddawane konwersji wrażliwej na kulturę. Jest to zaprojektowane tak, aby adresy URL można udostępniać między ustawieniami regionalnymi.

Aby dostawca wartości trasy ASP.NET Core i dostawca wartości ciągu zapytania przeszedł konwersję wrażliwą na kulturę:

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

Specjalne typy danych

Istnieją pewne specjalne typy danych, które mogą obsłużyć powiązanie modelu.

IFormFile i IFormFileCollection

Przekazany plik uwzględniony w żądaniu HTTP. Obsługiwane jest IEnumerable<IFormFile> również w przypadku wielu plików.

Cancellationtoken

Akcje mogą opcjonalnie powiązać element CancellationToken jako parametr. RequestAborted Wiąże się to z sygnałem, gdy połączenie bazowe żądania HTTP zostanie przerwane. Akcje mogą używać tego parametru do anulowania długotrwałych operacji asynchronicznych wykonywanych w ramach akcji kontrolera.

FormCollection

Służy do pobierania wszystkich wartości z opublikowanych danych formularza.

Formatery danych wejściowych

Dane w treści żądania mogą być w JSformacie ON, XML lub innym. Aby przeanalizować te dane, powiązanie modelu używa formatującego danych wejściowych skonfigurowanego do obsługi określonego typu zawartości. Domyślnie ASP.NET Core zawiera JSoparte na danych formatery wejściowe do obsługi JSdanych WŁ. Możesz dodać inne formatery dla innych typów zawartości.

ASP.NET Core wybiera formatery wejściowe na podstawie atrybutu Consumes . Jeśli atrybut nie jest obecny, używa nagłówka Content-Type.

Aby użyć wbudowanych formatów wejściowych XML:

Dostosowywanie powiązania modelu za pomocą formaterów wejściowych

Moduł formatujący dane wejściowe ponosi pełną odpowiedzialność za odczytywanie danych z treści żądania. Aby dostosować ten proces, skonfiguruj interfejsy API używane przez program formatujący dane wejściowe. W tej sekcji opisano sposób dostosowywania opartego na formatatorze System.Text.Jsondanych wejściowych w celu zrozumienia niestandardowego typu o nazwie ObjectId.

Rozważmy następujący model, który zawiera właściwość niestandardową ObjectId :

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

Aby dostosować proces powiązania modelu podczas używania metody System.Text.Json, utwórz klasę pochodzącą z JsonConverter<T>klasy :

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

Aby użyć konwertera niestandardowego, zastosuj JsonConverterAttribute atrybut do typu. W poniższym przykładzie ObjectId typ jest skonfigurowany ObjectIdConverter jako konwerter niestandardowy:

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

Aby uzyskać więcej informacji, zobacz Jak pisać konwertery niestandardowe.

Wykluczanie określonych typów z powiązania modelu

Zachowanie systemów powiązań i walidacji modelu jest sterowane przez ModelMetadataprogram . Możesz dostosować ModelMetadata , dodając dostawcę szczegółów do elementu MvcOptions.ModelMetadataDetailsProviders. Wbudowani dostawcy szczegółów są dostępni do wyłączania powiązania modelu lub walidacji dla określonych typów.

Aby wyłączyć powiązanie modelu dla wszystkich modeli określonego typu, dodaj element w elemExcludeBindingMetadataProvider.Program.cs Aby na przykład wyłączyć powiązanie modelu dla wszystkich modeli typu System.Version:

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

Aby wyłączyć walidację właściwości określonego typu, dodaj element SuppressChildValidationMetadataProvider w Program.cspliku . Aby na przykład wyłączyć walidację właściwości typu System.Guid:

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

Niestandardowe powiązania modelu

Powiązanie modelu można rozszerzyć, pisząc powiązanie modelu niestandardowego i używając atrybutu [ModelBinder] , aby wybrać go dla danego obiektu docelowego. Dowiedz się więcej o powiązaniu modelu niestandardowego.

Ręczne powiązanie modelu

Powiązanie modelu można wywołać ręcznie przy użyciu TryUpdateModelAsync metody . Metoda jest definiowana w klasach ControllerBase i PageModel . Przeciążenia metody umożliwiają określenie prefiksu i dostawcy wartości do użycia. Metoda zwraca false wartość , jeśli powiązanie modelu nie powiedzie się. Oto przykład:

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

return Page();

TryUpdateModelAsync używa dostawców wartości do pobierania danych z treści formularza, ciągu zapytania i kierowania danych. TryUpdateModelAsync jest zwykle:

  • Używane z aplikacjami Razor Pages i MVC przy użyciu kontrolerów i widoków, aby zapobiec nadmiernemu delegowaniu.
  • Nie jest używany z internetowym interfejsem API, chyba że są używane z danych formularza, ciągów zapytań i danych trasy. Punkty końcowe internetowego interfejsu API, które używają JSfunkcji ON, używają formatatorów danych wejściowych do deserializacji treści żądania do obiektu.

Aby uzyskać więcej informacji, zobacz TryUpdateModelAsync.

[FromServices] , atrybut

Nazwa tego atrybutu jest zgodna ze wzorcem atrybutów powiązania modelu, które określają źródło danych. Nie chodzi jednak o powiązanie danych od dostawcy wartości. Pobiera wystąpienie typu z kontenera wstrzykiwania zależności. Jego celem jest zapewnienie alternatywy dla iniekcji konstruktora, jeśli potrzebujesz usługi tylko wtedy, gdy wywoływana jest określona metoda.

Jeśli wystąpienie typu nie jest zarejestrowane w kontenerze wstrzykiwania zależności, aplikacja zgłasza wyjątek podczas próby powiązania parametru. Aby ustawić parametr jako opcjonalny, użyj jednego z następujących metod:

  • Ustaw parametr na wartość null.
  • Ustaw wartość domyślną parametru.

W przypadku parametrów dopuszczających wartość null upewnij się, że parametr nie null jest używany przed uzyskaniem do niego dostępu.

Dodatkowe zasoby

W tym artykule wyjaśniono, czym jest powiązanie modelu, jak działa i jak dostosować jego zachowanie.

Wyświetl lub pobierz przykładowy kod (jak pobrać).

Co to jest powiązanie modelu

Kontrolery i Razor strony współpracują z danymi pochodzącymi z żądań HTTP. Na przykład dane trasy mogą zawierać klucz rekordu, a opublikowane pola formularza mogą zawierać wartości właściwości modelu. Pisanie kodu w celu pobrania każdej z tych wartości i przekonwertowanie ich z ciągów na typy platformy .NET byłoby żmudne i podatne na błędy. Powiązanie modelu automatyzuje ten proces. System powiązania modelu:

  • Pobiera dane z różnych źródeł, takich jak dane trasy, pola formularza i ciągi zapytań.
  • Udostępnia dane kontrolerom i Razor stronom w parametrach metody i właściwościach publicznych.
  • Konwertuje dane ciągu na typy platformy .NET.
  • Aktualizacje właściwości typów złożonych.

Przykład

Załóżmy, że masz następującą metodę akcji:

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

Aplikacja otrzymuje żądanie z następującym adresem URL:

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

Powiązanie modelu wykonuje następujące kroki po wybraniu przez system routingu metody akcji:

  • Znajduje pierwszy parametr , GetByIdliczba całkowita o nazwie id.
  • Przegląda dostępne źródła w żądaniu HTTP i znajduje id = "2" w danych trasy.
  • Konwertuje ciąg "2" na liczbę całkowitą 2.
  • Znajduje następny parametr , GetByIdwartość logiczną o nazwie dogsOnly.
  • Przegląda źródła i znajduje ciąg zapytania "DogsOnly=true". Dopasowywanie nazw nie jest uwzględniane wielkości liter.
  • Konwertuje ciąg "true" na wartość logiczną true.

Następnie struktura wywołuje metodę GetById , przekazując wartość 2 dla parametru id i true parametru dogsOnly .

W poprzednim przykładzie docelowe powiązania modelu to parametry metody, które są prostymi typami. Obiekty docelowe mogą być również właściwościami typu złożonego. Po pomyślnym powiązaniu każdej właściwości walidacja modelu jest wykonywana dla tej właściwości. Rekord danych powiązanych z modelem oraz wszelkich błędów powiązań lub walidacji jest przechowywany w pliku ControllerBase.ModelState lub PageModel.ModelState. Aby dowiedzieć się, czy ten proces zakończył się pomyślnie, aplikacja sprawdza flagę ModelState.IsValid .

Elementy docelowe

Powiązanie modelu próbuje znaleźć wartości dla następujących rodzajów obiektów docelowych:

  • Parametry metody akcji kontrolera, do którego jest kierowane żądanie.
  • Razor Parametry metody obsługi stron, do których jest kierowane żądanie.
  • Publiczne właściwości kontrolera lub PageModel klasy, jeśli są określone przez atrybuty.

[BindProperty] , atrybut

Można zastosować do właściwości publicznej kontrolera lub PageModel klasy, aby spowodować powiązanie modelu z tą właściwością docelową:

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

[BindProperties] , atrybut

Dostępne w systemie ASP.NET Core 2.1 lub nowszym. Można zastosować do kontrolera lub PageModel klasy, aby poinformować powiązanie modelu w celu kierowania wszystkich właściwości publicznych klasy:

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

Powiązanie modelu dla żądań HTTP GET

Domyślnie właściwości nie są powiązane z żądaniami HTTP GET. Zazwyczaj wszystko, czego potrzebujesz do żądania GET, to parametr identyfikatora rekordu. Identyfikator rekordu służy do wyszukiwania elementu w bazie danych. W związku z tym nie ma potrzeby wiązania właściwości, która zawiera wystąpienie modelu. W scenariuszach, w których chcesz, aby właściwości powiązane z danymi z żądań GET ustawiły SupportsGet właściwość na true:

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

Źródła

Domyślnie powiązanie modelu pobiera dane w postaci par klucz-wartość z następujących źródeł w żądaniu HTTP:

  1. Pola formularza
  2. Treść żądania (dla kontrolerów, które mają atrybut [ApiController].
  3. Dane trasy
  4. Parametry ciągu zapytania
  5. Przekazane pliki

Dla każdego parametru docelowego lub właściwości źródła są skanowane w kolejności wskazanej na powyższej liście. Istnieje kilka wyjątków:

  • Dane trasy i wartości ciągu zapytania są używane tylko dla prostych typów.
  • Przekazane pliki są powiązane tylko z typami docelowymi, które implementują IFormFile program lub IEnumerable<IFormFile>.

Jeśli domyślne źródło nie jest poprawne, użyj jednego z następujących atrybutów, aby określić źródło:

  • [FromQuery] — Pobiera wartości z ciągu zapytania.
  • [FromRoute] - Pobiera wartości z danych trasy.
  • [FromForm] - Pobiera wartości z opublikowanych pól formularza.
  • [FromBody] — Pobiera wartości z treści żądania.
  • [FromHeader] - Pobiera wartości z nagłówków HTTP.

Te atrybuty:

  • Są dodawane do właściwości modelu indywidualnie (nie do klasy modelu), jak w poniższym przykładzie:

    public class Instructor
    {
        public int ID { get; set; }
    
        [FromQuery(Name = "Note")]
        public string NoteFromQueryString { get; set; }
    
  • Opcjonalnie zaakceptuj wartość nazwy modelu w konstruktorze. Ta opcja jest dostępna w przypadku, gdy nazwa właściwości nie jest zgodna z wartością w żądaniu. Na przykład wartość w żądaniu może być nagłówkiem z łącznikiem w jego nazwie, jak w poniższym przykładzie:

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

[FromBody] , atrybut

Zastosuj atrybut do parametru, [FromBody] aby wypełnić jego właściwości z treści żądania HTTP. Środowisko uruchomieniowe ASP.NET Core deleguje odpowiedzialność za odczytywanie treści do elementu formatującego dane wejściowe. Formatery danych wejściowych zostały wyjaśnione w dalszej części tego artykułu.

Po [FromBody] zastosowaniu do parametru typu złożonego wszystkie atrybuty źródła powiązania zastosowane do jego właściwości są ignorowane. Na przykład następująca Create akcja określa, że jego pet parametr jest wypełniany z treści:

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

Klasa Pet określa, że jego Breed właściwość jest wypełniana z parametru ciągu zapytania:

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

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

W powyższym przykładzie:

  • Atrybut [FromQuery] jest ignorowany.
  • Właściwość Breed nie jest wypełniana z parametru ciągu zapytania.

Formatery wejściowe odczytują tylko treść i nie rozumieją atrybutów źródła powiązania. Jeśli w treści znajduje się odpowiednia wartość, ta wartość jest używana do wypełnienia Breed właściwości.

Nie należy stosować [FromBody] do więcej niż jednego parametru dla metody akcji. Gdy strumień żądania zostanie odczytany przez program formatujący dane wejściowe, nie będzie już dostępny do ponownego odczytania w celu powiązania innych [FromBody] parametrów.

Dodatkowe źródła

Dane źródłowe są dostarczane do systemu powiązania modelu przez dostawców wartości. Można zapisywać i rejestrować niestandardowych dostawców wartości, którzy pobierają dane na potrzeby powiązania modelu z innych źródeł. Na przykład możesz chcieć uzyskać dane ze cookiestanu s lub sesji. Aby pobrać dane z nowego źródła:

  • Utwórz klasę, która implementuje IValueProvider.
  • Utwórz klasę, która implementuje IValueProviderFactory.
  • Zarejestruj klasę fabryki w pliku Startup.ConfigureServices.

Przykładowa aplikacja zawiera dostawcę wartości i przykład fabryki, który pobiera wartości z cookies. Oto kod rejestracji w pliku 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();

Pokazany kod umieszcza niestandardowego dostawcę wartości po wszystkich wbudowanych dostawcach wartości. Aby ustawić ją jako pierwszą na liście, wywołaj metodę Insert(0, new CookieValueProviderFactory()) zamiast Add.

Brak źródła dla właściwości modelu

Domyślnie błąd stanu modelu nie jest tworzony, jeśli dla właściwości modelu nie zostanie znaleziona żadna wartość. Właściwość jest ustawiona na wartość null lub wartość domyślną:

  • Proste typy dopuszczane do wartości null są ustawione na nullwartość .
  • Typy wartości innych niż null są ustawione na default(T)wartość . Na przykład parametr int id ma wartość 0.
  • W przypadku złożonych typów powiązanie modelu tworzy wystąpienie przy użyciu konstruktora domyślnego bez ustawiania właściwości.
  • Tablice są ustawione na Array.Empty<T>(), z tą różnicą, że byte[] tablice są ustawione na null.

Jeśli stan modelu powinien zostać unieważniony, jeśli nic nie zostanie znalezione w polach formularza dla właściwości modelu, użyj atrybutu [BindRequired] .

Należy pamiętać, że to [BindRequired] zachowanie dotyczy powiązania modelu z opublikowanych danych formularza, a nie danych JSON lub XML w treści żądania. Dane treści żądania są obsługiwane przez osoby formatujące dane wejściowe.

Błędy konwersji typów

Jeśli źródło zostanie znalezione, ale nie można go przekonwertować na typ docelowy, stan modelu jest oznaczony jako nieprawidłowy. Docelowy parametr lub właściwość jest ustawiona na wartość null lub wartość domyślną, jak wspomniano w poprzedniej sekcji.

W kontrolerze interfejsu [ApiController] API, który ma atrybut, nieprawidłowy stan modelu powoduje automatyczną odpowiedź HTTP 400.

Razor Na stronie redisplay strony z komunikatem o błędzie:

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

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

Walidacja po stronie klienta przechwytuje większość nieprawidłowych danych, które w przeciwnym razie zostaną przesłane do Razor formularza strony. Ta walidacja utrudnia wyzwolenie poprzedniego wyróżnionego kodu. Przykładowa aplikacja zawiera przycisk Prześlij z nieprawidłową datą , który umieszcza nieprawidłowe dane w polu Data zatrudnienia i przesyła formularz. Ten przycisk pokazuje, jak kod ponownego tworzenia strony działa po wystąpieniu błędów konwersji danych.

Gdy strona jest redisplayed przez poprzedni kod, nieprawidłowe dane wejściowe nie są wyświetlane w polu formularza. Jest to spowodowane tym, że właściwość modelu została ustawiona na wartość null lub wartość domyślną. Nieprawidłowe dane wejściowe są wyświetlane w komunikacie o błędzie. Jeśli jednak chcesz ponownie odtworzyć nieprawidłowe dane w polu formularza, rozważ ręczne utworzenie właściwości modelu jako ciągu i ręczne przeprowadzenie konwersji danych.

Ta sama strategia jest zalecana, jeśli nie chcesz, aby błędy konwersji typów powodowały błędy stanu modelu. W takim przypadku utwórz właściwość modelu jako ciąg.

Typy proste

Proste typy, które binder modelu mogą konwertować ciągi źródłowe na następujące:

Typy złożone

Typ złożony musi mieć publiczny konstruktor domyślny i publiczne właściwości zapisywalne do powiązania. Po utworzeniu powiązania modelu klasę tworzy się przy użyciu publicznego konstruktora domyślnego.

Dla każdej właściwości typu złożonego powiązanie modelu analizuje źródła wzorca nazw prefix.property_name. Jeśli nic nie zostanie znalezione, szuka tylko property_name bez prefiksu.

W przypadku powiązania z parametrem prefiks jest nazwą parametru. W przypadku powiązania z właściwością publiczną PageModel prefiks jest nazwą właściwości publicznej. Niektóre atrybuty mają Prefix właściwość, która umożliwia zastąpienie domyślnego użycia parametru lub nazwy właściwości.

Załóżmy na przykład, że typ złożony to następująca Instructor klasa:

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

Prefiks = nazwa parametru

Jeśli model, który ma być powiązany, jest parametrem o nazwie instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza instructorToUpdate.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Prefiks = nazwa właściwości

Jeśli model, który ma być powiązany, jest właściwością o nazwie Instructor kontrolera lub PageModel klasy:

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

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza Instructor.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Prefiks niestandardowy

Jeśli model, który ma być powiązany, jest parametrem o nazwie instructorToUpdate , a Bind atrybut określa Instructor jako prefiks:

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

Powiązanie modelu rozpoczyna się od przejrzenia źródeł klucza Instructor.ID. Jeśli to nie zostanie znalezione, szuka ID bez prefiksu.

Atrybuty dla obiektów docelowych typu złożonego

Dostępnych jest kilka wbudowanych atrybutów do kontrolowania powiązania modelu typów złożonych:

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

Ostrzeżenie

Te atrybuty wpływają na powiązanie modelu, gdy opublikowane dane formularza są źródłem wartości. Nie mają one wpływu na formatery danych wejściowych, które przetwarzają treści JSżądań ON i XML. Formatery danych wejściowych zostały wyjaśnione w dalszej części tego artykułu.

[Bind] , atrybut

Można zastosować do klasy lub parametru metody. Określa, które właściwości modelu powinny być uwzględnione w powiązaniu modelu. [Bind]nie ma wpływu na formatery wejściowe.

W poniższym przykładzie tylko określone właściwości Instructor modelu są powiązane, gdy wywoływana jest dowolna metoda obsługi lub akcji:

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

W poniższym przykładzie po wywołaniu OnPost metody są powiązane tylko określone właściwości Instructor modelu:

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

Atrybut [Bind] może służyć do ochrony przed przesłanianiem w scenariuszach tworzenia . Nie działa dobrze w scenariuszach edycji, ponieważ wykluczone właściwości są ustawione na wartość null lub wartość domyślną, a nie pozostawione bez zmian. W przypadku ochrony przed przesłanianiem zalecane są modele wyświetlania, a nie atrybutu [Bind] . Aby uzyskać więcej informacji, zobacz Uwaga dotycząca zabezpieczeń dotycząca zastępowania.

[ModelBinder] , atrybut

ModelBinderAttribute można stosować do typów, właściwości lub parametrów. Umożliwia określenie typu powiązania modelu używanego do powiązania określonego wystąpienia lub typu. Na przykład:

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

Atrybut [ModelBinder] może również służyć do zmiany nazwy właściwości lub parametru, gdy jest powiązany model:

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

    public string Name { get; set; }
}

[BindRequired] , atrybut

Można stosować tylko do właściwości modelu, a nie do parametrów metody. Powoduje, że powiązanie modelu powoduje dodanie błędu stanu modelu, jeśli powiązanie nie może wystąpić dla właściwości modelu. Oto przykład:

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

Zobacz również omówienie atrybutu [Required] w temacie Walidacja modelu.

[BindNever] , atrybut

Można stosować tylko do właściwości modelu, a nie do parametrów metody. Uniemożliwia ustawienie właściwości modelu przez powiązanie modelu. Oto przykład:

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

Kolekcje

W przypadku obiektów docelowych, które są kolekcjami prostych typów, powiązanie modelu wyszukuje dopasowania do parameter_name lub property_name. Jeśli nie zostanie znalezione dopasowanie, szuka jednego z obsługiwanych formatów bez prefiksu. Na przykład:

  • Załóżmy, że parametr, który ma być powiązany, to tablica o nazwie selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Dane formularza lub ciągu zapytania mogą być w jednym z następujących formatów:

    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
    

    Unikaj powiązania parametru lub właściwości o nazwie index lub Index , jeśli sąsiaduje z wartością kolekcji. Powiązanie modelu próbuje użyć index jako indeksu kolekcji, co może spowodować nieprawidłowe powiązanie. Rozważmy na przykład następującą akcję:

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

    W poprzednim kodzie index parametr ciągu zapytania jest powiązany z parametrem index metody, a także jest używany do powiązania kolekcji produktów. Zmiana nazwy parametru index lub użycie atrybutu powiązania modelu w celu skonfigurowania powiązania pozwala uniknąć tego problemu:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Następujący format jest obsługiwany tylko w danych formularza:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • We wszystkich poprzednich przykładowych formatach powiązanie modelu przekazuje tablicę dwóch elementów do parametru selectedCourses :

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

    Formaty danych używające liczb w indeksie dolnym (... [0] ... [1] ...) musi upewnić się, że są one numerowane sekwencyjnie rozpoczynające się od zera. Jeśli występują luki w numerowaniu indeksu dolnego, wszystkie elementy po przerwie są ignorowane. Jeśli na przykład indeksy dolny to 0 i 2 zamiast 0 i 1, drugi element jest ignorowany.

Słowniki

W przypadku Dictionary obiektów docelowych powiązanie modelu wyszukuje dopasowania do parameter_name lub property_name. Jeśli nie zostanie znalezione dopasowanie, szuka jednego z obsługiwanych formatów bez prefiksu. Na przykład:

  • Załóżmy, że parametr docelowy Dictionary<int, string> ma nazwę selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Dane opublikowanego formularza lub ciągu zapytania mogą wyglądać podobnie do jednego z następujących przykładów:

    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
    
  • We wszystkich poprzednich przykładowych formatach powiązanie modelu przekazuje słownik dwóch elementów do parametru selectedCourses :

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

Powiązanie konstruktora i typy rekordów

Powiązanie modelu wymaga, aby złożone typy miały konstruktor bez parametrów. Zarówno System.Text.Json metody formatowania danych wejściowych, jak i Newtonsoft.Json obsługują deserializację klas, które nie mają konstruktora bez parametrów.

W języku C# 9 wprowadzono typy rekordów, które są doskonałym sposobem zwięźle reprezentowania danych za pośrednictwem sieci. ASP.NET Core dodaje obsługę wiązania modelu i sprawdzania poprawności typów rekordów za pomocą jednego konstruktora:

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

public class PersonController
{
   public IActionResult Index() => View();

   [HttpPost]
   public IActionResult Index(Person person)
   {
       ...
   }
}

Person/Index.cshtml:

@model Person

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

Podczas sprawdzania poprawności typów rekordów środowisko uruchomieniowe wyszukuje metadane powiązania i walidacji w szczególności na parametrach, a nie we właściwościach.

Platforma umożliwia wiązanie i weryfikowanie typów rekordów:

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

Aby poprzedni element działał, typ musi:

  • Być typem rekordu.
  • Mieć dokładnie jeden publiczny konstruktor.
  • Zawierają parametry, które mają właściwość o tej samej nazwie i typie. Nazwy nie mogą się różnić literami.

Obiekty POCO bez konstruktorów bez parametrów

Obiekty POC, które nie mają konstruktorów bez parametrów, nie mogą być powiązane.

Poniższy kod powoduje wyjątek z informacją, że typ musi mieć konstruktor bez parametrów:

public class Person(string Name)

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

Typy rekordów z ręcznie utworzonymi konstruktorami

Typy rekordów z ręcznie utworzonymi konstruktorami, które wyglądają jak konstruktory podstawowe działają

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

Typy rekordów, walidacja i metadane powiązania

W przypadku typów rekordów używane są metadane walidacji i powiązania parametrów. Wszystkie metadane właściwości są ignorowane

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

Walidacja i metadane

Walidacja używa metadanych w parametrze, ale używa właściwości do odczytania wartości. W zwykłych przypadkach z konstruktorami podstawowymi te dwa byłyby identyczne. Istnieją jednak sposoby, aby go pokonać:

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

Funkcja TryUpdateModel nie aktualizuje parametrów typu rekordu

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

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

W takim przypadku usługa MVC nie podejmie ponownej próby powiązania Name . Age Można jednak zaktualizować

Zachowanie globalizacji danych trasy powiązania modelu i ciągów zapytań

Dostawca wartości trasy ASP.NET Core i dostawca wartości ciągu zapytania:

  • Traktuj wartości jako niezmienną kulturę.
  • Spodziewaj się, że adresy URL są niezmienne dla kultury.

Natomiast wartości pochodzące z danych formularza są poddawane konwersji wrażliwej na kulturę. Jest to zaprojektowane tak, aby adresy URL można udostępniać między ustawieniami regionalnymi.

Aby dostawca wartości trasy ASP.NET Core i dostawca wartości ciągu zapytania przeszedł konwersję wrażliwą na kulturę:

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

Specjalne typy danych

Istnieją pewne specjalne typy danych, które mogą obsłużyć powiązanie modelu.

IFormFile i IFormFileCollection

Przekazany plik uwzględniony w żądaniu HTTP. Obsługiwane jest IEnumerable<IFormFile> również w przypadku wielu plików.

Cancellationtoken

Akcje mogą opcjonalnie powiązać element CancellationToken jako parametr. RequestAborted Wiąże się to z sygnałem, gdy połączenie bazowe żądania HTTP zostanie przerwane. Akcje mogą używać tego parametru do anulowania długotrwałych operacji asynchronicznych wykonywanych w ramach akcji kontrolera.

FormCollection

Służy do pobierania wszystkich wartości z opublikowanych danych formularza.

Formatery danych wejściowych

Dane w treści żądania mogą być w JSformacie ON, XML lub innym. Aby przeanalizować te dane, powiązanie modelu używa formatującego danych wejściowych skonfigurowanego do obsługi określonego typu zawartości. Domyślnie ASP.NET Core zawiera JSoparte na danych formatery wejściowe do obsługi JSdanych WŁ. Możesz dodać inne formatery dla innych typów zawartości.

ASP.NET Core wybiera formatery wejściowe na podstawie atrybutu Consumes . Jeśli atrybut nie jest obecny, używa nagłówka Content-Type.

Aby użyć wbudowanych formatów wejściowych XML:

  • Microsoft.AspNetCore.Mvc.Formatters.Xml Zainstaluj pakiet NuGet.

  • W Startup.ConfigureServicespliku wywołaj metodę AddXmlSerializerFormatters lub 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 Zastosuj atrybut do klas kontrolerów lub metod akcji, które powinny oczekiwać XML w treści żądania.

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

    Aby uzyskać więcej informacji, zobacz Wprowadzenie serializacji XML.

Dostosowywanie powiązania modelu za pomocą formaterów wejściowych

Moduł formatujący dane wejściowe ponosi pełną odpowiedzialność za odczytywanie danych z treści żądania. Aby dostosować ten proces, skonfiguruj interfejsy API używane przez program formatujący dane wejściowe. W tej sekcji opisano sposób dostosowywania opartego na formatatorze System.Text.Jsondanych wejściowych w celu zrozumienia niestandardowego typu o nazwie ObjectId.

Rozważmy następujący model, który zawiera właściwość niestandardową ObjectId o nazwie Id:

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

Aby dostosować proces powiązania modelu podczas używania metody System.Text.Json, utwórz klasę pochodzącą z JsonConverter<T>klasy :

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

Aby użyć konwertera niestandardowego, zastosuj JsonConverterAttribute atrybut do typu. W poniższym przykładzie ObjectId typ jest skonfigurowany ObjectIdConverter jako konwerter niestandardowy:

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

    public int Id { get; }
}

Aby uzyskać więcej informacji, zobacz Jak pisać konwertery niestandardowe.

Wykluczanie określonych typów z powiązania modelu

Zachowanie systemów powiązań i walidacji modelu jest sterowane przez ModelMetadataprogram . Możesz dostosować ModelMetadata , dodając dostawcę szczegółów do elementu MvcOptions.ModelMetadataDetailsProviders. Wbudowani dostawcy szczegółów są dostępni do wyłączania powiązania modelu lub walidacji dla określonych typów.

Aby wyłączyć powiązanie modelu dla wszystkich modeli określonego typu, dodaj element w elemExcludeBindingMetadataProvider.Startup.ConfigureServices Aby na przykład wyłączyć powiązanie modelu dla wszystkich modeli typu 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();

Aby wyłączyć walidację właściwości określonego typu, dodaj element SuppressChildValidationMetadataProvider w Startup.ConfigureServicespliku . Aby na przykład wyłączyć walidację właściwości typu 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();

Niestandardowe powiązania modelu

Powiązanie modelu można rozszerzyć, pisząc powiązanie modelu niestandardowego i używając atrybutu [ModelBinder] , aby wybrać go dla danego obiektu docelowego. Dowiedz się więcej o powiązaniu modelu niestandardowego.

Ręczne powiązanie modelu

Powiązanie modelu można wywołać ręcznie przy użyciu TryUpdateModelAsync metody . Metoda jest definiowana w klasach ControllerBase i PageModel . Przeciążenia metody umożliwiają określenie prefiksu i dostawcy wartości do użycia. Metoda zwraca false wartość , jeśli powiązanie modelu nie powiedzie się. Oto przykład:

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 używa dostawców wartości do pobierania danych z treści formularza, ciągu zapytania i kierowania danych. TryUpdateModelAsync jest zwykle:

  • Używane z aplikacjami Razor Pages i MVC przy użyciu kontrolerów i widoków, aby zapobiec nadmiernemu delegowaniu.
  • Nie jest używany z internetowym interfejsem API, chyba że są używane z danych formularza, ciągów zapytań i danych trasy. Punkty końcowe internetowego interfejsu API, które używają JSfunkcji ON, używają formatatorów danych wejściowych do deserializacji treści żądania do obiektu.

Aby uzyskać więcej informacji, zobacz TryUpdateModelAsync.

[FromServices] , atrybut

Nazwa tego atrybutu jest zgodna ze wzorcem atrybutów powiązania modelu, które określają źródło danych. Nie chodzi jednak o powiązanie danych od dostawcy wartości. Pobiera wystąpienie typu z kontenera wstrzykiwania zależności. Jego celem jest zapewnienie alternatywy dla iniekcji konstruktora, jeśli potrzebujesz usługi tylko wtedy, gdy wywoływana jest określona metoda.

Dodatkowe zasoby