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" вместо этого.