Udostępnij za pośrednictwem


Powiązanie modelu w ASP.NET Core

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla .NET 9.

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ą, zobacz wersję tego artykułu dla .NET 9.

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 wią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 tekstowe na typy .NET.
  • Aktualizuje 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 GetById, czyli liczbę całkowitą 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 GetById, typ logiczny o nazwie dogsOnly.
  • Przegląda źródła i znajduje ciąg zapytania "DogsOnly=true". Dopasowywanie nazw nie uwzględnia 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.

Atrybut [BindProperty]

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 powiązanie modelu kierowało się na wszystkie właściwości publiczne 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 przypisywania właściwości, która zawiera instancję modelu. W scenariuszach, w których chcesz, aby właściwości były powiązane z danymi z żądań GET, ustaw właściwość SupportsGet na true.

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

Powiązanie modelu z prostymi i złożonymi typami

Powiązanie modelu używa określonych definicji dla typów, na których działa. Prosty typ jest konwertowany z jednego ciągu za pomocą metody TypeConverter lub TryParse. 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 do konwersji z string na SomeType, 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 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 [FromBody] do parametru, 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.

Kiedy [FromBody] jest zastosowane do parametru typu złożonego, wszelkie 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 właściwości Breed.

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ł. Możesz na przykład chcieć dane z plików cookie lub stanu 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 plików cookie. 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 niemające wartości null są ustawione na default(T). 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 z danych JSON lub XML w treści żądania. Dane treści żądania obsługiwane są przez formatery danych wejściowych.

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 API z atrybutem [ApiController], nieprawidłowy stan modelu skutkuje automatyczną odpowiedzią HTTP 400.

Na stronie Razor ponownie wyświetl stronę z komunikatem o błędzie:

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

    // ...

    return RedirectToPage("./Index");
}

Gdy strona jest ponownie wyświetlana przez poprzedni kod, nieprawidłowe dane wejściowe nie są widoczne 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 Proste i złożone typy powiązań modelu.

Proste typy, na które wiązanie modelu może konwertować ciągi źródłowe, to następujące:

Połącz z IParsable<T>.TryParse

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

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

Następująca klasa DateRange implementuje IParsable<TSelf>, aby obsłużyć powiązanie 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:

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

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 klasa Locale implementuje IParsable<TSelf> aby wspierać powiązanie z 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 klasy Locale do powiązania ciągu CultureInfo.

// 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 klas DateRange i Locale do powiązania zakresu dat z 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 na GitHubie pokazuje powyższy przykład dla kontrolera interfejsu API.

Połącz z TryParse

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

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.

Następująca klasa implementuje DateRangeTP:

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, które można powiązać. 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 nazwprefix.property_name. Jeśli nic nie zostanie znalezione, szuka tylko property_name bez prefiksu. Decyzja o użyciu prefiksu nie jest podejmowana dla każdej właściwości. Na przykład z zapytaniem zawierającym ?Instructor.Id=100&Name=foo, powiązanym 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ść żądań JSON 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 tylko określone właściwości modelu Instructor są powiązane, gdy wywołana zostanie metoda OnPost.

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

Atrybut [Bind] może być użyty do ochrony przed nadpisywaniem 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. Aby zabezpieczyć się przed nadpisywaniem, zalecane są modele wyświetlania zamiast atrybutu [Bind]. Aby uzyskać więcej informacji, zobacz Informacja dotycząca zabezpieczeń dotycząca nadpisywania.

[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 podczas wiązania modelu.

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

    // ...
}

Atrybut [BindRequired]

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 wią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] ...) muszą upewnić się, że są one numerowane sekwencyjnie, zaczynając 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"]="Chemia"
    • selectedCourses["2000"]="Ekonomia"

Powiązanie konstruktora i typy rekordów

