Custom errors on specific MVC 5 controllers - c#

I am having an issue where I want to display custom 404 and 500 views only for specific controllers.
The reason I want to do this is that we have APIControllers and standard Controllers in the same project and I do not want to rewrite the API error responses.
I am trying to achieve this with a custom attribute which inherits HandleErrorAttribute, but I cannot seem to get 404's to go through it. Here is what I have for OnException so far:
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
return;
//Defaults to 500 if it cannot be determined
int statusCode = new HttpException(null, filterContext.Exception).GetHttpCode();
//We only want to capture 500 and 404 at this stage
switch (statusCode)
{
case 404:
View = _404ViewName;
break;
case 500:
View = _500ViewName;
break;
default:
return;
}
Master = _layoutViewName;
string controllerName = (string)filterContext.RouteData.Values["controller"];
string actionName = (string)filterContext.RouteData.Values["action"];
HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
ViewDataDictionary viewData = new ViewDataDictionary<HandleErrorInfo>(model);
filterContext.Result = new ViewResult
{
ViewName = View,
MasterName = Master,
ViewData = viewData,
TempData = filterContext.Controller.TempData
};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
// Certain versions of IIS will sometimes use their own error page when
// they detect a server error. Setting this property indicates that we
// want it to try to render ASP.NET MVC's error page instead.
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
I have previously tried this in the config file:
<httpErrors errorMode="DetailedLocalOnly" existingResponse="Replace">
<remove statusCode="404"/>
<error statusCode="404" responseMode="ExecuteURL" path="/Error/404"/>
<remove statusCode="500"/>
<error statusCode="500" responseMode="ExecuteURL" path="/Error/500"/>
</httpErrors>
But that rewrote the API error responses.
I currently only use the <customErrors mode="On" /> tag instead which works great for 500 errors but I get generic IIS errors for 404s using that.
If I add <httpErrors errorMode="Custom" existingResponse="PassThrough" /> into the web config, the 500 still gives me my custom message view, but the 404 now just shows a blank page.
What do I need to do in order to get 404s to go through my custom attribute as well?
Or if this is not possible, what is a different approach I can take that is not going to affect the API Controllers?

Related

Custom Error Page is not working on live server

