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?
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);
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With