HttpContext.Current.Items cleared via responseMode=“ExecuteURL”?

眉间皱痕 提交于 2019-12-10 04:52:49

问题


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;
    }
}

回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/22663322/httpcontext-current-items-cleared-via-responsemode-executeurl

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!