Обработка проверки ModelState в веб-API ASP.NET


106

Мне было интересно, как я могу добиться проверки модели с помощью веб-API ASP.NET. У меня модель такая:

public class Enquiry
{
    [Key]
    public int EnquiryId { get; set; }
    [Required]
    public DateTime EnquiryDate { get; set; }
    [Required]
    public string CustomerAccountNumber { get; set; }
    [Required]
    public string ContactName { get; set; }
}

Затем у меня есть действие Post в моем контроллере API:

public void Post(Enquiry enquiry)
{
    enquiry.EnquiryDate = DateTime.Now;
    context.DaybookEnquiries.Add(enquiry);
    context.SaveChanges();
}

Как добавить, if(ModelState.IsValid)а затем обработать сообщение об ошибке, чтобы передать его пользователю?

Ответы:


186

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

using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace System.Web.Http.Filters
{
    public class ValidationActionFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var modelState = actionContext.ModelState;

            if (!modelState.IsValid)
                actionContext.Response = actionContext.Request
                     .CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
        }
    }
}

27
Пространства имен , необходимые для этого являются System.Net.Http, System.Net System.Web.Http.Controllersи System.Web.Http.Filters.
Кристофер Стивенсон

11
Аналогичная реализация также есть на официальной странице веб- API
Эрик Ширбум

1
Даже если не указать [ValidationActionFilter] над веб-API, он все равно вызовет код и даст мне неверный запрос.
micronyks

1
Стоит отметить, что возвращаемый ответ об ошибке контролируется IncludeErrorDetailPolicy . По умолчанию ответ на удаленный запрос содержит только типовое сообщение «Произошла ошибка», но установка этого значения IncludeErrorDetailPolicy.Alwaysбудет включать подробные сведения (с риском раскрытия подробностей для ваших пользователей)
Роб

Есть ли конкретная причина, по которой вы не предложили вместо этого использовать IAsyncActionFilter?
Ravior

30

Может быть, это не то, что вы искали, но, возможно, приятно, чтобы кто-то знал:

Если вы используете .net Web Api 2, вы можете просто сделать следующее:

if (!ModelState.IsValid)
     return BadRequest(ModelState);

В зависимости от ошибок модели вы получите такой результат:

{
   Message: "The request is invalid."
   ModelState: {
       model.PropertyA: [
            "The PropertyA field is required."
       ],
       model.PropertyB: [
             "The PropertyB field is required."
       ]
   }
}

1
Не забывайте, когда я задавал этот вопрос, Web API 1 только что был выпущен, вероятно, с тех пор он
сильно изменился

Обязательно пометьте свойства как необязательные, иначе вы получите бесполезное общее сообщение «Произошла ошибка». сообщение об ошибке.
Bouke,

1
Есть ли способ изменить сообщение?
saquib adil 01

29

Вот так, например:

public HttpResponseMessage Post(Person person)
{
    if (ModelState.IsValid)
    {
        PersonDB.Add(person);
        return Request.CreateResponse(HttpStatusCode.Created, person);
    }
    else
    {
        // the code below should probably be refactored into a GetModelErrors
        // method on your BaseApiController or something like that

        var errors = new List<string>();
        foreach (var state in ModelState)
        {
            foreach (var error in state.Value.Errors)
            {
                errors.Add(error.ErrorMessage);
            }
        }
        return Request.CreateResponse(HttpStatusCode.Forbidden, errors);
    }
}

Это вернет такой ответ (при условии JSON, но тот же базовый принцип для XML):

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
(some headers removed here)

["A value is required.","The field First is required.","Some custom errorm essage."]

Вы, конечно, можете создать свой объект / список ошибок любым способом, например, добавив имена полей, идентификаторы полей и т. Д.

Даже если это «односторонний» вызов Ajax, такой как POST нового объекта, вы все равно должны вернуть что-то вызывающему - что-то, что указывает, был ли запрос успешным. Представьте себе сайт, на котором ваш пользователь добавит информацию о себе с помощью запроса AJAX POST. Что делать, если информация, которую они пытались ввести, недействительна - как они узнают, было ли их действие сохранения успешным или нет?

Лучший способ сделать это - использовать старые добрые коды состояния HTTP, например, 200 OKи так далее. Таким образом, ваш JavaScript может правильно обрабатывать сбои, используя правильные обратные вызовы (ошибка, успех и т. Д.).

Вот хороший учебник по более продвинутой версии этого метода с использованием ActionFilter и jQuery: http://asp.net/web-api/videos/getting-started/custom-validation


Это просто возвращает мой enquiryобъект, но не говорит, какие свойства недействительны? Итак, если я оставил CustomerAccountNumberпустым, должно появиться сообщение проверки по умолчанию (требуется поле CusomterAccountNumber ..)
CallumVass

