8

If a normal page load errors I can report the exception details to the user via the Error view and HandleErrorInfo model.

If an ajax call expecting a Json result errors, I can explicitly handle the error and pass details to the client:

public JsonResult Whatever()
{
    try
    {
        DoSomething();
        return Json(new { status = "OK" });
    }
    catch (Exception e)
    {
        return Json(new { status = "Error", message = e.Message });
    }
}

So, my problem, I can't see any way to report error details from an Ajax call to an action returning a partial view.

$.ajax({
    url: 'whatever/trevor',
    error: function (jqXHR, status, error) {
        alert('An error occured: ' + error);
    },
    success: function (html) {
        $container.html(html);
    }
});

This will only report an Http error code (e.g. Internal Server Error) which is not helpful to the client. Is there some clever trick to pass either a successful PartialView (html) result or an error message?

Explicitly evaluating the html from the ViewResult and returning it as part of a Json object along with a status seems too smelly. Is there an established pattern for handling this scenario?

rene
  • 37,946
  • 78
  • 99
  • 132
fearofawhackplanet
  • 47,230
  • 49
  • 149
  • 249

2 Answers2

15

Controller action:

public ActionResult Foo()
{
    // Obviously DoSomething could throw but if we start 
    // trying and catching on every single thing that could throw
    // our controller actions will resemble some horrible plumbing code more
    // than what they normally should resemble: a.k.a being slim and focus on
    // what really matters which is fetch a model and pass to the view

    // Here you could return any type of view you like: JSON, HTML, XML, CSV, PDF, ...

    var model = DoSomething();
    return PartialView(model);
}

Then we define a Global error handler for our application:

protected void Application_Error(object sender, EventArgs e)
{
    var exception = Server.GetLastError();
    var httpException = exception as HttpException;
    Response.Clear();
    Server.ClearError();

    if (new HttpRequestWrapper(Request).IsAjaxRequest())
    {
        // Some error occurred during the execution of the request and 
        // the client made an AJAX request so let's return the error
        // message as a JSON object but we could really return any JSON structure
        // we would like here

        Response.StatusCode = 500;
        Response.ContentType = "application/json";
        Response.Write(new JavaScriptSerializer().Serialize(new 
        { 
            errorMessage = exception.Message 
        }));
        return;
    }

    // Here we do standard error handling as shown in this answer:
    // http://stackoverflow.com/q/5229581/29407

    var routeData = new RouteData();
    routeData.Values["controller"] = "Errors";
    routeData.Values["action"] = "General";
    routeData.Values["exception"] = exception;
    Response.StatusCode = 500;
    if (httpException != null)
    {
        Response.StatusCode = httpException.GetHttpCode();
        switch (Response.StatusCode)
        {
            case 404:
                routeData.Values["action"] = "Http404";
                break;
            case 500:
                routeData.Values["action"] = "Http500";
                break;
        }
    }

    IController errorsController = new ErrorsController();
    var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
    errorsController.Execute(rc);
}

Here's how the ErrorsController used in the global error handler could look like. Probably we could define some custom views for the 404 and 500 actions:

public class ErrorsController : Controller
{
    public ActionResult Http404()
    {
        return Content("Oops 404");
    }

    public ActionResult Http500()
    {
        return Content("500, something very bad happened");
    }
}

Then we could subscribe for a global error handler for all AJAX errors so that we don't have to repeat this error handling code for all AJAX requests but if we wanted we could repeat it:

$('body').ajaxError(function (evt, jqXHR) {
    var error = $.parseJSON(jqXHR.responseText);
    alert('An error occured: ' + error.errorMessage);
});

And finally we fire an AJAX request to the controller action that we hope will return an HTML partial in this case:

$.ajax({
    url: 'whatever/trevor',
    success: function (html) {
        $container.html(html);
    }
});
Darin Dimitrov
  • 960,118
  • 257
  • 3,196
  • 2,876
  • 1
    Darin your knowledge of MVC astounds me again. I didn't fully understand a couple of points here, but I figured out that I could achieve something similar a little more simply with an ajax filter registered in global filters. It seems to be working ok. Thank you. – fearofawhackplanet Nov 23 '11 at 16:14
  • Just a note, if you're using the above code to trap 404's and show a custom page you probably want to add a Response.TrySkipIisCustomErrors = true (.net 3.5 onwards only I believe) – Daniel Powell Apr 10 '13 at 03:30
  • Works like a charm, aweseome – Dilip Oct 15 '15 at 13:07
0

Create an overriden version of HandleErrorAttribute (JsonHandleErrorAttribute ?) and add [JsonHandleError] on your json action.

Have a look at AjaxAuthorizeAttribute in asp.net mvc [handleerror] [authorize] with JsonResult?

Community
  • 1
  • 1
Softlion
  • 11,311
  • 10
  • 52
  • 78