Если я удаляю [FromBody], player устанавливается в значение по умолчанию, а не в значения, которые я публикую.
Чтение данных из тела - opt-in (если вы не используете [ApiController]
). Когда вы удаляете [FromBody]
из параметра Player
, процесс привязки модели будет искать заполнение свойств Player
, используя маршрут, строку запроса и значения формы по умолчанию. В вашем примере таких свойств нет в этих местах, и поэтому ни одно из свойств Player
не установлено.
Если я поставлю [FromBody] перед параметром действия Player, player будет установлен в null.
При наличии атрибута [FromBody]
процесс привязки модели пытается выполнить чтение из тела в соответствии с Content-Type
, предоставленным в запросе. Если это application/json
, тело будет проанализировано как JSON и сопоставлено со свойствами Player
. В вашем примере процесс JSON-разбора завершается неудачей, поскольку он не знает, как преобразовать из string
в ObjectId
. Когда это произойдет, ModelState.IsValid
в вашем контроллере вернет false
, а ваш параметр Player
будет null
.
Чтобы этот код работал, я имею в виду запуск связывателя модели, я унаследовал контроллер от Controller и удалил атрибут [FromBody] из параметра Player.
Когда вы удаляете [FromBody]
, атрибут [ModelBinder(...)]
, который вы установили в свойстве Id
, соблюдается, и ваш код запускается. Однако при наличии [FromBody
] этот атрибут фактически игнорируется. Здесь много чего происходит за кулисами, но по сути это сводится к тому, что вы уже включили привязку модели из тела в виде JSON, и в этом случае привязка модели останавливается в этом сценарии.
Я упоминал выше, что это процесс синтаксического анализа JSON, который здесь не работает из-за непонимания того, как обрабатывать ObjectId
. Поскольку этот разбор JSON обрабатывается Newtonsoft.Json (он же JSON.NET), возможное решение - создать пользовательский JsonConverter
. Это хорошо описано здесь, в Stack Overflow, поэтому я не буду вдаваться в подробности как работает. Вот полный пример (обработка ошибок опущена для краткости и лени):
public class ObjectIdJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) =>
objectType == typeof(ObjectId);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
ObjectId.Parse(reader.Value as string);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
writer.WriteValue(((ObjectId)value).ToString());
}
Чтобы использовать это, просто замените существующий атрибут [ModelBinder(...)]
атрибутом [JsonConverter(...)]
, например:
[BsonId]
[JsonConverter(typeof(ObjectIdJsonConverter))]
public ObjectId Id { get; set; }
Кроме того, вы можете зарегистрировать ObjectIdJsonConverter
в глобальном масштабе, чтобы он применялся ко всем ObjectId
свойствам, используя что-то подобное в Startup.ConfigureServices
:
services.AddMvc()
.AddJsonOptions(options =>
options.SerializerSettings.Converters.Add(new ObjectIdJsonConverter());
);