Обробіть ModelState Validation у веб-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; }
}

Потім у моєму контролері 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
Аналогічна реалізація також є на офіційній сторінці ASP.NET Web Api: asp.net/web-api/overview/formats-and-model-binding/…
Ерік Щербоом

1
Навіть якщо не поставити [ValidationActionFilter] вище веб-api, він все одно зателефонує до коду та подасть мені поганий запит.
мікронікс

1
Варто зазначити, що повернута відповідь про помилку контролюється увімкненням IncludeErrorDetailPolicy . За замовчуванням відповідь на віддалений запит містить лише загальне повідомлення "Виникла помилка", але встановлення цього параметра IncludeErrorDetailPolicy.Alwaysбуде включати деталі (ризикуючи розкрити деталі для користувачів)
Rob

Чи є конкретна причина, чому ви не запропонували замість цього використовувати IAsyncActionFilter?
Равиця

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 щойно був випущений, він, мабуть, з тих пір
перемістився

Обов’язково позначте властивості як необов’язкові, інакше ви отримаєте непотрібну загальну інформацію про помилку. повідомлення про помилку.
Буке

1
Чи є спосіб змінити Повідомлення?
saquib adil

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
Anders Arpi

Цей метод та метод, обраний як відповідь "повинні" бути функціонально однаковими, тому ця відповідь має додаткову цінність показувати вам, як ви могли це зробити самостійно без фільтра дій.
Шон Вілсон

Мені довелося змінити лінію, 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

У мене виникла проблема з реалізацією прийнятого шаблону рішення, де мій 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.