Поблочное тестирование не может быть запоздалой мыслью, это и должно быть что-то который факторы в к Вашему дизайну. Я сказал бы даже что, даже если Вы никогда не пишете или называете тест единого блока, преимущества, которыми он обладает в продвижении трудного компонента, управляемое программное обеспечение стоит усилия 95% времени.
В конечном итоге проблема была вызвана отсутствием различия между различными типами контекста, предоставляемыми традиционным приложением ASP.NET и приложением ASP.NET MVC. Обеспечив проверку для определения типа контекста, с которым я имел дело, я смог отреагировать соответствующим образом.
Я добавил отдельные методы для HttpTransfer и MvcTransfer, которые позволяют мне перенаправлять на страницу ошибки, особенно при необходимости. Я также изменил логику, чтобы я мог легко получить свой YSOD на моих локальных машинах и машинах разработки без того, чтобы обработчик проглотил исключение.
За исключением кода, используемого для регистрации исключения в базе данных (обозначенного комментарием TODO ), последний код, который мы используем:
using System;
using System.Net;
using System.Security.Principal;
using System.Web;
using System.Web.Configuration;
using System.Web.Mvc;
using Diagnostics;
/// <summary>
/// Provides a standardized mechanism for handling exceptions within a web application.
/// </summary>
public sealed class ErrorHandlerModule : IHttpModule
{
#region Public Methods
/// <summary>
/// Disposes of the resources (other than memory) used by the module that implements
/// <see cref="T:System.Web.IHttpModule"/>.
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Initializes a module and prepares it to handle requests.
/// </summary>
/// <param name="context">
/// An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events
/// common to all application objects within an ASP.NET application.</param>
public void Init(HttpApplication context)
{
context.Error += OnError;
}
#endregion
#region Private Static Methods
/// <summary>
/// Performs a Transfer for an MVC request.
/// </summary>
/// <param name="url">The URL to transfer to.</param>
/// <param name="currentContext">The current context.</param>
private static void HttpTransfer(string url, HttpContext currentContext)
{
currentContext.Server.TransferRequest(url);
}
/// <summary>
/// Performs a Transfer for an MVC request.
/// </summary>
/// <param name="url">The URL to transfer to.</param>
/// <param name="currentContext">The current context.</param>
private static void MvcTransfer(string url, HttpContext currentContext)
{
var uriBuilder = new UriBuilder(
currentContext.Request.Url.Scheme,
currentContext.Request.Url.Host,
currentContext.Request.Url.Port,
currentContext.Request.ApplicationPath);
uriBuilder.Path += url;
string path = currentContext.Server.UrlDecode(uriBuilder.Uri.PathAndQuery);
HttpContext.Current.RewritePath(path, false);
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(HttpContext.Current);
}
#endregion
#region Private Methods
/// <summary>
/// Called when an error occurs within the application.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private static void OnError(object source, EventArgs e)
{
var httpContext = HttpContext.Current;
var lastException = HttpContext.Current.Server.GetLastError().GetBaseException();
var httpException = lastException as HttpException;
var statusCode = (int)HttpStatusCode.InternalServerError;
if (httpException != null)
{
if (httpException.Message == "File does not exist.")
{
httpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
httpContext.ClearError();
return;
}
statusCode = httpException.GetHttpCode();
}
if ((statusCode != (int)HttpStatusCode.NotFound) && (statusCode != (int)HttpStatusCode.ServiceUnavailable))
{
// TODO : Your error logging code here.
}
var redirectUrl = string.Empty;
if (!httpContext.IsCustomErrorEnabled)
{
return;
}
var errorsSection = WebConfigurationManager.GetSection("system.web/customErrors") as CustomErrorsSection;
if (errorsSection != null)
{
redirectUrl = errorsSection.DefaultRedirect;
if (httpException != null && errorsSection.Errors.Count > 0)
{
var item = errorsSection.Errors[statusCode.ToString()];
if (item != null)
{
redirectUrl = item.Redirect;
}
}
}
httpContext.Response.Clear();
httpContext.Response.StatusCode = statusCode;
httpContext.Response.TrySkipIisCustomErrors = true;
httpContext.ClearError();
if (!string.IsNullOrEmpty(redirectUrl))
{
var mvcHandler = httpContext.CurrentHandler as MvcHandler;
if (mvcHandler == null)
{
try
{
HttpTransfer(redirectUrl, httpContext);
}
catch (InvalidOperationException)
{
MvcTransfer(redirectUrl, httpContext);
}
}
else
{
MvcTransfer(redirectUrl, httpContext);
}
}
}
#endregion
}
почему бы вам не поймать 404 в вашем global.asax?
protected void Application_Error(object sender, EventArgs args) {
var ex = Server.GetLastError() as HttpException;
if (ex != null && ex.ErrorCode == -2147467259) {
}
}
Если я правильно понимаю, вы хотите обрабатывать ошибки только для действий, которые приводят к 404?
Вы можете проверить, является ли маршрут для запроса нулевым или остановлен маршрутизацией - это по сути как работает обработчик маршрутизации URL-адресов, чтобы решить, следует ли продолжать запрос в конвейер mvc.
var iHttpContext = new HttpContextWrapper( httpContext );
var routeData = RouteTable.Routes.GetRouteData( iHttpContext );
if( routeData == null || routeData.RouteHandler is StopRoute )
{
// This is a route that would not normally be handled by the MVC pipeline
httpContext.ClearError();
return;
}
В стороне, перенаправление из-за ошибки 404 вызывает неидеальное взаимодействие с пользователем и является пережитком ASP.NET (где вы не могли » t отделить обработку просмотра от обработки запроса). Правильный способ управления ошибкой 404 - это вернуть в браузер код состояния 404 и отобразить страницу с пользовательской ошибкой, а не выполнять перенаправление (что приводит к отправке в браузер кода состояния 302).