Поделиться через


Привязка модели в ASP.NET Core

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 9 этой статьи.

В этой статье объясняется, что такое привязка модели, как это работает и как настроить ее поведение.

Что такое привязка модели

Контроллеры и Razor страницы работают с данными, поступающими из HTTP-запросов. Например, данные о маршруте могут предоставлять ключ записи, а опубликованные поля формы могут предоставлять значения для свойств модели. Написание кода для получения этих значений и их преобразования из строк в типы .NET будет утомительной задачей с высоким риском ошибок. Привязка модели позволяет автоматизировать этот процесс. Система привязки модели:

  • Извлекает данные из различных источников, таких как данные о маршруте, поля формы и строки запроса.
  • Предоставляет данные контроллерам и страницам в параметрах метода и Razor общедоступных свойствах.
  • Преобразует строковые данные в типы .NET.
  • Обновляет свойства сложных типов.

Пример

Предположим, у вас есть следующий метод действия:

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

И приложение получает запрос с этим URL-адресом:

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

Привязка модели выполняет следующие действия, после того, как система маршрутизации выберет метод действия:

  • Находит первый параметр GetById, целое число с именем id.
  • Просматривает доступные источники в HTTP-запросе и находит id = "2" в данных маршрута.
  • Преобразует строку "2" в целое число 2.
  • Находит следующий параметр GetById, логическое значение с именем dogsOnly.
  • Просматривает источники и находит "DogsOnly=true" в строке запроса. Сопоставление имен не учитывает регистр.
  • Преобразует строку "true" в логическое значение true.

Затем платформа вызывает метод GetById, передавая 2 для параметра id и true для параметра dogsOnly.

В предыдущем примере целевые объекты привязки модели — это параметры метода, которые являются простыми типами. Целевые объекты также могут быть свойствами сложного типа. После успешной привязки каждого свойства осуществляется проверка модели для этого свойства. Записи о данных, привязанных к модели, а также об ошибках привязки или проверки хранятся в ControllerBase.ModelState или PageModel.ModelState. Чтобы узнать об успешном выполнении этого процесса, приложение проверяет наличие флага ModelState.IsValid.

Целевые объекты

Привязка модели попытается найти значения для следующих типов целевых объектов:

  • Параметры метода действия контроллера, к которому направлен запрос.
  • Параметры метода обработчика Razor Pages, в который направляется запрос.
  • Открытые свойства контроллера или класса PageModel, если задано атрибутами.

Атрибут [BindProperty]

Может применяться к открытому свойству контроллера или класса PageModel для привязки модели для этого свойства:

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

    // ...
}

Атрибут [BindProperties]

Может применяться к контроллеру или классу PageModel, чтобы привязка модели была направлена на все открытые свойства этого класса:

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

    // ...
}

Привязка модели для HTTP-запросов GET

По умолчанию свойства не привязываются к HTTP-запросам GET. Как правило, для запроса GET вам нужен только параметр идентификатора записи. Идентификатор записи используется для поиска элемента в базе данных. Поэтому не нужно привязывать свойство, которое содержит экземпляр модели. Если вы хотите привязать свойства к данным от запросов GET, задайте для свойства SupportsGet значение true:

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

Привязка модели простых и сложных типов

Для типов, с которыми работает привязка данных, используются специальные определения. Простой тип преобразуется из одной строки с помощью TypeConverter или TryParse метода. Сложный тип преобразуется из нескольких входных значений. Платформа определяет разницу на основе существования или TypeConverter TryParse. Рекомендуется создать преобразователь типов или использовать TryParse для string SomeType преобразования, не требующего внешних ресурсов или нескольких входных данных.

Источники

По умолчанию привязка модели получает данные в виде пар "ключ-значение" из следующих источников в HTTP-запросе:

  1. Поля формы
  2. Текст запроса (для контроллеров, имеющих атрибут [ApiController].)
  3. Отправка данных
  4. Параметры строки запроса
  5. Отправленные файлы

Для каждого целевого параметра или свойства источники проверяются в порядке, указанном в предыдущем списке. Существует несколько исключений:

  • Значения строк маршрутизации и запроса используются только для простых типов.
  • Отправленные файлы привязаны только к типам целевых объектов, которые реализуют IFormFile или IEnumerable<IFormFile>.

Если источник по умолчанию неверен, используйте один из следующих атрибутов, чтобы указать источник:

  • [FromQuery]: возвращает значения из строки запроса.
  • [FromRoute]: возвращает значения из данных маршрута.
  • [FromForm]: возвращает значения из опубликованных полей формы.
  • [FromBody]: возвращает значения из текста запроса.
  • [FromHeader]: возвращает значения из заголовков HTTP.

Эти атрибуты:

  • Добавляются в свойства модели по отдельности, а не в класс модели, как показано в следующем примере:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • При необходимости принимают значение имени модели в конструкторе. Этот параметр предоставляется в том случае, если имя свойства не соответствует значению в запросе. Например, значение в запросе может быть заголовком с дефисом в имени, как показано в следующем примере:

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

Атрибут [FromBody]

Примените атрибут [FromBody] к параметру, чтобы заполнить его свойства из тела HTTP-запроса. Среда выполнения ASP.NET Core делегирует ответственность за считывание тела форматировщику входных данных. Форматировщики входных данных описываются далее в этой статье.

При применении [FromBody] к параметру сложного типа все атрибуты источника привязки, применяемые к его свойствам, игнорируются. Например, следующее действие Create указывает, что параметр pet заполняется из тела:

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

Класс Pet указывает, что свойство Breed заполняется из параметра строки запроса:

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

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

В предыдущем примере:

  • Атрибут [FromQuery] не учитывается.
  • Свойство Breed не заполняется из параметра строки запроса.

Форматировщики входных данных считывают только тело и не распознают атрибуты источника привязки. Если подходящее значение найдено в теле, оно используется для заполнения свойства Breed.

Не применяют [FromBody] к нескольким параметрам в методе действия. После считывания потока запроса форматировщиком входных данных он больше не доступен для повторного чтения для привязки других параметров [FromBody].

Дополнительные источники

Исходные данные предоставляются системой привязки модели поставщиками значений. Вы можете записать и зарегистрировать пользовательские поставщики значений, которые получают данные для привязки модели из других источников. Например, вам могут потребоваться данные из файлов cookie или состояния сеанса. Для получения данных из нового источника:

  • Создайте класс, реализующий перехватчик IValueProvider.
  • Создайте класс, реализующий перехватчик IValueProviderFactory.
  • Зарегистрируйте класс фабрики в Program.cs.

Пример включает в себя поставщик значений и пример фабрики, который получает значения из файлов cookie. Регистрация фабрик поставщика пользовательских значений в Program.cs:

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

Предыдущий код помещает поставщика настраиваемых значений после всех встроенных поставщиков значений. Чтобы сделать его первым в списке, вызовите Insert(0, new CookieValueProviderFactory()) вместо Add.

Отсутствие источника для свойства модели

По умолчанию ошибка состояния модели не создается, если не найдено значение для свойства модели. Свойство получает значение NULL или значение по умолчанию:

  • Для простых типов, допускающих значение NULL, задано значение null.
  • Типам значений, не допускающим значение NULL, задается значение default(T). Например, параметр int id получает значение 0.
  • Для сложных типов привязка модели создает экземпляр с помощью конструктора по умолчанию без задания свойств.
  • Массивы имеют значение Array.Empty<T>(), за исключением массивов byte[], которые имеют значение null.

Если состояние модели должно быть признано недействительным, когда в полях формы не найдены данные для свойства модели, используйте атрибут [BindRequired].

Обратите внимание, это поведение [BindRequired] применяется к привязке модели из опубликованных данных формы, а не к данным JSON или XML в тексте запроса. Данные основного текста запроса обрабатываются форматировщиками входных данных.

Ошибки преобразования типа

Если источник найден, но его нельзя преобразовать в тип целевого объекта, состояние модели помечается как недопустимое. Параметр или свойство целевого объекта получает значение NULL или значение по умолчанию, как отмечалось в предыдущем разделе.

В контроллере API с атрибутом [ApiController] недопустимое состояние модели приводит к автоматическому ответу HTTP 400.

Razor На странице переиграйте страницу с сообщением об ошибке:

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

    // ...

    return RedirectToPage("./Index");
}

При повторном воспроизведении страницы в приведенном выше коде недопустимые входные данные не отображаются в поле формы. Это связано с тем, что свойству модели задано значение NULL или значение по умолчанию. Недопустимые входные данные отображаются в сообщении об ошибке. Если вы хотите повторно воспроизвести плохие данные в поле формы, попробуйте сделать свойство модели строкой и выполнять преобразование данных вручную.

Та же стратегия рекомендуется в том случае, если вы не хотите, чтобы ошибки преобразования типов приводили к ошибкам состояния модели. В этом случае следует сделать свойство модели строкой.

Простые типы

Сведения о простых и сложных типах см. в статье "Привязка модели" для объяснения простых и сложных типов.

Связыватель модели может преобразовать исходные строки в следующие примитивные типы:

Привязка с помощью IParsable<T>.TryParse

IParsable<TSelf>.TryParse API поддерживает значения параметров действия контроллера привязки:

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

DateRange Следующий класс реализует для поддержки IParsable<TSelf> привязки диапазона дат:

public class DateRange : IParsable<DateRange>
{
    public DateOnly? From { get; init; }
    public DateOnly? To { get; init; }

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

        return result;
    }

    public static bool TryParse(string? value,
                                IFormatProvider? provider, out DateRange dateRange)
    {
        var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries 
                                       | StringSplitOptions.TrimEntries);

        if (segments?.Length == 2
            && DateOnly.TryParse(segments[0], provider, out var fromDate)
            && DateOnly.TryParse(segments[1], provider, out var toDate))
        {
            dateRange = new DateRange { From = fromDate, To = toDate };
            return true;
        }

        dateRange = new DateRange { From = default, To = default };
        return false;
    }
}

Предыдущий код:

  • Преобразует строку, представляющую две даты в DateRange объект
  • Привязыватель модели использует IParsable<TSelf>.TryParse метод для привязки DateRange.

Следующее действие контроллера использует DateRange класс для привязки диапазона дат:

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

Locale Следующий класс реализует для поддержки привязки IParsable<TSelf> к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;
        }
    }
}

Следующее действие контроллера использует Locale класс для привязки 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);
}

Следующее действие контроллера использует DateRange и Locale классы для привязки диапазона дат к CultureInfo:

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

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

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

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

    return View("Index", weatherForecasts);
}

Пример приложения API на GitHub показывает предыдущий пример для контроллера API.

Привязка с помощью TryParse

TryParse API поддерживает значения параметров действия контроллера привязки:

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

IParsable<T>.TryParse рекомендуется использовать для привязки параметров, так как в отличие TryParseот отражения.

DateRangeTP Следующий класс реализуетTryParse:

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

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

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

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

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

Следующее действие контроллера использует DateRangeTP класс для привязки диапазона дат:

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

Сложные типы

Сложный тип должен иметь открытый конструктор по умолчанию, а также открытые и доступные для записи свойства, подлежащие привязке. Когда происходит привязка модели, класс создается с помощью открытого конструктора по умолчанию.

