I am handling errors in my controller and I have [CustomErrorHandleAttribute] which I have written what to do when there is an exception in my actions. Even there is no error in my code it is redirecting to customerrorhandle and throwing error. I am not able to find the error why it is doing this.
Here is my code:
namespace ExceptionHandlingInMVC.Controllers
{
[CustomHandleError]
public class HomeController : Controller
{
//
// GET: /Home/
public object Index()
{
try
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Current time is:" + DateTime.Now.ToLongTimeString();
var x = 10;
var y = 10;
var result = x / y;
ViewData["Result"] = result;
return View();
}
catch (Exception e)
{
throw e;
}
}
[CustomHandleError]
public object About()
{
ViewData["Title"] = "About Page";
return View();
}
}
public class ErrorPresentation
{
public String ErrorMessage { get; set; }
public Exception TheException { get; set; }
public Boolean ShowMessage { get; set; }
public Boolean ShowLink { get; set; }
}
}
CustomHandleErrorAttribute that i've wrote:
namespace ExceptionHandlingInMVC
{
/// <summary>
/// This attribute (AOP) filter is used to override the Error handling and make sure that all erros are recorded in the event logs, so that they can in turn be picked up by
/// our SIEM tool so that we a) stop customers seing a bad error message and b) we are capturing all the events that happen and c) improives security for
/// by preventing a hacker from seing s=details of how our application is put together
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class CustomHandleErrorAttribute : ActionFilterAttribute
{
/// <summary>
/// This event is called when the action is called i.e. an error has just occured
/// </summary>
/// <param name="filterContext"></param>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
try
{
// Bail if we can't do anything; app will crash.
if (filterContext == null)
return;
// since we're handling this, log to ELMAH(Error logging modules and handler)
var ex = filterContext.Exception ?? new Exception("No further information exists.");
WriteToEventLog(ex);
filterContext.ExceptionHandled = true;
var data = new ErrorPresentation
{
ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
TheException = ex,
ShowMessage = filterContext.Exception != null,
ShowLink = false
};
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Home/ErrorPage.aspx"
};
}
catch (Exception exception)
{
throw;
}
}
/// <summary>
/// This method writes the exception to the event log we have specified in the web.config or the app.config
/// </summary>
/// <param name="exception"></param>
public void WriteToEventLog(Exception exception)
{
// pick up which machine we are on, this will already be set for all websites
var machineName = ConfigurationManager.AppSettings["MachineName"];
// PIck up the eventlog we are going to write to
var eventLogName = ConfigurationManager.AppSettings["EventLogName"];
EventLog.WriteEntry("abc", exception.Message, EventLogEntryType.Error);
}
}
}
You should really be performing your error handling by overriding Application_Error in global.asax. That way you can be sure that your code will only execute when an error occurs. Using OnActionExecuted means that your code will execute regardless or whether or not an error is thrown.
Here's the function:
void Application_Error(object sender, EventArgs e)
{
//do your stuff here
}
Try this:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
try
{
// Bail if we can't do anything; app will crash.
if (filterContext == null)
return;
// since we're handling this, log to ELMAH(Error logging modules and handler)
if (filterContext.Exception == null || filterContext.ExceptionHandled)
{
var ex = filterContext.Exception ?? new Exception("No further information exists.");
this.WriteToEventLog(ex);
return;
};
filterContext.ExceptionHandled = true;
var data = new ErrorPresentation
{
ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
TheException = ex,
ShowMessage = filterContext.Exception != null,
ShowLink = false
};
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Home/ErrorPage.aspx"
};
}
catch (Exception exception)
{
throw;
}
}
if there is no exeption you need to return, because this attribute fires every time, not only when you have an error.
update:
I suggest to you write code below in global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomErrorHandle());
}
to fire this attibute to all actions. So you don't need to write attribute to any action.
Event should be fired when there is error only and I am writing to eventlog:
In my global.asax I added the following code:
/// <summary>
/// Managing errors from a single location
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Application_Error(object sender, EventArgs e)
{
// 1. Get the last error raised
var error = Server.GetLastError();
//2. Get the error code to respond with
var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;
//3.. Log the error ( I am ignoring 404 error)
if (code != 404)
{
// Write error details to eventlog
WriteToEventLog(error);
}
//4. Clear the response stream
Response.Clear();
//5. Clear the server error
Server.ClearError();
//6. Render the Error handling controller without a redirect
string path = Request.Path;
Context.RewritePath(string.Format("~/Home/Error",code),false);
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(Context);
Context.RewritePath(path,false);
}
/// <summary>
/// This method writes the exception to the event log we have specified in the web.config or the app.config
/// </summary>
/// <param name="exception"></param>
public void WriteToEventLog(Exception exception)
{
EventLog.WriteEntry("abc", exception.Message, EventLogEntryType.Error);
}
Related
In my first asp.net mvc application I'm handling errors with try-catch blocks and returning specific messages to the user as Httpstatuscode.
In every crud operation there is same code block.
I tried using exceptionhandler attribute but I couldn't manage to return status code or custom message with it.
Is there any way to replace these try catch blocks on every function and return a message to user?
This is what I tried :
public class ExceptionHandlerFilterAttribute : FilterAttribute, IExceptionFilter
{
private ILogger _logger;
public void OnException(ExceptionContext filterContext)
{
_logger = new NLogLogger();
if (!filterContext.ExceptionHandled)
{
var controller = filterContext.RouteData.Values["controller"].ToString();
var action = filterContext.RouteData.Values["action"].ToString();
var message = filterContext.Exception;
_logger.Log(Business.Enums.LogLevel.Error, string.Concat("/",controller,"/",action), message);
filterContext.ExceptionHandled = true;
filterContext.Result = new ViewResult()
{
ViewName = "Error"
};
}
}
}
This is an example method :
public HttpStatusCodeResult Create(Product product)
{
if (!ModelState.IsValid) return new HttpStatusCodeResult(HttpStatusCode.BadGateway);
try
{
_productService.Create(product);
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
catch (Exception) { return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); }
}
I would like to replace repetitive try-catch blocks for a better code.
You can wrap your methods with something like this:
/// <summary>
/// Tries the specified action.
/// </summary>
/// <param name="action">The action.</param>
public static HttpStatusCodeResult Try(Action action, ModelState model)
{
if (!model.IsValid) return new HttpStatusCodeResult(HttpStatusCode.BadGateway);
try
{
action();
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
catch (Exception) { return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); }
}
And you can use your Try:
public HttpStatusCodeResult Create(Product product)
{
return Try(()=> {
_productService.Create(product);
}, ModelState);
}
Here an wrapper example in github
And the call of that try
Try to set HttpStatusCodeResult for Result property of filterContext:
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
I am trying to build a FilterAttribute that will handle my errors for my Web API. Currently it logs the errors to the database, but it always returns a 500 Internal Server error no matter what I do.
My filter looks like this:
public class LogExceptionFilterAttribute : ExceptionFilterAttribute
{
// Private properties
private readonly ILogProvider _logger;
/// <summary>
/// Our default constructor
/// </summary>
/// <param name="logger"></param>
public LogExceptionFilterAttribute(ILogProvider logger)
{
_logger = logger;
}
/// <summary>
/// Invoked when an exception has been thrown
/// </summary>
/// <param name="context">The context</param>
public override async void OnException(HttpActionExecutedContext context)
{
// Get our user
var requestContext = context.Request.GetRequestContext();
var user = requestContext.Principal.Identity;
// Create our response
var message = await _logger.ErrorAsync(context.Exception, user);
var statusCode = GetStatusCodeFromException(context);
var content = new HttpResponseMessage(statusCode);
//{
// Content = new StringContent(message),
// StatusCode = statusCode
//};
// Assign our response to our context
context.Response = content;
}
/// <summary>
/// Gets a status code from an error
/// </summary>
/// <param name="context">The context</param>
/// <returns></returns>
private static HttpStatusCode GetStatusCodeFromException(HttpActionExecutedContext context)
{
// Cast the exception as an HttpException
var exception = context.Exception as HttpException;
// If there is still an exception, return the code
if (exception != null)
return (HttpStatusCode)exception.GetHttpCode();
// Switch on the exception type
switch (context.Exception)
{
// Not found
case ObjectNotFoundException ex:
return HttpStatusCode.NotFound;
// Not implemented
case NotImplementedException ex:
return HttpStatusCode.NotImplemented;
// Internal server error
default:
return HttpStatusCode.InternalServerError;
}
}
}
I have put a breakpoint in the GetStatusCodeFromException method and verified that the actual status code I should be getting is 501 (Not Implemented) but the return is always 500. I have tried with other status codes too and it is always a 500.
Does someone know how I can actually get it to return a different status code?
Update
I have found out more.
If I change my OnException method to this:
public override async void OnException(HttpActionExecutedContext context)
{
// Get our user
var request = context.Request;
//var requestContext = request.GetRequestContext();
//var user = requestContext.Principal.Identity;
//// Log our error and get the status code
//var message = await _logger.ErrorAsync(context.Exception, user);
//var statusCode = GetStatusCodeFromException(context);
// Create our response
context.Response = request.CreateResponse(HttpStatusCode.NotImplemented);
}
I get a nice 501 error when using postman, but as soon as I uncomment the requestContext line, it get an error 500. I think that invoking GetRequestContext somehow changes the HttpRequestMessage so that when I invoke CreateResponse it always generates a 500.
Anyone know why? Or how to get around it?
I’m trying to implement a global exception handling in my MVC 5 application.
To achieve that I’ve added a handler to the Application_Error event in Global.asax.cs.
In my handler, I read the error details from the Server GetLastError.
I store the error details in a cookie and do a redirect to an error controller which reads the cookie and displays the error details to the user.
The cookie gets set correctly, but when I try to read it in my error controller, the cookie does not exist.
This is my code:
protected void Application_Error( Object sender, EventArgs e )
{
const String route = "Default";
const String controller = "Errors";
const String area = "";
var action = "InternalServerError";
var errorCode = 500;
try
{
// Get the previous exception.
var exception = Server.GetLastError() ?? new HttpException( (Int32) HttpStatusCode.InternalServerError, "Unknown internal server error occurred." );
// Clear the exception
Server.ClearError();
// Bypass IIS custom errors
Response.TrySkipIisCustomErrors = true;
// Check for HTTP code
var httpException = exception as HttpException;
if ( httpException != null )
errorCode = httpException.GetHttpCode();
// ReSharper disable once SwitchStatementMissingSomeCases
switch ( errorCode )
{
case 401:
case 403:
action = "Forbidden";
break;
case 404:
action = "NotFound";
break;
}
// Try to collect some error details
try
{
var details = new WebErrorDetails
{
Exception = exception,
ErrorSource = HttpContext.Current.Request.Url.ToString()
};
HttpContext.Current.Response.Cookies.Set(new HttpCookie(CommonConstants.ErrorDetails, JsonConvert.SerializeObject(details))
{
Expires = DateTime.Now.Add(2.ToMinutes()),
HttpOnly = true
});
}
catch
{
// ignore
}
Response.RedirectToRoute( route, new RouteValueDictionary( new { area, controller, action } ) );
}
catch
{
Response.RedirectToRoute( route, new RouteValueDictionary( new { area, controller, action = "InternalServerError" } ) );
}
}
public class ErrorsController : ControllerBase
{
#region Ctor
/// <summary>
/// Initialize a new instance of the <see cref="ErrorsController" /> class.
/// </summary>
/// <param name="loggerFactory">A <see cref="ILoggerFactory" />.</param>
public ErrorsController( ILoggerFactory loggerFactory )
: base( loggerFactory.CreateLogger( typeof(ErrorsController) ) )
{
Logger.Trace( "Enter." );
}
#endregion
#region Private Members
[NotNull]
private WebErrorDetails PopErrorDetails()
{
try
{
// GetRequestCookie looks like this => HttpContext.Current.Request.Cookies[cookieName];
var cookie = HttpContextService.GetRequestCookie( CommonConstants.ErrorDetails );
if ( cookie != null )
{
var errorDetails = JsonConvert.DeserializeObject<WebErrorDetails>( cookie.Value );
if ( errorDetails != null )
return errorDetails;
}
}
catch ( Exception ex )
{
Logger.Warn( ex, "Failed to pop error details." );
}
// Fall-back value
return new WebErrorDetails
{
Exception = new Exception( "Exception details missing." ),
ErrorSource = "-"
};
}
private void StoreErrorDetails( WebErrorDetails errorDetails )
{
try
{
HttpContextService.AddCookieToResponse( new HttpCookie( CommonConstants.ErrorDetails, JsonConvert.SerializeObject( errorDetails ) )
{
Expires = DateTime.Now.Add( 2.ToMinutes() ),
HttpOnly = true
} );
}
catch ( Exception ex )
{
Logger.Warn( ex, "Failed to store error details." );
}
}
#endregion
#region Action Methods
/// <summary>
/// Returns a error view for 500 internal server errors.
/// </summary>
/// <returns>Returns a error view for 500 internal server errors.</returns>
public async Task<ActionResult> InternalServerError()
{
Logger.Info( "Enter error action method." );
WebErrorDetails errorDetails = null;
try
{
errorDetails = PopErrorDetails();
// Get the layout view model
var layoutVm = await PrepareLayoutViewModel();
// Build the view model
var vm = new LayoutApplicationErrorViewModel
{
Exception = errorDetails.Exception,
ErrorSource = errorDetails.ErrorSource,
ViewTitle = CommonResources.Common_Static_InternalServerError
};
HttpContextService.StatusCode = (Int32) HttpStatusCode.InternalServerError;
// Set the layout view model
SetLayoutData( layoutVm, vm );
return View( "Error", vm );
}
catch ( Exception ex )
{
try
{
Logger.Error( ex, "Unexpected exception occurred." );
if ( errorDetails != null )
StoreErrorDetails( errorDetails );
else
StoreErrorDetails( new WebErrorDetails
{
ErrorSource = HttpContextService.RequestUrl.ToString(),
Exception = ex
} );
}
catch
{
// ignore
}
return RedirectToAction( "GeneralError", "Errors" );
}
}
/// <summary>
/// Returns a general error view without any layout.
/// </summary>
/// <returns>Returns a general error view without any layout.</returns>
public ActionResult GeneralError()
{
Logger.Info( "Enter general error action method." );
try
{
// Build the view model
var errorDetails = PopErrorDetails();
var vm = new LayoutApplicationErrorViewModel
{
Exception = errorDetails.Exception,
ErrorSource = errorDetails.ErrorSource,
ViewTitle = "Error"
};
HttpContextService.StatusCode = (Int32) HttpStatusCode.InternalServerError;
return View( vm );
}
catch ( Exception ex )
{
Logger.Fatal( ex, "Could not display basic error view." );
}
}
#endregion
}
Note: Setting and Reading cookies works everywhere else.
I assume the problem is related to the redirect?
//Clear the response.
Response.Clear();
I'm trying to write a log class to log to a file, however I keep getting issues with the logging due to different threads trying to log at the same time.
A first chance exception of type 'System.UnauthorizedAccessException' occurred in mscorlib.dll
System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Log.Diag.<DebugPrint>d__0.MoveNext()
Here is my code:
public static async void DebugPrint(string msg, LogLevel level)
{
if (ShouldLog(level))
{
#if DEBUG
// Only do this in debug
Debug.WriteLine(msg);
#endif
#if !DEBUG // Never crash in release build
try
{
#endif
if (sFile == null && !(await GetLogFile()))
{
throw new FileNotFoundException("Cannot create ms-appdata:///local/log.txt");
}
try
{
await Windows.Storage.FileIO.AppendTextAsync(sFile, ComposeMessage(msg, level));
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
#if !DEBUG
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
#endif
}
}
/// <summary>
/// Initialise the log file.
/// </summary>
/// <returns></returns>
private async static Task<bool> GetLogFile()
{
try
{
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
sFile = await localFolder.CreateFileAsync("log.txt", CreationCollisionOption.OpenIfExists);
return true;
}
catch (Exception)
{
return false;
}
}
What can I do to ensure that all threads can log to the file?
Here is how I did it using event tracing.
Task.cs
sealed class LogEventSource : EventSource
{
public static LogEventSource Log = new LogEventSource();
[Event(1, Level = EventLevel.LogAlways)]
public void Debug(string message)
{
this.WriteEvent(1, message);
}
}
/// <summary>
/// Storage event listner to do thread safe logging to a file.
/// </summary>
sealed class StorageFileEventListener : EventListener
{
private object syncObj = new object();
private List<string> logLines;
private StorageFile logFile;
private ThreadPoolTimer periodicTimer;
public StorageFileEventListener()
{
Debug.WriteLine("StorageFileEventListener for {0}", GetHashCode());
logLines = new List<string>();
}
// Should be called right after the constructor (since constructors can't have async calls)
public async Task InitializeAsync()
{
logFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("logs.txt", CreationCollisionOption.OpenIfExists);
// We don't want to write to disk every single time a log event occurs, so let's schedule a
// thread pool task
periodicTimer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
// We have to lock when writing to disk as well, otherwise the in memory cache could change
// or we might try to write lines to disk more than once
lock (syncObj)
{
if (logLines.Count > 0)
{
// Write synchronously here. We'll never be called on a UI thread and you
// cannot make an async call within a lock statement
FileIO.AppendLinesAsync(logFile, logLines).AsTask().Wait();
logLines = new List<string>();
}
}
CheckLogFile();
}, TimeSpan.FromSeconds(5));
}
private async void CheckLogFile()
{
BasicProperties p = await logFile.GetBasicPropertiesAsync();
if(p.Size > (1024 * 1024))
{
// TODO: Create new log file and compress old.
}
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// This could be called from any thread, and we want our logs in order, so lock here
lock (syncObj)
{
logLines.Add((string)eventData.Payload[0]);
}
}
}
Wrapped in a logging class.
/// <summary>
/// A static class for help with debugging and logging.
/// </summary>
public static class Log
{
public enum LogLevel {
NONE = 0,
FATAL,
ERROR,
INFO,
DEBUG,
VERBOSE,
TRACE
};
private static StorageFileEventListener eventListener;
#if DEBUG
public static LogLevel logLevel = LogLevel.DEBUG;
#else
public static LogLevel logLevel = LogLevel.NONE;
#endif
/// <summary>
/// Print out the debug message.
/// </summary>
/// <param name="msg">Message to print</param>
/// <param name="level">Debug level of message</param>
public async static void DebugPrint(string msg, LogLevel level)
{
if (ShouldLog(level))
{
msg = ComposeMessage(msg, level);
#if DEBUG
// Only do this in debug
Debug.WriteLine(msg);
#endif
#if !DEBUG // Never crash in release build
try
{
#endif
if (eventListener == null)
{
eventListener = new StorageFileEventListener();
eventListener.EnableEvents(LogEventSource.Log, EventLevel.LogAlways);
await eventListener.InitializeAsync();
}
LogEventSource.Log.Debug(msg);
#if !DEBUG
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
#endif
}
}
/// <summary>
/// Construc the formatted log message
/// </summary>
/// <param name="msg">Main message</param>
/// <param name="level">Log level</param>
/// <returns>Formated message</returns>
private static string ComposeMessage(string msg, LogLevel level)
{
return DateTime.Now.ToString(#"M/d/yyyy hh:mm:ss.fff tt") + " [" + Environment.CurrentManagedThreadId.ToString("X4") + "] " + LevelToString(level) + " " + msg;
}
/// <summary>
/// Get the string alias for a log level.
/// </summary>
/// <param name="level">The log level</param>
/// <returns>String representation of the log level.</returns>
private static string LevelToString(LogLevel level)
{
string res = "NOT FOUND";
switch (level)
{
case LogLevel.NONE:
throw new Exception("You should not log at this level (NONE)");
case LogLevel.FATAL: res = "FATAL"; break;
case LogLevel.ERROR: res = "ERROR"; break;
case LogLevel.INFO: res = "INFO"; break;
case LogLevel.DEBUG: res = "DEBUG"; break;
case LogLevel.VERBOSE: res = "VERBOSE"; break;
case LogLevel.TRACE: res = "TRACE"; break;
}
return res;
}
/// <summary>
/// Check the passed log level against the current log level
/// to see if the message should be logged.
/// </summary>
/// <param name="level">Log level to check against</param>
/// <returns>True is should be logeed otherwise false.</returns>
private static bool ShouldLog(LogLevel level)
{
if (level <= logLevel)
return true;
else
return false;
}
}
Usage:
Log.DebugPrint("Hello, Thread safe logger!", Log.LogLevel.DEBUG);
In order to avoid concurrency issues you need to use locks.
It would be better to write all the messages into a queue and use a background thread to write the queue into the file. This has many advantages:
easy to make it multithreading saving. Just lock every access to the queue
only 1 thread writing to the file => no more multi threading problems
Adding to the queue is very fast (microseconds) and will hardly lock, while writing to the file does not just create multi threading problems, but might create milliseconds delays or even exceptions.
The logging can start right from the first line of code. Messages are written into the queue, which will only get emptied once file system is ready
I am handling error in Base controller. I need to display the error stored in tempdata, Exception type in a razor view. How can I do that?
Base Controller code
protected override void OnException(ExceptionContext filterContext)
{
// if (filterContext.ExceptionHandled)
// return;
//Let the request know what went wrong
filterContext.Controller.TempData["Exception"] = filterContext.Exception.Message;
//redirect to error handler
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(
new { controller = "Error", action = "Index" }));
// Stop any other exception handlers from running
filterContext.ExceptionHandled = true;
// CLear out anything already in the response
filterContext.HttpContext.Response.Clear();
}
Razor View Code
<div>
This is the error Description
#Html.Raw(Html.Encode(TempData["Exception"]))
</div>
Try to make common exception attribute handling and register it as global filters. Like,
Common Exception Handling attribute :
/// <summary>
/// This action filter will handle the errors which has http response code 500.
/// As Ajax is not handling this error.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class HandleErrorAttribute : FilterAttribute, IExceptionFilter
{
private Type exceptionType = typeof(Exception);
private const string DefaultView = "Error";
private const string DefaultAjaxView = "_Error";
public Type ExceptionType
{
get
{
return this.exceptionType;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
this.exceptionType = value;
}
}
public string View { get; set; }
public string Master { get; set; }
public void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled))
{
Exception innerException = filterContext.Exception;
// adding the internal server error (500 status http code)
if ((new HttpException(null, innerException).GetHttpCode() == 500) && this.ExceptionType.IsInstanceOfType(innerException))
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
// checking for Ajax request
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var result = new PartialViewResult
{
ViewName = string.IsNullOrEmpty(this.View) ? DefaultAjaxView : this.View,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
filterContext.Result = result;
}
else
{
var result = this.CreateActionResult(filterContext, model);
filterContext.Result = result;
}
filterContext.ExceptionHandled = true;
}
}
}
private ActionResult CreateActionResult(ExceptionContext filterContext, HandleErrorInfo model)
{
var result = new ViewResult
{
ViewName = string.IsNullOrEmpty(this.View) ? DefaultView : this.View,
MasterName = this.Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData,
};
result.TempData["Exception"] = filterContext.Exception;
return result;
}
}
And Error/_Error view
#model HandleErrorInfo
<div>
This is the error Description
#TempData["Exception"]
</div>
I agree that you should never expose an exception to your view but if you really need to, try using a custom attribute.
public class CustomExceptionAttribute : System.Web.Mvc.HandleErrorAttribute
{
public override void OnException(System.Web.Mvc.ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled)
{
filterContext.Controller.TempData.Add("Exception", filterContext.Exception);
filterContext.ExceptionHandled = true;
}
}
}
public class MyController : System.Web.Mvc.Controller
{
[CustomException]
public ActionResult Test()
{
throw new InvalidOperationException();
}
}
If you override the OnException method in the base controller, then every action will get an Exception object placed in temp data. This maybe the desired behavior but with an attribute you can selectively enable this feature.
I would strongly suggest not to show any detailed exception information in any public facing application as this could end up as a security issue. However, if this is an intranet application with controlled access or if you REALLY want to show the exception details, create a DisplayTemplate and use it as follows:
<div>
Exception Details
#Html.Display(TempData["Exception"])
</div>