Powiązanie modelu wymaga, aby złożone typy miały konstruktor bez parametrów. Zarówno formattery danych wejściowych System.Text.Json, 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

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

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:

  • Bądź typem rekordu.
  • Powinien posiadać 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ć wielkością liter.

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 {
    public 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 zdefiniowanymi konstruktorami

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

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 do walidacji i wią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łym przypadku z konstruktorami podstawowymi oba te elementy 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 . Można jednak zaktualizować Age

Globalizacja zachowania wiązania modelu, danych tras i ciągów zapytań

Dostawca wartości trasy w ASP.NET Core oraz dostawca wartości z 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ć w różnych lokalizacjach.

Aby dostawca wartości tras ASP.NET Core oraz dostawca wartości z ciągu zapytania mogły zostać poddane konwersji uwzględniającej kontekst kulturowy:

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 może obsłużyć powiązanie modelu.

IFormFile i IFormFileCollection

Plik przesłany i dołączony do żądania HTTP. Obsługiwane jest IEnumerable<IFormFile> również w przypadku wielu plików.

Token anulowania (CancellationToken)

Akcje mogą opcjonalnie powiązać element CancellationToken jako parametr. Wiąże to RequestAborted, który sygnalizuje, kiedy połączenie bazowe żądania HTTP zostaje 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 formacie JSON, XML lub innym formacie. 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 formatery wejściowe oparte na formacie JSON do obsługi danych JSON. 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.

Wyklucz określone typy z powiązania modelu

Zachowanie systemów wiązania modelu i walidacji jest sterowane przez ModelMetadata. 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 typu określonego, dodaj SuppressChildValidationMetadataProvider w Program.cs. 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

Możesz rozszerzyć bindowanie modelu, pisząc niestandardowy wiązacz modelu i używając atrybutu kodu [ModelBinder], aby wybrać go dla danego obiektu docelowego. Dowiedz się więcej o niestandardowym wiązaniu modelu.

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, których należy użyć. Metoda zwraca false, 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 Razor Pages i aplikacjami MVC wykorzystującymi kontrolery i widoki, aby zapobiec nadmiernemu przesyłaniu danych.
  • Nie jest używany z internetowym interfejsem API, chyba że konsumowany z danych formularza, ciągów zapytań i danych trasy. Punkty końcowe internetowego interfejsu API, które używają formatu JSON, używają formatatorów wejściowych do deserializacji treści żądania do obiektu.

Aby uzyskać więcej informacji, zobacz TryUpdateModelAsync.

Atrybut [FromServices]

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 wstrzykiwania przez konstruktor na wypadek, gdy potrzebujesz usługi tylko wtedy, gdy wywoływana jest określona metoda.

Jeśli instancja danego typu nie jest zarejestrowana w kontenerze wstrzykiwania zależności, aplikacja zgłasza wyjątek przy próbie 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 ma wartości null 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 wią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 tekstowe na typy .NET.
  • Aktualizuje 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 GetById, czyli liczbę całkowitą 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 GetById, typ logiczny o nazwie dogsOnly.
  • Przegląda źródła i znajduje ciąg zapytania "DogsOnly=true". Dopasowywanie nazw nie uwzględnia 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.

Atrybut [BindProperty]

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 powiązanie modelu kierowało się na wszystkie właściwości publiczne 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 przypisywania właściwości, która zawiera instancję modelu. W scenariuszach, w których chcesz, aby właściwości były powiązane z danymi z żądań GET, ustaw właściwość SupportsGet na true.

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

Powiązanie modelu z prostymi i złożonymi typami

Powiązanie modelu używa określonych definicji dla typów, na których działa. Prosty typ jest konwertowany z jednego ciągu za pomocą metody TypeConverter lub TryParse. 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 do konwersji z string na SomeType, 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 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 [FromBody] do parametru, 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.

Kiedy [FromBody] jest zastosowane do parametru typu złożonego, wszelkie 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 właściwości Breed.

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ł. Możesz na przykład chcieć dane z plików cookie lub stanu 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 plików cookie. 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 niemające wartości null są ustawione na default(T). 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 obsługiwane są przez formatery danych wejściowych.

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 API z atrybutem [ApiController], nieprawidłowy stan modelu skutkuje automatyczną odpowiedzią HTTP 400.

Na stronie Razor ponownie wyświetl stronę z komunikatem o błędzie:

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

    // ...

    return RedirectToPage("./Index");
}