Для каждого свойства сложного типа привязка модели просматривает источники для шаблона имен prefix.property_name. Если ничего не найдено, он ищет только property_name без префикса. Решение об использовании префикса не принимается для каждого свойства. Например, при запросе, содержающем метод, привязанном к методуOnGet(Instructor instructor), результирующий ?Instructor.Id=100&Name=fooобъект типа Instructor содержит:

  • Id установите значение 100.
  • Name установите значение null. Привязка модели ожидает, Instructor.Name так как Instructor.Id она использовалась в предыдущем параметре запроса.

Для привязки к параметру префикс является именем параметра. Для привязки к открытому свойству PageModel префикс является именем открытого свойства. Некоторые атрибуты имеют свойство Prefix, которое позволяет переопределить использование по умолчанию для имени параметра или свойства.

Предположим, например, что сложный тип принадлежит к следующему классу Instructor:

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

Префикс — это имя параметра

Если модель, которую нужно привязать, является параметром с именем instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Привязка модели начинается с поиска источников ключа instructorToUpdate.ID. Если он не найден, ищется ID без префикса.

Префикс — это имя свойства

Если модель для привязки является свойством с именем Instructor контроллера или класса PageModel:

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

Привязка модели начинается с поиска источников ключа Instructor.ID. Если он не найден, ищется ID без префикса.

Пользовательский префикс

Если модель для привязки — это параметр с именем instructorToUpdate, а атрибут Bind задает Instructor как префикс:

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

Привязка модели начинается с поиска источников ключа Instructor.ID. Если он не найден, ищется ID без префикса.

Атрибуты для целевых объектов сложного типа

Несколько встроенных атрибутов доступны для управления привязкой моделей сложных типов:

Предупреждение

Эти атрибуты влияют на привязку модели, если опубликованные данные формы являются источником значений. Они не влияют на входные форматировщики, которые обрабатывают опубликованные тексты ЗАПРОСОВ JSON и XML. Форматировщики входных данных описываются далее в этой статье.

Атрибут [Bind]

Может быть применен к классу или параметру метода. Указывает, какие свойства модели должны быть включены в привязку модели. [Bind]не влияет на входные форматировщики.

В следующем примере только указанные свойства модели Instructor привязываются, когда вызывается любой метод действия или обработчик:

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

В следующем примере только указанные свойства модели Instructor привязываются, когда вызывается метод OnPost:

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

Атрибут [Bind] может использоваться для защиты от чрезмерной передачи данных при создании. Он не работает в сценариях редактирования, поскольку исключенным свойствам задается значение NULL или значение по умолчанию, но не оставляется значение без изменений. Для защиты от перепоставки модели представления рекомендуется вместо атрибута [Bind] . Дополнительные сведения см. в разделе Примечание по безопасности о чрезмерной передаче данных.

Атрибут [ModelBinder]

ModelBinderAttribute можно применять к типам, свойствам или параметрам. Он позволяет указать тип привязки модели, используемый для привязки конкретного экземпляра или типа. Например:

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

Атрибут [ModelBinder] также можно использовать для изменения имени свойства или параметра при привязке модели:

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

    // ...
}

Атрибут [BindRequired]

Приводит к тому, что привязка модели добавляет ошибку состояния модели, если привязка для свойства модели невозможна. Приведем пример:

public class InstructorBindRequired
{
    // ...

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

Также см. обсуждение атрибута [Required] в разделе Проверка модели.

Атрибут [BindNever]

Может применяться к свойству или типу. Запрещает привязке модели задавать свойство модели. При применении к типу система привязки модели исключает все свойства, которые определяет тип. Приведем пример:

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

    // ...
}

Коллекции

Для целевых объектов, которые являются коллекциями примитивных типов, привязка модели ищет совпадения с parameter_name или property_name. Если совпадений не найдено, она ищет один из поддерживаемых форматов без префикса. Например:

  • Предположим, что параметром для привязки является массив с именем selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Строковые данные формы или запроса могут иметь один из следующих форматов:

    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
    

    Избегайте привязки параметра или свойства с именем index или Index если он находится рядом со значением коллекции. Привязка модели пытается использовать index в качестве индекса для коллекции, что может привести к неправильной привязке. Например, рассмотрим следующее действие:

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

    В приведенном выше коде index параметр строки запроса привязывается к index параметру метода, а также используется для привязки коллекции продуктов. Переименование index параметра или использование атрибута привязки модели для настройки привязки позволяет избежать этой проблемы:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Следующий формат поддерживается только в данных формы:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Для всех перечисленных ранее форматов привязка модели передает массив из двух элементов в параметр selectedCourses:

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

    Форматы данных, которые используют номера нижних индексов (... [0] ... [1] ...), должны нумероваться последовательно, начиная с нуля. Если в нумерации есть пробелы, все элементы после пробела не учитываются. Например, если указаны индексы 0 и 2, а не 0 и 1, второй элемент игнорируется.

Словари

Для целевых объектов Dictionary привязка модели ищет совпадения с parameter_name или property_name. Если совпадений не найдено, она ищет один из поддерживаемых форматов без префикса. Например:

  • Предположим, что целевой параметр является Dictionary<int, string> с именем selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Опубликованные строковые данные формы или запроса могут выглядеть как один из следующих примеров:

    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
    
  • Для всех перечисленных ранее форматов привязка модели передает словарь из двух элементов в параметр selectedCourses:

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

Привязка конструктора и типы записей

Для привязки модели требуется, чтобы сложные типы имели конструктор без параметров. Newtonsoft.Json Оба System.Text.Json и основанные входные формататоры поддерживают десериализацию классов, которые не имеют конструктора без параметров.

Типы записей — отличный способ представить данные по сети. ASP.NET Core поддерживает привязку моделей и проверку типов записей с помощью одного конструктора:

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>

При проверке типов записей среда выполнения выполняет поиск метаданных привязки и проверки, а не параметров, а не свойств.

Платформа позволяет привязывать и проверять типы записей:

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

Для работы предыдущего типа необходимо:

  • Будьте типом записи.
  • Есть ровно один открытый конструктор.
  • Содержит параметры, имеющие свойство с одинаковым именем и типом. Имена не должны отличаться по регистру.

POCOs без конструкторов без параметров

Объекты POC, у которых нет конструкторов без параметров, не могут быть привязаны.

Следующий код приводит к исключению, указывающее, что тип должен иметь конструктор без параметров:

public class Person(string Name)

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

Типы записей с созданными вручную конструкторами

Типы записей с созданными вручную конструкторами, которые выглядят как основные конструкторы

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

Типы записей, проверка и привязка метаданных

Для типов записей используется метаданные проверки и привязки к параметрам. Все метаданные свойств игнорируются

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

Проверка и метаданные

Проверка использует метаданные для параметра, но использует свойство для чтения значения. В обычном случае с основными конструкторами они будут идентичны. Однако есть способы победить его:

public record Person([Required] string Name)
{
    private readonly string _name;

    // The following property is never null.
    // However this object could have been constructed as "new Person(null)".
    public string Name { get; init => _name = value ?? string.Empty; }
}

TryUpdateModel не обновляет параметры типа записи

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

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

В этом случае MVC не попытается выполнить привязку Name снова. Тем не менее, Age разрешено обновлять

Поведение глобализации для данных маршрутов привязки модели и строк запросов

Поставщик значений маршрутов и поставщик значений для строк запросов ASP.NET Core:

  • обрабатывают значения как имеющие инвариантные язык и региональные параметры.
  • Следует ожидать, что URL-адреса имеют инвариантные язык и региональные параметры.

Напротив, значения, поступающие из данных форм, подвергаются преобразованию с учетом языка и региональных параметров. Это сделано намеренно, чтобы URL-адреса были общими в разных языковых стандартах.

Чтобы поставщик значений маршрутов и поставщик значений для строк запросов ASP.NET Core производили преобразование с учетом языка и региональных параметров, выполните указанные ниже действия.

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

Специальные типы данных

Существуют некоторые особые типы данных, которые может обрабатывать привязка модели.

IFormFile и IFormFileCollection

Переданный файл, включенный в HTTP-запрос. Также поддерживается IEnumerable<IFormFile> для нескольких файлов.

CancellationToken

Действия могут при необходимости привязать как CancellationToken параметр. Это привязывает, что сигнализирует RequestAborted о прерванном подключении, базовом HTTP-запросе. Действия могут использовать этот параметр для отмены длительных асинхронных операций, выполняемых в рамках действий контроллера.

FormCollection

Используется для извлечения всех значений из опубликованных данных формы.

Форматировщики входных данных

Данные в тексте запроса могут быть в формате JSON, XML или другом формате. Для анализа этих данных модель привязки использует форматировщик входных данных, настроенный для обработки определенного типа содержимого. По умолчанию ASP.NET Core включает форматировщики входных данных на основе JSON для обработки данных JSON. Вы можете добавить другие форматировщики для других типов содержимого.

ASP.NET Core выбирает форматировщики входных данных на основе атрибута Consumes. Если атрибут отсутствует, используется Заголовок Content-Type.

Чтобы использовать встроенные форматировщики входных данных XML:

  • В Program.cs вызовите AddXmlSerializerFormatters или AddXmlDataContractSerializerFormatters.

    builder.Services.AddControllers()
        .AddXmlSerializerFormatters();
    
  • Примените атрибут Consumes к классам контроллера или методам действий, которые должны ожидать XML в тексте запроса.

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

    Дополнительные сведения см. в разделе Введение в сериализацию XML.

Настройка привязки модели с помощью форматировщиков входных данных

Форматировщик входных данных полностью отвечает за чтение данных из текста запроса. Чтобы настроить этот процесс, настройте API-интерфейсы, используемые форматировщиками входных данных. В этом разделе описывается, как настроить форматировщик входных данных на основе System.Text.Json так, чтобы он понимал настраиваемый тип с именем ObjectId.

Рассмотрим следующую модель, содержащую пользовательское ObjectId свойство:

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

Чтобы настроить процесс привязки модели при использовании System.Text.Json, создайте производный класс на основе класса JsonConverter<T>:

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

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

Чтобы использовать пользовательский преобразователь, примените к типу атрибут JsonConverterAttribute. В следующем примере тип ObjectId настраивается с ObjectIdConverter в качестве пользовательского преобразователя:

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

Дополнительные сведения см. в статье How to write custom converters for JSON serialization (marshalling) in .NET (Создание пользовательских преобразователей для сериализации JSON (маршалинг) в .NET).

Исключение указанных типов из привязки модели

Поведение систем привязки и проверки модели определяется ModelMetadata. Вы можете настроить ModelMetadata, добавив поставщик сведений в MvcOptions.ModelMetadataDetailsProviders. Встроенные поставщики сведений доступны для отключения привязки модели или проверки для указанных типов.

Чтобы отключить привязку модели для всех моделей указанного типа, добавьте ExcludeBindingMetadataProvider в Program.cs. Например, для отключения привязки модели для всех моделей типа System.Version:

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

Чтобы отключить проверку свойств указанного типа, добавьте SuppressChildValidationMetadataProvider в Program.cs. Например, чтобы отключить проверку по свойствам типа System.Guid:

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

Настраиваемые связыватели модели

Привязку модели можно расширить путем написания пользовательского связывателя модели и с помощью атрибута [ModelBinder], чтобы выбрать его для заданного целевого объекта. Узнайте больше о пользовательской привязке модели.

Привязка модели вручную

Привязка модели может вызываться вручную с помощью метода TryUpdateModelAsync. Этот метод определен в классах ControllerBase и PageModel. Перегрузки метода позволяют задать поставщик префиксов и значений. Этот метод возвращает false при сбое привязки модели. Приведем пример:

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