I have implemented Custom Error Functionality in my project, and its working on local IIS but not working on live server. I have implemented this functionality using Global.asax file and i am calling my custom error action method in my custom error controller in MVC. I have published and run on local IIS and its work well,but on live server.
my Global.asax.cs file
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//do not register HandleErrorAttribute. use classic error handling mode
filters.Add(new HandleErrorAttribute());
}
protected void Application_Error(Object sender, EventArgs e)
{
LogException(Server.GetLastError());
CustomErrorsSection customErrorsSection = (CustomErrorsSection)ConfigurationManager.GetSection("system.web/customErrors");
string defaultRedirect = customErrorsSection.DefaultRedirect;
if (customErrorsSection.Mod e== CustomErrorsMode.On)
{
var ex = Server.GetLastError().GetBaseException();
Server.ClearError();
var routeData = new RouteData();
routeData.Values.Add("controller", "Common");
routeData.Values.Add("action", "CustomError");
if (ex is HttpException)
{
var httpException = (HttpException)ex;
var code = httpException.GetHttpCode();
routeData.Values.Add("status", code);
}
else
{
routeData.Values.Add("status", 500);
}
routeData.Values.Add("error", ex);
IController errorController = new Test.Controllers.CommonController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
}
My Custom error Controller and its action method
public ActionResult CustomError(int status, Exception error)
{
var model = new CustomErrorModel();
model.Code = status;
model.Description = Convert.ToString(error);
Response.StatusCode = status;
return View(model);
}
So what should I do?
I had this problem where errors on live IIS server weren't showing custom error pages (which return proper HttpStatusCodes) but it WAS working on local IIS (localhost address using default website - not Cassini). They should have worked exactly the same I would have thought - anyway this web.config setting fixed it.
<configuration>
<system.webServer>
<httpErrors existingResponse="PassThrough"></httpErrors>
</system.webServer>
</configuration>
Note that my setup uses Application_Error in global.asax and just this other web.config setting:
<customErrors mode="On">
<!-- There is custom handling of errors in Global.asax -->
</customErrors>
2 approaches
Route Method
// We couldn't find a route to handle the request. Show the 404 page.
routes.MapRoute("Error", "{*url}", new { controller = "Error", action = "CustomError" } );
or
Custom Error Handler in Web.config:
<customErrors mode="On" >
<error statusCode="404" redirect="~/CatchallController/CustomError" />
</customErrors>
The condition raised by no route matching is a 404. This way you direct all non-match to your ~/CatchallController/CustomError

How to return proper message on 403 forbidden on MVC3?

I know I can fill the ReasonPhrase property of a HttpResponseMessage using Web API Framework. But how can I do the same with the normal "MVC" ?
This is my current code:
if (filterContext.Exception != null && filterContext.Exception is ArgumentException)
{
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.HttpContext.Response.StatusDescription = filterContext.Exception.Message;
//filterContext.HttpContext.Response.Write(filterContext.Exception.Message);
filterContext.ExceptionHandled = true;
filterContext.Exception = null;
}
I also know that the commented line would provide the error message to the client but in the Response body. I don't know if this is the best practice.
What I would like to do is the same as this link.
ehy not handle the error directly in your webcofig if you get specific error webcofig shoulld redirect to specific error page
<customErrors defaultRedirect="GenericError.htm" mode="RemoteOnly">
<error statusCode="403" redirect="pagenotfoundlError.htm"/>
</customErrors>

How to get your app to display the correct HTTP error code

I am using ASP.NET MVC3 and running the website on Windows Server 2003. I'm trying to figure out how to handle all HTTP errors. I have implemented code but I sometimes get a 403 error displayed in the browser when a 404 error is returned.
Here is my code that I have implemented:
In my global.asax.cs:
protected void Application_Error(object sender, EventArgs e)
{
MvcApplication app = (MvcApplication)sender;
HttpContext context = app.Context;
Exception exception = app.Server.GetLastError();
context.Response.Clear();
context.ClearError();
HttpException httpException = exception as HttpException;
RouteData routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Index";
routeData.Values["httpException"] = httpException;
Server.ClearError();
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
ErrorController.cs file:
public class ErrorController : Controller
{
public ActionResult Index()
{
ErrorModel model = new ErrorModel();
HttpException httpException = RouteData.Values["httpException"] as HttpException;
int httpCode = (httpException == null) ? 500 : httpException.GetHttpCode();
switch (httpCode)
{
case 403:
//Response.StatusCode = 403;
model.Heading = "Forbidden";
model.Message = "You aren't authorised to access this page.";
break;
case 404:
//Response.StatusCode = 404;
model.Heading = "Page not found";
model.Message = "We couldn't find the page you requested.";
break;
case 500:
default:
Response.StatusCode = 500;
model.Heading = "Error";
model.Message = "Sorry, something went wrong. It's been logged.";
break;
}
Response.TrySkipIisCustomErrors = true;
return View(model);
}
}
I have nothing set in my web.config.
I want it to display the correct error when the user is trying to access my directories such as app_code and similar directories. Is this possible?
When I type in http://localhost:43596/app_code then I see that it seems to be a 404 error but it displays the default 403 error page of IE. How do I get my code to display the correct HTTP error message?
The reason why I want it this way is because I need to log all attempts if a user is trying to sabotage the site in any way. I want to be able to see who is doing the wrong accessing.
When I type in http://localhost:43596/app_code then I see that it
seems to be a 404 error but it displays the default 403 error page of
IE.
What's wrong with 403?
In the HTTP used on the World Wide Web, 403 Forbidden is an HTTP
status code returned by a web server when a user requests a web page
or media that the server does not allow them to access. In other
words, the server can be reached, but the server declined to allow
access to the page. Microsoft IIS responds in
the same way when directory listings are denied.
Wikipedia
When an user is trying to access the App_Data folder and if you return 404 it means to the user that the folder not exists in the server but the truth is the folder may or may not exists and since it is a special folder ASP.NET doesn't allow any one to access that and it is forbidden so I think returning 403 is perfectly valid in this case.
404 vs 403 when directory index is missing
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
Try to review web.config section "customErrors" set "mode=Off"
<configuration>
<system.web>
<customErrors defaultRedirect="GenericError.htm" mode="RemoteOnly">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>

Once and for all what is the best routing approach to handle errors, exceptions and 404's in MVC

There are many articles on SO and the web in attempts to handle 404's and exceptions gracefully.
From what I have read the best advice seems to be have a route for 404's like so:
routes.MapRoute(
"404-PageNotFound",
"{*url}",
new { controller = "ErrorController", action = "PageNotFound" }
);
Then for other errors have the HandleError atribute on the Controller and have CustomErrors turned on in web.config so it goes to the error.cshtml page.
However I have read that if you get a exception that does not set the HTTP code to 500 the HandleError will not work.
Can we finally produce an answer/best practice that handles 404's/Exceptions/ASP.Net errors where we can apply to this to all our projects?
Thanks
I use a simple error handling setup. Nice and simple. More info can be found at http://erictopia.com/2010/04/building-a-mvc2-template-part-7-custom-web-errors-and-adding-support-for-elmah/
Install ELMAH and have it handle all the errors.
Next create an Error controller. Add a catch all route like this:
routes.MapRoute(
"ErrorHandler", // Route name
"{*path}", // URL
new { controller = "Error", action = "Index" }
);
Then in web.config add this section:
<customErrors mode="RemoteOnly" defaultRedirect="/Error/Index">
<error statusCode="403" redirect="/Error/NoAccess" />
<error statusCode="404" redirect="/Error/NotFound" />
</customErrors>
No need to set up a 404 route.
In global asax application start, set up a global filter to catch 404 where the controller exists but not the action, or if an action returns a 404 result.
filters.Add(new HttpNotFoundFilterAttribute { Order = 99 });
where the filter is an ActionFilterAttribute with this override:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Result !=null &&
(filterContext.Result.GetType() == typeof(HttpNotFoundResult) )
{
//You can transfer to a known route for example
filterContext.Result = new TransferResult(SomeAction, SomeController);
}
}
And also in Application_Error, in case no controller exists:
Exception ex = Server.GetLastError();
string uri = null;
if (Context != null && Context.Request != null)
{
uri = Context.Request.Url.AbsoluteUri;
}
Exception baseEx = ex.GetBaseException();
var httpEx = ex as HttpException;
if ((httpEx != null && httpEx.GetHttpCode()==404)
|| (uri != null && Context.Response.StatusCode == 404) )
{ /* do what you want. */
//Example: show some known url
Server.ClearError();
Server.TransferRequest(transferUrl);
}
To avoid handling 404 for static resources, you should install SP1 on Windows 7 or windows 2008 R2 to upgrade IIS7 and set in web.config:
...
<modules runAllManagedModulesForAllRequests="false">
...

How to prevent "aspxerrorpath" being passed as a query string to ASP.NET custom error pages

In my ASP.NET web application, I have defined custom error pages in my web.config file as follows:
<customErrors mode="On" defaultRedirect="~/default.html">
<error statusCode="404" redirect="~/PageNotFound.html" />
</customErrors>
In the case of a 404 error, my site redirects to the default.html page, but it passes "aspxerrorpath" as a query string parameter to the custom error page as follows:
http://www.example.com/default.html?aspxerrorpath=/somepathcausederror/badpage.aspx
I don't want that behavior. I want the redirect URL to simply read:
http://www.example.com/default.html
Is there a way to achieve this?
If you supply your own query string variable when specifying the path, then .NET will NOT tack on the "aspxerrorpath". Who knew?
For example:
<customErrors mode="On" defaultRedirect="errorpage.aspx?error=1" >
This will do the trick.
I had to add this to a bunch of apps since URLScan for IIS by default rejects anything with "aspxerrorpath" in it anyway.
In the global.asax, catch the 404 error and redirect to the file not found page. I didn't require the aspxerrorpath and it worked a treat for me.
void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
if (ex is HttpException && ((HttpException)ex).GetHttpCode() == 404)
{
Response.Redirect("~/filenotfound.aspx");
}
else
{
// your global error handling here!
}
}
You could just send your own url params to the error page
<customErrors mode="On" defaultRedirect="~/default.html?404">
<error statusCode="404" redirect="~/PageNotFound.html?404" />
</customErrors>
My first thought would be to create a HttpHandler which catches url's with aspxerrorpath in it, and strips it. You could probably do the same with the rewrite module in IIS7 as well.
I think you'd instead implement/use the Application_Error event in Global.asax, and do your processing/redirects there.
Providing you call Server.ClearError in that handler, I don't think it will use the customErrors config at all.
I use javascript like
if (location.search != "") { window.location.href = "/404.html"; }
If you remove aspxerrorpath=/ and you use response redirect during error handling you'll get exception there will be redirection loop.
Add redirectMode="ResponseRewrite" in the Custom Error like this,
<customErrors mode="On" defaultRedirect="~/NotFound">
<error statusCode="404" redirect="~/NotFound" redirectMode="ResponseRewrite"/>
</customErrors>
this solution works for me.
The best solution (more a workaround..) I implemented since now to prevent aspxerrorpath issue continuing to use ASP.NET CustomErrors support, is redirect to the action that implements Error handling.
These are some step of my solution in an ASP.NET MVC web app context:
First enable custom errors module in web.config
<customErrors mode="On" defaultRedirect="~/error/500">
<error statusCode="404" redirect="~/error/404"/>
</customErrors>
Then define a routing rule:
routes.MapRoute(
name: "Error",
url: "error/{errorType}/{aspxerrorpath}",
defaults: new { controller = "Home", action = "Error", errorType = 500, aspxerrorpath = UrlParameter.Optional },
);
Finally implement following action (and related views..):
public ActionResult Error(int errorType, string aspxerrorpath)
{
if (!string.IsNullOrEmpty(aspxerrorpath)) {
return RedirectToRoute("Error", errorType);
}
switch (errorType) {
case 404:
return View("~/Views/Shared/Errors/404.cshtml");
case 500:
default:
return View("~/Views/Shared/Errors/500.cshtml");
}
}
In my case, i prefer not use Web.config. Then i created code above in Global.asax file:
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
//Not Found (When user digit unexisting url)
if(ex is HttpException && ((HttpException)ex).GetHttpCode() == 404)
{
HttpContextWrapper contextWrapper = new HttpContextWrapper(this.Context);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "NotFound");
IController controller = new ErrorController();
RequestContext requestContext = new RequestContext(contextWrapper, routeData);
controller.Execute(requestContext);
Response.End();
}
else //Unhandled Errors from aplication
{
ErrorLogService.LogError(ex);
HttpContextWrapper contextWrapper = new HttpContextWrapper(this.Context);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "Index");
IController controller = new ErrorController();
RequestContext requestContext = new RequestContext(contextWrapper, routeData);
controller.Execute(requestContext);
Response.End();
}
}
And thtat is my ErrorController.cs
public class ErrorController : Controller
{
// GET: Error
public ViewResult Index()
{
Response.StatusCode = 500;
Exception ex = Server.GetLastError();
return View("~/Views/Shared/SAAS/Error.cshtml", ex);
}
public ViewResult NotFound()
{
Response.StatusCode = 404;
return View("~/Views/Shared/SAAS/NotFound.cshtml");
}
}
And that is my ErrorLogService.cs
//common service to be used for logging errors
public static class ErrorLogService
{
public static void LogError(Exception ex)
{
//Do what you want here, save log in database, send email to police station
}
}
If you want to resolve or handle error request you can insert into Handler try catch statement.
like this:
try {
// Block of code that generate error
}
catch(Exception e) {
// Block of code to handle errors ||| HERE you can put error in your response and handle it without get xhr redirect error.
}

Categories