So basically I have an Custom Authorize attribute and at some point it throwns an exception. I want to catch the exception and display the message to the user.
this is the contoller
[HandleError]
public class TestController : BaseController
{
[CustomAuthorize("someperm, someperm2")]
public ActionResult GamblerUser()
{
return View();
}
}
this is the code in FilterConfig
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
this is the code in global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
and the web.config
<customErrors defaultRedirect="~/Shared/Error" mode="On">
</customErrors>
and finally the view itself
#model HandleErrorInfo
#{
ViewBag.Title = "Error";
}
<h2>Sorry Error Occured</h2> <br />
<h2>#Model.Exception.Message</h2>
and this is the message I get whenever the exception occurs.
Runtime Error
Description: An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated.
I know that I am missing something very basic but this thing is just giving me headache.
I've look at tons of examples I just cant get this thing working.
ViewBag is null. I would use a static title in the html or find a way around it like this.
Related
I have an Application_Error() in Global.asax defined with some error handling code that will make the error redirect to the ErrorController to its specific action(say Error500). I have this working good for Web Actions like ActionResult..
But an WebApi action like IHttpActionResult or HttpResponseMessage doesnt redirect/hit on Application_Error() for GlobalAsax in WebApi.
Im writing the action as for eg.
public IHttpActionResult PersonDetail(int id)
{
if(id == null)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
else
{
return Ok();
}
}
But this will display me as Error500 on that url/page but doesnt take me to Application_Error(). So what do i need to do make it hit Application_Error() so i can redirect it to ErrorController to handle it more nicely.. ?
I suggest you to use custom exception handler, in that case, you can to consider specific behavior for each exception in only one method (not catch block in every error prone code blocks), look at this simple example:
public class MyExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
if (context.Exception is SqlException)
{
//do something...
}
else if (context.Exception is HttpListenerException)
{
//do something...
}
else
{
//do something else...
}
}
}
After that, you have to register your custom exception handler in global.asax:
config.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler());
I'm currently using ASP.NET Core, how do I set a 404 default page for unhandled exceptions or NotFound()?
IActionResult Foo()
{
throw new Exception("Message!");
}
IActionResult Bar()
{
return NotFound("Message!");
}
I think there is a IApplicationBuilder.UseExceptionHandler method to set an error page, but I don't know how to configure it.
This is explained here under Configuring status code pages
app.UseStatusCodePagesWithRedirects("/error/{0}");
You'll need an ErrorController which could look like:
public class ErrorController : Controller
{
public IActionResult Index(string errorCode)
{
return View(errorCode);
}
}
Your views (in the Error folder) would need to be called:
500.cshtml
404.cshtml
...etc
I have a custom filter that I've used for years for handling RequestValidationExceptions in a more user-friendly way. It works without issues in all scenarios until I introduce an Area:
public class HandleHttpRequestValidationExceptionAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
//base.OnException(filterContext);
if (!(filterContext.Exception is HttpRequestValidationException))
return;
const string viewName = "~/Views/Errors/HttpRequestValidationException.cshtml";
var result = new ViewResult
{
ViewName = viewName,
ViewData = { Model = filterContext.Exception.Message }
};
//result.ViewBag.StatusCode = 200;
filterContext.Result = result;
filterContext.RouteData.Values["area"] = "";
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 200;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Server.ClearError();
}
}
...registered in FilterConfig:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//filters.Add(new HandleErrorAttribute());
filters.Add(new HandleMemberIsNotActivatedOrWaiveredAttribute());
filters.Add(new HandleMemberNotAuthorizedException());
filters.Add(new HandleHttpRequestValidationExceptionAttribute());
}
}
Any RequestValidationException thrown gets handled without issues (I get my pretty error page with some user-friendly description of what happened and what to do about it) except if one is thrown in an Area. In that case, I get a blank response with customErrors="On" (and the detailed YSOD if customErrrors="Off"). If I remove my filter, then I get the no-details YSOD (which is also pointless). Either way, Application_Error in Global.asax.cs does not get fired. Moreover, all my other custom filters and global exception handling works without any issues regardless of where the exception is thrown from.
How can I handle RequestValidationException in a user-friendly manner regardless of where the exception originates from (regardless of whether or not it is thrown from within an Area)?
Update: even doing a filterContext.Result = new RedirectResult("/"); results in the same blank page (and stepping through it indicates all is well, but no proper response).
I believe you are going to need to forward the error model out of the area to your error controller in order to generate the view. or you will have to route your error controller to the area.
Look at this link. Redirect From Action Filter Attribute
I want to trap 404 page not founds to go to a nice little sexy page I've created.
Let's start at step 1 first.
1)---------
I have an Error.chstml in my Shared views folder.
2)---------
I have added:
<customErrors mode="On" />
to my Web.Config (in the root directory) inside system.web
3)----------
I have added:
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
to my Global.asax ApplicationStart method and added a FilterConfig.cs file to my App Start folder with the following code:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
4)------------
I then tried to add this to the customErrors XML:
<customErrors mode="On" defaultRedirect="/Error" />
5)--------------
I had tried to create my own ErrorController and used my routes to redirect to it... but they never get fired:
routes.MapRoute(
"404-PageNotFound",
"{*url}",
new { controller = "Error", action = "Error404" }
);
...
public class ErrorController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ViewResult Error404()
{
return View();
}
}
This still returns the ugly smelly web 1.0 error page :) : HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
What am I doing wrong???? Something that I thought would take like 20 seconds has ended up taking 2 hours lol
With your current routes it will fail to redirect to that Error404 action method. Start by removing the 404-PageNotFound route. Then rename Error404 method to Index. Don't forget to rename the view too.
public class ErrorController : Controller
{
public ViewResult Index()
{
return View();
}
}
Now when user is redirected to http://example.org/Error like you specified web.config, this action method will be called.
I have the following in my web.config:
<customErrors mode="On" defaultRedirect="Error">
<error statusCode="404" redirect="Error/NotFound" />
</customErrors>
I have a
[HandleError]
at the top of my HomeController class. To test, I create and action that simply throws an exception . . and it redirects to my
ErrorController/Index
method but when it gets to my view which binds to HandleErrorInfo my model is null so I somehow have lost the reference to the error.
I am sure it has something to do with the Error getting lost in the redirect so I wanted to see if i was missing something and if anyone had suggestions where I can have a view that shows the Stacktrace and error message.
I can see the misconception. You want to do the MVC thing and redirect to a controller action.
But defaultRedirect is itself a Web Form convention and thereby limited. The moment you redirect to another controller, you will lose your HttpContext, and thereby lose your HandleErrorInfo Object
Your [HandleError] Attribute requires a View to direct its error message to. Going by your example above, I assume that you have a Views/Error Folder for your ErrorController, and in it you have an Index View. If you want to your Filter Context to send a HandleErrorInfo object to that view,
Try this syntax:
[HandleError(View="~/Views/Error/Index")]
Public class HomeController : Controller
But what about Logging?!?!?
I suspect your intention is more than just displaying error stack to users. In fact, I suspect you have no such intention at all. I suspect what your real aim is to log your error (probably to db) and to display some bland message to your user.
What I've explained so far was "what is best [way to] show unhandled exceptions in my view". The [HandleError] attribute is good for that.
But when you want to move to the next step (logging the error) you have a few options:
1) Override your base controller's On Exception method; create your own Controller inheriting from the MVC Controller class but override the On Exception Method. This approach can be used in conjunction with [HandleError] attribute
2) Create a custom exception handler Create your own Exception Handler that logs the error. Your exception handler can then call a View of choice or can work in conjunction with [HandleError(order=2)] since filter attributes can take an order argument applying precedence.
Nitin Sawant asks what an error view would look like. The
#model System.Web.Mvc.HandleErrorInfo
<h2>Exception details</h2>
<p> Controller: #Model.ControllerName </p>
<p> Action: #Model.ActionName </p>
<p> Exception: #Model.Exception </p>
I do something similar to maxlego which handles all errors (not just those occurring in controllers with the HandleError attribute).
My MvcApplication class (in global.asax.cs) has this:
public class MvcApplication : HttpApplication
{
// usual stuff here...
protected void Application_Error(object sender, EventArgs e)
{
Server.HandleError(((MvcApplication)sender).Context);
}
}
The above code uses an extension method from my MVC library of useful stuff. With this in place I don't need any error handling attributes, customErrors config or custom filters. Instead the extension method will log the error details then invoke an appropriate view, either:
AccessDenied
NotFound
InternalServerError
The extension method code to make this work is:
public static class HttpServerUtilityExtensions
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public static void HandleError(this HttpServerUtility server, HttpContext httpContext)
{
var currentController = " ";
var currentAction = " ";
var currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
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 exception = server.GetLastError();
Logger.ErrorException(exception.Message, exception);
var controller = DependencyResolver.Current.GetService<ErrorController>();
var routeData = new RouteData();
var action = "InternalServerError";
if (exception is HttpException)
{
var httpEx = exception as HttpException;
switch (httpEx.GetHttpCode())
{
case 404:
action = "NotFound";
break;
case 401:
action = "AccessDenied";
break;
}
}
httpContext.ClearError();
httpContext.Response.Clear();
httpContext.Response.StatusCode = exception is HttpException ? ((HttpException)exception).GetHttpCode() : 500;
httpContext.Response.TrySkipIisCustomErrors = true;
routeData.Values["controller"] = "Error";
routeData.Values["action"] = action;
controller.ViewData.Model = new HandleErrorInfo(exception, currentController, currentAction);
((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
}
}
Note, the above uses NLog for logging the error details but could easily be changed to support something else.
Also, this method respects your IoC container when resolving the ErrorController.
I have used this little piece of code to show users handled errors page. Whether page was not found or some other error occurred.
void Application_Error(object sender, EventArgs e)
{
// this value can be fetched from config or depend on DEBUG smybol
if (!handleErrors)
return;
var error = Server.GetLastError();
var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;
if (code == 404)
{
// do something if page was not found. log for instance
}
else
{
// collect request info and log exception
}
// pass exception to ErrorsController
Request.RequestContext.RouteData.Values["ex"] = error;
// execute controller action
IController errorController = new ErrorsController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), Request.RequestContext.RouteData));
}
And errors controller look something like this. If you need detailed exception, it is accessible via RouteData
public class ErrorsController : Controller
{
/// <summary>
/// Page not found
/// </summary>
/// <returns></returns>
public ActionResult Http404()
{
return View();
}
/// <summary>
/// All other errors
/// </summary>
/// <param name="actionName"></param>
protected override void HandleUnknownAction(string actionName)
{
// in case detailed exception is required.
var ex = (Exception) RouteData.Values["ex"];
return View();
}
}
You can add different view for each http code. Just implement action Http{Code}