return Page();

TryUpdateModelAsync использует поставщиков значений для получения данных из текста формы, строки запроса и данных маршрута. TryUpdateModelAsync как правило:

  • Используется с Razor приложениями Pages и MVC с помощью контроллеров и представлений, чтобы предотвратить чрезмерное размещение.
  • не используется с веб-API, если только не используется из данных формы, строк запроса и данных маршрута. Конечные точки веб-API, которые используют JSON, применяют форматировщики входных данных для десериализации текста запроса в объект.

Дополнительные сведения см. в разделе TryUpdateModelAsync.

Атрибут [FromServices]

Имя этого атрибута соответствует шаблону атрибутов привязки модели, которые указывают источник данных. Но это не связано с привязкой данных от поставщика значений. Он получает экземпляр типа из контейнера внедрения зависимостей. Он предназначен для предоставления альтернативы внедрению через конструктор, когда вам нужна служба, только если вызывается конкретный метод.

Если экземпляр типа не зарегистрирован в контейнере внедрения зависимостей, приложение создает исключение при попытке привязать параметр. Чтобы сделать параметр необязательным, используйте один из следующих подходов:

  • Сделайте параметр пустым.
  • Задайте значение по умолчанию для параметра.

Для параметров, допускающих значение NULL, убедитесь, что параметр недоступен null перед доступом к нему.

Дополнительные ресурсы

В этой статье объясняется, что такое привязка модели, как это работает и как настроить ее поведение.

Что такое привязка модели

Контроллеры и Razor страницы работают с данными, поступающими из HTTP-запросов. Например, данные о маршруте могут предоставлять ключ записи, а опубликованные поля формы могут предоставлять значения для свойств модели. Написание кода для получения этих значений и их преобразования из строк в типы .NET будет утомительной задачей с высоким риском ошибок. Привязка модели позволяет автоматизировать этот процесс. Система привязки модели:

  • Извлекает данные из различных источников, таких как данные о маршруте, поля формы и строки запроса.
  • Предоставляет данные контроллерам и страницам в параметрах метода и Razor общедоступных свойствах.
  • Преобразует строковые данные в типы .NET.
  • Обновляет свойства сложных типов.

Пример

Предположим, у вас есть следующий метод действия:

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

И приложение получает запрос с этим URL-адресом:

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

Привязка модели выполняет следующие действия, после того, как система маршрутизации выберет метод действия:

  • Находит первый параметр GetById, целое число с именем id.
  • Просматривает доступные источники в HTTP-запросе и находит id = "2" в данных маршрута.
  • Преобразует строку "2" в целое число 2.
  • Находит следующий параметр GetById, логическое значение с именем dogsOnly.
  • Просматривает источники и находит "DogsOnly=true" в строке запроса. Сопоставление имен не учитывает регистр.
  • Преобразует строку "true" в логическое значение true.

Затем платформа вызывает метод GetById, передавая 2 для параметра id и true для параметра dogsOnly.

В предыдущем примере целевые объекты привязки модели — это параметры метода, которые являются простыми типами. Целевые объекты также могут быть свойствами сложного типа. После успешной привязки каждого свойства осуществляется проверка модели для этого свойства. Записи о данных, привязанных к модели, а также об ошибках привязки или проверки хранятся в ControllerBase.ModelState или PageModel.ModelState. Чтобы узнать об успешном выполнении этого процесса, приложение проверяет наличие флага ModelState.IsValid.

Целевые объекты

Привязка модели попытается найти значения для следующих типов целевых объектов:

  • Параметры метода действия контроллера, к которому направлен запрос.
  • Параметры метода обработчика Razor Pages, в который направляется запрос.
  • Открытые свойства контроллера или класса PageModel, если задано атрибутами.

Атрибут [BindProperty]

Может применяться к открытому свойству контроллера или класса PageModel для привязки модели для этого свойства:

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

    // ...
}

Атрибут [BindProperties]

Может применяться к контроллеру или классу PageModel, чтобы привязка модели была направлена на все открытые свойства этого класса:

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

    // ...
}

Привязка модели для HTTP-запросов GET

По умолчанию свойства не привязываются к HTTP-запросам GET. Как правило, для запроса GET вам нужен только параметр идентификатора записи. Идентификатор записи используется для поиска элемента в базе данных. Поэтому не нужно привязывать свойство, которое содержит экземпляр модели. Если вы хотите привязать свойства к данным от запросов GET, задайте для свойства SupportsGet значение true:

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

Привязка модели простых и сложных типов

Для типов, с которыми работает привязка данных, используются специальные определения. Простой тип преобразуется из одной строки с помощью TypeConverter или TryParse метода. Сложный тип преобразуется из нескольких входных значений. Платформа определяет разницу на основе существования или TypeConverter TryParse. Рекомендуется создать преобразователь типов или использовать TryParse для string SomeType преобразования, не требующего внешних ресурсов или нескольких входных данных.

Источники

По умолчанию привязка модели получает данные в виде пар "ключ-значение" из следующих источников в HTTP-запросе:

  1. Поля формы
  2. Текст запроса (для контроллеров, имеющих атрибут [ApiController].)
  3. Отправка данных
  4. Параметры строки запроса
  5. Отправленные файлы

Для каждого целевого параметра или свойства источники проверяются в порядке, указанном в предыдущем списке. Существует несколько исключений:

  • Значения строк маршрутизации и запроса используются только для простых типов.
  • Отправленные файлы привязаны только к типам целевых объектов, которые реализуют IFormFile или IEnumerable<IFormFile>.

Если источник по умолчанию неверен, используйте один из следующих атрибутов, чтобы указать источник:

  • [FromQuery]: возвращает значения из строки запроса.
  • [FromRoute]: возвращает значения из данных маршрута.
  • [FromForm]: возвращает значения из опубликованных полей формы.
  • [FromBody]: возвращает значения из текста запроса.
  • [FromHeader]: возвращает значения из заголовков HTTP.

Эти атрибуты:

  • Добавляются в свойства модели по отдельности, а не в класс модели, как показано в следующем примере:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • При необходимости принимают значение имени модели в конструкторе. Этот параметр предоставляется в том случае, если имя свойства не соответствует значению в запросе. Например, значение в запросе может быть заголовком с дефисом в имени, как показано в следующем примере:

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

Атрибут [FromBody]

Примените атрибут [FromBody] к параметру, чтобы заполнить его свойства из тела HTTP-запроса. Среда выполнения ASP.NET Core делегирует ответственность за считывание тела форматировщику входных данных. Форматировщики входных данных описываются далее в этой статье.

При применении [FromBody] к параметру сложного типа все атрибуты источника привязки, применяемые к его свойствам, игнорируются. Например, следующее действие Create указывает, что параметр pet заполняется из тела:

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

Класс Pet указывает, что свойство Breed заполняется из параметра строки запроса:

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

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

В предыдущем примере:

  • Атрибут [FromQuery] не учитывается.
  • Свойство Breed не заполняется из параметра строки запроса.

Форматировщики входных данных считывают только тело и не распознают атрибуты источника привязки. Если подходящее значение найдено в теле, оно используется для заполнения свойства Breed.

Не применяют [FromBody] к нескольким параметрам в методе действия. После считывания потока запроса форматировщиком входных данных он больше не доступен для повторного чтения для привязки других параметров [FromBody].

Дополнительные источники

Исходные данные предоставляются системой привязки модели поставщиками значений. Вы можете записать и зарегистрировать пользовательские поставщики значений, которые получают данные для привязки модели из других источников. Например, вам могут потребоваться данные из файлов cookie или состояния сеанса. Для получения данных из нового источника:

  • Создайте класс, реализующий перехватчик IValueProvider.
  • Создайте класс, реализующий перехватчик IValueProviderFactory.
  • Зарегистрируйте класс фабрики в Program.cs.

Пример включает в себя поставщик значений и пример фабрики, который получает значения из файлов cookie. Регистрация фабрик поставщика пользовательских значений в Program.cs:

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

Предыдущий код помещает поставщика настраиваемых значений после всех встроенных поставщиков значений. Чтобы сделать его первым в списке, вызовите Insert(0, new CookieValueProviderFactory()) вместо Add.

Отсутствие источника для свойства модели

По умолчанию ошибка состояния модели не создается, если не найдено значение для свойства модели. Свойство получает значение NULL или значение по умолчанию:

  • Для простых типов, допускающих значение NULL, задано значение null.
  • Типам значений, не допускающим значение NULL, задается значение default(T). Например, параметр int id получает значение 0.
  • Для сложных типов привязка модели создает экземпляр с помощью конструктора по умолчанию без задания свойств.
  • Массивы имеют значение Array.Empty<T>(), за исключением массивов byte[], которые имеют значение null.

Если состояние модели должно быть признано недействительным, когда в полях формы не найдены данные для свойства модели, используйте атрибут [BindRequired].

Обратите внимание, это поведение [BindRequired] применяется к привязке модели из опубликованных данных формы, а не к данным JSON или XML в тексте запроса. Данные основного текста запроса обрабатываются форматировщиками входных данных.

Ошибки преобразования типа

Если источник найден, но его нельзя преобразовать в тип целевого объекта, состояние модели помечается как недопустимое. Параметр или свойство целевого объекта получает значение NULL или значение по умолчанию, как отмечалось в предыдущем разделе.

В контроллере API с атрибутом [ApiController] недопустимое состояние модели приводит к автоматическому ответу HTTP 400.

Razor На странице переиграйте страницу с сообщением об ошибке:

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

    // ...

    return RedirectToPage("./Index");
}

При повторном воспроизведении страницы в приведенном выше коде недопустимые входные данные не отображаются в поле формы. Это связано с тем, что свойству модели задано значение NULL или значение по умолчанию. Недопустимые входные данные отображаются в сообщении об ошибке. Если вы хотите повторно воспроизвести плохие данные в поле формы, попробуйте сделать свойство модели строкой и выполнять преобразование данных вручную.

Та же стратегия рекомендуется в том случае, если вы не хотите, чтобы ошибки преобразования типов приводили к ошибкам состояния модели. В этом случае следует сделать свойство модели строкой.

Простые типы

Сведения о простых и сложных типах см. в статье "Привязка модели" для объяснения простых и сложных типов.

Связыватель модели может преобразовать исходные строки в следующие примитивные типы:

Привязка с помощью IParsable<T>.TryParse

IParsable<TSelf>.TryParse API поддерживает значения параметров действия контроллера привязки:

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

DateRange Следующий класс реализует для поддержки IParsable<TSelf> привязки диапазона дат:

public class DateRange : IParsable<DateRange>
{
    public DateOnly? From { get; init; }
    public DateOnly? To { get; init; }

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

        return result;
    }

    public static bool TryParse(string? value,
                                IFormatProvider? provider, out DateRange dateRange)
    {
        var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries 
                                       | StringSplitOptions.TrimEntries);

        if (segments?.Length == 2
            && DateOnly.TryParse(segments[0], provider, out var fromDate)
            && DateOnly.TryParse(segments[1], provider, out var toDate))
        {
            dateRange = new DateRange { From = fromDate, To = toDate };
            return true;
        }

        dateRange = new DateRange { From = default, To = default };
        return false;
    }
}

Предыдущий код:

  • Преобразует строку, представляющую две даты в DateRange объект
  • Привязыватель модели использует IParsable<TSelf>.TryParse метод для привязки DateRange.

Следующее действие контроллера использует DateRange класс для привязки диапазона дат:

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

