ASP.NET Web API'de ModelState Doğrulamasını İşleyin


106

ASP.NET Web API ile nasıl model doğrulayabileceğimi merak ediyordum. Benim modelim şöyle:

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; }
}

Daha sonra API Denetleyicimde bir Gönder eylemim var:

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

if(ModelState.IsValid)Kullanıcıya iletilecek hata mesajını nasıl eklerim ve sonra işleyebilirim?

Yanıtlar:


186

Kaygıyı ayırmak için, model doğrulama için eylem filtresi kullanmanızı öneririm, bu nedenle api denetleyicinizde doğrulama işlemini nasıl yapacağınızla ilgilenmenize gerek kalmaz:

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
Bunun için gerekli ad alanları vardır System.Net.Http, System.Net System.Web.Http.Controllersve System.Web.Http.Filters.
Christopher Stevenson

11
Resmi ASP.NET Web Api sayfasında da benzer bir uygulama var: asp.net/web-api/overview/formats-and-model-binding/…
Erik Schierboom

1
[ValidationActionFilter] öğesini web api'sinin üstüne koymasanız bile, yine de kodu çağırıyor ve bana kötü bir istek veriyor.
micronyks

1
Döndürülen hata yanıtının IncludeErrorDetailPolicy tarafından kontrol edildiğini belirtmek gerekir . Varsayılan olarak, uzak bir isteğe verilen yanıt yalnızca genel bir "Bir hata oluştu" mesajı içerir, ancak bunu IncludeErrorDetailPolicy.Always, ayrıntıyı içerecek şekilde ayarlamak (ayrıntıları kullanıcılarınıza ifşa etme riski altında)
Rob

Bunun yerine IAsyncActionFilter kullanılmasını önermemenizin belirli bir nedeni var mı?
Ravior

30

Belki aradığınız şey değil, ama belki birinin bilmesi güzel:

.Net Web Api 2 kullanıyorsanız, aşağıdakileri yapabilirsiniz:

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

Model hatalarına bağlı olarak şu sonucu alırsınız:

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

1
Bu soruyu sorduğumda aklımda kalan şey, Web API 1 henüz piyasaya sürüldü, muhtemelen o zamandan bu yana çok fazla
değişti

Özellikleri isteğe bağlı olarak işaretlediğinizden emin olun, aksi takdirde yardımcı olmayan bir genel "Bir hata oluştu" mesajı alırsınız. hata mesajı.
Bouke

1
Mesajı değiştirmenin bir yolu var mı?
saquib adil

29

Bunun gibi, örneğin:

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);
    }
}

Bu, şöyle bir yanıt döndürür (JSON varsayılır, ancak XML için aynı temel ilke):

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."]

Elbette, hata nesnenizi / listenizi istediğiniz gibi oluşturabilirsiniz, örneğin alan adları, alan kimlikleri vb. Eklemek.

Yeni bir varlığın POST'u gibi "tek yönlü" bir Ajax çağrısı olsa bile, arayana yine de bir şey göndermelisiniz - isteğin başarılı olup olmadığını gösteren bir şey. Kullanıcınızın bir AJAX POST isteği aracılığıyla kendileri hakkında bazı bilgiler ekleyeceği bir site hayal edin. Ya girmeye çalıştıkları bilgiler geçerli değilse - Kaydet eylemlerinin başarılı olup olmadığını nasıl anlayacaklar?

Bunu yapmanın en iyi yolu kullanıyor Good Old HTTP Durum Kodları gibi 200 OKvb. Bu şekilde, JavaScript'iniz doğru geri aramaları (hata, başarı vb.) Kullanarak hataları düzgün bir şekilde halledebilir.

ActionFilter ve jQuery kullanarak, bu yöntemin daha gelişmiş bir sürümüyle ilgili güzel bir öğretici: http://asp.net/web-api/videos/getting-started/custom-validation


Bu sadece nesnemi döndürüyor enquiry, ancak hangi özelliklerin geçersiz olduğunu söylemiyor mu? Yani CustomerAccountNumberboş bırakırsam , varsayılan doğrulama mesajını söylemeli (CusomterAccountNumber alanı gereklidir ..)
CallumVass

Anlıyorum, öyleyse Model Doğrulamayı ele almanın "doğru" yolu bu mu? Bana biraz dağınık geliyor ..
CallumVass

JQuery doğrulamasıyla bağlantı kurmak gibi bunu yapmanın başka yolları da var. İşte güzel bir Microsoft örneği: asp.net/web-api/videos/getting-started/custom-validation
Anders Arpi

Bu yöntem ve yanıtı "olmalıdır" olarak seçilen yöntem işlevsel olarak aynıdır, bu nedenle bu yanıt, bir eylem filtresi olmadan bunu kendiniz nasıl yapabileceğinizi gösterme katma değerine sahiptir.
Shaun Wilson

Bunun benim için çalışmasını errors.Add(error.ErrorMessage);sağlamak errors.Add(error.Exception.Message);için hattı değiştirmem gerekiyordu.
Caltor

9

8

Veya uygulamalarınız için basit bir hata derlemesi arıyorsanız, işte benim uygulamam:

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);
        }
    }

Hata Mesajı Yanıtı şöyle görünecektir:

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

5

Startup.cs dosyasına aşağıdaki kodu ekleyin

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

Burada model durumu hatasını tek tek göstermeyi kontrol edebilirsiniz.

 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

Ben uygulayan bir sorunu vardı kabul çözüm desen benim ModelStateFilterhep döneceğini false(ve daha sonra 400) için actionContext.ModelState.IsValidbelirli bir model nesneler için:

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

Yalnızca JSON'u kabul ediyorum, bu nedenle özel bir model bağlayıcı sınıfı uyguladım:

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;
    }
}

Modelimden sonra doğrudan kaydettirdiğim

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.