Gdy strona jest ponownie wyświetlana przez poprzedni kod, nieprawidłowe dane wejściowe nie są widoczne 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 Proste i złożone typy powiązań modelu.

Proste typy, na które wiązanie modelu może konwertować ciągi źródłowe, to następujące:

Połącz z IParsable<T>.TryParse

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

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

Następująca klasa DateRange implementuje IParsable<TSelf>, aby obsłużyć powiązanie 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:

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

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 klasa Locale implementuje IParsable<TSelf> aby wspierać powiązanie z 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 klasy Locale do powiązania ciągu CultureInfo.

// 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 klas DateRange i Locale do powiązania zakresu dat z 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 na GitHubie pokazuje powyższy przykład dla kontrolera interfejsu API.

Połącz z TryParse

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

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.

Następująca klasa implementuje DateRangeTP:

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, które można powiązać. 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 nazwprefix.property_name. Jeśli nic nie zostanie znalezione, szuka tylko property_name bez prefiksu. Decyzja o użyciu prefiksu nie jest podejmowana dla każdej właściwości. Na przykład z zapytaniem zawierającym ?Instructor.Id=100&Name=foo, powiązanym 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ść żądań JSON 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 tylko określone właściwości modelu Instructor są powiązane, gdy wywołana zostanie metoda OnPost.

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

Atrybut [Bind] może być użyty do ochrony przed nadpisywaniem 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. Aby zabezpieczyć się przed nadpisywaniem, zalecane są modele wyświetlania zamiast atrybutu [Bind]. Aby uzyskać więcej informacji, zobacz Informacja dotycząca zabezpieczeń dotycząca nadpisywania.

[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 podczas wiązania modelu.

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

    // ...
}

Atrybut [BindRequired]

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 wią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] ...) muszą upewnić się, że są one numerowane sekwencyjnie, zaczynając 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"]="Chemia"
    • selectedCourses["2000"]="Ekonomia"

Powiązanie konstruktora i typy rekordów

Powiązanie modelu wymaga, aby złożone typy miały konstruktor bez parametrów. Zarówno formattery danych wejściowych System.Text.Json, 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

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

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:

  • Bądź typem rekordu.
  • Powinien posiadać 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ć wielkością liter.

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

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

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 do walidacji i wią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łym przypadku z konstruktorami podstawowymi oba te elementy 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 . Można jednak zaktualizować Age

Globalizacja zachowania wiązania modelu, danych tras i ciągów zapytań

Dostawca wartości trasy w ASP.NET Core oraz dostawca wartości z 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ć w różnych lokalizacjach.

Aby dostawca wartości tras ASP.NET Core oraz dostawca wartości z ciągu zapytania mogły zostać poddane konwersji uwzględniającej kontekst kulturowy:

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 może obsłużyć powiązanie modelu.

IFormFile i IFormFileCollection

Plik przesłany i dołączony do żądania HTTP. Obsługiwane jest IEnumerable<IFormFile> również w przypadku wielu plików.

Token anulowania (CancellationToken)

Akcje mogą opcjonalnie powiązać element CancellationToken jako parametr. Wiąże to RequestAborted, który sygnalizuje, kiedy połączenie bazowe żądania HTTP zostaje 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 formacie JSON, XML lub innym formacie. 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 formatery wejściowe oparte na formacie JSON do obsługi danych JSON. 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.

Wyklucz określone typy z powiązania modelu

Zachowanie systemów wiązania modelu i walidacji jest sterowane przez ModelMetadata. 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 typu określonego, dodaj SuppressChildValidationMetadataProvider w Program.cs. 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