Locale Следующий класс реализует для поддержки привязки IParsable<TSelf> к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;
        }
    }
}

Следующее действие контроллера использует Locale класс для привязки 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);
}

Следующее действие контроллера использует DateRange и Locale классы для привязки диапазона дат к CultureInfo:

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

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

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

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

    return View("Index", weatherForecasts);
}

Пример приложения API на GitHub показывает предыдущий пример для контроллера API.

Привязка с помощью TryParse

TryParse API поддерживает значения параметров действия контроллера привязки:

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

IParsable<T>.TryParse рекомендуется использовать для привязки параметров, так как в отличие TryParseот отражения.

DateRangeTP Следующий класс реализуетTryParse:

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

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

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

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

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

Следующее действие контроллера использует DateRangeTP класс для привязки диапазона дат:

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

Сложные типы

Сложный тип должен иметь открытый конструктор по умолчанию, а также открытые и доступные для записи свойства, подлежащие привязке. Когда происходит привязка модели, класс создается с помощью открытого конструктора по умолчанию.

Для каждого свойства сложного типа привязка модели просматривает источники для шаблона имен prefix.property_name. Если ничего не найдено, он ищет только property_name без префикса. Решение об использовании префикса не принимается для каждого свойства. Например, при запросе, содержающем метод, привязанном к методуOnGet(Instructor instructor), результирующий ?Instructor.Id=100&Name=fooобъект типа Instructor содержит:

  • Id установите значение 100.
  • Name установите значение null. Привязка модели ожидает, Instructor.Name так как Instructor.Id она использовалась в предыдущем параметре запроса.

Для привязки к параметру префикс является именем параметра. Для привязки к открытому свойству PageModel префикс является именем открытого свойства. Некоторые атрибуты имеют свойство Prefix, которое позволяет переопределить использование по умолчанию для имени параметра или свойства.

Предположим, например, что сложный тип принадлежит к следующему классу Instructor:

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

Префикс — это имя параметра

Если модель, которую нужно привязать, является параметром с именем instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Привязка модели начинается с поиска источников ключа instructorToUpdate.ID. Если он не найден, ищется ID без префикса.

Префикс — это имя свойства

Если модель для привязки является свойством с именем Instructor контроллера или класса PageModel:

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

Привязка модели начинается с поиска источников ключа Instructor.ID. Если он не найден, ищется ID без префикса.

Пользовательский префикс

Если модель для привязки — это параметр с именем instructorToUpdate, а атрибут Bind задает Instructor как префикс:

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

Привязка модели начинается с поиска источников ключа Instructor.ID. Если он не найден, ищется ID без префикса.

Атрибуты для целевых объектов сложного типа

Несколько встроенных атрибутов доступны для управления привязкой моделей сложных типов:

Предупреждение

Эти атрибуты влияют на привязку модели, если опубликованные данные формы являются источником значений. Они не влияют на входные форматировщики, которые обрабатывают опубликованные тексты ЗАПРОСОВ JSON и XML. Форматировщики входных данных описываются далее в этой статье.

Атрибут [Bind]

Может быть применен к классу или параметру метода. Указывает, какие свойства модели должны быть включены в привязку модели. [Bind]не влияет на входные форматировщики.

В следующем примере только указанные свойства модели Instructor привязываются, когда вызывается любой метод действия или обработчик:

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

В следующем примере только указанные свойства модели Instructor привязываются, когда вызывается метод OnPost:

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

Атрибут [Bind] может использоваться для защиты от чрезмерной передачи данных при создании. Он не работает в сценариях редактирования, поскольку исключенным свойствам задается значение NULL или значение по умолчанию, но не оставляется значение без изменений. Для защиты от перепоставки модели представления рекомендуется вместо атрибута [Bind] . Дополнительные сведения см. в разделе Примечание по безопасности о чрезмерной передаче данных.

Атрибут [ModelBinder]

ModelBinderAttribute можно применять к типам, свойствам или параметрам. Он позволяет указать тип привязки модели, используемый для привязки конкретного экземпляра или типа. Например:

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

Атрибут [ModelBinder] также можно использовать для изменения имени свойства или параметра при привязке модели:

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

    // ...
}

Атрибут [BindRequired]

Приводит к тому, что привязка модели добавляет ошибку состояния модели, если привязка для свойства модели невозможна. Приведем пример:

public class InstructorBindRequired
{
    // ...

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

Также см. обсуждение атрибута [Required] в разделе Проверка модели.

Атрибут [BindNever]

Может применяться к свойству или типу. Запрещает привязке модели задавать свойство модели. При применении к типу система привязки модели исключает все свойства, которые определяет тип. Приведем пример:

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

    // ...
}

Коллекции

Для целевых объектов, которые являются коллекциями примитивных типов, привязка модели ищет совпадения с parameter_name или property_name. Если совпадений не найдено, она ищет один из поддерживаемых форматов без префикса. Например:

  • Предположим, что параметром для привязки является массив с именем selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Строковые данные формы или запроса могут иметь один из следующих форматов:

    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
    

    Избегайте привязки параметра или свойства с именем index или Index если он находится рядом со значением коллекции. Привязка модели пытается использовать index в качестве индекса для коллекции, что может привести к неправильной привязке. Например, рассмотрим следующее действие:

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

    В приведенном выше коде index параметр строки запроса привязывается к index параметру метода, а также используется для привязки коллекции продуктов. Переименование index параметра или использование атрибута привязки модели для настройки привязки позволяет избежать этой проблемы:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Следующий формат поддерживается только в данных формы:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Для всех перечисленных ранее форматов привязка модели передает массив из двух элементов в параметр selectedCourses:

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

    Форматы данных, которые используют номера нижних индексов (... [0] ... [1] ...), должны нумероваться последовательно, начиная с нуля. Если в нумерации есть пробелы, все элементы после пробела не учитываются. Например, если указаны индексы 0 и 2, а не 0 и 1, второй элемент игнорируется.

Словари

Для целевых объектов Dictionary привязка модели ищет совпадения с parameter_name или property_name. Если совпадений не найдено, она ищет один из поддерживаемых форматов без префикса. Например:

  • Предположим, что целевой параметр является Dictionary<int, string> с именем selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Опубликованные строковые данные формы или запроса могут выглядеть как один из следующих примеров:

    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
    
  • Для всех перечисленных ранее форматов привязка модели передает словарь из двух элементов в параметр selectedCourses:

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

Привязка конструктора и типы записей

Для привязки модели требуется, чтобы сложные типы имели конструктор без параметров. Newtonsoft.Json Оба System.Text.Json и основанные входные формататоры поддерживают десериализацию классов, которые не имеют конструктора без параметров.

Типы записей — отличный способ представить данные по сети. ASP.NET Core поддерживает привязку моделей и проверку типов записей с помощью одного конструктора:

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>

При проверке типов записей среда выполнения выполняет поиск метаданных привязки и проверки, а не параметров, а не свойств.

Платформа позволяет привязывать и проверять типы записей:

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

Для работы предыдущего типа необходимо:

  • Будьте типом записи.
  • Есть ровно один открытый конструктор.
  • Содержит параметры, имеющие свойство с одинаковым именем и типом. Имена не должны отличаться по регистру.

POCOs без конструкторов без параметров

Объекты POC, у которых нет конструкторов без параметров, не могут быть привязаны.

Следующий код приводит к исключению, указывающее, что тип должен иметь конструктор без параметров:

public class Person(string Name)

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

Типы записей с созданными вручную конструкторами

Типы записей с созданными вручную конструкторами, которые выглядят как основные конструкторы

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

Типы записей, проверка и привязка метаданных

Для типов записей используется метаданные проверки и привязки к параметрам. Все метаданные свойств игнорируются

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

Проверка и метаданные

Проверка использует метаданные для параметра, но использует свойство для чтения значения. В обычном случае с основными конструкторами они будут идентичны. Однако есть способы победить его:

public record Person([Required] string Name)
{
    private readonly string _name;

    // The following property is never null.
    // However this object could have been constructed as "new Person(null)".
    public string Name { get; init => _name = value ?? string.Empty; }
}

TryUpdateModel не обновляет параметры типа записи

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

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

В этом случае MVC не попытается выполнить привязку Name снова. Тем не менее, Age разрешено обновлять

Поведение глобализации для данных маршрутов привязки модели и строк запросов

Поставщик значений маршрутов и поставщик значений для строк запросов ASP.NET Core:

  • обрабатывают значения как имеющие инвариантные язык и региональные параметры.
  • Следует ожидать, что URL-адреса имеют инвариантные язык и региональные параметры.

Напротив, значения, поступающие из данных форм, подвергаются преобразованию с учетом языка и региональных параметров. Это сделано намеренно, чтобы URL-адреса были общими в разных языковых стандартах.

Чтобы поставщик значений маршрутов и поставщик значений для строк запросов ASP.NET Core производили преобразование с учетом языка и региональных параметров, выполните указанные ниже действия.

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

Специальные типы данных

Существуют некоторые особые типы данных, которые может обрабатывать привязка модели.

IFormFile и IFormFileCollection

Переданный файл, включенный в HTTP-запрос. Также поддерживается IEnumerable<IFormFile> для нескольких файлов.

CancellationToken

Действия могут при необходимости привязать как CancellationToken параметр. Это привязывает, что сигнализирует RequestAborted о прерванном подключении, базовом HTTP-запросе. Действия могут использовать этот параметр для отмены длительных асинхронных операций, выполняемых в рамках действий контроллера.

FormCollection

Используется для извлечения всех значений из опубликованных данных формы.

Форматировщики входных данных

Данные в тексте запроса могут быть в формате JSON, XML или другом формате. Для анализа этих данных модель привязки использует форматировщик входных данных, настроенный для обработки определенного типа содержимого. По умолчанию ASP.NET Core включает форматировщики входных данных на основе JSON для обработки данных JSON. Вы можете добавить другие форматировщики для других типов содержимого.

ASP.NET Core выбирает форматировщики входных данных на основе атрибута Consumes. Если атрибут отсутствует, используется Заголовок Content-Type.

Чтобы использовать встроенные форматировщики входных данных XML:

  • В Program.cs вызовите AddXmlSerializerFormatters или AddXmlDataContractSerializerFormatters.

    builder.Services.AddControllers()
        .AddXmlSerializerFormatters();
    
  • Примените атрибут Consumes к классам контроллера или методам действий, которые должны ожидать XML в тексте запроса.

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

    Дополнительные сведения см. в разделе Введение в сериализацию XML.

Настройка привязки модели с помощью форматировщиков входных данных

Форматировщик входных данных полностью отвечает за чтение данных из текста запроса. Чтобы настроить этот процесс, настройте API-интерфейсы, используемые форматировщиками входных данных. В этом разделе описывается, как настроить форматировщик входных данных на основе System.Text.Json так, чтобы он понимал настраиваемый тип с именем ObjectId.

Рассмотрим следующую модель, содержащую пользовательское ObjectId свойство:

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

Чтобы настроить процесс привязки модели при использовании System.Text.Json, создайте производный класс на основе класса JsonConverter<T>:

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

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

Чтобы использовать пользовательский преобразователь, примените к типу атрибут JsonConverterAttribute. В следующем примере тип ObjectId настраивается с ObjectIdConverter в качестве пользовательского преобразователя:

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

Дополнительные сведения см. в статье How to write custom converters for JSON serialization (marshalling) in .NET (Создание пользовательских преобразователей для сериализации JSON (маршалинг) в .NET).

