I've been fighting to get custom error pages to work and keep finding my way back to simply using a static page. Though the static page works, it would require remaking the navigation bar which we would like to avoid at this time. I'm currently using the following to specify custom error pages.
Asp.net Error Handling
<customErrors mode="On" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="~/404.aspx"/>
</customErrors>
IIS Error Handling
<httpErrors errorMode="Custom">
<remove statusCode="404"/>
<error statusCode="404" path="/404.html" responseMode="File"/>
</httpErrors>
Is there a method of implementing dynamic custom error pages that can handle for both IIS errors and Asp.net errors?
I struggled with this as well and searched for a long time. As far as I can tell there is no way to create a dynamic custom error page that gets served up for IIS Errors for requests that don't enter the .net pipeline. Like you, I ended up having two 404 error pages. One dynamic .aspx one for file not founds errors resulting from requests that entered the .net pipeline, and one .html one for file not found requests that never entered the .net pipeline. The person who downvoted your question probably didn't realize that you are asking a very good, and difficult question, that has no easy solution. I will upvote your question to help compensate.
I got around the same issue in my ASP.Net MVC projects by piping everything through .Net via handlers.
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*.*" verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*.*" verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*.*" verb="GET,HEAD,POST,DEBUG,PUT,DELETE" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
First an ErrorController
was created to handle request errors and not found requests like.
[AllowAnonymous]
public class ErrorController : Controller {
// GET: Error
public ActionResult NotFound() {
Response.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
Response.TrySkipIisCustomErrors = true;
HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
HttpContext.Response.TrySkipIisCustomErrors = true;
return View();
}
public ActionResult Error() {
Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
Response.TrySkipIisCustomErrors = true;
return View();
}
}
You will notice that I call TrySkipIisCustomErrors
to try to avoid IIS errors
Then a base controller to handle all unknown actions that would map to the ErrorController.NotFound
action was created.
public abstract class FrontOfficeControllerBase : Controller {
protected override void HandleUnknownAction(string actionName) {
var data = ViewData;
//Custom code to resolve the view.
//ViewResult view = this.View<ErrorController>(c => c.NotFound());
//Manually create view with view Data
ViewResult view = new ViewResult();
view.ViewData = new ViewDataDictionary();
view.ViewData["controller"] = "Error";
view.ViewData["action"] = "NotFound";
if (data != null && data.Count > 0) {
data.ToList().ForEach(view.ViewData.Add);
}
Response.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
Response.TrySkipIisCustomErrors = true;
view.ExecuteResult(this.ControllerContext);
}
}
All Controllers
would inherit from this base controller.
A catch all route was configured after all other routes.
routes.MapRoute(
name: "404-NotFound",
url: "NotFound",
defaults: new { controller = "Error", action = "NotFound" }
);
routes.MapRoute(
name: "500-Error",
url: "Error",
defaults: new { controller = "Error", action = "Error" }
);
routes.MapRoute(
name: "CatchAll",
url: "{*any}",
defaults: new { controller = "Error", action = "NotFound" });
This made sure that if a route was not matched to any of my controllers it would safely route to the ErrorController.NotFound
action.
For the views, I created the respective NotFound.shtml
and Error.cshtml
paged in the Views/Shared
folder and they benefited from access to the root layout, which is what I think you were looking for.
In the end I was able to remove both the customErrors
and httpErrors
from web.config
as there was no longer any need for them as every request was managed by the handlers and routed accordingly.
The original idea for this structure came from this article where I mixed and matched the available options till I found a solution that worked/suited my needs.
Exception handling in ASP.NET MVC (6 methods explained)
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With