0

I have some questions about exception handling at MVC 4.

I had implemented HandleErrorAttribute and derived a new attribute to customize. It works really good; but i don't want to redirect user a custom error page everytime.

Some of errors that i got at Actions, throwing from Web API and i want to show them to user at current page. For example if user wants to create a record but WebAPI throws an exception because of invalid modelstate, exception details shown in Create view in friendly way.

But HandleErrorAttribute redirects Error.cshtml by default.

I can handle every exception in Actions but i think there can be another way.

Also i followed http://www.prideparrot.com/blog/archive/2012/5/exception_handling_in_asp_net_mvc to implement 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;
        }
    }

I made Web API calls via HttpClient and wrapper class. For example a Get request is like below.

 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
  • Override the method and don't use base.xx? Commented Mar 8, 2013 at 13:24
  • Sorry i couldn't understand what you mentioned. Commented Mar 8, 2013 at 13:29
  • I assume you have something like this: public override onException, make sure you don't have base.onException(context); Commented Mar 8, 2013 at 13:31
  • Ohh, ok. I don't recall base. Also i edited my answer and shared my code. Commented Mar 8, 2013 at 13:35

1 Answer 1

2

Build a class that will wrap HttpClient and use it to make calls to your Web API. Return different HTTP Status Codes from Web API for situations where you want redirect to happen (i.e. 500 - internal server error, or 401 - unauthorized) and situation where you want to show model state errors (400 - Bad request would be my choice). Handle status code in your wrapper case to:

a) In case of an error when you want redirect (500 or 401 received from Web API), throw appropriate exception

b) When you don't want a redirect (400 received from Web API), just return some response model back from your wrapper class that can be displayed on client side

In your controller just assume you'll have response model returned from HTTP wrapper class since exception will cause it to never get back to controller (you will handle it globally and do a redirect).

If you need a code sample I can provide one, but I think you're looking more for a general concept rather than concrete code.

EDIT:

On Web API side:

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

In your global.asax:

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

Custom ApiError:

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

When it comes to MVC side, here is how your wrapper HttpClient wrapper class might look like:

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

And model for response would be:

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; }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Actually my architecture suits your suggestion. I edited my question and add a code sample. I am trying to throw a HttpApiRequestException and handle it from controller. How can i achieve this?
I've provided you with some code above. It might not work on copy + paste but it's implementation of what I've written just now. So your call to GetAsync method on MVC side would return wrapped response, that carries status code and either Error or Data. That will happen in case of BadRequest or OK/NoContent. If you have Error, extract Model State errors from it and return it to the view, otherwise return data. In case that you receive any other status code apart from those, throw an exception and capture it in your global error handling filter / do redirect somewhere.
Actually this is not what i am looking for, because i am trying to handle all exceptions with attributes but this seems ok, too. thanks for help.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.