Исключение указанных типов из привязки модели

Поведение систем привязки и проверки модели определяется ModelMetadata. Вы можете настроить ModelMetadata, добавив поставщик сведений в MvcOptions.ModelMetadataDetailsProviders. Встроенные поставщики сведений доступны для отключения привязки модели или проверки для указанных типов.

Чтобы отключить привязку модели для всех моделей указанного типа, добавьте ExcludeBindingMetadataProvider в Program.cs. Например, для отключения привязки модели для всех моделей типа System.Version:

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

Чтобы отключить проверку свойств указанного типа, добавьте SuppressChildValidationMetadataProvider в Program.cs. Например, чтобы отключить проверку по свойствам типа System.Guid:

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

Настраиваемые связыватели модели

Привязку модели можно расширить путем написания пользовательского связывателя модели и с помощью атрибута [ModelBinder], чтобы выбрать его для заданного целевого объекта. Узнайте больше о пользовательской привязке модели.

Привязка модели вручную

Привязка модели может вызываться вручную с помощью метода TryUpdateModelAsync. Этот метод определен в классах ControllerBase и PageModel. Перегрузки метода позволяют задать поставщик префиксов и значений. Этот метод возвращает false при сбое привязки модели. Приведем пример:

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

return Page();

TryUpdateModelAsync использует поставщиков значений для получения данных из текста формы, строки запроса и данных маршрута. TryUpdateModelAsync как правило:

  • Используется с Razor приложениями Pages и MVC с помощью контроллеров и представлений, чтобы предотвратить чрезмерное размещение.
  • не используется с веб-API, если только не используется из данных формы, строк запроса и данных маршрута. Конечные точки веб-API, которые используют JSON, применяют форматировщики входных данных для десериализации текста запроса в объект.

Дополнительные сведения см. в разделе TryUpdateModelAsync.

Атрибут [FromServices]

Имя этого атрибута соответствует шаблону атрибутов привязки модели, которые указывают источник данных. Но это не связано с привязкой данных от поставщика значений. Он получает экземпляр типа из контейнера внедрения зависимостей. Он предназначен для предоставления альтернативы внедрению через конструктор, когда вам нужна служба, только если вызывается конкретный метод.

Если экземпляр типа не зарегистрирован в контейнере внедрения зависимостей, приложение создает исключение при попытке привязать параметр. Чтобы сделать параметр необязательным, используйте один из следующих подходов:

  • Сделайте параметр пустым.
  • Задайте значение по умолчанию для параметра.

Для параметров, допускающих значение NULL, убедитесь, что параметр недоступен null перед доступом к нему.

Дополнительные ресурсы

В этой статье объясняется, что такое привязка модели, как это работает и как настроить ее поведение.

Что такое привязка модели

Контроллеры и Razor страницы работают с данными, поступающими из HTTP-запросов. Например, данные о маршруте могут предоставлять ключ записи, а опубликованные поля формы могут предоставлять значения для свойств модели. Написание кода для получения этих значений и их преобразования из строк в типы .NET будет утомительной задачей с высоким риском ошибок. Привязка модели позволяет автоматизировать этот процесс. Система привязки модели:

  • Извлекает данные из различных источников, таких как данные о маршруте, поля формы и строки запроса.
  • Предоставляет данные контроллерам и страницам в параметрах метода и Razor общедоступных свойствах.
  • Преобразует строковые данные в типы .NET.
  • Обновляет свойства сложных типов.

Пример

Предположим, у вас есть следующий метод действия:

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

И приложение получает запрос с этим URL-адресом:

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

Привязка модели выполняет следующие действия, после того, как система маршрутизации выберет метод действия:

  • Находит первый параметр GetById, целое число с именем id.
  • Просматривает доступные источники в HTTP-запросе и находит id = "2" в данных маршрута.
  • Преобразует строку "2" в целое число 2.
  • Находит следующий параметр GetById, логическое значение с именем dogsOnly.
  • Просматривает источники и находит "DogsOnly=true" в строке запроса. Сопоставление имен не учитывает регистр.
  • Преобразует строку "true" в логическое значение true.

Затем платформа вызывает метод GetById, передавая 2 для параметра id и true для параметра dogsOnly.

В приведенном выше примере целевые объекты привязки модели — это параметры методов, которые являются примитивными типами. Целевые объекты также могут быть свойствами сложного типа. После успешной привязки каждого свойства осуществляется проверка модели для этого свойства. Записи о данных, привязанных к модели, а также об ошибках привязки или проверки хранятся в ControllerBase.ModelState или PageModel.ModelState. Чтобы узнать об успешном выполнении этого процесса, приложение проверяет наличие флага ModelState.IsValid.

Целевые объекты

Привязка модели попытается найти значения для следующих типов целевых объектов:

  • Параметры метода действия контроллера, к которому направлен запрос.
  • Параметры метода обработчика Razor Pages, в который направляется запрос.
  • Открытые свойства контроллера или класса PageModel, если задано атрибутами.

Атрибут [BindProperty]

Может применяться к открытому свойству контроллера или класса PageModel для привязки модели для этого свойства:

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

    // ...
}

Атрибут [BindProperties]

Может применяться к контроллеру или классу PageModel, чтобы привязка модели была направлена на все открытые свойства этого класса:

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

    // ...
}

Привязка модели для HTTP-запросов GET

По умолчанию свойства не привязываются к HTTP-запросам GET. Как правило, для запроса GET вам нужен только параметр идентификатора записи. Идентификатор записи используется для поиска элемента в базе данных. Поэтому не нужно привязывать свойство, которое содержит экземпляр модели. Если вы хотите привязать свойства к данным от запросов GET, задайте для свойства SupportsGet значение true:

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

Источники

По умолчанию привязка модели получает данные в виде пар "ключ-значение" из следующих источников в HTTP-запросе:

  1. Поля формы
  2. Текст запроса (для контроллеров, имеющих атрибут [ApiController].)
  3. Отправка данных
  4. Параметры строки запроса
  5. Отправленные файлы

Для каждого целевого параметра или свойства источники проверяются в порядке, указанном в предыдущем списке. Существует несколько исключений:

  • Данные маршрутизации и значения строк запросов используются только для примитивных типов.
  • Отправленные файлы привязаны только к типам целевых объектов, которые реализуют IFormFile или IEnumerable<IFormFile>.

Если источник по умолчанию неверен, используйте один из следующих атрибутов, чтобы указать источник:

  • [FromQuery]: возвращает значения из строки запроса.
  • [FromRoute]: возвращает значения из данных маршрута.
  • [FromForm]: возвращает значения из опубликованных полей формы.
  • [FromBody]: возвращает значения из текста запроса.
  • [FromHeader]: возвращает значения из заголовков HTTP.

Эти атрибуты:

  • Добавляются в свойства модели по отдельности, а не в класс модели, как показано в следующем примере:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • При необходимости принимают значение имени модели в конструкторе. Этот параметр предоставляется в том случае, если имя свойства не соответствует значению в запросе. Например, значение в запросе может быть заголовком с дефисом в имени, как показано в следующем примере:

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

Атрибут [FromBody]

Примените атрибут [FromBody] к параметру, чтобы заполнить его свойства из тела HTTP-запроса. Среда выполнения ASP.NET Core делегирует ответственность за считывание тела форматировщику входных данных. Форматировщики входных данных описываются далее в этой статье.

При применении [FromBody] к параметру сложного типа все атрибуты источника привязки, применяемые к его свойствам, игнорируются. Например, следующее действие Create указывает, что параметр pet заполняется из тела:

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

Класс Pet указывает, что свойство Breed заполняется из параметра строки запроса:

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

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

В предыдущем примере:

  • Атрибут [FromQuery] не учитывается.
  • Свойство Breed не заполняется из параметра строки запроса.

Форматировщики входных данных считывают только тело и не распознают атрибуты источника привязки. Если подходящее значение найдено в теле, оно используется для заполнения свойства Breed.

Не применяют [FromBody] к нескольким параметрам в методе действия. После считывания потока запроса форматировщиком входных данных он больше не доступен для повторного чтения для привязки других параметров [FromBody].

Дополнительные источники

Исходные данные предоставляются системой привязки модели поставщиками значений. Вы можете записать и зарегистрировать пользовательские поставщики значений, которые получают данные для привязки модели из других источников. Например, вам могут потребоваться данные из файлов cookie или состояния сеанса. Для получения данных из нового источника:

  • Создайте класс, реализующий перехватчик IValueProvider.
  • Создайте класс, реализующий перехватчик IValueProviderFactory.
  • Зарегистрируйте класс фабрики в Program.cs.

Пример включает в себя поставщик значений и пример фабрики, который получает значения из файлов cookie. Регистрация фабрик поставщика пользовательских значений в Program.cs:

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

Предыдущий код помещает поставщика настраиваемых значений после всех встроенных поставщиков значений. Чтобы сделать его первым в списке, вызовите Insert(0, new CookieValueProviderFactory()) вместо Add.

Отсутствие источника для свойства модели

По умолчанию ошибка состояния модели не создается, если не найдено значение для свойства модели. Свойство получает значение NULL или значение по умолчанию:

  • Примитивным типам, допускающим значение NULL, задается значение null.
  • Типам значений, не допускающим значение NULL, задается значение default(T). Например, параметр int id получает значение 0.
  • Для сложных типов привязка модели создает экземпляр с помощью конструктора по умолчанию без задания свойств.
  • Массивы имеют значение Array.Empty<T>(), за исключением массивов byte[], которые имеют значение null.

Если состояние модели должно быть признано недействительным, когда в полях формы не найдены данные для свойства модели, используйте атрибут [BindRequired].

Обратите внимание, это поведение [BindRequired] применяется к привязке модели из опубликованных данных формы, а не к данным JSON или XML в тексте запроса. Данные основного текста запроса обрабатываются форматировщиками входных данных.

Ошибки преобразования типа

Если источник найден, но его нельзя преобразовать в тип целевого объекта, состояние модели помечается как недопустимое. Параметр или свойство целевого объекта получает значение NULL или значение по умолчанию, как отмечалось в предыдущем разделе.

В контроллере API с атрибутом [ApiController] недопустимое состояние модели приводит к автоматическому ответу HTTP 400.

Razor На странице переиграйте страницу с сообщением об ошибке:

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

    // ...

    return RedirectToPage("./Index");
}

При повторном воспроизведении страницы в приведенном выше коде недопустимые входные данные не отображаются в поле формы. Это связано с тем, что свойству модели задано значение NULL или значение по умолчанию. Недопустимые входные данные отображаются в сообщении об ошибке. Если вы хотите повторно воспроизвести плохие данные в поле формы, попробуйте сделать свойство модели строкой и выполнять преобразование данных вручную.

Та же стратегия рекомендуется в том случае, если вы не хотите, чтобы ошибки преобразования типов приводили к ошибкам состояния модели. В этом случае следует сделать свойство модели строкой.

Простые типы

Связыватель модели может преобразовать исходные строки в следующие примитивные типы:

Сложные типы

Сложный тип должен иметь открытый конструктор по умолчанию, а также открытые и доступные для записи свойства, подлежащие привязке. Когда происходит привязка модели, класс создается с помощью открытого конструктора по умолчанию.

