2014-08-02 5 views
8
public class MyModel 
{ 
    [JsonProperty(PropertyName = "foo", Required = Required.Always)] 
    public String Bar; 
} 

public class MyController : ApiController 
{ 
    public String PostVersion1([FromBody] MyModel myModel) 
    { 
     if (ModelState.IsValid) 
     { 
      if (myModel.Bar == null) 
       return "What?!"; 
      else 
       return "Input is valid."; 
     } 
     else 
     { 
      return "Input is invalid."; 
     } 
    } 
} 

Результаты:Как получить WebAPI для проверки моего JSON с помощью JsonProperty (обязательно = Required.Always)?

Input    |Output 
-------------------|------ 
{ "bad" : "test" } | What?! 
{ "Bar" : "test" } | What?! 
{ "foo" : "test" } | Input is valid. 

JsonPropertyAttribute явно поддерживаются, потому что я в состоянии установить ИмениСвойства и он вступит в силу. Однако я ожидал бы, что ModelState.IsValid будет ложным для первых двух примерных входов, потому что параметр Required JsonProprty был установлен на Always.

Если я просто запустить его через JsonConvert:

JsonConvert.DeserializeObject<MyModel>(@"{'bad':'test'}"); 

исключение выбрасывается во время десериализации, как и ожидалось:

Result Message: Newtonsoft.Json.JsonSerializationException : Required property 'foo' not found in JSON. Path '', line 1, position 14. 

ответ

2

для того, чтобы решить эту проблему, я в конечном итоге создать свой собственный пользовательский JSON.NET MediaTypeFormatter. Мой форматировщик позволяет JSON.NET исключения десериализации пузырь из которых приводит к информации, за исключением возвращается к абоненту.

Вот MediaTypeFormatter Я построил:

public class JsonMediaFormatter : MediaTypeFormatter 
{ 
    private readonly JsonSerializer _jsonSerializer = new JsonSerializer(); 

    public JsonMediaFormatter() 
    { 
     SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); 
    } 

    public override Boolean CanReadType(Type type) 
    { 
     if (type == null) 
      return false; 

     return true; 
    } 

    public override Boolean CanWriteType(Type type) 
    { 
     if (type == null) 
      return false; 

     return true; 
    } 

    public override Task<Object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) 
    { 
     return Task.FromResult(Deserialize(readStream, type)); 
    } 

    public override Task WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken) 
    { 
     Serialize(writeStream, value); 
     return Task.FromResult(0); 
    } 

    private Object Deserialize(Stream readStream, Type type) 
    { 
     var streamReader = new StreamReader(readStream); 
     return _jsonSerializer.Deserialize(streamReader, type); 
    } 

    private void Serialize(Stream writeStream, Object value) 
    { 
     var streamWriter = new StreamWriter(writeStream); 
     _jsonSerializer.Serialize(streamWriter, value); 
     streamWriter.Flush(); 
    } 
} 

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

config.Formatters.Insert(0, new Formatters.JsonMediaFormatter()); 

Вставив его с индексом 0, он имеет приоритет над встроенным форматировщиком. Если вам все равно, вы можете удалить встроенный форматировщик JSON.

В этом случае ModelState всегда действителен в действии, потому что исключение возвращается пользователю перед тем, как действие будет когда-либо запущено, если десериализация завершится с ошибкой. Необходимо выполнить еще большую работу, чтобы выполнить действие с нулевым параметром FromBody.

+1

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

+1

Решено, что ошибка отправляется с фильтром исключений, который создает специальный ответ (400), когда видит исключение JsonSerializationException. –

+0

Почему вы используете Task.Factory.StartNew() ?? Просто используйте Task.FromResult (...). Плохая практика использования Task.Factory.StarNew() для операций с привязкой к ЦП. –

4

по умолчанию JsonMediaTypeFormatter делает не полагаться на на него JsonProperty, чтобы решить, следует ли поля модели необходимы или нет. Однако он полагается, однако, на RequiredAttribute

Если вы хотите сделать это, то внесите новый IRequiredMemberSelector и установите его на MediaTypeFormatter.RequiredMemberSelector.

В вашей реализации IRequiredMemberSelector вы пройдете MemberInfo. Вы можете использовать это, чтобы оценить, имеют ли члены модели атрибут JsonProperty, и если установлен флаг, и, наконец, возвращает true или false. Это будет распространяться на ModelState.IsValid собственности (он не будет использовать сообщение об ошибке JSON.NET хотя, но DataAnnotations/WebAPI один.

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

+0

Я не могу использовать RequiredAttribute, потому что кажется, что это действует до маршрутизации имени десериализатора JSON. В приведенных выше примерах {«Bar»: «test»} будет проверяться, хотя после десериализации Bar будет null. –

+0

Казалось бы, сначала запускается RequiredMemberSelector, а затем после его проверки тип десериализуется. Можно ли десериализовать, а затем проверить или сделать оба за один шаг, не забирая мои действия с помощью JsonConvert.Deserialize вызовов? Кажется, что более правильный курс действий здесь (особенно, поскольку я хочу вернуть ошибку при неудаче десериализации), должен был сначала десериализоваться, а затем вызвать действие, если десериализация была успешной. Если я правильно понимаю, сначала выполняется проверка, а затем десериализация. –

+0

@MicahCaldwell: Позвольте мне проверить. –

0

Если вы хотите, чтобы поддержать JSON, то вы можете сделать это:

public String PostVersion1([FromBody] JObject json) 
{ 
    if(json == null) { 
    // Invalid JSON or wrong Content-Type 
    throw new HttpResponseException(HttpStatusCode.BadRequest); 
    } 

    MyModel model; 
    try 
    { 
    model = json.ToObject<MyModel>(); 
    } 
    catch(JsonSerializationException e) 
    { 
    // Serialization failed 
    throw new HttpResponseException(HttpStatusCode.BadRequest); 
    } 
} 

Можно было бы подумать, что Web API автоматически возвращает 400 BAD REQUEST (с дополнительной информацией об ошибке в формате JSON), если сериализация/проверка не пройдена, но meh ... Это не так продвинуто, как кажется.