Как я могу правильно обрабатывать 404 в ASP.NET MVC?

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

Пусть он работает ночью, а на следующий день у вас будет 100% правильный список включенных файлов, которые вы можете удалить.

Иногда грубая сила работает: -)


edit: а иногда это не так :-). Вот немного информации из комментариев:

  1. Иногда вы можете удалить два файла заголовка отдельно, но не оба вместе. Решение состоит в том, чтобы удалить файлы заголовков во время прогона и не возвращать их обратно. Это найдет список файлов, которые можно безопасно удалить, хотя может быть решение с большим количеством файлов для удаления, которые этот алгоритм не найдет. (это жадный поиск по пространству включенных файлов для удаления. Он найдет только локальный максимум)
  2. Могут быть незначительные изменения в поведении, если у вас есть несколько макросов, переопределенных по-разному в зависимости от некоторых #ifdefs. Я думаю, что это очень редкие случаи, и тесты единиц, которые являются частью сборки, должны улавливать эти изменения.
423
задан Flimzy 31 May 2018 в 10:21
поделиться

4 ответа

Код взят от http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx и работает в ASP.net MVC 1.0 также

, Вот то, как я обрабатываю http исключения:

protected void Application_Error(object sender, EventArgs e)
{
   Exception exception = Server.GetLastError();
   // Log the exception.

   ILogger logger = Container.Resolve<ILogger>();
   logger.Error(exception);

   Response.Clear();

   HttpException httpException = exception as HttpException;

   RouteData routeData = new RouteData();
   routeData.Values.Add("controller", "Error");

   if (httpException == null)
   {
       routeData.Values.Add("action", "Index");
   }
   else //It's an Http Exception, Let's handle it.
   {
       switch (httpException.GetHttpCode())
       {
          case 404:
              // Page not found.
              routeData.Values.Add("action", "HttpError404");
              break;
          case 500:
              // Server error.
              routeData.Values.Add("action", "HttpError500");
              break;

           // Here you can handle Views to other error codes.
           // I choose a General error template  
           default:
              routeData.Values.Add("action", "General");
              break;
      }
  }           

  // Pass exception details to the target error View.
  routeData.Values.Add("error", exception);

  // Clear the error on server.
  Server.ClearError();

  // Avoid IIS7 getting in the middle
  Response.TrySkipIisCustomErrors = true; 

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData));
}
263
ответ дан Eduardo Molteni 31 May 2018 в 20:21
поделиться

Требования для 404

Ниже приведены мои требования для решения 404 и ниже Я показываю, как я это реализую:

  • Я хочу обрабатывать совпадающие маршруты с плохими действиями
  • Я хочу обрабатывать совпадающие маршруты с плохими контроллерами
  • Я хочу обрабатывать несовпадающие маршруты (произвольные URL-адреса, которые может ' я не понимаю) - я не хочу, чтобы они поднимались до Global.asax или IIS, потому что тогда я не смогу правильно перенаправить обратно в свое приложение MVC
  • Мне нужен способ обработки таким же образом, как указано выше , пользовательские 404-е - например, когда идентификатор отправляется для объекта, который не существует (возможно, удален)
  • Я хочу, чтобы все мои 404-е возвращали представление MVC (а не статическую страницу), на которое я могу перекачать больше данных позже, если это необходимо ( хороший дизайн 404 ) и они должны возвращать код состояния HTTP 404

Решение

Я думаю, вам следует сохранить Application_Error в Global.asax для более высоких таких как необработанные исключения и ведение журнала (например, ответ Шэя Якоби показывает), но не обработку 404. Вот почему я предлагаю убрать 404 из файла Global.asax.

Шаг 1: Найдите общее место для логики ошибок 404

Это хорошая идея для удобства сопровождения. Используйте ErrorController , чтобы можно было легко адаптировать будущие улучшения вашей хорошо продуманной страницы 404 . Кроме того, убедитесь, что ваш ответ содержит код 404 !

public class ErrorController : MyController
{
    #region Http404

    public ActionResult Http404(string url)
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        var model = new NotFoundViewModel();
        // If the url is relative ('NotFound' route) then replace with Requested path
        model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
            Request.Url.OriginalString : url;
        // Dont get the user stuck in a 'retry loop' by
        // allowing the Referrer to be the same as the Request
        model.ReferrerUrl = Request.UrlReferrer != null &&
            Request.UrlReferrer.OriginalString != model.RequestedUrl ?
            Request.UrlReferrer.OriginalString : null;

        // TODO: insert ILogger here

        return View("NotFound", model);
    }
    public class NotFoundViewModel
    {
        public string RequestedUrl { get; set; }
        public string ReferrerUrl { get; set; }
    }

    #endregion
}