Для каждого свойства сложного типа привязка модели просматривает источники для шаблона имен prefix.property_name. Если ничего не найдено, он ищет только property_name без префикса. Решение об использовании префикса не принимается для каждого свойства. Например, при запросе, содержающем метод, привязанном к методуOnGet(Instructor instructor), результирующий ?Instructor.Id=100&Name=fooобъект типа Instructor содержит:

  • Id установите значение 100.
  • Name установите значение null. Привязка модели ожидает, Instructor.Name так как Instructor.Id она использовалась в предыдущем параметре запроса.

Для привязки к параметру префикс является именем параметра. Для привязки к открытому свойству PageModel префикс является именем открытого свойства. Некоторые атрибуты имеют свойство Prefix, которое позволяет переопределить использование по умолчанию для имени параметра или свойства.

Предположим, например, что сложный тип принадлежит к следующему классу Instructor:

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

Префикс — это имя параметра

Если модель, которую нужно привязать, является параметром с именем instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Привязка модели начинается с поиска источников ключа instructorToUpdate.ID. Если он не найден, ищется ID без префикса.

Префикс — это имя свойства

Если модель для привязки является свойством с именем Instructor контроллера или класса PageModel:

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

Привязка модели начинается с поиска источников ключа Instructor.ID. Если он не найден, ищется ID без префикса.

Пользовательский префикс

Если модель для привязки — это параметр с именем instructorToUpdate, а атрибут Bind задает Instructor как префикс:

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

Привязка модели начинается с поиска источников ключа Instructor.ID. Если он не найден, ищется ID без префикса.

Атрибуты для целевых объектов сложного типа

Несколько встроенных атрибутов доступны для управления привязкой моделей сложных типов:

Предупреждение

Эти атрибуты влияют на привязку модели, если опубликованные данные формы являются источником значений. Они не влияют на входные форматировщики, которые обрабатывают опубликованные тексты ЗАПРОСОВ JSON и XML. Форматировщики входных данных описываются далее в этой статье.

Атрибут [Bind]

Может быть применен к классу или параметру метода. Указывает, какие свойства модели должны быть включены в привязку модели. [Bind]не влияет на входные форматировщики.

В следующем примере только указанные свойства модели Instructor привязываются, когда вызывается любой метод действия или обработчик:

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

В следующем примере только указанные свойства модели Instructor привязываются, когда вызывается метод OnPost:

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

Атрибут [Bind] может использоваться для защиты от чрезмерной передачи данных при создании. Он не работает в сценариях редактирования, поскольку исключенным свойствам задается значение NULL или значение по умолчанию, но не оставляется значение без изменений. Для защиты от перепоставки модели представления рекомендуется вместо атрибута [Bind] . Дополнительные сведения см. в разделе Примечание по безопасности о чрезмерной передаче данных.

Атрибут [ModelBinder]

ModelBinderAttribute можно применять к типам, свойствам или параметрам. Он позволяет указать тип привязки модели, используемый для привязки конкретного экземпляра или типа. Например:

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

Атрибут [ModelBinder] также можно использовать для изменения имени свойства или параметра при привязке модели:

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

    // ...
}

Атрибут [BindRequired]

Приводит к тому, что привязка модели добавляет ошибку состояния модели, если привязка для свойства модели невозможна. Приведем пример:

public class InstructorBindRequired
{
    // ...

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

Также см. обсуждение атрибута [Required] в разделе Проверка модели.

Атрибут [BindNever]

Может применяться к свойству или типу. Запрещает привязке модели задавать свойство модели. При применении к типу система привязки модели исключает все свойства, которые определяет тип. Приведем пример:

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

    // ...
}

Коллекции

Для целевых объектов, которые являются коллекциями примитивных типов, привязка модели ищет совпадения с parameter_name или property_name. Если совпадений не найдено, она ищет один из поддерживаемых форматов без префикса. Например:

  • Предположим, что параметром для привязки является массив с именем selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Строковые данные формы или запроса могут иметь один из следующих форматов:

    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
    

    Избегайте привязки параметра или свойства с именем index или Index если он находится рядом со значением коллекции. Привязка модели пытается использовать index в качестве индекса для коллекции, что может привести к неправильной привязке. Например, рассмотрим следующее действие:

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

    В приведенном выше коде index параметр строки запроса привязывается к index параметру метода, а также используется для привязки коллекции продуктов. Переименование index параметра или использование атрибута привязки модели для настройки привязки позволяет избежать этой проблемы:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Следующий формат поддерживается только в данных формы:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Для всех перечисленных ранее форматов привязка модели передает массив из двух элементов в параметр selectedCourses:

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

    Форматы данных, которые используют номера нижних индексов (... [0] ... [1] ...), должны нумероваться последовательно, начиная с нуля. Если в нумерации есть пробелы, все элементы после пробела не учитываются. Например, если указаны индексы 0 и 2, а не 0 и 1, второй элемент игнорируется.

Словари

Для целевых объектов Dictionary привязка модели ищет совпадения с parameter_name или property_name. Если совпадений не найдено, она ищет один из поддерживаемых форматов без префикса. Например:

  • Предположим, что целевой параметр является Dictionary<int, string> с именем selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Опубликованные строковые данные формы или запроса могут выглядеть как один из следующих примеров:

    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
    
  • Для всех перечисленных ранее форматов привязка модели передает словарь из двух элементов в параметр selectedCourses:

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

Привязка конструктора и типы записей

Для привязки модели требуется, чтобы сложные типы имели конструктор без параметров. Newtonsoft.Json Оба System.Text.Json и основанные входные формататоры поддерживают десериализацию классов, которые не имеют конструктора без параметров.

Типы записей — отличный способ представить данные по сети. ASP.NET Core поддерживает привязку моделей и проверку типов записей с помощью одного конструктора:

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>

При проверке типов записей среда выполнения выполняет поиск метаданных привязки и проверки, а не параметров, а не свойств.

Платформа позволяет привязывать и проверять типы записей:

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

Для работы предыдущего типа необходимо:

  • Будьте типом записи.
  • Есть ровно один открытый конструктор.
  • Содержит параметры, имеющие свойство с одинаковым именем и типом. Имена не должны отличаться по регистру.

POCOs без конструкторов без параметров

Объекты POC, у которых нет конструкторов без параметров, не могут быть привязаны.

Следующий код приводит к исключению, указывающее, что тип должен иметь конструктор без параметров:

public class Person(string Name)

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

Типы записей с созданными вручную конструкторами

Типы записей с созданными вручную конструкторами, которые выглядят как основные конструкторы

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

Типы записей, проверка и привязка метаданных

Для типов записей используется метаданные проверки и привязки к параметрам. Все метаданные свойств игнорируются

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

Проверка и метаданные

Проверка использует метаданные для параметра, но использует свойство для чтения значения. В обычном случае с основными конструкторами они будут идентичны. Однако есть способы победить его:

public record Person([Required] string Name)
{
    private readonly string _name;

    // The following property is never null.
    // However this object could have been constructed as "new Person(null)".
    public string Name { get; init => _name = value ?? string.Empty; }
}

TryUpdateModel не обновляет параметры типа записи

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

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

В этом случае MVC не попытается выполнить привязку Name снова. Тем не менее, Age разрешено обновлять

Поведение глобализации для данных маршрутов привязки модели и строк запросов

Поставщик значений маршрутов и поставщик значений для строк запросов ASP.NET Core:

  • обрабатывают значения как имеющие инвариантные язык и региональные параметры.
  • Следует ожидать, что URL-адреса имеют инвариантные язык и региональные параметры.

Напротив, значения, поступающие из данных форм, подвергаются преобразованию с учетом языка и региональных параметров. Это сделано намеренно, чтобы URL-адреса были общими в разных языковых стандартах.

Чтобы поставщик значений маршрутов и поставщик значений для строк запросов ASP.NET Core производили преобразование с учетом языка и региональных параметров, выполните указанные ниже действия.

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

Специальные типы данных

Существуют некоторые особые типы данных, которые может обрабатывать привязка модели.

IFormFile и IFormFileCollection

Переданный файл, включенный в HTTP-запрос. Также поддерживается IEnumerable<IFormFile> для нескольких файлов.

CancellationToken

Действия могут при необходимости привязать как CancellationToken параметр. Это привязывает, что сигнализирует RequestAborted о прерванном подключении, базовом HTTP-запросе. Действия могут использовать этот параметр для отмены длительных асинхронных операций, выполняемых в рамках действий контроллера.

FormCollection

Используется для извлечения всех значений из опубликованных данных формы.

Форматировщики входных данных

Данные в тексте запроса могут быть в формате JSON, XML или другом формате. Для анализа этих данных модель привязки использует форматировщик входных данных, настроенный для обработки определенного типа содержимого. По умолчанию ASP.NET Core включает форматировщики входных данных на основе JSON для обработки данных JSON. Вы можете добавить другие форматировщики для других типов содержимого.

ASP.NET Core выбирает форматировщики входных данных на основе атрибута Consumes. Если атрибут отсутствует, используется Заголовок Content-Type.

Чтобы использовать встроенные форматировщики входных данных XML:

  • В Program.cs вызовите AddXmlSerializerFormatters или AddXmlDataContractSerializerFormatters.

    builder.Services.AddControllers()
        .AddXmlSerializerFormatters();
    
  • Примените атрибут Consumes к классам контроллера или методам действий, которые должны ожидать XML в тексте запроса.

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

    Дополнительные сведения см. в разделе Введение в сериализацию XML.

Настройка привязки модели с помощью форматировщиков входных данных

Форматировщик входных данных полностью отвечает за чтение данных из текста запроса. Чтобы настроить этот процесс, настройте API-интерфейсы, используемые форматировщиками входных данных. В этом разделе описывается, как настроить форматировщик входных данных на основе System.Text.Json так, чтобы он понимал настраиваемый тип с именем ObjectId.

Рассмотрим следующую модель, содержащую пользовательское ObjectId свойство:

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

Чтобы настроить процесс привязки модели при использовании System.Text.Json, создайте производный класс на основе класса JsonConverter<T>:

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

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

Чтобы использовать пользовательский преобразователь, примените к типу атрибут JsonConverterAttribute. В следующем примере тип ObjectId настраивается с ObjectIdConverter в качестве пользовательского преобразователя:

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

Дополнительные сведения см. в статье How to write custom converters for JSON serialization (marshalling) in .NET (Создание пользовательских преобразователей для сериализации JSON (маршалинг) в .NET).

Исключение указанных типов из привязки модели

Поведение систем привязки и проверки модели определяется ModelMetadata. Вы можете настроить ModelMetadata, добавив поставщик сведений в MvcOptions.ModelMetadataDetailsProviders. Встроенные поставщики сведений доступны для отключения привязки модели или проверки для указанных типов.

Чтобы отключить привязку модели для всех моделей указанного типа, добавьте ExcludeBindingMetadataProvider в Program.cs. Например, для отключения привязки модели для всех моделей типа System.Version:

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

Чтобы отключить проверку свойств указанного типа, добавьте SuppressChildValidationMetadataProvider в Program.cs. Например, чтобы отключить проверку по свойствам типа System.Guid:

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

Настраиваемые связыватели модели

Привязку модели можно расширить путем написания пользовательского связывателя модели и с помощью атрибута [ModelBinder], чтобы выбрать его для заданного целевого объекта. Узнайте больше о пользовательской привязке модели.

Привязка модели вручную

Привязка модели может вызываться вручную с помощью метода TryUpdateModelAsync. Этот метод определен в классах ControllerBase и PageModel. Перегрузки метода позволяют задать поставщик префиксов и значений. Этот метод возвращает false при сбое привязки модели. Приведем пример:

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

