HttpContext.Current.Items очищается с помощью responseMode = "ExecuteURL"?

Я избегаю подхода ASP.NET по умолчанию для перенаправления на ошибки (как это делают многие люди). Чистый код AJAX и SEO являются одними из причин.

Однако, я использую следующий метод для этого, и кажется, что я могу потерять HttpContext.Current.Items в передаче?

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

Я предположил, что он просто выполнил a Server.Transfer() под обложками, которые я понимаю, сохраняет Items. (См.: Область HttpContext.Current.Items и http://weblog.west-wind.com/posts/2010/Jan/20/HttpContextItems-and-ServerTransferExecute)

Но я также захватываю что-то в Items перед "ExecuteURL" и извлекаю/выводил его после передачи (или что-то еще), и, похоже, он исчезает. Я наблюдал, как он входит в коллекцию Items, я вижу, что Count raise до 5, а затем, когда значение извлекается, в коллекции есть только 2 элемента.

Что происходит?


Если вы хотите больше узнать о том, что я делаю, и рекомендовать альтернативную реализацию, я открыт для нее. Я использую это, чтобы вставить идентификатор ошибки ELMAH в ViewModel таким образом, чтобы он был свободен от условий гонки. (т.е. общий обходной путь для этого, который я заменяю, состоит в том, чтобы просто отображать самую последнюю ошибку.) Здесь мой код:

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

Я следил за трассировкой и определял следующее. Некоторые из них свободно выводятся.

Модуль CustomErrorModule (в стеке модулей IIS) получает уведомление SEND_RESPONSE.

HttpStatus - 500, поэтому он клонирует контекст, устанавливает новый URL-адрес (в соответствии с соответствующим пользовательским правилом ошибок) и выполняет запрос в этом контексте (см. ExecuteRequest).

Цель HttpContext.Items для документации:

Получает коллекцию ключей/значений, которая может использоваться для организации и совместного использования данные между интерфейсом IHttpModule и интерфейсом IHttpHandler во время HTTP-запроса.

Критическое рассмотрение этого определения функции, конечно, есть только "HTTP-запрос". Однако представляется вероятным, что словарь Items сам по себе является элементом в словаре, на который ссылается HttpContext, который является уникальной (клонированной) ссылкой в ​​этом исполняемом дочернем запросе. Трассировка показывает полный конвейер (все модули, например дублируемая аутентификация) для этого ExecuteURL, поэтому этот изолированный контекст, конечно, необходим.

Из неуправляемого кода тривиально GetParentContext. Однако из управляемого кода эта иерархия недоступна. Итак, я остался без способа получить исходный Items.

В качестве альтернативного решения может быть функционально использовать переменную Global.asax, так как мои тесты показали, что дочерний запрос имеет общий доступ к ApplicationInstance, но я не уверен, что клиентский доступ к этому обязательно является последовательным.

Другим, возможно, лучшим подходом было бы избежать повторного запуска всего конвейера; никогда не выходить из обработчика MVC (например, Controller.OnException и TransferToAction). Однако это предотвращает реализацию конфигурации с одной точкой правды для конфигурации страницы ошибки, поскольку ошибки также могут быть подняты за пределами осведомленности MVC.

Ответ 2

Как объяснено здесь, ExecuteURL генерирует два запроса: первый генерирует исключение, а второй генерирует ответ об ошибке.

Так как Context.Items очищается между запросами, ваш код всегда видит созданный 2-й запрос, следовательно, разницу между элементами.

Попробуйте sugestion в сообщении: используйте system.web > customErrors с redirectMode = "ResponseRewrite" вместо этого.