This is an amalgmation of techniques taken from various sources on how best to handle exceptions/errors in ASP.NET MVC. There are many ways to approach this, but this is one that I'm happy with at the moment, it is based on the premise that the application doesn't swallow up exceptions in try catch blocks and silently fail, instead the error is logged and then reported to the user so they can make a decision as to whether they can fix the issue/try again to resolve (e.g. for validation exceptions). Firstly, override the "Application_Error" in the global.asax so that any unhandled exceptions are caught and handled in a nicer way than the "yellow screen of death" provided by ASP.NET.
/// <summary>
        /// http://www.codeproject.com/Articles/422572/Exception-Handling-in-ASP-NET-MVC#handleerror
        /// http://prideparrot.com/blog/archive/2012/5/exception_handling_in_asp_net_mvc#elmah
        /// http://stackoverflow.com/questions/5226791/custom-error-pages-on-asp-net-mvc3
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">Args</param>
        protected void Application_Error(object sender, EventArgs e)
        {
            var httpContext = ((MvcApplication)sender).Context;

            var currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
            var currentController = " ";
            var currentAction = " ";

            if (currentRouteData != null)
            {
                if (currentRouteData.Values["controller"] != null && !string.IsNullOrEmpty(currentRouteData.Values["controller"].ToString()))
                {
                    currentController = currentRouteData.Values["controller"].ToString();
                }

                if (currentRouteData.Values["action"] != null && !string.IsNullOrEmpty(currentRouteData.Values["action"].ToString()))
                {
                    currentAction = currentRouteData.Values["action"].ToString();
                }
            }

            var ex = this.Server.GetLastError();
            Trace.TraceError(ex.MessageEx());

            var routeData = new RouteData();
            var controller = new ErrorController();
            var action = "Index";
            int httpStatusCode = (int)HttpStatusCode.InternalServerError;

            if (ex is HttpException)
            {
                var httpEx = ex as HttpException;

                switch (httpEx.GetHttpCode())
                {
                    case 404:
                        action = "Http404";
                        break;

                    // define additional http code error pages here
                    default:
                        action = "Index";
                        break;
                }
            }

            httpContext.ClearError();
            httpContext.Response.Clear();
            httpContext.Response.StatusCode = ex is HttpException ? ((HttpException)ex).GetHttpCode() : httpStatusCode;
            httpContext.Response.TrySkipIisCustomErrors = true;
            httpContext.Response.Headers.Add("Content-Type", "text/html");
            routeData.Values["controller"] = "Error";
            routeData.Values["action"] = action;

            controller.ViewData.Model = new HandleErrorInfo(ex, currentController, currentAction);
            ((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
        }
This works by interpreting the exception, logging the error to the trace listeners and then redirecting the users to an "/Error/Index" action (or 404). The content of the error page can be something like this:
@model HandleErrorInfo

@{
    ViewBag.Title = "Error";
}

<h1>Error</h1>

<div class="alert alert-danger" role="alert">
    <p><i class="fa fa-warning"></i> Sorry, an error occurred whilst processing your request.</p>
    <p>@Model.Exception.Message</p>
</div>
That will take care of problems in "normal" page loads and postbacks, i.e. non-async calls. For async calls this is handled slightly differently. Create an action filter that will check for exceptions and wrap the message inside a JsonResponse object (see http://www.dotnetcurry.com/ShowArticle.aspx?ID=496 ):
public class HandleJsonExceptionAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
            {
                filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
                filterContext.Result = new JsonResult()
                {
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                    Data = new
                    {
                        Message = filterContext.Exception.MessageEx()
                    }
                };

                Trace.TraceError(filterContext.Exception.MessageEx());
                filterContext.ExceptionHandled = true;
            }
        }
    }
Now decorate the actions you will be calling asynchronously with this attribute, usually these are the actions that return a JsonResult or a Partial View. For a consistent theme, you can return the same response "Message" for your WebAPI calls by applying the following exception filter:
public class HandleExceptionAttribute : ExceptionFilterAttribute, IFilter
    {
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            actionExecutedContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);

            actionExecutedContext.Response.Content = new StringContent(
                JObject.Parse(
                        $"{{'Message': '{actionExecutedContext.Exception.MessageEx().Replace("'", "\"")}'}}"
            ).ToString(), Encoding.UTF8, "application/json");

            Trace.TraceError(actionExecutedContext.Exception.MessageEx() + "Stack:" + actionExecutedContext.Exception.StackTrace);
        }
    }


// --- Register this in the WebApiConfig "Register" section..

config.Filters.Add(new HandleExceptionAttribute());
Noting that WebAPI doesn't use the global.asax error handler (since there are no views). Now if you are calling the MVC action or webAPI endpoint from a jQuery style $.Ajax request, then this response object can be read in the fail block of that code structure:
$.ajax({
            url: '/Controller/Action',
            method: "POST",
            data: {
                'example': data
            }
        }).done(function (result) {            
              // process a good result
        }).fail(function (errorResult) {
            alert("Could not perform the operation\n" + errorResult.responseJSON.Message);
        });
If you are using the Ajax.BeginForm approach, you can handle the failure condition by referencing your handler function in the "OnFailure" attribute of "AjaxOptions", (see http://johnculviner.com/ajax-beginform-ajaxoptions-custom-arguments-for-oncomplete-onsuccess-onfailure-and-onbegin/)
Ajax.BeginForm(
        "Controller",
        "Action",
        FormMethod.Post,
        new AjaxOptions()
        {
            HttpMethod = "POST",
            InsertionMode = InsertionMode.Replace,
            UpdateTargetId = "",
            OnSuccess = "HandleGoodResult();",
            OnFailure = "HandleFailure(xhr);"
        }, 
        new { id = "myForm" }))
Finally, you may be calling the API from some c# web client code, so you can parse the error as follows:
public string ParseErrorResponse(string responseText)
{
       string errorText = responseText;

       try
       {
           dynamic errorJson = (dynamic)JToken.Parse(errorText);
           errorText = errorJson.Message;
       }
       catch (Exception)
       {
       }

       return errorText;
}
Note: Remember that the exceptions you allow to arrive to user's browser should be legible and meaningul. Also, some exceptions can be "survived" so no need to bubble them, and finally exceptions in general are costly and so should be avoided in the first place (e.g. add clientside validation, add checks before calling dependencies that throw exceptions). For information on the "MessageEx()" extension, see my other post. NOTE: If you are deploying this website to Azure you will need to add the following tag to your system.webServer node of your web.config
<httpErrors existingResponse="PassThrough"/>