return Page();

TryUpdateModelAsync использует поставщиков значений для получения данных из текста формы, строки запроса и данных маршрута. TryUpdateModelAsync как правило:

  • Используется с Razor приложениями Pages и MVC с помощью контроллеров и представлений, чтобы предотвратить чрезмерное размещение.
  • не используется с веб-API, если только не используется из данных формы, строк запроса и данных маршрута. Конечные точки веб-API, которые используют JSON, применяют форматировщики входных данных для десериализации текста запроса в объект.

Дополнительные сведения см. в разделе TryUpdateModelAsync.

Атрибут [FromServices]

Имя этого атрибута соответствует шаблону атрибутов привязки модели, которые указывают источник данных. Но это не связано с привязкой данных от поставщика значений. Он получает экземпляр типа из контейнера внедрения зависимостей. Он предназначен для предоставления альтернативы внедрению через конструктор, когда вам нужна служба, только если вызывается конкретный метод.

Если экземпляр типа не зарегистрирован в контейнере внедрения зависимостей, приложение создает исключение при попытке привязать параметр. Чтобы сделать параметр необязательным, используйте один из следующих подходов:

  • Сделайте параметр пустым.
  • Задайте значение по умолчанию для параметра.

Для параметров, допускающих значение NULL, убедитесь, что параметр недоступен null перед доступом к нему.

Дополнительные ресурсы

В этой статье объясняется, что такое привязка модели, как это работает и как настроить ее поведение.

Просмотреть или скачать пример кода (описание скачивания).

Что такое привязка модели

Контроллеры и Razor страницы работают с данными, поступающими из HTTP-запросов. Например, данные о маршруте могут предоставлять ключ записи, а опубликованные поля формы могут предоставлять значения для свойств модели. Написание кода для получения этих значений и их преобразования из строк в типы .NET будет утомительной задачей с высоким риском ошибок. Привязка модели позволяет автоматизировать этот процесс. Система привязки модели:

  • Извлекает данные из различных источников, таких как данные о маршруте, поля формы и строки запроса.
  • Предоставляет данные контроллерам и страницам в параметрах метода и Razor общедоступных свойствах.
  • Преобразует строковые данные в типы .NET.
  • Обновляет свойства сложных типов.

Пример

Предположим, у вас есть следующий метод действия:

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

И приложение получает запрос с этим URL-адресом:

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

Привязка модели выполняет следующие действия, после того, как система маршрутизации выберет метод действия:

  • Находит первый параметр GetById, целое число с именем id.
  • Просматривает доступные источники в HTTP-запросе и находит id = "2" в данных маршрута.
  • Преобразует строку "2" в целое число 2.
  • Находит следующий параметр GetById, логическое значение с именем dogsOnly.
  • Просматривает источники и находит "DogsOnly=true" в строке запроса. Сопоставление имен не учитывает регистр.
  • Преобразует строку "true" в логическое значение true.

Затем платформа вызывает метод GetById, передавая 2 для параметра id и true для параметра dogsOnly.

В приведенном выше примере целевые объекты привязки модели — это параметры методов, которые являются примитивными типами. Целевые объекты также могут быть свойствами сложного типа. После успешной привязки каждого свойства осуществляется проверка модели для этого свойства. Записи о данных, привязанных к модели, а также об ошибках привязки или проверки хранятся в ControllerBase.ModelState или PageModel.ModelState. Чтобы узнать об успешном выполнении этого процесса, приложение проверяет наличие флага ModelState.IsValid.

Целевые объекты

Привязка модели попытается найти значения для следующих типов целевых объектов:

  • Параметры метода действия контроллера, к которому направлен запрос.
  • Параметры метода обработчика Razor Pages, в который направляется запрос.
  • Открытые свойства контроллера или класса PageModel, если задано атрибутами.

Атрибут [BindProperty]

Может применяться к открытому свойству контроллера или класса PageModel для привязки модели для этого свойства:

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

Атрибут [BindProperties]

Доступно в ASP.NET Core 2.1 и более поздней версии. Может применяться к контроллеру или классу PageModel, чтобы привязка модели была направлена на все открытые свойства этого класса:

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

Привязка модели для HTTP-запросов GET

По умолчанию свойства не привязываются к HTTP-запросам GET. Как правило, для запроса GET вам нужен только параметр идентификатора записи. Идентификатор записи используется для поиска элемента в базе данных. Поэтому не нужно привязывать свойство, которое содержит экземпляр модели. Если вы хотите привязать свойства к данным от запросов GET, задайте для свойства SupportsGet значение true:

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

Источники

По умолчанию привязка модели получает данные в виде пар "ключ-значение" из следующих источников в HTTP-запросе:

  1. Поля формы
  2. Текст запроса (для контроллеров, имеющих атрибут [ApiController].)
  3. Отправка данных
  4. Параметры строки запроса
  5. Отправленные файлы

Для каждого целевого параметра или свойства источники проверяются в порядке, указанном в предыдущем списке. Существует несколько исключений:

  • Данные маршрутизации и значения строк запросов используются только для примитивных типов.
  • Отправленные файлы привязаны только к типам целевых объектов, которые реализуют IFormFile или IEnumerable<IFormFile>.

Если источник по умолчанию неверен, используйте один из следующих атрибутов, чтобы указать источник:

  • [FromQuery]: возвращает значения из строки запроса.
  • [FromRoute]: возвращает значения из данных маршрута.
  • [FromForm]: возвращает значения из опубликованных полей формы.
  • [FromBody]: возвращает значения из текста запроса.
  • [FromHeader]: возвращает значения из заголовков HTTP.

Эти атрибуты:

  • Добавляются к свойствам модели по отдельности (не к классу модели), как показано в следующем примере:

    public class Instructor
    {
        public int ID { get; set; }
    
        [FromQuery(Name = "Note")]
        public string NoteFromQueryString { get; set; }
    
  • При необходимости принимают значение имени модели в конструкторе. Этот параметр предоставляется в том случае, если имя свойства не соответствует значению в запросе. Например, значение в запросе может быть заголовком с дефисом в имени, как показано в следующем примере:

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

Атрибут [FromBody]

Примените атрибут [FromBody] к параметру, чтобы заполнить его свойства из тела HTTP-запроса. Среда выполнения ASP.NET Core делегирует ответственность за считывание тела форматировщику входных данных. Форматировщики входных данных описываются далее в этой статье.

При применении [FromBody] к параметру сложного типа все атрибуты источника привязки, применяемые к его свойствам, игнорируются. Например, следующее действие Create указывает, что параметр pet заполняется из тела:

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

Класс Pet указывает, что свойство Breed заполняется из параметра строки запроса:

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

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

В предыдущем примере:

  • Атрибут [FromQuery] не учитывается.
  • Свойство Breed не заполняется из параметра строки запроса.

Форматировщики входных данных считывают только тело и не распознают атрибуты источника привязки. Если подходящее значение найдено в теле, оно используется для заполнения свойства Breed.

Не применяют [FromBody] к нескольким параметрам в методе действия. После считывания потока запроса форматировщиком входных данных он больше не доступен для повторного чтения для привязки других параметров [FromBody].

Дополнительные источники

Исходные данные предоставляются системой привязки модели поставщиками значений. Вы можете записать и зарегистрировать пользовательские поставщики значений, которые получают данные для привязки модели из других источников. Например, вам могут потребоваться данные из файлов cookie или состояния сеанса. Для получения данных из нового источника:

  • Создайте класс, реализующий перехватчик IValueProvider.
  • Создайте класс, реализующий перехватчик IValueProviderFactory.
  • Зарегистрируйте класс фабрики в Startup.ConfigureServices.

Пример приложения включает пример поставщика значений и фабрики, которая получает значения из файлов cookie. Ниже приведен код регистрации в 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();

Этот код помещает поставщик пользовательских значений после всех встроенных поставщиков значений. Чтобы сделать его первым в списке, вызовите Insert(0, new CookieValueProviderFactory()) вместо Add.

Отсутствие источника для свойства модели

По умолчанию ошибка состояния модели не создается, если не найдено значение для свойства модели. Свойство получает значение NULL или значение по умолчанию:

  • Примитивным типам, допускающим значение NULL, задается значение null.
  • Типам значений, не допускающим значение NULL, задается значение default(T). Например, параметр int id получает значение 0.
  • Для сложных типов привязка модели создает экземпляр с помощью конструктора по умолчанию без задания свойств.
  • Массивы имеют значение Array.Empty<T>(), за исключением массивов byte[], которые имеют значение null.

Если состояние модели должно быть признано недействительным, когда в полях формы не найдены данные для свойства модели, используйте атрибут [BindRequired].

Обратите внимание, это поведение [BindRequired] применяется к привязке модели из опубликованных данных формы, а не к данным JSON или XML в тексте запроса. Данные основного текста запроса обрабатываются форматировщиками входных данных.

Ошибки преобразования типа

Если источник найден, но его нельзя преобразовать в тип целевого объекта, состояние модели помечается как недопустимое. Параметр или свойство целевого объекта получает значение NULL или значение по умолчанию, как отмечалось в предыдущем разделе.

В контроллере API с атрибутом [ApiController] недопустимое состояние модели приводит к автоматическому ответу HTTP 400.

Razor На странице переиграйте страницу с сообщением об ошибке:

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

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

Проверка на стороне клиента перехватывает большинство плохих данных, которые в противном случае будут отправлены в Razor форму Pages. Эта проверка затрудняет срабатывание выделенного выше кода. Пример приложения включает кнопку Отправить с неверной датой, которая помещает недопустимые данные в поле Дата приема на работу и отправляет форму. Эта кнопка показывает, как работает код для повторного отображения страницы, если возникла ошибка преобразования данных.

Когда страница отображается повторно приведенным выше кодом, недопустимые входные данные не отображаются в поле формы. Это связано с тем, что свойству модели задано значение NULL или значение по умолчанию. Недопустимые входные данные отображаются в сообщении об ошибке. Но если требуется повторно отобразить неправильные данные в поле формы, возможно, следует сделать свойство модели строкой и выполнить преобразование данных вручную.

Та же стратегия рекомендуется в том случае, если вы не хотите, чтобы ошибки преобразования типов приводили к ошибкам состояния модели. В этом случае следует сделать свойство модели строкой.

Простые типы

Связыватель модели может преобразовать исходные строки в следующие примитивные типы:

Сложные типы

Сложный тип должен иметь открытый конструктор по умолчанию, а также открытые и доступные для записи свойства, подлежащие привязке. Когда происходит привязка модели, класс создается с помощью открытого конструктора по умолчанию.

Для каждого свойства сложного типа привязка модели ищет в источниках шаблон имени prefix.property_name. Если ничего не найдено, он ищет только property_name без префикса.

Для привязки к параметру префикс является именем параметра. Для привязки к открытому свойству PageModel префикс является именем открытого свойства. Некоторые атрибуты имеют свойство Prefix, которое позволяет переопределить использование по умолчанию для имени параметра или свойства.

Предположим, например, что сложный тип принадлежит к следующему классу Instructor:

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

Префикс — это имя параметра

Если модель, которую нужно привязать, является параметром с именем instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Привязка модели начинается с поиска источников ключа instructorToUpdate.ID. Если он не найден, ищется ID без префикса.

Префикс — это имя свойства

Если модель для привязки является свойством с именем Instructor контроллера или класса PageModel:

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

Привязка модели начинается с поиска источников ключа Instructor.ID. Если он не найден, ищется ID без префикса.

Пользовательский префикс

Если модель для привязки — это параметр с именем instructorToUpdate, а атрибут Bind задает Instructor как префикс:

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

Привязка модели начинается с поиска источников ключа Instructor.ID. Если он не найден, ищется ID без префикса.

Атрибуты для целевых объектов сложного типа

Несколько встроенных атрибутов доступны для управления привязкой моделей сложных типов:

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

Предупреждение

Эти атрибуты влияют на привязку модели, если опубликованные данные формы являются источником значений. Они не влияют на входные форматировщики, которые обрабатывают опубликованные тексты ЗАПРОСОВ JSON и XML. Форматировщики входных данных описываются далее в этой статье.

Атрибут [Bind]

Может быть применен к классу или параметру метода. Указывает, какие свойства модели должны быть включены в привязку модели. [Bind]не влияет на входные форматировщики.

В следующем примере только указанные свойства модели Instructor привязываются, когда вызывается любой метод действия или обработчик:

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

В следующем примере только указанные свойства модели Instructor привязываются, когда вызывается метод OnPost:

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

Атрибут [Bind] может использоваться для защиты от чрезмерной передачи данных при создании. Он не работает в сценариях редактирования, поскольку исключенным свойствам задается значение NULL или значение по умолчанию, но не оставляется значение без изменений. Для защиты от перепоставки модели представления рекомендуется вместо атрибута [Bind] . Дополнительные сведения см. в разделе Примечание по безопасности о чрезмерной передаче данных.

Атрибут [ModelBinder]

ModelBinderAttribute можно применять к типам, свойствам или параметрам. Он позволяет указать тип привязки модели, используемый для привязки конкретного экземпляра или типа. Например:

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

Атрибут [ModelBinder] также можно использовать для изменения имени свойства или параметра при привязке модели:

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

    public string Name { get; set; }
}

