ASP.NET Web API'sındaki hataları döndürmek için en iyi uygulama


384

Hataları istemciye döndürme biçimimizle ilgili endişelerim var.

Bir hata aldığımızda HttpResponseException durumunu atarak hatayı hemen döndürüyor muyuz:

public void Post(Customer customer)
{
    if (string.IsNullOrEmpty(customer.Name))
    {
        throw new HttpResponseException("Customer Name cannot be empty", HttpStatusCode.BadRequest) 
    }
    if (customer.Accounts.Count == 0)
    {
         throw new HttpResponseException("Customer does not have any account", HttpStatusCode.BadRequest) 
    }
}

Veya tüm hataları biriktirip müşteriye geri göndeririz:

public void Post(Customer customer)
{
    List<string> errors = new List<string>();
    if (string.IsNullOrEmpty(customer.Name))
    {
        errors.Add("Customer Name cannot be empty"); 
    }
    if (customer.Accounts.Count == 0)
    {
         errors.Add("Customer does not have any account"); 
    }
    var responseMessage = new HttpResponseMessage<List<string>>(errors, HttpStatusCode.BadRequest);
    throw new HttpResponseException(responseMessage);
}

Bu sadece bir örnek kod, doğrulama hataları veya sunucu hatası önemli değil, sadece en iyi uygulama, her yaklaşımın artılarını ve eksilerini bilmek istiyorum.


1
Kullanmanız gereken stackoverflow.com/a/22163675/200442 adresine bakın ModelState.
Daniel Little

1
Buradaki yanıtların yalnızca denetleyicinin kendisine atılan Özel Durumları kapsadığını unutmayın. API'niz henüz yürütülmemiş bir IQueryable <Model> döndürürse, istisna denetleyicide değildir ve yakalanmaz ...
Jess

3
Çok güzel bir soru ama bir şekilde HttpResponseExceptionsenin yazı belirtilen iki parametre alır sınıf herhangi bir kurucu aşırı almıyorum - HttpResponseException("Customer Name cannot be empty", HttpStatusCode.BadRequest) yaniHttpResponseException(string, HttpStatusCode)
RBT

Yanıtlar:


293

Benim için genellikle bir geri gönderirim HttpResponseExceptionve atılan istisnaya bağlı olarak durum kodunu ayarlıyorum ve istisna ölümcül olup olmadığı HttpResponseExceptionderhal geri gönderip göndermeyeceğimi belirleyecektir .

Günün sonunda, görüntülemeleri değil yanıtları geri gönderen bir API olduğunu, bu yüzden tüketiciye istisna ve durum kodu içeren bir mesaj geri göndermenin iyi olduğunu düşünüyorum. Çoğu istisna genellikle yanlış parametreler veya çağrılar vb.Nedeniyle şu anda hataları toplamam ve geri göndermem gerekmiyordu.

Uygulamamda bir örnek, bazen müşterinin veri isteyebileceğidir, ancak kullanılabilir veri yoktur, bu nedenle özel NoDataAvailableException Web API uygulamasına geçmesine izin veriyorum, burada özel filtremde geri göndererek ilgili mesajla birlikte doğru durum kodu.

Bunun için en iyi uygulamanın ne olduğundan% 100 emin değilim, ama bu şu anda benim için çalışıyor, bu yüzden yapıyorum.

Güncelleme :

Bu soruyu cevapladığımdan beri konuyla ilgili birkaç blog yazısı yazıldı:

https://weblogs.asp.net/fredriknormen/asp-net-web-api-exception-handling

(bu gece yapılarında bazı yeni özellikler var) https://docs.microsoft.com/archive/blogs/youssefm/error-handling-in-asp-net-webapi

Güncelleme 2

Hata işleme sürecimizde güncelleme, iki durumumuz var:

  1. Bulunamayan genel hatalar veya bir eyleme geçersiz parametreler iletilmesi için, işlemi HttpResponseExceptionhemen durdurmak için bir a döndürürüz . Ayrıca eylemlerimizdeki model hataları için, model durumu sözlüğünü Request.CreateErrorResponseuzantıya teslim edeceğiz ve a HttpResponseException. Model durumu sözlüğünün eklenmesi, yanıt gövdesinde gönderilen model hatalarının bir listesi ile sonuçlanır.

  2. Daha yüksek katmanlarda, sunucu hatalarında meydana gelen hatalar için, istisna balonunun Web API uygulamasına girmesine izin veriyoruz, burada istisnaya bakan, ELMAH ile günlüğe kaydeden ve doğru HTTP ayarını anlamaya çalışan bir global istisna filtresine sahibiz Durum kodu ve ilgili bir dost hata mesajı tekrar vücut olarak bir HttpResponseException. İstemcinin beklemediğimiz istisnalar için varsayılan 500 dahili sunucu hatası, ancak güvenlik nedeniyle genel bir mesaj alırsınız.