Шаг 2: Используйте базовый класс Controller, чтобы вы могли легко вызывать свое настраиваемое действие 404 и подключать HandleUnknownAction

404 в ASP.NET MVC нужно перехватывать в нескольких местах. Первый - HandleUnknownAction .

Метод InvokeHttp404 создает обычное место для перенаправления на ErrorController и наше новое действие Http404 . Подумайте DRY !

public abstract class MyController : Controller
{
    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        // If controller is ErrorController dont 'nest' exceptions
        if (this.GetType() != typeof(ErrorController))
            this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

Шаг 3. Используйте внедрение зависимостей в фабрике контроллеров и подключите 404 HttpExceptions

Примерно так (это не обязательно должно быть StructureMap):

Пример MVC1.0:

public class StructureMapControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}

Пример MVC2.0:

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == 404)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }

Я думаю, что лучше выявлять ошибки ближе к тому месту, где они возникают. Вот почему я предпочитаю описанное выше обработчику Application_Error .

Это второе место, где можно поймать ошибку 404.

Шаг 4. Добавьте маршрут NotFound в Global.asax для URL-адресов, которые не удалось проанализировать в вашем приложении.

Этот маршрут должен указывать на наше действие Http404 . Обратите внимание, что параметр url будет относительным URL-адресом, потому что механизм маршрутизации отсекает здесь часть домена? Вот почему у нас есть вся эта логика условного URL-адреса на шаге 1.

        routes.MapRoute("NotFound", "{*url}", 
            new { controller = "Error", action = "Http404" });

Это третье и последнее место, где можно поймать 404 в приложении MVC, которое вы не вызываете сами.Если вы не поймаете здесь несовпадающие маршруты, тогда MVC передаст проблему ASP.NET (Global.asax), и вам это не нужно в этой ситуации.

Шаг 5: Наконец, вызовите 404, когда ваше приложение не может что-то найти

Например, когда в мой контроллер ссуд (получен из MyController ) отправляется неверный идентификатор:

    //
    // GET: /Detail/ID

    public ActionResult Detail(int ID)
    {
        Loan loan = this._svc.GetLoans().WithID(ID);
        if (loan == null)
            return this.InvokeHttp404(HttpContext);
        else
            return View(loan);
    }

Было бы неплохо, если бы все это можно было подключить в меньшем количестве мест с меньшим количеством кода, но я думаю, что это решение более удобное в обслуживании, более тестируемое и довольно прагматичное.

Спасибо за обратную связь. Я бы хотел получить больше.

ПРИМЕЧАНИЕ: это было значительно отредактировано по сравнению с моим исходным ответом, но цель / требования те же - вот почему я не добавил новый ответ

253
ответ дан 22 November 2019 в 23:18
поделиться

Мне очень нравится решение Коттсакса, и я думаю, что оно очень четко объяснено. Мое единственное дополнение - изменить шаг 2 следующим образом

public abstract class MyController : Controller
{

    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        //if controller is ErrorController dont 'nest' exceptions
        if(this.GetType() != typeof(ErrorController))
        this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

В основном это останавливает URL, содержащие некорректные действия И контроллеры, от запуска процедуры исключения дважды. Например, для таких URL, как asdfsdf/dfgdfgd

13
ответ дан 22 November 2019 в 23:18
поделиться

Единственный способ заставить метод @cottsak работать с недопустимыми контроллерами - это изменить существующий запрос маршрута в CustomControllerFactory, например:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType); 
            else
                return ObjectFactory.GetInstance(controllerType) as Controller;
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                requestContext.RouteData.Values["controller"] = "Error";
                requestContext.RouteData.Values["action"] = "Http404";
                requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);

                return ObjectFactory.GetInstance<ErrorController>();
            }
            else
                throw ex;
        }
    }
}

Я должен упомянуть, что использую MVC 2.0.

6
ответ дан 22 November 2019 в 23:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: