CustomErrors does not work when setting redirectMode="ResponseRewrite"
Solution 1:
It is important to note for anyone trying to do this in an MVC application that ResponseRewrite
uses Server.Transfer
behind the scenes. Therefore, the defaultRedirect
must correspond to a legitimate file on the file system. Apparently, Server.Transfer
is not compatible with MVC routes, therefore, if your error page is served by a controller action, Server.Transfer
is going to look for /Error/Whatever, not find it on the file system, and return a generic 404 error page!
Solution 2:
The only way that worked perfectly for me is to turn off custom errors and replace iis's error pages via web.config. It sends the correct status code with the response and has the benefit of not going through the mvc.
here's the code
-
Turn off custom errors
<customErrors mode="Off" />
-
Replace error pages
<httpErrors errorMode="Custom" existingResponse="Replace"> <remove statusCode="404" subStatusCode="-1" /> <remove statusCode="500" subStatusCode="-1" /> <error statusCode="404" path="Error404.html" responseMode="File" /> <error statusCode="500" path="Error.html" responseMode="File" /> </httpErrors>
Note. Use responsemode="file"
if the url is a direct link to a file
info : http://tipila.com/tips/use-custom-error-pages-aspnet-mvc
Solution 3:
What's happening is IIS is seing the error status code and presenting it's own error page instead of yours. To solve you need to set this in the code behind page of your error page to prevent IIS from doing this:
Response.TrySkipIisCustomErrors = true;
This will only work in IIS7 or above, for earlier versions of IIS you'll need to play with the error page settings.
Solution 4:
Due to the reliance on Server.Transfer
it seems that the internal implementation of ResponseRewrite
isn't compatible with MVC.
This seems like a glaring functionality hole to me, so I decided to re-implement this feature using a HTTP module, so that it just works. The solution below allows you to handle errors by redirecting to any valid MVC route (including physical files) just as you would do normally.
<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="404.aspx" />
<error statusCode="500" redirect="~/MVCErrorPage" />
</customErrors>
This has been tested on the following platforms;
- MVC4 in Integrated Pipeline Mode (IIS Express 8)
- MVC4 in Classic Mode (VS Development Server, Cassini)
- MVC4 in Classic Mode (IIS6)
namespace Foo.Bar.Modules {
/// <summary>
/// Enables support for CustomErrors ResponseRewrite mode in MVC.
/// </summary>
public class ErrorHandler : IHttpModule {
private HttpContext HttpContext { get { return HttpContext.Current; } }
private CustomErrorsSection CustomErrors { get; set; }
public void Init(HttpApplication application) {
System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");
application.EndRequest += Application_EndRequest;
}
protected void Application_EndRequest(object sender, EventArgs e) {
// only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {
int statusCode = HttpContext.Response.StatusCode;
// if this request has thrown an exception then find the real status code
Exception exception = HttpContext.Error;
if (exception != null) {
// set default error status code for application exceptions
statusCode = (int)HttpStatusCode.InternalServerError;
}
HttpException httpException = exception as HttpException;
if (httpException != null) {
statusCode = httpException.GetHttpCode();
}
if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {
Dictionary<int, string> errorPaths = new Dictionary<int, string>();
foreach (CustomError error in CustomErrors.Errors) {
errorPaths.Add(error.StatusCode, error.Redirect);
}
// find a custom error path for this status code
if (errorPaths.Keys.Contains(statusCode)) {
string url = errorPaths[statusCode];
// avoid circular redirects
if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {
HttpContext.Response.Clear();
HttpContext.Response.TrySkipIisCustomErrors = true;
HttpContext.Server.ClearError();
// do the redirect here
if (HttpRuntime.UsingIntegratedPipeline) {
HttpContext.Server.TransferRequest(url, true);
}
else {
HttpContext.RewritePath(url, false);
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(HttpContext);
}
// return the original status code to the client
// (this won't work in integrated pipleline mode)
HttpContext.Response.StatusCode = statusCode;
}
}
}
}
}
public void Dispose() {
}
}
}
Usage
Include this as the final HTTP module in your web.config
<system.web>
<httpModules>
<add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
</httpModules>
</system.web>
<!-- IIS7+ -->
<system.webServer>
<modules>
<add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
</modules>
</system.webServer>