Güncelleme 3

Son zamanlarda, Web API 2'yi aldıktan sonra, genel hataları geri göndermek için artık IHttpActionResult arayüzünü, özellikle de System.Web.Http.Resultsuygun olmadığında, NotFound, BadRequest gibi ad alanındaki yerleşik sınıfları kullanıyoruz, örneğin, genişletmediklerinde, örneğin Yanıt mesajı içeren bir NotFound sonucu:

public class NotFoundWithMessageResult : IHttpActionResult
{
    private string message;

    public NotFoundWithMessageResult(string message)
    {
        this.message = message;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotFound);
        response.Content = new StringContent(message);
        return Task.FromResult(response);
    }
}

Cevabınız için teşekkür ederiz geepie, bu iyi bir deneyim, bu yüzden hemen expcetion göndermeyi mi tercih ediyorsunuz?
cuongle

Dediğim gibi gerçekten istisna bağlıdır. Örneğin kullanıcı Web Api geçersiz bir parametre bir uç noktaya geçer gibi ölümcül bir istisna, o zaman bir HttpResponseException oluşturmak ve hemen tüketen app dönecekti.
gdp

Sorudaki istisnalar gerçekten doğrulama hakkında daha fazla bilgi için bkz. Stackoverflow.com/a/22163675/200442 .
Daniel Little

1
@DanielLittle Sorusunu tekrar okuyun. Ben alıntı: "Bu sadece bir örnek kod, doğrulama hataları veya sunucu hatası önemli değil"
gdp

@gdp Buna rağmen gerçekten iki bileşeni var, doğrulama ve istisnalar, bu yüzden her ikisini de kapsamak en iyisidir.
Daniel Little

184

ASP.NET Web API 2 gerçekten basitleştirdi. Örneğin, aşağıdaki kod:

public HttpResponseMessage GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var message = string.Format("Product with id = {0} not found", id);
        HttpError err = new HttpError(message);
        return Request.CreateResponse(HttpStatusCode.NotFound, err);
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.OK, item);
    }
}

öğe bulunmadığında aşağıdaki içeriği tarayıcıya döndürür:

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Aug 2012 23:27:18 GMT
Content-Length: 51

{
  "Message": "Product with id = 12 not found"
}

Öneri: Yıkıcı bir hata olmadıkça HTTP Hatası 500 atmayın (örneğin, WCF Hata İstisnası). Verilerinizin durumunu temsil eden uygun bir HTTP durum kodu seçin. (Aşağıdaki apigee bağlantısına bakın.)

Bağlantılar:


4
Bir adım daha ileri gitmek ve ResALNotFoundException türü için Web Api 2.2 ExceptionHandler içinde kontrol DAL / Repo bir ResourceNotFoundException atmak ve sonra "id xxx ile ürün bulunamadı" döndürür. Bu şekilde, her eylem yerine genel olarak mimariye sabitlenir.
Pascal

1
İçin herhangi bir spesifik kullanımı var mıdır return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState); arasındaki fark nedir CreateResponseveCreateErrorResponse
Zapnologica

10
Göre, W3.org/Protocols/rfc2616/rfc2616-sec10.html dosyasına , bir istemci hatası 400 düzey kod ve bir sunucu hatası 500 düzey koddur. Bu nedenle, 500 hata kodu yalnızca "yıkıcı" hatalar için değil, bir Web API'sı için de çok uygun olabilir.
Jess

2
Gösterilmesi using System.Net.Http;için CreateResponse()uzantı yöntemine ihtiyacınız var .
Adam Szabo

Request.CreateResponse () kullanımı hakkında sevmiyorum, "<string xmlns =" schemas.microsoft.com/2003/10/Serialization/">Burada hatam </ string gibi gereksiz Microsoft'a özel serileştirme bilgileri döndürüyor >". 400 durumunun uygun olduğu durumlarda ApiController.BadRequest (string message) daha iyi bir "<Error> <Message> Hatam burada </Message> </Error>" dizesi döndürdü. Ama basit bir mesajla 500 statüsü için eşdeğerini bulamıyorum.
vkelman

76