Понятно, значит, это «правильный» способ проведения проверки модели? Мне кажется немного запутанным ..
CallumVass

Есть и другие способы сделать это, например, подключиться к проверке jQuery. Вот хороший пример Microsoft: asp.net/web-api/videos/getting-started/custom-validation
Андерс Арпи,

Этот метод и метод, выбранный в качестве ответа, «должны» быть функционально идентичными, поэтому этот ответ имеет дополнительную ценность, показывая вам, как вы могли бы сделать это самостоятельно без фильтра действий.
Шон Уилсон

Мне пришлось изменить строку errors.Add(error.ErrorMessage);на, errors.Add(error.Exception.Message);чтобы это работало для меня.
Caltor

9

8

Или, если вы ищете простой набор ошибок для своих приложений ... вот моя реализация этого:

public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var modelState = actionContext.ModelState;

        if (!modelState.IsValid) 
        {

            var errors = new List<string>();
            foreach (var state in modelState)
            {
                foreach (var error in state.Value.Errors)
                {
                    errors.Add(error.ErrorMessage);
                }
            }

            var response = new { errors = errors };

            actionContext.Response = actionContext.Request
                .CreateResponse(HttpStatusCode.BadRequest, response, JsonMediaTypeFormatter.DefaultMediaType);
        }
    }

Ответ на сообщение об ошибке будет выглядеть так:

{
  "errors": [
    "Please enter a valid phone number (7+ more digits)",
    "Please enter a valid e-mail address"
  ]
}

5

Добавьте ниже код в файл startup.cs

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).ConfigureApiBehaviorOptions(options =>
            {
                options.InvalidModelStateResponseFactory = (context) =>
                {
                    var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => new ErrorModel()
                   {
                       ErrorCode = ((int)HttpStatusCode.BadRequest).ToString(CultureInfo.CurrentCulture),
                        ErrorMessage = p.ErrorMessage,
                        ServerErrorMessage = string.Empty
                    })).ToList();
                    var result = new BaseResponse
                    {
                        Error = errors,
                        ResponseCode = (int)HttpStatusCode.BadRequest,
                        ResponseMessage = ResponseMessageConstants.VALIDATIONFAIL,

                    };
                    return new BadRequestObjectResult(result);
                };
           });

3

Здесь вы можете проверить, чтобы отображать ошибки состояния модели одну за другой

 public HttpResponseMessage CertificateUpload(employeeModel emp)
    {
        if (!ModelState.IsValid)
        {
            string errordetails = "";
            var errors = new List<string>();
            foreach (var state in ModelState)
            {
                foreach (var error in state.Value.Errors)
                {
                    string p = error.ErrorMessage;
                    errordetails = errordetails + error.ErrorMessage;

                }
            }
            Dictionary<string, object> dict = new Dictionary<string, object>();



            dict.Add("error", errordetails);
            return Request.CreateResponse(HttpStatusCode.BadRequest, dict);


        }
        else
        {
      //do something
        }
        }

}


3

C #

    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid == false)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }

...

    [ValidateModel]
    public HttpResponseMessage Post([FromBody]AnyModel model)
    {

Javascript

$.ajax({
        type: "POST",
        url: "/api/xxxxx",
        async: 'false',
        contentType: "application/json; charset=utf-8",
        data: JSON.stringify(data),
        error: function (xhr, status, err) {
            if (xhr.status == 400) {
                DisplayModelStateErrors(xhr.responseJSON.ModelState);
            }
        },
....


function DisplayModelStateErrors(modelState) {
    var message = "";
    var propStrings = Object.keys(modelState);

    $.each(propStrings, function (i, propString) {
        var propErrors = modelState[propString];
        $.each(propErrors, function (j, propError) {
            message += propError;
        });
        message += "\n";
    });

    alert(message);
};

2

У меня возникла проблема с реализацией принятого шаблона решения, при котором my ModelStateFilterвсегда возвращал false(а затем 400) actionContext.ModelState.IsValidдля определенных объектов модели:

public class ModelStateFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest};
        }
    }
}

Я принимаю только JSON, поэтому я реализовал собственный класс связывания модели:

public class AddressModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
    {
        var posted = actionContext.Request.Content.ReadAsStringAsync().Result;
        AddressDTO address = JsonConvert.DeserializeObject<AddressDTO>(posted);
        if (address != null)
        {
            // moar val here
            bindingContext.Model = address;
            return true;
        }
        return false;
    }
}

Который я регистрирую сразу после моей модели через

config.BindParameter(typeof(AddressDTO), new AddressModelBinder());

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.