I am using the Application_Error in the Global class to handle 404s.
Once a 404 is caught I send an email for the purpose of logging it. To do this I call a static method to write the error.
ErrorHandler.WriteError(Exception ex)
Within this method I create a new instance of my EmailHandler in order to initialise the System.Net.Mail class. Within my EmailHandler object I have a private member
private MailMessage m_mail = null;
In the constructor of the EmailHandler class I create a new MailMesage instance in order to construct the email.
public EmailHandler()
{
m_mail = new MailMessage();
}
At this point I get the following error.
System.Security.Cryptography.CryptographicException
"The handle is invalid."
I need to point out that this only happens when the request is to a specific file only (such as a www.mysite.com/random-incorrect-image.jpg).
It doesn't happen when I am requesting a page like www.mysite.com/random-incorrect-page.aspx
Maybe you should try the following for static file 404 :
Configure custom 404 in your web.config
<system.webServer>
<httpErrors>
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" prefixLanguageFilePath="" path="/yourpath/yourcustom404.aspx" responseMode="ExecuteURL" />
</httpErrors>
404 Redirection for static files will then occur to http://www.mysite.com/yourpath/yourcustom404.aspx?404;http://www.mysite.com/random-incorrect-image.jpg
You can then process the queryString to extract the url in the pageLoad of custom404.aspx, send your email, and display a friendly message
You can do the same for aspx pages.
In your web.config :
<customErrors mode="On">
<error statusCode="404" redirect="/yourpath/yourcustom404.aspx" />
</customErrors>
the redirection will be slightly different though :
http://www.mysite.com/yourpath/yourcustom404.aspx?aspxerrorpath=/random-incorrect-page.aspx
Related
I have read tons of articles on handling 404 errors with the new ASP.NET 5 stuff, but I'm still unsure how to set up the following scenario:
Let Web API controllers return 404 errors, perhaps with a custom message in the response body like "the specified spell id could not be found".
All other 404 errors keep the current url, return a 404 status code, and display the contents of either an MVC error view or a static html file (don't care which).
Allow IIS to handle static files rather than ASP.NET (this condition complicates things because it means we can't put a catch-all route or a catch-all middleware to keep all requests from falling through to IIS).
Here's the example that is not working:
In Startup.cs Configure method:
app.UseErrorHandler("/error");
app.UseStatusCodePagesWithReExecute("/error/{0}");
app.UseMvc(...);
In web.config:
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404"/>
<error statusCode="404" path="/error/404" responseMode="ExecuteURL" />
<remove statusCode="500"/>
<error statusCode="500" path="/error" responseMode="ExecuteURL" />
</httpErrors>
And a very basic test controller:
[Route("api/blah")]
public class BlahController : Controller
{
// GET: spell/12345
[HttpGet("[action]/{id}")]
public IActionResult Spell(int id)
{
if (id != 12345)
return new HttpNotFoundObjectResult(new { Message = "not there bro" });
return new ObjectResult(new SpellJson
{
Id = id,
Name = "test"
});
}
}
I am running in IIS Express.
Here's what happens:
1. If I browse to a non-existent url like http://localhost:49858/asdf, I see the html generated by my /error/404 view, great!
If I call /api/blah/12345 from javascript, I get my JSON data, great!
If I call /api/blah/999 from javascript, I get a 404 error but the response body is the html generated by /error/404 rather than the JSON { Message: "not there bro" }, bad!
Which part up there is making my controller that explicitly returns a HttpNotFoundObjectResult instead execute the error page? How do I set things up to allow Web API controller methods to return 404 without going to the error page, but catch all other 404 errors with the error page?
I avoid the default ASP.NET approach of redirecting on errors (as many people do). Clean AJAX code and SEO are among the reasons.
However, I'm using the following method to do it, and it seems that I may lose HttpContext.Current.Items in the transfer?
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="401" />
<remove statusCode="403" />
<remove statusCode="404" />
<remove statusCode="500" />
<error statusCode="401" responseMode="ExecuteURL" path="/Account/SignIn" />
<error statusCode="403" responseMode="ExecuteURL" path="/Site/Forbidden" />
<error statusCode="404" responseMode="ExecuteURL" path="/Site/NotFound" />
<error statusCode="500" responseMode="ExecuteURL" path="/Site/Error" />
</httpErrors>
I assumed it just performed a Server.Transfer() under the covers, which I understand preserves Items. (See: Scope of HttpContext.Current.Items and http://weblog.west-wind.com/posts/2010/Jan/20/HttpContextItems-and-ServerTransferExecute )
But I'm also capturing something in Items before the "ExecuteURL", and retrieving/outputting it after the transfer (or whatever it is), and it seems to disappear. I've watched it go into the Items collection, I see the Count raise to 5, and then when the value is retrieved there are only 2 items in the collection.
What is going on?
If you'd like to understand more about what I'm doing and recommend an alternate implementation, I'm open to it. I'm using this to push the ELMAH Error Id into a ViewModel in a way that is free from race conditions. (i.e. a common workaround for this that I'm replacing is to merely display the most recent error.) Here's my code:
Global.asax
protected void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args) {
ElmahSupplement.CurrentId = args.Entry.Id;
}
void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e) {
if (ElmahSupplement.IsNotFound(e.Exception)) {
ElmahSupplement.LogNotFound((e.Context as HttpContext).Request);
e.Dismiss();
}
}
SiteController.cs
public virtual ActionResult Error() {
Response.StatusCode = 500;
return View(MVC.Site.Views.Error, ElmahSupplement.CurrentId);
}
ElmahSupplement.cs
public class ElmahSupplement {
// TODO: This is a rather fragile way to access this info
private static readonly Guid contextId = new Guid("A41A67AA-8966-4205-B6C1-14128A653F21");
public static string CurrentId {
get {
return
// Elmah 1.2 will fail to log when enumerating form values that raise RequestValidationException (angle brackets)
// https://code.google.com/p/elmah/issues/detail?id=217
// So this id could technically be empty here
(HttpContext.Current.Items[contextId] as string);
}
set {
HttpContext.Current.Items[contextId] = value;
}
}
public static void LogNotFound(HttpRequest request) {
var context = RepositoryProxy.Context;
context.NotFoundErrors.Add(new NotFoundError {
RecordedOn = DateTime.UtcNow,
Url = request.Url.ToString(),
ClientAddress = request.UserHostAddress,
Referrer = request.UrlReferrer == null ? "" : request.UrlReferrer.ToString()
});
context.SaveChanges();
}
public static bool IsNotFound(Exception e) {
HttpException he = e as HttpException;
return he != null && he.GetHttpCode() == 404;
}
}
As explained here, the ExecuteURL generates two requests: the first one throws the exception and the second one generates the error response.
Since Context.Items is cleared between requests, your code always see the 2nd request generated hence the diff between the Items.
Try the sugestion in the post: use system.web > customErrors with redirectMode="ResponseRewrite" instead.
I've followed a trace and determined the following. Some is loosely inferred.
The CustomErrorModule (in the IIS module stack) receives the SEND_RESPONSE notification.
The HttpStatus is 500, so it clones the context, sets a new URL (according the the matching custom error rule), and executes the request on this context (see ExecuteRequest).
The purpose of HttpContext.Items per documentation is:
Gets a key/value collection that can be used to organize and share
data between an IHttpModule interface and an IHttpHandler interface
during an HTTP request.
Viewing this function definition critically, of course, there is only "HTTP request". However, it seems likely that the Items dictionary is itself an item in a dictionary keyed on the HttpContext, which is a unique (cloned) reference in this executing child request. The trace shows the full pipeline (all modules, e.g. duplicate authentication) being run for this ExecuteURL, so this isolated context is of course required.
From unmanaged code, it is trivial to GetParentContext. However, from managed code this hierarchy is not available. So, I'm left without a way to retrieve the original Items.
As an alternate solution, it might be functional to leverage a Global.asax variable, since my tests showed the child request sharing an ApplicationInstance, but I'm not certain client access to this is necessarily sequential.
Another, possibly better approach, would be to avoid re-running the entire pipeline; to never exit the MVC handler (e.g. Controller.OnException and TransferToAction). However, this prevents implementing a Single-Point-of-Truth for error page configuration, since errors can also be raised outside of MVC's awareness.
I am using the approach below to handle 404 errors on my sites. This has worked for a long time but suddenly within the last month I am getting a "Handle is not initialized" exception with a number of sites on our dedicated server (some still work, and on development machine it works). Anyone have any thoughts?
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<httpErrors existingResponse="Replace">
<remove statusCode="500" subStatusCode="-1"/>
<remove statusCode="404" subStatusCode="-1"/>
<error statusCode="404" prefixLanguageFilePath="" path="/default.aspx" responseMode="ExecuteURL"/>
<error statusCode="500" prefixLanguageFilePath="" path="/error.aspx" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
On default.aspx page:
protected void Page_PreRender(object sender, EventArgs e)
{
if (!Page.IsPostBack && Request.Url.ToString().Contains("?404;"))
{
HttpContext.Current.RewritePath("~/");
Page.Header.Controls.AddAt(0, new LiteralControl("<base href='" + Request.Url.Scheme + "://" + Request.Url.Authority + "'/>"));
Response.StatusCode = 404;
Util.DisplayAlert("The page you are looking for no longer exists. If you navigated to this page by clicking a link within this site please <a href='/contact.aspx'>contact us</a> to let us know.");
}
}
Exception details:
Exception information:
Exception type: InvalidOperationException
Exception message: Handle is not initialized.
at System.Runtime.InteropServices.GCHandle.FromIntPtr(IntPtr value)
at System.Web.Hosting.PipelineRuntime.GetManagedPrincipalHandler(IntPtr pRootedObjects)
at System.Web.Hosting.UnsafeIISMethods.MgdGetPrincipal(IntPtr pHandler, IntPtr& pToken, IntPtr& ppAuthType, Int32& pcchAuthType, IntPtr& ppUserName, Int32& pcchUserName, IntPtr& pManagedPrincipal)
at System.Web.Hosting.IIS7WorkerRequest.GetUserPrincipal()
at System.Web.Hosting.IIS7WorkerRequest.SynchronizeVariables(HttpContext context)
at System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context)
I am having the same problem but haven't found a solution yet. I have confirmed that the problem is caused by setting the StatusCode or Status properties on the response, it affects IIS 7 and IIS Express but not Cassini which makes sense given the stack trace.
I'll keep investigating.
Edit: No luck in finding a solution. I posted the same question on the IIS forums: http://forums.iis.net/p/1187959/2016914.aspx#2016914
Edit 2: Confirmed as fixed in .NET 4.5 RC.
Levi: We have tracked down the root cause and are queuing a fix.
Andrew McLachlan Confirmed as fixed in .NET 4.5 RC
I have the following code in the web.config file for my ASP.NET C# app that's hosed on Azure:
<!-- Turn on Custom Errors -->
<!-- Switch the mode to RemoteOnly for Retail/Production -->
<!-- Switch the mode to On for to see error pages in the IDE during development -->
<customErrors mode="On" defaultRedirect="ErrorPage.aspx">
<error statusCode="403" redirect="ErrorPage403.aspx"/>
<error statusCode="404" redirect="ErrorPage404.aspx"/>
</customErrors>
This works great for errors when I'm hitting my site site natively (http://ipredikt.com/ErrorPage.aspx), but I also have a Facebook version of the app in which all of the pages use a different MasterPage and hence a different URL (http://ipredikt.com/ErrorPageFB.aspx).
Is it possible to modify the customError redirect values at runtime when I'm running in Facebook app mode, as if I had the following settings in web.config:
<customErrors mode="On" defaultRedirect="ErrorPageFB.aspx">
<error statusCode="403" redirect="ErrorPage403FB.apx"/>
<error statusCode="404" redirect="ErrorPage404FB.apx"/>
</customErrors>
I don't think I can set this at the Application scope since it's individual pages in my app that have knowledge of whether they are running in Facebook mode.
Hi I think what you can do is make another redirect inside your custom error page acording to the referrer - Request.UrlReferrer sometime the referrer is null so make sure you deal with that
So here's a brute force solution. I'm using this on the page for the non-Facebook mode 404 errors:
protected override void OnInit(System.EventArgs e)
{
// If the user tries, for example, to navigate to" /fb/foo/bar
// then the Request.Url.Query will be as follows after the 404 error: ?aspxerrorpath=/fb/foo/bar
string queryString = Request.RequestContext.HttpContext.Request.Url.Query;
string[] str = queryString.Split('=');
if (str.Length > 0)
{
string[] str2 = str[1].Split('/');
if (str2.Length > 1)
{
string test = str2[1].ToLowerInvariant();
if (test == "fb")
{
string pathAndQuery = Request.RequestContext.HttpContext.Request.Url.PathAndQuery;
string absolutePath = Request.RequestContext.HttpContext.Request.Url.AbsolutePath;
string mungedVirtualPath = pathAndQuery.Replace(absolutePath, "/ErrorPage404FB.aspx");
Response.Redirect(mungedVirtualPath);
}
}
}
base.OnInit(e);
}
Hardly ideal, but it works.
"Facebook mode" seems like something you could track in Session, which would be accessible in ErrorPage.aspx to trigger a transfer to ErrorPageFB.aspx.
Update - you can clean up your brute-force solution quite a bit by using Request.QueryString:
protected override void OnInit(System.EventArgs e)
{
// If the user tries, for example, to navigate to" /fb/foo/bar
// then the Request.Url.Query will be as follows after the 404 error: ?aspxerrorpath=/fb/foo/bar
var requestedPath = Request.RequestContext.HttpContext.Request.QueryString["aspxerrorPath"];
if (requestedPath.StartsWith("/fb/", StringComparison.OrdinalIgnoreCase))
{
var requestedUrl = Request.RequestContext.HttpContext.Request.Url;
var pathAndQuery = requestedUrl.PathAndQuery;
var absolutePath = requestedUrl.AbsolutePath;
var mungedVirtualPath = pathAndQuery.Replace(absolutePath, "/ErrorPage404FB.aspx");
Response.Redirect(mungedVirtualPath);
}
base.OnInit(e);
}
Does Request.RequestContext.HttpContext.Request actually return a different instance than simply Request?
The easy way to do it is using session but if you don't use session on you website you can always use cookie and when the user arrive to the error page examine the cookie and decide if you want to redirect him to a new error page
I need to handle 404 exceptions differently than all other types of them. What is the best way to identify those 404 exceptions (distinguish them from other exceptions)?
The problem is that there is no a special exception class for 404 errors, I get regular System.Web.HttpException with Message = "File does not exist."
Should I just use exception's message for it or is there a better way?
Thank you.
You can try to cast the exception as an HttpException, and then use the GetHttpCode method to check whether it is a 404 or not.
For example:
Exception ex = Server.GetLastError();
HttpException httpEx = ex as HttpException;
if (httpEx != null && httpEx.GetHttpCode() == 404)
{
//do what you want for 404 errors
}
I'd suggest that you configure your application to redirect 404 errors to a specific page, such as ~/FourOhFour.aspx. In this page you can inspect the aspxerrorpath querystring parameter, which will report the page the user was attempting to visit. From here you can do all sorts of interesting things, from logging the 404, to emailing yourself a message, to trying to determine the correct URL and auto-redirecting the user to that.
To configure your web application to redirect the user to a custom page in the face of a 404, add the following markup to web.config in the <system.web> section:
<customErrors mode="On" defaultRedirect="~/GeneralError.aspx">
<error statusCode="404" redirect="~/FourOhFour.aspx" />
</customErrors>
For more information, see:
<customErrors> element documentation
You can catch the exception. You're trying to catch this in a client application, correct?
HttpWebRequest req = ( HttpWebRequest )WebRequest.Create( someURL );
try
{
HttpWebResponse resp = req.GetResponse();
}
catch( WebException webEx )
{
if( webEx.Response != null )
{
HttpWebResponse response = webEx.Response as HttpWebResponse;
switch( response.StatusCode )
{
case HttpStatusCode.NotFound:
// do something
break;
In the Web.Config file you can specifiy a seperate File for each error code.
<customErrors mode="Off" defaultRedirect="GenericErrorPage.htm">
<error statusCode="404" redirect="FileNotFound.aspx" />
</customErrors>