Doğrulama konusunda hatalardan / istisnalardan daha fazla sorun yaşıyorsanız, her ikisi hakkında da biraz söyleyeceğim.

onaylama

Denetleyici eylemleri genellikle, doğrulamanın doğrudan modelde bildirildiği Giriş Modellerini almalıdır.

public class Customer
{ 
    [Require]
    public string Name { get; set; }
}

Ardından ActionFilter, istemciye otomatik olarak doğrulama iletileri gönderen bir kullanabilirsiniz .

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

Bu konuda daha fazla bilgi için http://ben.onfabrik.com/posts/automatic-modelstate-validation-in-aspnet-mvc adresine bakın.

Hata yönetimi

İstemciye, gerçekleşen özel durumu (ilgili durum koduyla birlikte) temsil eden bir ileti döndürmek en iyisidir.

Kullanmanız gereken kutunun dışında Request.CreateErrorResponse(HttpStatusCode, message)Bir mesaj belirtmek istiyorsanız . Ancak, bu, kodu Requestyapmanız gerekmeyen nesneye bağlar .

Ben genellikle istemcinin genel 500 hata ile diğerlerini işlemek ve sarmak için nasıl bileceğini beklediğim kendi "güvenli" istisna oluşturmak.

İstisnaları işlemek için bir eylem filtresi kullanmak şöyle görünür:

public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        var exception = context.Exception as ApiException;
        if (exception != null) {
            context.Response = context.Request.CreateErrorResponse(exception.StatusCode, exception.Message);
        }
    }
}

Sonra küresel olarak kaydedebilirsiniz.

GlobalConfiguration.Configuration.Filters.Add(new ApiExceptionFilterAttribute());

Bu benim özel istisna tipim.

using System;
using System.Net;

namespace WebApi
{
    public class ApiException : Exception
    {
        private readonly HttpStatusCode statusCode;

        public ApiException (HttpStatusCode statusCode, string message, Exception ex)
            : base(message, ex)
        {
            this.statusCode = statusCode;
        }

        public ApiException (HttpStatusCode statusCode, string message)
            : base(message)
        {
            this.statusCode = statusCode;
        }

        public ApiException (HttpStatusCode statusCode)
        {
            this.statusCode = statusCode;
        }

        public HttpStatusCode StatusCode
        {
            get { return this.statusCode; }
        }
    }
}

API'mın atabileceği örnek bir istisna.

public class NotAuthenticatedException : ApiException
{
    public NotAuthenticatedException()
        : base(HttpStatusCode.Forbidden)
    {
    }
}

ApiExceptionFilterAttribute sınıf tanımında hata işleme yanıtı ile ilgili bir sorun var. OnException yönteminde, istisna bir WebException olduğundan istisna.StatusCode geçerli değildir. Bu durumda ne yapabilirim?
razp26

1
@ razp26 Eğer var exception = context.Exception as WebException;bir yazım hatası gibi bir şeyden bahsediyorsanız , olmalıydıApiException
Daniel Little

2
Lütfen ApiExceptionFilterAttribute sınıfının nasıl kullanılacağına dair bir örnek ekleyebilir misiniz?
razp26

36

Bir HttpResponseException kurabilirsiniz

HttpResponseMessage response = 
    this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "your message");
throw new HttpResponseException(response);

23

Web API 2 için yöntemlerimi tutarlı bir şekilde IHttpActionResult döndürür, bu yüzden kullanıyorum ...

public IHttpActionResult Save(MyEntity entity)
{
  ....

    return ResponseMessage(
        Request.CreateResponse(
            HttpStatusCode.BadRequest, 
            validationErrors));
}

Bu cevap uygundur, ancak referans eklemeniz gerekirSystem.Net.Http
Bellash

19

ASP.NET Web API 2 kullanıyorsanız, en kolay yol ApiController Kısa Yöntemini kullanmaktır. Bu bir BadRequestResult sonucunu verecektir.

return BadRequest("message");

Kesinlikle model doğrulama hataları için ModelState nesnesini kabul eden BadRequest () aşırı yüklenmesini kullanıyorum:return BadRequest(ModelState);
timmi4sa 9:01 '

4

modeli doğrulamak için Web Api'de özel ActionFilter kullanabilirsiniz

