Обработка валидации ModelState в ASP.NET Web API
Мне было интересно, как я могу добиться проверки модели с ASP.NET Web API. У меня есть моя модель так:
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)
а затем обработать сообщение об ошибке для передачи пользователю?
11 ответов
Для разделения проблем я бы предложил вам использовать фильтр действий для проверки модели, поэтому вам не нужно заботиться о том, как выполнять проверку в вашем контроллере 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);
}
}
}
Может быть, не то, что вы искали, но, возможно, приятно, чтобы кто-то знал:
Если вы используете.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."
]
}
}
Вот так, например:
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
Вы можете использовать атрибуты из System.ComponentModel.DataAnnotations
пространство имен для установки правил проверки. См. Проверка модели - Майк Уоссон для деталей.
Также см. Видео ASP.NET Web API, часть 5: Пользовательская проверка - Джон Галлоуэй
Другие ссылки
- Прогуляйтесь на стороне клиента с помощью WebAPI и WebForms
- Как ASP.NET Web API связывает сообщения HTTP с моделями доменов и как работать с медиаформатами в Web API.
- Доминик Байер - Защита веб-API ASP.NET
- Подключение проверки AngularJS к валидации ASP.NET Web API
- Отображение ошибок ModelState с AngularJS в ASP.NET MVC
- Как вынести ошибки клиенту? AngularJS / WebApi ModelState
- Внедрение зависимостей в веб-API
Или, если вы ищете простой набор ошибок для ваших приложений... вот моя реализация этого:
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" ] }
Добавьте ниже код в файл 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);
};
});
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);
};
Здесь вы можете проверить, чтобы показать ошибку состояния модели по одному
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
}
}
}
У меня была проблема с реализацией принятого шаблона решения, когда мой 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());
Поместите это в файл startup.cs
services.AddMvc().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = (context) =>
{
var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p =>p.ErrorMessage)).ToList();
var result = new Response
{
Succeeded = false,
ResponseMessage = string.Join(", ",errors)
};
return new BadRequestObjectResult(result);
};
});
Вы также можете выбросить исключения, как описано здесь: http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx
Обратите внимание, чтобы сделать то, что предлагает эта статья, не забудьте включить System.Net.Http