2016-01-20 5 views
2

Я создал очень простой контроллер OData v4. Контроллер содержит в основном Entity Framework, поддержанные методы CRUD для следующего Pet объекта:Как предотвратить недопоставление в службе ASP.NET Web API OData?

public class Pet 
{ 
    public int Id { get; set; } 

    [Required] 
    public string Name { get; set; } 

    public int Age { get; set; } 
} 

Важным моментом здесь является то, что Pet.Age является ненулевое обязательным свойством.

Вот сам контроллер (только Post метод показан):

public class PetController : ODataController 
{ 
    private DatabaseContext db = new DatabaseContext(); 

    // POST: odata/Pet 
    public IHttpActionResult Post(Pet pet) 
    { 
     if (!ModelState.IsValid) 
     { 
      return BadRequest(ModelState); 
     } 

     db.Pet.Add(pet); 
     db.SaveChanges(); 

     return Created(pet); 
    } 

    // Other controller methods go here... 
} 

И это моя конфигурация WebApiConfig контроллер:

ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); 
builder.EntitySet<Pet>("Pet"); 
config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel()); 

Теперь, если я хочу, чтобы создать в новой Pet мой базы данных, я излагаю POST запрос:

POST http://localhost:8080/odata/Pet 
Content-type: application/json 

{ Name: "Cat", Age: 5 } 

Однако я могу просто опустить свойство Age в запросе JSON, поэтому десериализатор JSON будет использовать значение по умолчанию 0, в то время как я хочу вернуть 400 Bad Request статус. Эта проблема называется недопоставкой.

Его можно легко решить при использовании обычных контроллеров WebApi (описано решение here). Вы просто создать PetViewModel и сделать свой контроллер, чтобы принять PetViewModel вместо фактического Pet объекта:

public class PetViewModel 
{ 
    // Make the property nullable and set the Required attribute 
    // to distinguish between "zero" and "not set" 
    [Required] 
    public int? Age { get; set; } 

    // Other properties go here... 
} 

Тогда в контроллере вы просто конвертировать PetViewModel в Pet объект и сохранить его в базу данных, как обычно.

К сожалению, этот подход не работает с контроллерами OData: если я изменить свой Post метод принять PetViewModel вместо Pet, я получаю следующее сообщение об ошибке:

System.Net.Http.UnsupportedMediaTypeException: No MediaTypeFormatter is available to read an object of type 'PetViewModel' from content with media type 'application/json'.

at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable'1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)

at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable'1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)

at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)

Итак, есть ли способ предотвратить под -posting при использовании контроллеров OData?

+1

В этом примере вы можете использовать «RangeAttribute» и указать его с 1 по 999. Затем «ModelState.IsValid» должен поймать, что значение 0 не находится в пределах диапазона и возвращает статус «BadRequest». Другой вариант - создать настраиваемый фильтр и вручную проанализировать входящий JSON, прежде чем он будет сопоставлен с моделью, но это похоже на излишний. – Igor

+0

@Igor Я решил проблему, используя второй подход, потому что требуется общее решение для различения значений по умолчанию и 'null'. Посмотрите на ответ, если вы заинтересованы. Спасибо за помощь! –

ответ

1

После некоторого расследования я решил эту проблему. Не уверен, является ли это «официальным» или предпочтительным способом решения проблемы underposting в OData, но, по крайней мере, он отлично подходит для меня. Таким образом, из-за отсутствия официальной информации, вот мой рецепт:

Во-первых, создать соответствующую проверку ViewModel для OData объекта:

public class PetViewModel 
{ 
    public int Id { get; set; } 

    [Required] 
    [StringLength(50)] 
    public string Name { get; set; } 

    // Make the property nullable and set the Required attribute 
    // to distinguish between "zero" and "not set" 
    [Required] 
    public new int? Age { get; set; } 
} 

Затем добавьте свой собственный ODataUnderpostingValidationAttribute. Моя реализация выглядит следующим образом:

public class ODataUnderpostingValidationAttribute: ActionFilterAttribute 
{ 
    public ODataUnderpostingValidationAttribute(Type viewModelType) 
    { 
     ViewModelType = viewModelType; 
    } 

    public Type ViewModelType { get; set; } 

    public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) 
    { 
     // Rewind requestStream so it can be read again. 
     var requestStream = await actionContext.Request.Content.ReadAsStreamAsync(); 
     if (requestStream.CanSeek) 
     { 
      requestStream.Position = 0; 
     } 

     // Read the actual JSON payload. 
     var json = await actionContext.Request.Content.ReadAsStringAsync(); 

     // Deserialize JSON to corresponding validation ViewModel. 
     var viewModel = JsonConvert.DeserializeObject(json, ViewModelType); 
     var context = new ValidationContext(viewModel); 
     var results = new List<ValidationResult>(); 
     var isValid = Validator.TryValidateObject(viewModel, context, results); 

     if (!isValid) 
     { 
      // Throw HttpResponseException instead of setting actionContext.Response, so the exception will be logged by the ExceptionLogger. 
      var responseMessage = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage))); 
      throw new HttpResponseException(responseMessage); 
     } 

     await base.OnActionExecutingAsync(actionContext, cancellationToken); 
    } 
} 

После этого, примените этот пользовательский фильтр для вашего ODataController:

[ODataUnderpostingValidation(typeof(PetViewModel))] 
public class PetController : ODataController 
{ /* Implementation here */ } 

вуаля! Теперь у вас все на месте. Подтверждение подкрепления работает нормально.

0

У вас есть несколько вариантов, как я это вижу:

Первый В контроллере вы можете проверить целочисленное значение, и если его ниже определенного значения возврата 404.

if (Age <= 0) 
    return NotFound(); 

Это может быть трудоемкий, и если вы делаете это для каждого метода контроллера, это не очень СУХОЙ.

Второй в вашем классе Pet вы можете использовать диапазон атрибутов DataAnnotations, например.

[Range(0, 80, ErrorMessage = "Value for {0} must be between {1} and {2}")] 
public int Age { get; set; } 

Где возраст может быть не более 80. https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.rangeattribute(v=vs.110).aspx

Наконец я думаю, что ваш более постоянное решение для Вас было бы создать свою собственную проверку:

public class AgeValidation : ValidationAttribute { 
public override bool IsValid(object value) { 
    if (Object.Equals(value, null)) { 
     return false; 
    } 
    int getage; 
    if (int.TryParse(value.ToString(), out getage)) { 

     if (getage == 0) 
      return false; 

     if (getage > 0) 
      return true; 
    } 
    return false; 
} 

}

Затем в классе оных Pet:

[AgeValidation(ErrorMessage = "Age is wack")] 
public int Age { get; set; } 

Займы от How to do Integer model validation in asp.net mvc 2

+0

К сожалению, этот подход не решает реальной проблемы различения значений _0_ и _not set_. –

+0

Итак, каков был бы вред при добавлении атрибута [Обязательный] в свойство Age? Есть ли случаи, когда вы хотите, чтобы возраст отсутствовал? –

+0

Свойство 'Age' имеет тип' int', который является типом значения, а не ссылочным типом. После десериализации никогда не будет установлено значение «null». При использовании атрибута 'Required' возникает исключение проверки, если свойство равно null, содержит пустую строку (" ") или содержит только символы пробела. Таким образом, этот атрибут будет бесполезен в этом сценарии. –

Смежные вопросы