public class DRFValidationFilters : ActionFilterAttribute
{

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = actionContext.Request
                 .CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);

            //BadRequest(actionContext.ModelState);
        }
    }
    public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {

        return Task.Factory.StartNew(() => {

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

    }

public class AspirantModel
{
    public int AspirantId { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }        
    public string LastName { get; set; }
    public string AspirantType { get; set; }       
    [RegularExpression(@"^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$", ErrorMessage = "Not a valid Phone number")]
    public string MobileNumber { get; set; }
    public int StateId { get; set; }
    public int CityId { get; set; }
    public int CenterId { get; set; }

}

    [HttpPost]
    [Route("AspirantCreate")]
    [DRFValidationFilters]
    public IHttpActionResult Create(AspirantModel aspirant)
    {
            if (aspirant != null)
            {

            }
            else
            {
                return Conflict();
            }
          return Ok();

}

CustomAttribute sınıfını webApiConfig.cs config.Filters.Add'ye (yeni DRFValidationFilters ()) kaydedin;


4

Manish JainCevabını temel alarak (işleri basitleştiren Web API 2 içindir):

1) Mümkün olduğunca çok sayıda doğrulama hatasına yanıt vermek için doğrulama yapılarını kullanın . Bu yapılar, formlardan gelen isteklere yanıt vermek için de kullanılabilir.

public class FieldError
{
    public String FieldName { get; set; }
    public String FieldMessage { get; set; }
}

// a result will be able to inform API client about some general error/information and details information (related to invalid parameter values etc.)
public class ValidationResult<T>
{
    public bool IsError { get; set; }

    /// <summary>
    /// validation message. It is used as a success message if IsError is false, otherwise it is an error message
    /// </summary>
    public string Message { get; set; } = string.Empty;

    public List<FieldError> FieldErrors { get; set; } = new List<FieldError>();

    public T Payload { get; set; }

    public void AddFieldError(string fieldName, string fieldMessage)
    {
        if (string.IsNullOrWhiteSpace(fieldName))
            throw new ArgumentException("Empty field name");

        if (string.IsNullOrWhiteSpace(fieldMessage))
            throw new ArgumentException("Empty field message");

        // appending error to existing one, if field already contains a message
        var existingFieldError = FieldErrors.FirstOrDefault(e => e.FieldName.Equals(fieldName));
        if (existingFieldError == null)
            FieldErrors.Add(new FieldError {FieldName = fieldName, FieldMessage = fieldMessage});
        else
            existingFieldError.FieldMessage = $"{existingFieldError.FieldMessage}. {fieldMessage}";

        IsError = true;
    }

    public void AddEmptyFieldError(string fieldName, string contextInfo = null)
    {
        AddFieldError(fieldName, $"No value provided for field. Context info: {contextInfo}");
    }
}

public class ValidationResult : ValidationResult<object>
{

}

2) Hizmet katmanıValidationResult , işlemin başarılı olup olmadığına bakılmaksızın geri dönecektir . Örneğin:

    public ValidationResult DoSomeAction(RequestFilters filters)
    {
        var ret = new ValidationResult();

        if (filters.SomeProp1 == null) ret.AddEmptyFieldError(nameof(filters.SomeProp1));
        if (filters.SomeOtherProp2 == null) ret.AddFieldError(nameof(filters.SomeOtherProp2 ), $"Failed to parse {filters.SomeOtherProp2} into integer list");

        if (filters.MinProp == null) ret.AddEmptyFieldError(nameof(filters.MinProp));
        if (filters.MaxProp == null) ret.AddEmptyFieldError(nameof(filters.MaxProp));


        // validation affecting multiple input parameters
        if (filters.MinProp > filters.MaxProp)
        {
            ret.AddFieldError(nameof(filters.MinProp, "Min prop cannot be greater than max prop"));
            ret.AddFieldError(nameof(filters.MaxProp, "Check"));
        }

        // also specify a global error message, if we have at least one error
        if (ret.IsError)
        {
            ret.Message = "Failed to perform DoSomeAction";
            return ret;
        }

        ret.Message = "Successfully performed DoSomeAction";
        return ret;
    }

3) API Denetleyicisi , hizmet işlevi sonucuna göre yanıtı oluşturur

Bir seçenek, neredeyse tüm parametreleri isteğe bağlı olarak koymak ve daha anlamlı bir yanıt veren özel doğrulama yapmaktır. Ayrıca, herhangi bir istisnanın hizmet sınırlarının ötesine geçmemesine dikkat ediyorum.

    [Route("DoSomeAction")]
    [HttpPost]
    public HttpResponseMessage DoSomeAction(int? someProp1 = null, string someOtherProp2 = null, int? minProp = null, int? maxProp = null)
    {
        try
        {
            var filters = new RequestFilters 
            {
                SomeProp1 = someProp1 ,
                SomeOtherProp2 = someOtherProp2.TrySplitIntegerList() ,
                MinProp = minProp, 
                MaxProp = maxProp
            };

            var result = theService.DoSomeAction(filters);
            return !result.IsError ? Request.CreateResponse(HttpStatusCode.OK, result) : Request.CreateResponse(HttpStatusCode.BadRequest, result);
        }
        catch (Exception exc)
        {
            Logger.Log(LogLevel.Error, exc, "Failed to DoSomeAction");
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, new HttpError("Failed to DoSomeAction - internal error"));
        }
    }

