I'm trying to pass the accessed url to my error controller called ErrorController so that I can log what page was being accessed at the time.
In my Global.asax.cs I have a method Application_Error looking like this:
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 = Server.GetLastError();
var controller = new ErrorController();
var routeData = new RouteData();
var action = "Index";
if (ex is HttpException)
{
var httpEx = ex as HttpException;
switch (httpEx.GetHttpCode())
{
case 404:
action = "NotFound";
// Pass along some data about accessed page here
break;
// others if any
default:
action = "Index";
break;
}
}
httpContext.ClearError();
httpContext.Response.Clear();
httpContext.Response.StatusCode = ex is HttpException ? ((HttpException)ex).GetHttpCode() : 500;
httpContext.Response.TrySkipIisCustomErrors = true;
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));
}
And my ErrorController looks like this:
public class ErrorController : BaseController
{
private readonly ILog _logger;
public ErrorController()
{
_logger = LogManager.GetLogger("CustomHandleErrorAttribute.class");
}
//
// GET: /Error/
public ActionResult Index()
{
return View();
}
public ActionResult NotFound(string error)
{
_logger.Error(error);
return View();
}
}
How should I go about populating the error parameter so I can log this to my file?
I think you're over complicating it a bit. Just create a shared function that logs the exception and page in your error controller class. This way you can forget about routing all together. You can use the UrlReferrer property on the request to get the page that the error occurred on.
Global.asax (vb.net):
Sub Application_Error()
Dim ex As Exception = Server.GetLastError()
Dim page As String = If(Not IsNothing(Request), Request.UrlReferrer.AbsoluteUri, Nothing)
ErrorController.LogError(ex, page)
Server.ClearError()
End Sub
Ended up doing the following in my ErrorController.cs:
if (Request.Url != null)
{
var path = Request.Url.AbsoluteUri;
_logger.Error("404: " + path);
}
Related
I am using a custom HandleErrorAttribute which is applied globally by being registered in the filters. The problem is, I can't catch the proper http error code. Every time it is 200 code inside that attribute.
public class HandleAndLogErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
return;
var statusCode = filterContext.HttpContext.Response.StatusCode; //always 200
LogManager.Error(filterContext.Exception);
filterContext.Result = CreateActionResult(filterContext, statusCode);
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
//filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
{
var ctx = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
var statusCodeName = ((HttpStatusCode)statusCode).ToString();
var viewName = "~/Views/Shared/Error.cshtml";
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
var result = new ViewResult
{
ViewName = viewName,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
};
result.ViewBag.StatusCode = statusCode;
return result;
}
}
Going through the source code for HandleErrorAttribute.cs, it looks like you can get the status code using the below code
var statusCode = new HttpException(null, filterContext.Exception).GetHttpCode();
Sorry if this is a duplicate question. However, I've tried looking for the answer and can't seem to find it.
Is there a way in ASP.NET to redirect to a page when a specific error occurs (in my case, when the request is too large). This needs to be just when the error occurs on a specific page, and not just on any page.
Thanks in advance!
As ADyson says in the comments, perhaps a try - catch block could be used for this situation.
try
{
// put the code that you want to try here
}
catch(Exception specificException)
{
return RedirectToAction(actionName, controllerName, routeValues);
}
Let me know if this helps.
Yes! there is as follows:
In the Global.asax file:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
HttpException httpException = exception as HttpException;
if (httpException != null)
{
if (httpException.GetHttpCode() == 404)
{
Server.ClearError();
Response.Redirect("~/Home/PageNotFound");
return;
}
}
//Ignore from here if don't want to store the error in database
HttpContextBase context = new HttpContextWrapper(HttpContext.Current);
RouteData routeData = RouteTable.Routes.GetRouteData(context);
string controllerName = null;
string actionName = null;
if (routeData != null)
{
controllerName = routeData.GetRequiredString("controller");
actionName = routeData.GetRequiredString("action");
}
ExceptionModel exceptionModel = new ExceptionModel()
{
ControllerName = controllerName ?? "Not in controller",
ActionOrMethodName = actionName ?? "Not in Action",
ExceptionMessage = exception.Message,
InnerExceptionMessage = exception.InnerException != null ? exception.InnerException.Message : "No Inner exception",
ExceptionTime = DateTime.Now
};
using (YourDbContext dbContext = new YourDbContext())
{
dbContext.Exceptions.Add(exceptionModel);
dbContext.SaveChanges();
}
// Ignore till here if you don't want to store the error on database
// clear error on server
Server.ClearError();
Response.Redirect("~/Home/Error");
}
Then in the controller:
public class HomeController : Controller
{
[AllowAnonymous]
public ActionResult Error()
{
return View();
}
[AllowAnonymous]
public ActionResult PageNotFound()
{
return View();
}
}
Here is everything you need to handle error in ASP.NET MVC Application.You can also customize according to your personal preference.
I have my custom error handler defined in Global.asax:
void Application_Error(Object sender, EventArgs e)
{
var exception = Server.GetLastError();
var httpException = exception as HttpException;
Response.Clear();
Server.ClearError();
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "General";
routeData.Values["exception"] = exception;
Response.StatusCode = 500;
if(httpException != null)
{
Response.StatusCode = httpException.GetHttpCode();
switch(Response.StatusCode)
{
case 403:
routeData.Values["action"] = "Http403";
break;
case 404:
routeData.Values["action"] = "Http404";
break;
}
}
IController errorController = new ErrorController();
var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
errorController.Execute(rc);
}
I'm trying to test my error handler by raising an error like this in my service model:
if(albums != null)
{
albums = null;
ctx.SpotifyAlbums.AddRange(albums);
ctx.SaveChanges();
}
This raises an exception, but how can I "catch" this exception with my custom error handler?
I usually create a controller with actions that throw specific exceptions.
public class ErrorTestController : Controller
{
public ActionResult ThrowHttpException(int httpStatusCode)
{
throw new HttpException(httpStatusCode, "Error!");
}
public ActionResult ThrowApplicationError()
{
Throw new Exception("Boo!");
}
}
Not fancy, but it does the job. And a 404 handler is the easiest to test - just enter a bad url.
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>
I am inheriting the HandleErrorAttribute in my MVC application so I can log the error:
public class HandleAndLogErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
if( filterContext.Exception != null )
{
// log here
}
}
}
I'm adding this as a global filter:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleAndLogErrorAttribute());
}
Is it possible to specify a custom view for specific exception types as well? For example:
if( filterContext.Exception is DivideByZeroException )
{
// how do i specify that the view should be DivideByZero?
}
Create a new filter which inherits HandleErrorAttribute (or implements IExceptionFilter directly)
Register it in global.asax (by replacing filters.Add(new HandleError());):
Here is a filter that I've created that tries to find a view per specific HTTP status code:
public class MyErrorHandler : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
return;
var statusCode = (int) HttpStatusCode.InternalServerError;
if (filterContext.Exception is HttpException)
{
statusCode = filterContext.Exception.As<HttpException>().GetHttpCode();
}
else if (filterContext.Exception is UnauthorizedAccessException)
{
//to prevent login prompt in IIS
// which will appear when returning 401.
statusCode = (int)HttpStatusCode.Forbidden;
}
_logger.Error("Uncaught exception", filterContext.Exception);
var result = CreateActionResult(filterContext, statusCode);
filterContext.Result = result;
// Prepare the response code.
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
{
var ctx = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
var statusCodeName = ((HttpStatusCode) statusCode).ToString();
var viewName = SelectFirstView(ctx,
"~/Views/Error/{0}.cshtml".FormatWith(statusCodeName),
"~/Views/Error/General.cshtml",
statusCodeName,
"Error");
var controllerName = (string) filterContext.RouteData.Values["controller"];
var actionName = (string) filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
var result = new ViewResult
{
ViewName = viewName,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
};
result.ViewBag.StatusCode = statusCode;
return result;
}
protected string SelectFirstView(ControllerContext ctx, params string[] viewNames)
{
return viewNames.First(view => ViewExists(ctx, view));
}
protected bool ViewExists(ControllerContext ctx, string name)
{
var result = ViewEngines.Engines.FindView(ctx, name, null);
return result.View != null;
}
}