Атрибут [BindRequired]

Может применяться только к свойствам модели, а не к параметрам метода. Приводит к тому, что привязка модели добавляет ошибку состояния модели, если привязка для свойства модели невозможна. Приведем пример:

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

Также см. обсуждение атрибута [Required] в разделе Проверка модели.

Атрибут [BindNever]

Может применяться только к свойствам модели, а не к параметрам метода. Запрещает привязке модели задавать свойство модели. Приведем пример:

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

Коллекции

Для целевых объектов, которые являются коллекциями примитивных типов, привязка модели ищет совпадения с parameter_name или property_name. Если совпадений не найдено, она ищет один из поддерживаемых форматов без префикса. Например:

  • Предположим, что параметром для привязки является массив с именем selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Строковые данные формы или запроса могут иметь один из следующих форматов:

    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
    

    Избегайте привязки параметра или свойства с именем index или Index если он находится рядом со значением коллекции. Привязка модели пытается использовать index в качестве индекса для коллекции, что может привести к неправильной привязке. Например, рассмотрим следующее действие:

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

    В приведенном выше коде index параметр строки запроса привязывается к index параметру метода, а также используется для привязки коллекции продуктов. Переименование index параметра или использование атрибута привязки модели для настройки привязки позволяет избежать этой проблемы:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Следующий формат поддерживается только в данных формы:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Для всех перечисленных ранее форматов привязка модели передает массив из двух элементов в параметр selectedCourses:

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

    Форматы данных, которые используют номера нижних индексов (... [0] ... [1] ...), должны нумероваться последовательно, начиная с нуля. Если в нумерации есть пробелы, все элементы после пробела не учитываются. Например, если указаны индексы 0 и 2, а не 0 и 1, второй элемент игнорируется.

Словари

Для целевых объектов Dictionary привязка модели ищет совпадения с parameter_name или property_name. Если совпадений не найдено, она ищет один из поддерживаемых форматов без префикса. Например:

  • Предположим, что целевой параметр является Dictionary<int, string> с именем selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Опубликованные строковые данные формы или запроса могут выглядеть как один из следующих примеров:

    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
    
  • Для всех перечисленных ранее форматов привязка модели передает словарь из двух элементов в параметр selectedCourses:

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

Привязка конструктора и типы записей

Для привязки модели требуется, чтобы сложные типы имели конструктор без параметров. Newtonsoft.Json Оба System.Text.Json и основанные входные формататоры поддерживают десериализацию классов, которые не имеют конструктора без параметров.

C# 9 представляет типы записей, которые являются отличным способом представить данные по сети. ASP.NET Core добавляет поддержку привязки модели и проверки типов записей с одним конструктором:

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>

При проверке типов записей среда выполнения выполняет поиск метаданных привязки и проверки, а не параметров, а не свойств.

Платформа позволяет привязывать и проверять типы записей:

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

Для работы предыдущего типа необходимо:

  • Будьте типом записи.
  • Есть ровно один открытый конструктор.
  • Содержит параметры, имеющие свойство с одинаковым именем и типом. Имена не должны отличаться по регистру.

POCOs без конструкторов без параметров

Объекты POC, у которых нет конструкторов без параметров, не могут быть привязаны.

Следующий код приводит к исключению, указывающее, что тип должен иметь конструктор без параметров:

public class Person(string Name)

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

Типы записей с созданными вручную конструкторами

Типы записей с созданными вручную конструкторами, которые выглядят как основные конструкторы

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

Типы записей, проверка и привязка метаданных

Для типов записей используется метаданные проверки и привязки к параметрам. Все метаданные свойств игнорируются

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

Проверка и метаданные

Проверка использует метаданные для параметра, но использует свойство для чтения значения. В обычном случае с основными конструкторами они будут идентичны. Однако есть способы победить его:

public record Person([Required] string Name)
{
   private readonly string _name;
   public Name { get; init => _name = value ?? string.Empty; } // Now this property is never null. However this object could have been constructed as `new Person(null);`
}

TryUpdateModel не обновляет параметры типа записи

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

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

В этом случае MVC не попытается выполнить привязку Name снова. Тем не менее, Age разрешено обновлять

Поведение глобализации для данных маршрутов привязки модели и строк запросов

Поставщик значений маршрутов и поставщик значений для строк запросов ASP.NET Core:

  • обрабатывают значения как имеющие инвариантные язык и региональные параметры.
  • Следует ожидать, что URL-адреса имеют инвариантные язык и региональные параметры.

Напротив, значения, поступающие из данных форм, подвергаются преобразованию с учетом языка и региональных параметров. Это сделано намеренно, чтобы URL-адреса были общими в разных языковых стандартах.

Чтобы поставщик значений маршрутов и поставщик значений для строк запросов ASP.NET Core производили преобразование с учетом языка и региональных параметров, выполните указанные ниже действия.

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

Специальные типы данных

Существуют некоторые особые типы данных, которые может обрабатывать привязка модели.

IFormFile и IFormFileCollection

Переданный файл, включенный в HTTP-запрос. Также поддерживается IEnumerable<IFormFile> для нескольких файлов.

CancellationToken

Действия могут при необходимости привязать как CancellationToken параметр. Это привязывает, что сигнализирует RequestAborted о прерванном подключении, базовом HTTP-запросе. Действия могут использовать этот параметр для отмены длительных асинхронных операций, выполняемых в рамках действий контроллера.

FormCollection

Используется для извлечения всех значений из опубликованных данных формы.

Форматировщики входных данных

Данные в тексте запроса могут быть в формате JSON, XML или другом формате. Для анализа этих данных модель привязки использует форматировщик входных данных, настроенный для обработки определенного типа содержимого. По умолчанию ASP.NET Core включает форматировщики входных данных на основе JSON для обработки данных JSON. Вы можете добавить другие форматировщики для других типов содержимого.

ASP.NET Core выбирает форматировщики входных данных на основе атрибута Consumes. Если атрибут отсутствует, используется Заголовок Content-Type.

Чтобы использовать встроенные форматировщики входных данных XML:

  • Установите пакет NuGet Microsoft.AspNetCore.Mvc.Formatters.Xml.

  • В Startup.ConfigureServices вызовите AddXmlSerializerFormatters или 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 к классам контроллера или методам действий, которые должны ожидать XML в тексте запроса.

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

    Дополнительные сведения см. в разделе Введение в сериализацию XML.

Настройка привязки модели с помощью форматировщиков входных данных

Форматировщик входных данных полностью отвечает за чтение данных из текста запроса. Чтобы настроить этот процесс, настройте API-интерфейсы, используемые форматировщиками входных данных. В этом разделе описывается, как настроить форматировщик входных данных на основе System.Text.Json так, чтобы он понимал настраиваемый тип с именем ObjectId.

Рассмотрим следующую модель, которая содержит настраиваемое свойство ObjectId с именем Id:

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

Чтобы настроить процесс привязки модели при использовании System.Text.Json, создайте производный класс на основе класса JsonConverter<T>:

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

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

Чтобы использовать пользовательский преобразователь, примените к типу атрибут JsonConverterAttribute. В следующем примере тип ObjectId настраивается с ObjectIdConverter в качестве пользовательского преобразователя:

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

    public int Id { get; }
}

Дополнительные сведения см. в статье How to write custom converters for JSON serialization (marshalling) in .NET (Создание пользовательских преобразователей для сериализации JSON (маршалинг) в .NET).

Исключение указанных типов из привязки модели

Поведение систем привязки и проверки модели определяется ModelMetadata. Вы можете настроить ModelMetadata, добавив поставщик сведений в MvcOptions.ModelMetadataDetailsProviders. Встроенные поставщики сведений доступны для отключения привязки модели или проверки для указанных типов.

Чтобы отключить привязку модели для всех моделей указанного типа, добавьте ExcludeBindingMetadataProvider в Startup.ConfigureServices. Например, для отключения привязки модели для всех моделей типа 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();

Чтобы отключить проверку свойств указанного типа, добавьте SuppressChildValidationMetadataProvider в Startup.ConfigureServices. Например, чтобы отключить проверку по свойствам типа 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();

Настраиваемые связыватели модели

Привязку модели можно расширить путем написания пользовательского связывателя модели и с помощью атрибута [ModelBinder], чтобы выбрать его для заданного целевого объекта. Узнайте больше о пользовательской привязке модели.

Привязка модели вручную

Привязка модели может вызываться вручную с помощью метода TryUpdateModelAsync. Этот метод определен в классах ControllerBase и PageModel. Перегрузки метода позволяют задать поставщик префиксов и значений. Этот метод возвращает false при сбое привязки модели. Приведем пример:

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 использует поставщиков значений для получения данных из текста формы, строки запроса и данных маршрута. TryUpdateModelAsync как правило:

  • Используется с Razor приложениями Pages и MVC с помощью контроллеров и представлений, чтобы предотвратить чрезмерное размещение.
  • не используется с веб-API, если только не используется из данных формы, строк запроса и данных маршрута. Конечные точки веб-API, которые используют JSON, применяют форматировщики входных данных для десериализации текста запроса в объект.

Дополнительные сведения см. в разделе TryUpdateModelAsync.

Атрибут [FromServices]

Имя этого атрибута соответствует шаблону атрибутов привязки модели, которые указывают источник данных. Но это не связано с привязкой данных от поставщика значений. Он получает экземпляр типа из контейнера внедрения зависимостей. Он предназначен для предоставления альтернативы внедрению через конструктор, когда вам нужна служба, только если вызывается конкретный метод.

Дополнительные ресурсы