3

Yerleşik "InternalServerError" yöntemini kullanın (ApiController'da bulunur):

return InternalServerError();
//or...
return InternalServerError(new YourException("your message"));

0

Sadece ASP.NET WebAPI'nin geçerli durumunu güncelleştirmek için. Arayüz şimdi çağrıldı IActionResultve uygulama pek değişmedi:

[JsonObject(IsReference = true)]
public class DuplicateEntityException : IActionResult
{        
    public DuplicateEntityException(object duplicateEntity, object entityId)
    {
        this.EntityType = duplicateEntity.GetType().Name;
        this.EntityId = entityId;
    }

    /// <summary>
    ///     Id of the duplicate (new) entity
    /// </summary>
    public object EntityId { get; set; }

    /// <summary>
    ///     Type of the duplicate (new) entity
    /// </summary>
    public string EntityType { get; set; }

    public Task ExecuteResultAsync(ActionContext context)
    {
        var message = new StringContent($"{this.EntityType ?? "Entity"} with id {this.EntityId ?? "(no id)"} already exist in the database");

        var response = new HttpResponseMessage(HttpStatusCode.Ambiguous) { Content = message };

        return Task.FromResult(response);
    }

    #endregion
}

Bu ilginç görünüyor, ancak bu kod özellikle projede nereye yerleştirilir? Vb.net web api 2 projemi yapıyorum.
Altın Dışı

Sadece hatayı döndürmek için bir modeldir ve her yerde bulunabilir. Denetleyicinize yukarıdaki sınıfın yeni bir örneğini döndürürdünüz. Ama dürüst olmak gerekirse, mümkün olduğunca yerleşik sınıfları kullanmaya çalışıyorum: this.Ok (), CreatedAtRoute (), NotFound (). Yöntemin imzası IHttpActionResult olur. Tüm bunları NetCore ile değiştirip değiştirmediklerini bilmiyorum
Thomas Hagström

-2

Modelstate.isvalid öğesinin false olduğu bu hatalar için, genellikle kodu atarken hatayı gönderirim. Hizmetimi tüketen geliştirici için anlaşılması kolay. Genellikle sonucu aşağıdaki kodu kullanarak gönderirim.

     if(!ModelState.IsValid) {
                List<string> errorlist=new List<string>();
                foreach (var value in ModelState.Values)
                {
                    foreach(var error in value.Errors)
                    errorlist.Add( error.Exception.ToString());
                    //errorlist.Add(value.Errors);
                }
                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.BadRequest,errorlist);}

Bu, istemciye temelde hataların bir listesi olan aşağıdaki biçimde gönderir:

    [  
    "Newtonsoft.Json.JsonReaderException: **Could not convert string to integer: abc. Path 'Country',** line 6, position 16.\r\n   
at Newtonsoft.Json.JsonReader.ReadAsInt32Internal()\r\n   
at Newtonsoft.Json.JsonTextReader.ReadAsInt32()\r\n   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter, Boolean inArray)\r\n   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)",

       "Newtonsoft.Json.JsonReaderException: **Could not convert string to integer: ab. Path 'State'**, line 7, position 13.\r\n   
at Newtonsoft.Json.JsonReader.ReadAsInt32Internal()\r\n   
at Newtonsoft.Json.JsonTextReader.ReadAsInt32()\r\n   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter, Boolean inArray)\r\n   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)"
    ]

Bu harici bir API (yani genel internet maruz) istisna dışında bu düzeyde ayrıntı geri gönderme tavsiye etmem. Filtrede biraz daha çalışma yapmalı ve yalnızca bir istisna ToStringinden ziyade hatayı detaylandıran bir JSON nesnesi (veya seçilen biçimse XML) geri göndermelisiniz.
Sudhanshu Mishra

Harici API için bu istisnayı göndermeyin. Ancak dahili API'daki ve test sırasındaki hataları ayıklamak için kullanabilirsiniz.
Ashish Sahu
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.