Możesz rozszerzyć bindowanie modelu, pisząc niestandardowy wiązacz modelu i używając atrybutu kodu [ModelBinder], aby wybrać go dla danego obiektu docelowego. Dowiedz się więcej o niestandardowym wiązaniu modelu.

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, których należy użyć. Metoda zwraca false, 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 Razor Pages i aplikacjami MVC wykorzystującymi kontrolery i widoki, aby zapobiec nadmiernemu przesyłaniu danych.
  • Nie jest używany z internetowym interfejsem API, chyba że konsumowany z danych formularza, ciągów zapytań i danych trasy. Punkty końcowe internetowego interfejsu API, które używają formatu JSON, używają formatatorów wejściowych do deserializacji treści żądania do obiektu.

Aby uzyskać więcej informacji, zobacz TryUpdateModelAsync.

Atrybut [FromServices]

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 wstrzykiwania przez konstruktor na wypadek, gdy potrzebujesz usługi tylko wtedy, gdy wywoływana jest określona metoda.

Jeśli instancja danego typu nie jest zarejestrowana w kontenerze wstrzykiwania zależności, aplikacja zgłasza wyjątek przy próbie 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 ma wartości null 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 wią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 tekstowe na typy .NET.
  • Aktualizuje 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 GetById, czyli liczbę całkowitą 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 GetById, typ logiczny o nazwie dogsOnly.
  • Przegląda źródła i znajduje ciąg zapytania "DogsOnly=true". Dopasowywanie nazw nie uwzględnia 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.

Atrybut [BindProperty]

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 powiązanie modelu kierowało się na wszystkie właściwości publiczne 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 przypisywania właściwości, która zawiera instancję modelu. W scenariuszach, w których chcesz, aby właściwości były powiązane z danymi z żądań GET, ustaw właściwość SupportsGet 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 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 [FromBody] do parametru, 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.

Kiedy [FromBody] jest zastosowane do parametru typu złożonego, wszelkie 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 właściwości Breed.

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ł. Możesz na przykład chcieć dane z plików cookie lub stanu 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 plików cookie. 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 danych dopuszczające wartości null są ustawione na null.
  • Typy wartości niemające wartości null są ustawione na default(T). 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 obsługiwane są przez formatery danych wejściowych.

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 API z atrybutem [ApiController], nieprawidłowy stan modelu skutkuje automatyczną odpowiedzią HTTP 400.

Na stronie Razor ponownie wyświetl stronę z komunikatem o błędzie:

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

    // ...

    return RedirectToPage("./Index");
}

Gdy strona jest ponownie wyświetlana przez poprzedni kod, nieprawidłowe dane wejściowe nie są widoczne 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, na które wiązanie modelu może konwertować ciągi źródłowe, to następujące:

Typy złożone

Typ złożony musi mieć publiczny konstruktor domyślny i publiczne właściwości zapisywalne, które można powiązać. 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 nazwprefix.property_name. Jeśli nic nie zostanie znalezione, szuka tylko property_name bez prefiksu. Decyzja o użyciu prefiksu nie jest podejmowana dla każdej właściwości. Na przykład z zapytaniem zawierającym ?Instructor.Id=100&Name=foo, powiązanym 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ść żądań JSON 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 tylko określone właściwości modelu Instructor są powiązane, gdy wywołana zostanie metoda OnPost.

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

Atrybut [Bind] może być użyty do ochrony przed nadpisywaniem 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. Aby zabezpieczyć się przed nadpisywaniem, zalecane są modele wyświetlania zamiast atrybutu [Bind]. Aby uzyskać więcej informacji, zobacz Informacja dotycząca zabezpieczeń dotycząca nadpisywania.

[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 podczas wiązania modelu.

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

    // ...
}

Atrybut [BindRequired]

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 wią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] ...) muszą upewnić się, że są one numerowane sekwencyjnie, zaczynając 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"]="Chemia"
    • selectedCourses["2000"]="Ekonomia"

Powiązanie konstruktora i typy rekordów

Powiązanie modelu wymaga, aby złożone typy miały konstruktor bez parametrów. Zarówno formattery danych wejściowych System.Text.Json, 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

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

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:

  • Bądź typem rekordu.
  • Powinien posiadać 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ć wielkością liter.

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

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

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 do walidacji i wią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łym przypadku z konstruktorami podstawowymi oba te elementy 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 . Można jednak zaktualizować Age

