0

MVC4での例外処理についていくつか質問があります。

HandleErrorAttributeカスタマイズする新しい属性を実装して導出しました。それは本当にうまくいきます。しかし、私はユーザーに毎回カスタムエラーページをリダイレクトしたくありません。

アクションで発生したエラーのいくつかは、Web APIからスローされ、現在のページでユーザーに表示したいと思います。たとえば、ユーザーがレコードを作成したいが、モデル状態が無効であるためにWebAPIが例外をスローした場合、例外の詳細は[ビューの作成]にわかりやすい方法で表示されます。

ただし、HandleErrorAttributeは、デフォルトでError.cshtmlをリダイレクトします。

アクションですべての例外を処理できますが、別の方法があると思います。

また、http://www.prideparrot.com/blog/archive/2012/5/exception_handling_in_asp_net_mvcに従って実装しましたHandleErrorAttribute

 public class CustomHandleErrorAttribute : HandleErrorAttribute {
        private readonly ILogger _logger;

        public CustomHandleErrorAttribute() {
            _logger = new NLogger(typeof(CustomHandleErrorAttribute));
        }

        public override void OnException(ExceptionContext filterContext) {
            if(filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled) {
                return;
            }

            if(new HttpException(null, filterContext.Exception).GetHttpCode() != 500) {
                return;
            }

            if(!ExceptionType.IsInstanceOfType(filterContext.Exception)) {
                return;
            }

            // if the request is AJAX return JSON else view.
            if(filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest") {
                filterContext.Result = new JsonResult {
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                    Data = new {
                        error = true,
                        message = filterContext.Exception.Message
                    }
                };
            }
            else {
                var controllerName = (string)filterContext.RouteData.Values["controller"];
                var actionName = (string)filterContext.RouteData.Values["action"];
                var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
                filterContext.Result = new ViewResult {

                    ViewName = View,
                    MasterName = Master,
                    ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
                    TempData = filterContext.Controller.TempData
                };
            }

            _logger.Error(filterContext.Exception.Message, filterContext.Exception);

            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.StatusCode = 500;

            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
        }
    }

HttpClientとラッパークラスを介してWebAPI呼び出しを行いました。たとえば、Getリクエストは次のようになります。

 public async Task<BrandInfo> Create(BrandInfo entity) {
            using(var apiResponse = await base.PostAsync(BaseUriTemplate, entity)) {

                if(apiResponse.IsSuccess) {
                    return apiResponse.Model;
                }

                throw new HttpApiRequestException(
                    string.Format(HttpRequestErrorFormat, (int)apiResponse.Response.StatusCode, apiResponse.Response.ReasonPhrase),
                    apiResponse.Response.StatusCode, apiResponse.HttpError);
            }
        }
4

1 に答える 1

2

HttpClient をラップするクラスを作成し、それを使用して Web API を呼び出します。リダイレクトを発生させたい状況 (つまり、500 - 内部サーバー エラー、または 401 - 無許可) と、モデル状態エラーを表示したい状況 (400 - 不正な要求が私の選択です) に対して、Web API から異なる HTTP ステータス コードを返します。ラッパーケースでステータスコードを処理して、次のことを行います。

a) リダイレクトしたいときにエラーが発生した場合 (Web API から受信した 500 または 401)、適切な例外をスローします。

b) リダイレクト (Web API から受信した 400) が必要ない場合は、クライアント側で表示できるラッパー クラスから応答モデルを返すだけです。

コントローラーでは、例外によりコントローラーに戻らないため、HTTP ラッパー クラスから応答モデルが返されると想定してください (グローバルに処理し、リダイレクトを行います)。

コード サンプルが必要な場合は提供できますが、具体的なコードよりも一般的な概念を探していると思います。

編集:

Web API 側:

public class ModelValidationFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            Dictionary<string,string> errors = new Dictionary<string, string>();
            foreach (KeyValuePair<string, ModelState> keyValue in actionContext.ModelState)
            {
                errors[keyValue.Key] = keyValue.Value.Errors.Select(e => e.ErrorMessage).FirstOrDefault();
            }
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, new ApiError(ApiErrorCode.ModelBindingError, errors));
        }
    }
}

あなたのglobal.asaxで:

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

カスタム API エラー:

public class ApiError
{
    public ApiErrorCode ErrorCode { get; set; }
    public string ErrorMessage { get; set; }
    public Dictionary<string, string> ModelStateErrors;
}

MVC 側に関して言えば、ラッパー HttpClient ラッパー クラスは次のようになります。

public class RPCService
{

    public async Task<RPCResponseModel<T>> GetAsync<T>(string controller, string action, Dictionary<string, string> queryParams, Dictionary<string, string> headers)
    {
        using (HttpClient client = new HttpClient())
        {
            client.BaseAddress = new Uri("your host goes here");
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); /// Tell RPC to return data as json
            if (headers != null) foreach (var header in headers) client.DefaultRequestHeaders.Add(header.Key, header.Value);
            string query = Query(queryParams);
                var response = await client.GetAsync(controller + "/" + action + query);
                if (response.IsSuccessStatusCode)
                {
                    return new RPCResponseModel<T>
                    {
                        StatusCode = response.StatusCode,
                        Data = await response.Content.ReadAsAsync<T>()
                    };
                }
                else if(response.StatusCode == HttpStatusCode.BadRequest)
                {
                    return new RPCResponseModel<T>
                    {
                        Error = await response.Content.ReadAsAsync<RPCErrorModel>(),
                        StatusCode = response.StatusCode
                    };
                }
else
{
    /// throw your exception to handle globally
}
        }
    }

応答のモデルは次のようになります。

public class RPCErrorModel
{
    public int Code { get; set; }
    public string Message { get; set; }
    public Dictionary<string, string> ModelErrors;
}

public class RPCResponseModel
{
    public RPCErrorModel Error { get; set; }
    public HttpStatusCode StatusCode { get; set; }
}

public class RPCResponseModel<T> : RPCResponseModel
{
    public T Data { get; set; }
}
于 2013-03-09T21:41:05.367 に答える