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


Привязка модели в 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 метода. Сложный тип преобразуется из нескольких входных значений. Платформа определяет разницу на основе существования или TypeConverterTryParse. Рекомендуется создать преобразователь типов или использовать TryParse для stringSomeType преобразования, не требующего внешних ресурсов или нескольких входных данных.

Источники

По умолчанию привязка модели получает данные в виде пар "ключ-значение" из следующих источников в 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 или значение по умолчанию:

  • Типы simple, допускающие значение 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

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

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

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

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 без префикса. Решение об использовании префикса не принимается для каждого свойства. Например, при запросе, содержающем метод, привязанном к методу?Instructor.Id=100&Name=foo, результирующий OnGet(Instructor instructor)объект типа 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] может использоваться для защиты от чрезмерной передачи данных при создании. Он не работает в сценариях редактирования, поскольку исключенным свойствам присваивается нулевое или значение по умолчанию вместо оставления их неизменными. Для защиты от избыточной передачи данных рекомендуется использовать модели представления вместо атрибута [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"]="Химия"
    • selectedCourses["2000"]="Экономика"

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

Для привязки модели требуется, чтобы сложные типы имели конструктор без параметров. И System.Text.Json, и Newtonsoft.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 без конструкторов с параметрами

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

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

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

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

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

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 будет утомительной задачей с высоким риском ошибок. Привязка модели позволяет автоматизировать этот процесс. Система привязки модели:

  • Извлекает данные из различных источников, таких как данные о маршруте, поля формы и строки запроса.
  • Предоставляет данные контроллерам и страницам в параметрах метода и свойствах, доступных публично.
  • Преобразует строковые данные в типы .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 для stringSomeType преобразования, не требующего внешних ресурсов или нескольких входных данных.

Источники

По умолчанию привязка модели получает данные в виде пар "ключ-значение" из следующих источников в 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 или значение по умолчанию:

  • Для типов simple, допускающих значение 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

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

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 без префикса. Решение об использовании префикса не принимается для каждого свойства. Например, запрос, содержащий ?Instructor.Id=100&Name=foo, привязан к методу OnGet(Instructor instructor), а результирующий объект типа 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"]="Химия"
    • selectedCourses["2000"]="Экономика"

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

Для привязки модели требуется, чтобы сложные типы имели конструктор без параметров. System.Text.Json и Newtonsoft.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. Это привязывает 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 без префикса. Решение об использовании префикса не принимается для каждого свойства. Например, при запросе, содержащем ?Instructor.Id=100&Name=foo, привязанном к методу OnGet(Instructor instructor), результирующий объект типа 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] может использоваться для защиты от чрезмерной передачи данных в create сценариях. Он не работает в сценариях редактирования, поскольку исключенным свойствам задается значение 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"]="Химия"
    • selectedCourses["2000"]="Экономика"

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

Для привязки модели требуется, чтобы сложные типы имели конструктор без параметров. Как System.Text.Json, так и Newtonsoft.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 в качестве параметра. Это привязывает 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] может использоваться для защиты от избыточной передачи данных в сценариях создания. Он плохо работает в сценариях редактирования, поскольку исключенным свойствам задается нулевое значение или значение по умолчанию, а не оставляется без изменений. Для защиты от перезаполнения рекомендуется использовать модели представления вместо атрибута [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"]="Химия"
    • selectedCourses["2000"]="Экономика"

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

Для привязки модели требуется, чтобы сложные типы имели конструктор без параметров. System.Text.Json Оба Newtonsoft.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 без конструкторов без параметров

POCO-объекты, у которых нет конструкторов без параметров, не подлежат привязке.

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

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 как параметр. Это связывает 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]

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

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