Привязка к пользовательской модели ASP.Net Web API с отправляемыми данными x-www-form-urlencoded - ничего не работает
У меня возникли проблемы с привязкой пользовательской модели к работе при отправке данных x-www-form-urlencoded
. Я пробовал все, о чем я могу думать, и ничто, кажется, не дает желаемого результата. Обратите внимание, что при отправке данных JSON, моих JsonConverters и т.д. Все работает нормально. Это когда я отправляю как x-www-form-urlencoded
, что система не может понять, как связать мою модель.
Мой тестовый пример: я хотел бы привязать объект TimeZoneInfo как часть моей модели.
Здесь мое модельное связующее:
public class TimeZoneModelBinder : SystemizerModelBinder
{
protected override object BindModel(string attemptedValue, Action<string> addModelError)
{
try
{
return TimeZoneInfo.FindSystemTimeZoneById(attemptedValue);
}
catch(TimeZoneNotFoundException)
{
addModelError("The value was not a valid time zone ID. See the GetSupportedTimeZones Api call for a list of valid time zone IDs.");
return null;
}
}
}
Вот базовый класс, который я использую:
public abstract class SystemizerModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var name = GetModelName(bindingContext.ModelName);
var valueProviderResult = bindingContext.ValueProvider.GetValue(name);
if(valueProviderResult == null || string.IsNullOrWhiteSpace(valueProviderResult.AttemptedValue))
return false;
var success = true;
var value = BindModel(valueProviderResult.AttemptedValue, s =>
{
success = false;
bindingContext.ModelState.AddModelError(name, s);
});
bindingContext.Model = value;
bindingContext.ModelState.SetModelValue(name, new System.Web.Http.ValueProviders.ValueProviderResult(value, valueProviderResult.AttemptedValue, valueProviderResult.Culture));
return success;
}
private string GetModelName(string name)
{
var n = name.LastIndexOf(".", StringComparison.Ordinal);
return n < 0 || n >= name.Length - 1 ? name : name.Substring(n + 1);
}
protected abstract object BindModel(string attemptedValue, Action<string> addModelError);
}
Я использовал базовый класс, подобный этому, чтобы упростить создание дополнительных пользовательских привязок к образцам.
Здесь моя модель поставщика связующего. Обратите внимание, что это правильно вызвано из моего контейнера IoC, поэтому я не буду пытаться показать этот аспект моего кода.
public class SystemizerModelBinderProvider : ModelBinderProvider
{
public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
{
if(modelType == typeof(TimeZoneInfo))
return new TimeZoneModelBinder();
return null;
}
}
Наконец, здесь метод действия и класс модели:
[DataContract)]
public class TestModel
{
[DataMember]
public TimeZoneInfo TimeZone { get; set; }
}
[HttpPost]
public HttpResponseMessage Test(TestModel model)
{
return Request.CreateResponse(HttpStatusCode.OK, model);
}
Для метода действия я попытался:
public HttpResponseMessage Test([FromBody] TestModel model)
Это вызывает FormUrlEncodedMediaFormatter
, который, кажется, вообще игнорирует мое собственное связующее устройство.
public HttpResponseMessage Test([ModelBinder] TestModel model)
Это вызывает мое настраиваемое связующее устройство, как и ожидалось, но тогда оно предоставляет только ValueProviders для RouteData
и QueryString
и по какой-то причине не предоставляет ничего для содержимого тела. См. Ниже:
![Value Providers]()
Я также попытался украсить сам класс ModelBinder(typeof(SystemizerModelBinderProvider))
Почему привязка модели ТОЛЬКО возникает, когда я использую атрибут [ModelBinder] и почему она ТОЛЬКО пытается читать значения маршрута и запроса и игнорировать содержимое тела? Почему FromBody
игнорирует моего поставщика привязки для конкретной модели?
Как создать сценарий, в котором я могу получать данные POSTED x-www-form-urlencoded
и успешно связывать свойства модели с помощью пользовательской логики?
Ответы
Ответ 1
Я бы порекомендовал вам прочитать following blog post
, в котором Майк Столл подробно объясняет, как привязка модели работает в веб-интерфейсе API:
Существует 2 метода привязки параметров: привязка модели и Форматтеры. На практике WebAPI использует привязку модели для чтения из строку запроса и Formatters для чтения из тела.
Вот основные правила, чтобы определить, читается ли параметр с помощью привязка модели или форматировщик:
- Если параметр не имеет атрибута на нем, то решение принимается чисто по параметрам типа .NET. "Простые типы" использует модель связывание. В сложных типах используются форматирующие элементы. "Простой тип" включает в себя: примитивы, TimeSpan, DateTime, Guid, Decimal, String или что-то еще с TypeConverter, который преобразует из строк.
- Вы можете использовать атрибут
[FromBody]
, чтобы указать, что параметр должен быть прочитан из тело. - Вы можете использовать атрибут
[ModelBinder]
для параметра или тип параметров, чтобы указать, что параметр должен быть привязан к модели. Этот атрибут также позволяет настраивать связующее устройство модели. [FromUri]
производный экземпляр [ModelBinder]
, который специально настраивает модель, чтобы смотреть только в URI. - Тело можно читать только один раз. Поэтому, если в сигнатуре есть 2 сложных типа, по крайней мере один из них должен иметь атрибут [ModelBinder].
Итак, если источником ваших данных является тело запроса, вы можете создать собственный MediaTypeFormatter, а не привязку к модели.
Ответ 2
ModelBinder выглядит относительно лучше, чем MediaTypeFormatter. Вам не нужно регистрировать его по всему миру.
Я нашел другую альтернативу использованию связующего устройства для связывания сложных типов объектов в веб-API. В связующем модуле я читаю тело запроса как строку, а затем используя JSON.NET для десериализации его для требуемого типа объекта. Его также можно использовать для сопоставления массива сложных типов объектов.
Я добавил модельное связующее следующим образом:
public class PollRequestModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var body = actionContext.Request.Content.ReadAsStringAsync().Result;
var pollRequest = JsonConvert.DeserializeObject<PollRequest>(body);
bindingContext.Model = pollRequest;
return true;
}
}
И затем я использую его в контроллере Web API следующим образом:
public async Task<PollResponse> Post(Guid instanceId, [ModelBinder(typeof(PollRequestModelBinder))]PollRequest request)
{
// api implementation
}