430

クライアントにエラーを返す方法に懸念があります。

エラーが発生したときにHttpResponseExceptionをスローして、すぐにエラーを返しますか。

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

または、すべてのエラーを蓄積してからクライアントに送り返します。

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

これは単なるサンプル コードです。検証エラーやサーバー エラーは関係ありません。ベスト プラクティス、各アプローチの長所と短所を知りたいだけです。

4

12 に答える 12

324

私の場合、私は通常、スローされた例外に応じてを返送し、それに応じてステータスコードを設定します。例外が致命的であるかどうかによって、すぐHttpResponseExceptionに返送するかどうかが決まります。HttpResponseException

結局のところ、これはビューではなく応答を返すAPIであるため、例外とステータスコードを含むメッセージをコンシューマーに送り返すのは問題ないと思います。ほとんどの例外は通常、誤ったパラメータや呼び出しなどが原因であるため、現在、エラーを蓄積して返送する必要はありません。

私のアプリの例では、クライアントがデータを要求することがありますが、利用可能なデータがないため、カスタムをスローNoDataAvailableExceptionしてWeb APIアプリにバブルさせ、カスタムフィルターでキャプチャしてデータを送り返します。関連するメッセージと正しいステータスコード。

これのベストプラクティスが100%わからないのですが、これは現在私のために機能しているので、それが私が行っていることです。

更新

この質問に答えてから、このトピックについていくつかのブログ投稿が書かれています。

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

(これはナイトリービルドにいくつかの新機能があります) https://docs.microsoft.com/archive/blogs/youssefm/error-handling-in-asp-net-webapi

アップデート2

エラー処理プロセスを更新すると、次の2つのケースがあります。

  1. 見つからないなどの一般的なエラーや、無効なパラメータがアクションに渡された場合は、を返し、HttpResponseException処理をすぐに停止します。さらに、アクションのモデルエラーについては、モデル状態ディクショナリをRequest.CreateErrorResponse拡張機能に渡し、でラップしHttpResponseExceptionます。モデル状態ディクショナリを追加すると、応答本文で送信されるモデルエラーのリストが表示されます。

  2. 上位層で発生するエラー、サーバーエラーについては、例外をWeb APIアプリにバブルさせます。ここでは、例外を確認し、ELMAHでログに記録し、正しいHTTPを設定することを理解しようとするグローバル例外フィルターがあります。ステータスコードと関連するわかりやすいエラーメッセージを本文としてHttpResponseException。クライアントがデフォルトの500内部サーバーエラーを受け取ることを期待していない例外については、セキュリティ上の理由から一般的なメッセージを受け取ります。

アップデート3

最近、Web API 2を取得した後、一般的なエラーを返送するために、IHttpActionResultインターフェイスを使用します。具体的には、名前空間に組み込まれSystem.Web.Http.ResultsているNotFound、BadRequestなどの組み込みクラスが適切な場合はそれらを拡張します。 NotFoundの結果と応答メッセージ:

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);
    }
}
于 2012-05-24T09:27:40.627 に答える
81

エラー/例外よりも検証に問題があるようですので、両方について少しお話しします。

検証

コントローラーのアクションは、通常、検証がモデルで直接宣言されている入力モデルを取る必要があります。

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

次に、ActionFilter検証メッセージをクライアントに自動的に送信する を使用できます。

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

詳細については、http://ben.onfabrik.com/posts/automatic-modelstate-validation-in-aspnet-mvcをご覧ください。

エラー処理

発生した例外を表すメッセージを (関連するステータス コードと共に) クライアントに返すことをお勧めします。

Request.CreateErrorResponse(HttpStatusCode, message)メッセージを指定する場合は、すぐに使用する必要があります。ただし、これによりコードがRequestオブジェクトに関連付けられるため、これを行う必要はありません。

私は通常、独自のタイプの「安全な」例外を作成し、クライアントが他のすべての例外を処理して一般的な 500 エラーでラップする方法を知っていると期待しています。

アクション フィルターを使用して例外を処理すると、次のようになります。

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

その後、グローバルに登録できます。

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

これは私のカスタム例外タイプです。

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 がスローできる例外の例。

public class NotAuthenticatedException : ApiException
{
    public NotAuthenticatedException()
        : base(HttpStatusCode.Forbidden)
    {
    }
}
于 2014-03-04T05:30:36.543 に答える
38

HttpResponseException をスローできます

HttpResponseMessage response = 
    this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "your message");
throw new HttpResponseException(response);
于 2014-03-04T05:15:18.760 に答える
5

Web Api でカスタム ActionFilter を使用して、モデルを検証できます。

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 クラスを webApiConfig.cs に登録します。 config.Filters.Add(new DRFValidationFilters());

于 2016-03-18T09:40:11.123 に答える
0

ASP.NET WebAPI の現在の状態を更新するだけです。インターフェイスが呼び出されるようIActionResultになり、実装はあまり変更されていません。

[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
}
于 2016-05-10T07:51:29.200 に答える
-2

modelstate.isvalid が false であるエラーについては、通常、コードによってスローされたときにエラーを送信します。私のサービスを利用している開発者にとっては理解しやすいものです。私は通常、以下のコードを使用して結果を送信します。

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

これにより、基本的にエラーのリストである以下の形式でクライアントにエラーが送信されます。

    [  
    "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)"
    ]
于 2016-01-19T10:41:54.863 に答える