Globalizacja zachowania wiązania modelu, danych tras i ciągów zapytań

Dostawca wartości trasy w ASP.NET Core oraz dostawca wartości z 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ć w różnych lokalizacjach.

Aby dostawca wartości tras ASP.NET Core oraz dostawca wartości z ciągu zapytania mogły zostać poddane konwersji uwzględniającej kontekst kulturowy:

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 może obsłużyć powiązanie modelu.

IFormFile i IFormFileCollection

Plik przesłany i dołączony do żądania HTTP. Obsługiwane jest IEnumerable<IFormFile> również w przypadku wielu plików.

Token anulowania (CancellationToken)

Akcje mogą opcjonalnie powiązać element CancellationToken jako parametr. Wiąże to RequestAborted, który sygnalizuje, kiedy połączenie bazowe żądania HTTP zostaje 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 formacie JSON, XML lub innym formacie. 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 formatery wejściowe oparte na formacie JSON do obsługi danych JSON. 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.

Wyklucz określone typy z powiązania modelu

Zachowanie systemów wiązania modelu i walidacji jest sterowane przez ModelMetadata. 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 typu określonego, dodaj SuppressChildValidationMetadataProvider w Program.cs. 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

Możesz rozszerzyć bindowanie modelu, pisząc niestandardowy wiązacz modelu i używając atrybutu kodu [ModelBinder], aby wybrać go dla danego obiektu docelowego. Dowiedz się więcej o niestandardowym wiązaniu modelu.

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, których należy użyć. Metoda zwraca false, 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 Razor Pages i aplikacjami MVC wykorzystującymi kontrolery i widoki, aby zapobiec nadmiernemu przesyłaniu danych.
  • Nie jest używany z internetowym interfejsem API, chyba że konsumowany z danych formularza, ciągów zapytań i danych trasy. Punkty końcowe internetowego interfejsu API, które używają formatu JSON, używają formatatorów wejściowych do deserializacji treści żądania do obiektu.

Aby uzyskać więcej informacji, zobacz TryUpdateModelAsync.

Atrybut [FromServices]

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 wstrzykiwania przez konstruktor na wypadek, gdy potrzebujesz usługi tylko wtedy, gdy wywoływana jest określona metoda.

Jeśli instancja danego typu nie jest zarejestrowana w kontenerze wstrzykiwania zależności, aplikacja zgłasza wyjątek przy próbie 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 ma wartości null 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 wią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 tekstowe na typy .NET.
  • Aktualizuje 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 GetById, czyli liczbę całkowitą 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 GetById, typ logiczny o nazwie dogsOnly.
  • Przegląda źródła i znajduje ciąg zapytania "DogsOnly=true". Dopasowywanie nazw nie uwzględnia 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.

Atrybut [BindProperty]

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 wersji ASP.NET Core 2.1 lub nowszej. Można zastosować do kontrolera lub PageModel klasy, aby powiązanie modelu kierowało się na wszystkie właściwości publiczne 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 przypisywania właściwości, która zawiera instancję modelu. W scenariuszach, w których chcesz, aby właściwości były powiązane z danymi z żądań GET, ustaw właściwość SupportsGet 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 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 [FromBody] do parametru, 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.

Kiedy [FromBody] jest zastosowane do parametru typu złożonego, wszelkie 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 właściwości Breed.

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ł. Możesz na przykład chcieć dane z plików cookie lub stanu 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 przykład dostawcy wartości i fabryki, który pobiera wartości z plików cookie. 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();

Kod przedstawiony umieszcza niestandardowego dostawcę wartości za wszystkimi wbudowanymi dostawcami 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 danych dopuszczające wartości null są ustawione na null.
  • Typy wartości niemające wartości null są ustawione na default(T). 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 obsługiwane są przez formatery danych wejściowych.

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 API z atrybutem [ApiController], nieprawidłowy stan modelu skutkuje automatyczną odpowiedzią HTTP 400.

