HttpContext.Current.Items cleared via responseMode="ExecuteURL"? - asp.net

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.

Related

How to create a completely empty (or close to it) response from asp.net?

I created an HttpModule to restrict access from certain IPs. I want to use as little bandwidth as possible and not return the default IIS 404 page. I would rather return no content at all.
private static void OnBeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
app.Response.StatusCode = 404;
app.Context.Response.Clear();
app.Context.Response.End();
}
Changing the status code to 200 works and create 0 bytes of content in the response but now I'm curious as to how to do it no matter the statuscode.
<customErrors mode="On" defaultRedirect="~/Error/Index.html">
<error statusCode="404" redirect="~/Error/404Error.html" />
</customErrors>
Add this to your web.config. And of course, adjust urls based on your project.

asp.net 5 404 handling

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?

How to redirect to a custom page if maxQueryStringLength exception occured?

How can I replace the standard error page in the case the request length exceeds the one specified in maxQueryStringLength and show the user something more friendly?
Note: Although as an HttpException this falls into generic 400th error, I want to separate the QueryLength condition and show a very specific page for this specific error. So, I cannot use "customErrors" section to indicate a page, but rather need to filter this programmatically. The problem with the below is it doesn't work.
protected virtual void Application_Error(Object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
if (logs != null) logs.ExceptionMsg("Application exception", this, ex);
var httpex = ex as System.Web.HttpException;
if (httpex != null && httpex.ErrorCode == -2147467259)
{
Server.ClearError();
Server.TransferRequest(#"~/main/maxlengthexceeded", false);
}
}
The problem is that Server.TransferRequest does not work. Is there an alternative to how I can tell ASP.NET which page to load?
if you are able to catch the error type/number you are getting, then you can configure a different error/redirect page only for that, here an example of configuration in the web.config:
<configuration>
<system.web>
<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly">
<error statusCode="500"
redirect="InternalError.htm"/>
</customErrors>
</system.web>
</configuration>
you can see the full article here: Displaying a Custom Error Page

How to override web.config customErrors settings at runtime?

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

Where do I catch and handle maxAllowedContentLength exceeded in IIS7?

I have an aspx page where I’m allowing a user to upload a file and I want to cap the max file upload size to be 10MB. IIS7, .NET 3.5. I have the following configured in my web.config file:
<location path="foo.aspx">
<system.web>
<!-- maxRequestLength: kbytes, executionTimeout:seconds -->
<httpRuntime maxRequestLength="10240" executionTimeout="120" />
<authorization>
<allow roles="customRole"/>
<!-- Deny everyone else -->
<deny users="*"/>
</authorization>
</system.web>
<system.webServer>
<security>
<requestFiltering>
<!-- maxAllowedContentLength: bytes -->
<requestLimits maxAllowedContentLength="10240000"/>
</requestFiltering>
</security>
<handlers accessPolicy="Read, Script">
<add name="foo" path="foo.aspx" verb="POST"
type="System.Web.UI.PageHandlerFactory"
preCondition="integratedMode" />
</handlers>
</system.webServer>
</location>
I have a custom error handling module that implements IHttpModule. I’ve found that when maxRequestLength is exceeded, HttpApplication.Error does indeed get raised. However when I play with maxAllowedContentLength, the HttpApplication.Error event isn’t being raised and the user gets redirected to a 404.13 page. I've attached with Visual Studio with first chance exceptions turned on nothing is being thrown.
My first thought is to check the header content length in an earlier event – are there recommendations/best practices of where I do this? PostLogRequest? EndRequest?
After looking at the ASP.NET Application Life Cycle Overview for IIS 7.0 and doing my own experimentation, I'm assuming that the request validation is done internally by IIS before any of the events are raised.
It looks like only LogRequest, PostLogRequest, EndRequest, PreSendRequestContent, and PreSendRequestHeaders are raised after the internal validation with this error.
I've decided to attach an event handler to the HttpApplication.EndRequest event in my custom error handler and check for the 404.13 status code on POST and handle as I need it to be handled, which in my case is to redirect to the calling page which will check Server.GetLastError() and display a friendly error to the end user.
private void application_EndRequest(object sender, EventArgs e)
{
HttpRequest request = HttpContext.Current.Request;
HttpResponse response = HttpContext.Current.Response;
if ((request.HttpMethod == "POST") &&
(response.StatusCode == 404 && response.SubStatusCode == 13))
{
// Clear the response header but do not clear errors and
// transfer back to requesting page to handle error
response.ClearHeaders();
HttpContext.Current.Server.Transfer(
request.AppRelativeCurrentExecutionFilePath);
}
}
I'd welcome feedback on this approach and alternatives.
The simplest method is to handle it in the OnError method of page itself.
I think this works only in .NET 4.0, since the WebEventCode property is documented as NEW in .NET 4.0.
protected override void OnError(EventArgs e)
{
Exception err = Server.GetLastError();
if (err is HttpException)
{
if ((err as HttpException).WebEventCode == 3004)
{
Context.Items["error"] = "File exceeded maximum allowed length.";
Server.Transfer( Context.Request.Url.LocalPath );
return;
}
}
base.OnError(e);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!IsPostBack)
{
string error = Context.Items["error"] as string;
if (!string.IsNullOrEmpty( error ))
showErrorMessage( error );
}
}
What I did was:
get the last error with Server.GetLastError
check that it's a "Maximum request length exceeded." error (WebEventCode == 3004).
added a value to Context.Items collections to flag the request as an error
transfer the request back to the page itself with Server.Transfer(Context.Request.Url.LocalPath)
the page's OnLoad method checks for the error flag and displays a message if present
This ensures the error is handled entirely on the requested page, and the page is capable of reporting errors.
Also note that while the browser will eventually receive a proper response, the browser may take its time uploading the entire request before it handles the server's response and displays it. This behavior is probably defined as part of the server/browser interaction in the HTTP protocol, so probably not much can be done about that.

Resources