Na stronie Razor ponownie wyświetl stronę 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 formularza Razor Pages. 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 ponownie wyświetlona przez kod z poprzedniego kroku, 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, na które wiązanie modelu może konwertować ciągi źródłowe, to następujące:

Typy złożone

Typ złożony musi mieć publiczny konstruktor domyślny i publiczne właściwości zapisywalne, które można powiązać. 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ść żądań JSON 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 tylko określone właściwości modelu Instructor są powiązane, gdy wywołana zostanie metoda OnPost.

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

Atrybut [Bind] może być użyty do ochrony przed nadpisywaniem 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. Aby zabezpieczyć się przed nadpisywaniem, zalecane są modele wyświetlania zamiast atrybutu [Bind]. Aby uzyskać więcej informacji, zobacz Informacja dotycząca zabezpieczeń dotycząca nadpisywania.

[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 podczas wiązania modelu.

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

    public string Name { get; set; }
}

Atrybut [BindRequired]

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 wią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] ...) muszą upewnić się, że są one numerowane sekwencyjnie, zaczynając 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"]="Chemia"
    • selectedCourses["2000"]="Ekonomia"

Powiązanie konstruktora i typy rekordów

Powiązanie modelu wymaga, aby złożone typy miały konstruktor bez parametrów. Zarówno formattery danych wejściowych System.Text.Json, 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

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

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:

  • Bądź typem rekordu.
  • Powinien posiadać 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ć wielkością liter.

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

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

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 do walidacji i wią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łym przypadku z konstruktorami podstawowymi oba te elementy 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 . Można jednak zaktualizować Age

Globalizacja zachowania wiązania modelu, danych tras i ciągów zapytań

Dostawca wartości trasy w ASP.NET Core oraz dostawca wartości z 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ć w różnych lokalizacjach.

Aby dostawca wartości tras ASP.NET Core oraz dostawca wartości z ciągu zapytania mogły zostać poddane konwersji uwzględniającej kontekst kulturowy:

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 może obsłużyć powiązanie modelu.

IFormFile i IFormFileCollection

Plik przesłany i dołączony do żądania HTTP. Obsługiwane jest IEnumerable<IFormFile> również w przypadku wielu plików.

Token anulowania (CancellationToken)

Akcje mogą opcjonalnie powiązać element CancellationToken jako parametr. Wiąże to RequestAborted, który sygnalizuje, kiedy połączenie bazowe żądania HTTP zostaje 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 formacie JSON, XML lub innym formacie. 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 formatery wejściowe oparte na formacie JSON do obsługi danych JSON. 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.

Wyklucz określone typy z powiązania modelu

Zachowanie systemów wiązania modelu i walidacji jest sterowane przez ModelMetadata. 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 typu określonego, dodaj SuppressChildValidationMetadataProvider w Startup.ConfigureServices. 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

Możesz rozszerzyć bindowanie modelu, pisząc niestandardowy wiązacz modelu i używając atrybutu kodu [ModelBinder], aby wybrać go dla danego obiektu docelowego. Dowiedz się więcej o niestandardowym wiązaniu modelu.

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, których należy użyć. Metoda zwraca false, 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 Razor Pages i aplikacjami MVC wykorzystującymi kontrolery i widoki, aby zapobiec nadmiernemu przesyłaniu danych.
  • Nie jest używany z internetowym interfejsem API, chyba że konsumowany z danych formularza, ciągów zapytań i danych trasy. Punkty końcowe internetowego interfejsu API, które używają formatu JSON, używają formatatorów wejściowych do deserializacji treści żądania do obiektu.

Aby uzyskać więcej informacji, zobacz TryUpdateModelAsync.

Atrybut [FromServices]

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 wstrzykiwania przez konstruktor na wypadek, gdy potrzebujesz usługi tylko wtedy, gdy wywoływana jest określona metoda.

Dodatkowe zasoby