Сегодня обсудим, как на asp.net mvc можно настроить обработку ошибок 404, 500, ну и любых других. Рассмотрим на примере 404 и 500, как наиболее популярных и важных. Как вместо стандартного не очень красивого желтого окна ошибки показывать свои собственные красивые интересные страницы, и при этом как правильно отдавать код ошибки в браузер пользователя.
Казалось бы, задача довольно тривиальная и может быть решена написанием буквально пары строк кода. Действительно, так и есть, если вы используете любую популярную серверную технологию. Но только не ASP.NET. Если ваше приложение написано на ASP.NET MVC, и вы первый раз сталкиваетесь с проблемой обработки ошибок, очень легко запутаться и сделать неправильные настройки. Что впоследствии негативно отразится на продвижении сайта в поисковых системах, удобстве работы для пользователя, SEO-оптимизации.
Рассмотрим два подхода, как настроить страницы ошибок. Они в целом похожи, какой выбрать – решать вам.
Для начала вспомним, что означают наиболее популярные коды ошибок, которые отдает сервер.
Код ответа 200. Это значит что все ОК. Запрос клиента обработан успешно, и сервер отдал затребованные клиентом данные в полном объеме. Например, пользователь кликнул по гиперссылке, и в ответ на это в браузере отобразилась нужная ему информация.
Код ответа 404. Это означает, что запрошенный клиентом ресурс не найден на сервере. Например, указанная в адресе гиперссылки статья не найдена, или *.pdf файл был удален и теперь недоступен для скачивания.
Код ответа 500. Внутренняя ошибка на сайте. Что-то сломалось. Это может быть все что угодно, от неправильно написанного кода программистом, до отказа оборудования на сервере.
Допустим, мы только что создали новое веб-приложение типа MVC. На текущий момент, если никаких дополнительных действий для обработки ошибок не принимать, то стандартный сценарий обработки ошибок будет работать как нужно. В браузер пользователя будет отдаваться правильный код ошибки, пользователю будет показана стандартная страница с ошибкой и ее описанием.
Стандартная страница ошибки
Теперь займемся настройкой собственных страниц ошибок. При этом для нас важно не только показать пользователю красивую страницу ошибки, но также сохранить правильный код ответа сервера.
Вариант 1. Ссылка на статичные заранее подготовленные html-страницы.
Первым делом в файле web.config в разделе system.web добавляем новую секцию customErrors со следующими настройками:
web.config
<system.web>
<customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="~/404.aspx">
<error statusCode="404" redirect="~/404.aspx"/>
<error statusCode="500" redirect="~/500.aspx"/>
</customErrors>
...
</system.web>
Эта секция служит для обработки ошибок на уровне платформы ASP.NET.
Атрибут mode=»On» определяет, что пользовательские страницы ошибок включены. Также допустимы значения Off / RemoteOnly.
Атрибут redirectMode=»ResponseRewrite» определяет, следует ли изменять URL-адрес запроса при перенаправлении на пользовательскую страницу ошибки. Естественно, нам этого не нужно.
Атрибут defaultRedirect=»~/404.aspx» указывает на то, какая страница ошибки будет показана в случае возникновения кода ответа сервера, который мы не описали в настройках. Пусть при любых других ошибках пользователь будет думать, что страница не найдена.
И уже внутри этой секции мы определяем два кода, для которых у нас будут кастомные страницы ошибок.
Далее, как видно из настроек выше, нам понадобятся *.aspx файлы, на которые будет делаться редирект. Обратите внимание, что мы ссылаемся именно на *.aspx файлы, а не на *.html. Эти файлы являются проходными, служебными, в них содержатся настройки для ответа сервера. Содержимое файла 404.aspx:
404.aspx
<%@ Page Language="C#" %>
<%
var filePath = MapPath("~/404.html");
Response.StatusCode = 404;
Response.ContentType = "text/html; charset=utf-8";
Response.WriteFile(filePath);
%>
В коде выше мы указываем путь непосредственно до конечного *.html файла, а также дополняем настройки ответа сервера. Указываем код ответа, тип отдаваемого контента и кодировку. Если не указать кодировку, то браузер пользователя может интерпретировать ответ от сервера как не отформатированную строку, и, соответственно, не преобразует ее в html-разметку. А если не указать StatusCode = 404 , то получится следующая интересная ситуация:
Код ответа сервера отдается неверно
И хотя на рисунке выше нам показывается пользовательская страница с ошибкой, при этом код ответа 200 — это конечно же неверно. Когда-то давно на форумах Microsoft такое поведение зарепортили как баг. Однако Microsoft возразила, что это не баг, а фича и не стала ничего менять в будущих релизах ASP.NET. Поэтому приходится это исправлять вручную, и вручную в *.aspx файле в ответе сервера указывать код ответа 404.
Попробуйте собственноручно намеренно убрать какую-нибудь из объявленных на данный момент настроек из секции customErrors и понаблюдайте за результатом.
Также по аналогии создаем подобный *.aspx файл для ошибки 500.
И уже после этого нам нужно создать статичные html-файлы, соответственно для ошибок 404 и 500. Пусть они лежат в корне нашего проекта.
Статичные файлы расположены в корне проекта
Здесь же в файле web.config определяем раздел system.WebServer, если он еще не определен, и в нем объявляем секцию httpErrors:
web.config
<system.webServer>
<httpErrors errorMode="Custom" defaultResponseMode="File" defaultPath="c:\projects\mysite\404.html">
<remove statusCode="404" />
<remove statusCode="500" />
<error statusCode="404" path="404.html" responseMode="File" />
<error statusCode="500" path="500.html" responseMode="File" />
</httpErrors>
</system.webServer>
Эта секция служит для обработки ошибок на уровне сервера IIS. Суть в том, что иногда обработка запроса происходит непосредственно на уровне ASP.NET. А иногда ASP.NET просто определяет нужный код ответа и пропускает запрос выше, на уровень сервера. Такой сценарий может случиться, если, например, мы в действии контроллера возвращаем экземпляр класса HttpNotFound:
Или же когда система маршрутизации в MVC-приложении не может определить, к какому маршруту отнести запрошенный пользователем URL-адрес:
https://site.com/long/long/long/long/path
Для секции httpErrors важно отметить следующее. Так как мы ссылаемся на статичные *.html файлы, то и в качестве значений для нужных атрибутов здесь также указываем File . Для атрибута defaultPath необходимо указать абсолютный путь до файла ошибки. Относительный путь именно в этом месте работать не будет. Сам атрибут defaultPath определяет файл, который будет выбран для всех других ошибок, которые мы явно не указали. Но здесь есть одна небольшая проблема. Дело в том, что этот атрибут по умолчанию заблокирован на сервере IIS Express. Если вы разрабатываете свое приложение именно на локальном сервере, то это ограничение нужно снять. Для этого в директории своего проекта нужно найти файл конфигурации сервера и удалить этот атрибут из заблокированных, как это показано на рисунке:
Расположение файла applicationhost.config
Также проверьте папку App_Start. Если вы создали не пустое приложение, а работаете над реальным проектом, там может находиться класс FilterConfig, в котором регистрируются все глобальные фильтры в приложении. В методе регистрации удалите строчку кода, где регистрируется HandleErrorAttribute, в нашем случае он не понадобится.
Вот такой комплекс мер нужно предпринять, чтобы настроить обработку ошибок 404, 500, и любых других. Это настройки в файле web.config, и добавление в наш проект статичных файлов.
Вариант 2. Обработка ошибок с использованием специального контроллера.
Второй подход немного отличается от первого. Здесь нам не понадобится секция customErrors, так как обработку всех ошибок мы будем передавать сразу из приложения на уровень сервера, и он уже будет решать что делать. Можно удалить или закомментировать эту секцию в файле web.config.
Далее создадим специальный контроллер, который будет принимать все ошибки, которые мы хотим обрабатывать:
public class ErrorController : Controller
{
public ActionResult NotFound()
{
Response.StatusCode = 404;
return View();
}
public ActionResult Internal()
{
Response.StatusCode = 500;
return View();
}
}
Также создадим соответствующие представления с нужной нам красивой разметкой.
Также в файле web.config нам нужно изменить настройки в секции httpErrors. Если раньше мы ссылались на статичные html-файлы, то теперь мы будем обращаться по указанным URL, которые мы определили в ErrorController’е, чтобы именно там обрабатывать ошибки:
web.config
<httpErrors errorMode="Custom" existingResponse="Replace" defaultResponseMode="ExecuteURL" defaultPath="/Error/NotFound">
<remove statusCode="404"/>
<remove statusCode="500"/>
<error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL"/>
<error statusCode="500" path="/Error/Internal" responseMode="ExecuteURL"/>
</httpErrors>
Вариант 3. Фильтр HandleErrorAttribute
Замечу, что есть еще один способ взять под свой контроль обработку ошибок в приложении – это наследоваться от стандартного класса HandleErrorAttribute и написать свой фильтр. Но это уже более частный случай, когда нужно реализовать какую-то особенную логику при возникновении той или иной ошибки. В большинстве же более менее стандартных приложений наша проблема решается двумя выше описанными способами и в этом фильтре нет необходимости. Более подробную информацию, как работать с классом HandleErrorAttribute можно найти в официальной документации в интернете по этой ссылке.
Итого
Мы посмотрели на два разных подхода, которые можно применить при обработке ошибок на платформе ASP.NET. Опять же повторюсь, что если для вас не принципиально настраивать собственные страницы ошибок, то лучше не изменять эти настройки, так как даже одна упущенная деталь или неверно сконфигурированный параметр может очень сильно навредить репутации вашего сайта для конечного пользователя и в поисковых системах.
Время на прочтение
6 мин
Количество просмотров 26K
При разработке проекта на ASP.NET MVC возникла необходимость сделать собственную страницу ошибки 404. Я рассчитывал, что справлюсь с этой задачей за несколько минут. Через 6 часов работы я определил два варианта ее решения разной степени сложности. Описание — далее.
В ASP.NET MVC 3, с которой я работаю, появились глобальные фильтры. В шаблоне нового проекта, уже встроено ее использование для отображения собственной страницы ошибок (через HandleErrorAttribute). Замечательный метод, только он не умеет обрабатывать ошибки с кодом 404 (страница не найдена). Поэтому пришлось искать другой красивый вариант обработки этой ошибки.
Обработка ошибки 404 через Web.config
Платформа ASP.NET предоставляет возможность произвольно обрабатывать ошибки путем нехитрой настройки файла Web.config. Для это в секцию system.web нужно добавить следующий код:
<customErrors mode="On" >
<error statusCode="404" redirect="/Errors/Error404/" />
</customErrors>
При появлении ошибки 404 пользователь будет отправлен на страницу yoursite.ru/Errors/Error404/?aspxerrorpath=/Not/Found/Url. Кроме того, что это не очень красиво и удобно (нельзя отредактировать url), так еще и плохо для SEO — статья на habr.ru.
Способ можно несколько улучшить, добавив redirectMode=«ResponseRewrite» в customErrors:
<customErrors mode="On" redirectMode="ResponseRewrite" >
<error statusCode="404" redirect="/Errors/Error404/" />
</customErrors>
В данном случае должен происходить не редирект на страницу обработки ошибки, а подмена запрошенного ошибочного пути содержимым указанной страницы. Однако, и здесь есть свои сложности. На ASP.NET MVC этот способ в приведенном виде не работает. Достаточно подробное обсуждение (на английском) можно прочитать в топике. Кратко говоря, этот метод основан на методе Server.Transfer, который используется в классическом ASP.NET и, соответственно, работает только со статическими файлами. С динамическими страницами, как в примере, он работать отказывается (так как не видит на диске файл ‘/Errors/Error404/’). То есть, если заменить ‘/Errors/Error404/’ на, например, ‘/Errors/Error404.htm’, то описанные метод будет работать. Однако в этом случае не получится выполнять дополнительные действия по обработке ошибок, например, логирование.
В указанном топике было предложено добавить в каждую страницу следующий код:
Response.TrySkipIisCustomErrors = true;
Этот способ работает только с IIS 7 и выше, поэтому проверить этот метод не удалось — используем IIS 6. Поиски пришлось продолжить.
Танцы с бубном и Application_Error
Если описанный выше метод применить по каким-либо причинам не удается, то придется писать больше строк кода. Частичное решение приведено в статье.
Наиболее полное решение «с бубном» я нашел в топике. Обсуждение ведется на английском, поэтому переведу текст решения на русский.
Ниже приведены мои требования к решению проблемы отображения ошибки 404 NotFound:
- Я хочу обрабатывать пути, для которых не определено действие.
- Я хочу обрабатывать пути, для которых не определен контроллер.
- Я хочу обрабатывать пути, которые не удалось разобрать моему приложению. Я не хочу, чтобы эти ошибки обрабатывались в Global.asax или IIS, потому что потом я не смогу сделать редирект обратно на мое приложение.
- Я хочу обрабатывать собственные (например, когда требуемый товар не найден по ID) ошибки 404 в едином стиле.
- Я хочу, чтобы все ошибки 404 возвращали MVC View, а не статическую страницу, чтобы потом иметь возможность получить больше данных об ошибках. И они должны возвращать код статуса 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();
// Если путь относительный ('NotFound' route), тогда его нужно заменить на запрошенный путь
model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
Request.Url.OriginalString : url;
// Предотвращаем зацикливание при равенстве Referrer и Request
model.ReferrerUrl = Request.UrlReferrer != null &&
Request.UrlReferrer.OriginalString != model.RequestedUrl ?
Request.UrlReferrer.OriginalString : null;
// TODO: добавить реализацию ILogger
return View("NotFound", model);
}
public class NotFoundViewModel
{
public string RequestedUrl { get; set; }
public string ReferrerUrl { get; set; }
}
#endregion
}
Шаг 2: Используем собственный базовый класс для контроллеров, чтобы легче вызывать метод для ошибки 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)
{
// Если контроллер - ErrorController, то не нужно снова вызывать исключение
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 HttpException
Например, так (не обязательно использовать 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 для путей, которые не удалось определить нашему приложению
Этот маршрут должен вызывать действие Http404
. Обратите внимание, что параметр url
будет относительным адресом, потому что движок маршрутизации отсекает часть с доменным именем. Именно поэтому мы добавили все эти условные операторы на первом шаге.
routes.MapRoute("NotFound", "{*url}",
new { controller = "Error", action = "Http404" });
Это третье и последнее место в приложении MVC для отлова ошибок 404, которые Вы не вызываете самостоятельно. Если здесь не удалось сопоставить входящий путь ни какому контроллеру и действию, то MVC передаст обработку этой ошибки дальше платформе ASP.NET (в файл Global.asax). А мы не хотим, чтобы это случилось.
Шаг 5: Наконец, вызываем ошибку 404, когда приложению не удается что-либо найти
Например, когда нашему контроллеру Loan, унаследованному от MyController, передан неправильный параметр ID:
//
// 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);
}
Было бы замечательно, если бы можно было все это реализовать меньшим количеством кода. Но я считаю, что это решение легче поддерживать, тестировать, и в целом оно более удобно.
Библиотека для второго решения
Ну и на последок: уже готова библиотека, позволяющая организовать обработку ошибок описанным выше способом. Найти ее можно здесь — github.com/andrewdavey/NotFoundMvc.
Заключение
Интереса ради я посмотрел, как эта задача решена в Orchard. Был удивлен и несколько разочарован — разработчики решили вообще не обрабатывать это исключение — собственные страницы ошибок 404, на мой взгляд, давно стали стандартом в веб-разработке.
В своем приложении я использовал обработку ошибки через Web.config с использованием роутинга. До окончания разработки приложения, а останавливаться на обработке ошибки 404 довольно опасно — можно вообще приложение тогда никогда не выпустить. Ближе к окончанию, скорее всего, внедрю второе решение.
Ссылки по теме:
- ASP.NET, HTTP 404 и SEO.
- CustomErrors does not work when setting redirectMode=«ResponseRewrite».
- How can I properly handle 404 in ASP.NET MVC?
- Обработка ошибок для всего сайта в ASP.NET MVC 3.
- ASP.NET MVC 404 Error Handling.
- HandleUnknownAction in ASP.NET MVC – Be Careful.
- github.com/andrewdavey/NotFoundMvc.
In this post, we will explore ASP NET 404 error handling. The 404 error refers to a not-found resource/URL. The best way to handle the 404 NotFound error in ASP.NET MVC is to create a landing page for this specific error. In this post, I will show you the steps on how to create a 404 Error Handler. Here’s how a default 404 Error notification looks in ASP.NET MVC.
There are a lot of ways to handle 404 NotFound error and ASP NET 404 error handling is pretty straightforward. You can set the custom error mode to “On” from your project’s web.config then you can specify a redirect to a specific method to handle the error. You will understand more later about ASP NET 404 error handling and how an actual configuration will look like. Just follow the steps below.
Also Read: ASP.NET MVC Folder Structure
I. Configure Web.Config
To enable error redirection modify your Web.Config file.Search for <system.web> in your Web.Config and Add the code below inside the <system.web></system.web>.
<customErrors mode="On">
<error statusCode="404" redirect="~/Error/NotFound"/>
</customErrors>
II. Create ErrorController
Now, we need a controller that will handle a 404 error code and as specified on the redirect we set on the web.config what we need is an ErrorController and a NotFound method.
- Navigate to your Controller folder and create a controller named ErrorController.This will serve as our custom error handler.
- Inside this controller, create an action result method. Let’s named it NotFound this will return our custom view for 404 error.
The code snippet below is the controller and the method that I used.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace _404ErrorHandler.Controllers
{
public class ErrorController : Controller
{
public ActionResult NotFound()
{
return View();
}
}
}
III. Create a View
Now, all we need to do is to set a custom view for our NotFound action result. To do that, follow the steps below.
- By default, a view for the controller can be manually created inside the View->Error folder. Or you can do it by simply right-clicking on your NotFound method inside ErrorController and selecting Add View.
Below is the custom error view that I created for this tutorial. You can customize yours depends on your preference.
2. Let’s create a CSS stylesheet and add it using the bundle config. To add a bundle, you can copy the code below into your BundleConfig file where ~/Content/style.css is your stylesheet location, and ~/Content/Custom/Error is the name of your bundle.
Note: I used Bundle Config inside App_Start folder to call my custom style.css file.
Add(new StyleBundle("~/Content/Custom/Error").Include("~/Content/style.css"));
3. Then call this bundle using the code snippet below:
@Styles.Render("~/Content/Custom/Error")
4. Below are the codes from my NotFound view. You can copy the code snippet below or you create your own.
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
@Styles.Render("~/Content/Custom/Error")
<div class="w3l">
<div class="text">
<h1>PAGE NOT FOUND</h1>
<p>URL link can not be found. Please check the url or go to @Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" }) and log in using your credentials..</p>
</div>
<div class="image">
<img src="~/images/smile.png" />
</div>
<div class="clear"></div>
</div>
5. To add design to the view, we can also add icons. In my case, I used the image below. Which I included inside the image folder. And used <img src=”~/images/smile.png” /> to include image in my view.
6. Create styles for your view. In my case, I create a file name style.css inside the content folder from the project solution.
Style.css Source Code:
body {
font-family: 'Josefin Sans', sans-serif;
/*background: #2b82ad;*/
padding: 60px 80px 20px;
}
.clear{
clear:both;
}
h1 {
font-size: 70px;
text-align: left;
margin-bottom: 20px;
margin-top: 0px;
/*color: #ffffff;*/
font-weight: 700;
}
p {
font-weight: 600;
font-size: 20px;
text-align: left;
/*color: #ffffff;*/
line-height: 32px;
padding-top: 10px;
}
.agile {
width: 80%;
margin: 0 auto;
}
.image {
width: 30%;
float: left;
margin-top:40px;
}
.image img{
width:100%;
}
.text {
width: 64%;
float: left;
padding: 5em 0em 3em 0em;
}
.text a{
color:#000;
text-decoration:undrline;
}
.text a:hover{
color:#003b64;
text-decoration:underline;
}
.agileits_main_grid_left h1 {
font-size: 2.5em;
color: #000;
}
.agileits_main_grid_right {
float: right;
margin: 1.2em 0 0;
}
.agileits_main_grid_left {
float: left;
}
.back {
float: left;
}
.wthree {
background: #000;
padding: 1em;
}
.back a {
display: block;
font-size: 1.2em;
color: #fff;
text-decoration: none;
padding-top: 6px;
}
.w3l {
margin: 4em 0;
}
/*---- responsive-design -----*/
@media(max-width:1336px){
h1 {
font-size: 66px;
}
p {
font-size: 18px;
}
}
@media(max-width:1280px){
h1 {
font-size: 62px;
}
}
@media(max-width:1024px){
.agile {
width: 90%;
}
body {
padding: 53px 53px 20px;
}
h1 {
font-size: 56px;
}
.w3l {
margin: 1em 0;
}
}
@media(max-width:800px){
.agile {
width: 100%;
}
.text {
width: 70%;
padding: 3em 0em 3em 0em;
}
h1 {
font-size: 52px;
}
p {
font-size: 16px;
}
.footer {
padding: 1em 0em;
}
}
@media(max-width:768px){
}
@media(max-width:736px){
.footer p{
font-size:16px;
}
h1 {
font-size: 48px;
}
.agileits_main_grid_left h1 {
font-size: 2em;
}
.agileits_main_grid_right {
margin: 0.8em 0 0;
}
}
@media(max-width:667px){
.footer p{
font-size:15px;
}
h1 {
font-size: 43px;
}
.text {
padding: 3em 0em 1em 0em;
}
}
@media(max-width:640px){
h1 {
font-size: 40px;
}
p {
line-height: 26px;
}
.wthree {
background: #000;
padding: 0.8em;
}
}
@media(max-width:600px){
h1 {
font-size: 37px;
}
.footer p{
font-size:15px;
}
}
@media(max-width:568px){
p{
font-size:16px;
}
.footer p{
font-size:14px;
}
h1 {
font-size: 34px;
}
.back a {
font-size: 1em;
padding-top: 8px;
}
}
@media(max-width:480px){
body {
padding: 30px;
}
h1 {
font-size: 32px;
}
.text {
padding: 3em 0em 0em 0em;
}
}
@media(max-width:414px){
.text{
width:100%;
float: none;
}
.footer p{
font-size:14px;
}
.text {
padding: 2em 0em 0em 0em;
}
body {
padding: 24px;
}
.image {
width: 60%;
float: none;
margin: 16px auto;
}
.footer {
padding: 0em 0em;
}
}
@media(max-width:384px){
.footer p{
font-size:14px;
margin: 0px 0 10px;
}
.wthree {
padding: 0.5em;
}
}
@media(max-width:375px){
.footer p{
font-size:14px;
}
}
@media(max-width:320px){
body {
padding: 16px 16px;
}
.agileits_main_grid_left h1 {
font-size: 1.8em;
}
h1 {
font-size: 30px;
margin-bottom:0;
}
p {
font-size: 14px;
line-height:24px;
}
.footer p {
font-size: 14px;
padding-top: 0px;
}
.back {
float: none;
width: 100%;
text-align: center;
}
.social-icons {
float: none;
width: 100%;
text-align: center;
margin-top: 12px;
}
.back a {
padding-top: 0px;
}
.wthree {
padding: 0.8em;
}
.text {
padding: 1em 0em 0em 0em;
}
}
And we are done. That is how we can configure the ASP NET MVC 404 error handling. Suppose you have better ways to handle 404 NotFound error in ASP.NET MVC. Please comment down below. Every time a URL is invalid, this error notification will appear instead of the default 404 error notification.
Thank you for reading!! Happy coding!! Visit my Blog Page for my latest post.
Обработка ошибок
Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Последнее обновление: 06.11.2019
Ошибки в приложении можно условно разделить на два типа: исключения, которые возникают в процессе выполнения кода (например, деление на 0), и
стандартные ошибки протокола HTTP (например, ошибка 404).
Обычные исключения могут быть полезны для разработчика в процессе создания приложения, но простые пользователи не должны будут их видеть.
UseDeveloperExceptionPage
Если мы создаем проект ASP.NET Core, например, по типу Empty (да и в других типах проектов), то в классе Startup мы можем найти в начале метода Configure()
следующие строки:
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
Если приложение находится в состоянии разработки, то с помощью middleware app.UseDeveloperExceptionPage()
приложение перехватывает исключения и
выводит информацию о них разработчику.
Например, изменим класс Startup следующим образом:
public class Startup { public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async (context) => { int x = 0; int y = 8 / x; await context.Response.WriteAsync($"Result = {y}"); }); } }
В middleware app.Run симулируется генерация исключения при делении ноль. И если мы запустим проект, то в браузере мы увидим
информацию об исключении:
Этой информации достаточно, чтобы определить где именно в коде произошло исключение.
Теперь посмотрим, как все это будет выглядеть для простого пользователя. Для этого изменим метод Configure:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { env.EnvironmentName = "Production"; if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async (context) => { int x = 0; int y = 8 / x; await context.Response.WriteAsync($"Result = {y}"); }); }
Выражение env.EnvironmentName = "Production";
устанавливает режим развертывания вместо режима разработки. В этом случае выражение if (env.IsDevelopment())
будет возвращать false, и мы увидим в браузере что-то наподобие «HTTP ERROR 500»
UseExceptionHandler
Это не самая лучшая ситуация, и нередко все-таки возникает необходимость дать пользователям некоторую информацию о том, что же все-таки произошло. Либо потребуется как-то обработать данную ситуацию.
Для этих целей можно использовать еще один встроенный middleware в виде метода UseExceptionHandler(). Он перенаправляет
при возникновении исключения на некоторый адрес и позволяет обработать исключение. Например, изменим метод Configure следующим образом:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { env.EnvironmentName = "Production"; if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/error"); } app.Map("/error", ap => ap.Run(async context => { await context.Response.WriteAsync("DivideByZeroException occured!"); })); app.Run(async (context) => { int x = 0; int y = 8 / x; await context.Response.WriteAsync($"Result = {y}"); }); }
Метод app.UseExceptionHandler("/error");
перенаправляет при возникновении ошибки на адрес «/error».
Для обработки пути по определенному адресу здесь использовался метод app.Map()
. В итоге при возникновении исключения будет срабатывать делегат
из метода app.Map.
Следует учитывать, что оба middleware — app.UseDeveloperExceptionPage()
и app.UseExceptionHandler()
следует помещать ближе к началу конвейера middleware.
Обработка ошибок HTTP
В отличие от исключений стандартный функционал проекта ASP.NET Core почти никак не обрабатывает ошибки HTTP, например, в случае если ресурс не найден.
При обращении к несуществующему ресурсу мы увидим в браузере пустую страницу, и только через консоль веб-браузера мы сможем увидеть статусный код.
Но с помощью компонента StatusCodePagesMiddleware можно добавить в проект отправку информации о статусном коде.
Для этого добавим в метод Configure()
класса Startup вызов app.UseStatusCodePages()
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // обработка ошибок HTTP app.UseStatusCodePages(); app.Map("/hello", ap => ap.Run(async (context) => { await context.Response.WriteAsync($"Hello ASP.NET Core"); })); }
Здесь мы можем обращаться только по адресу «/hello». При обращении ко всем остальным адресам браузер отобразит базовую информацию об ошибке:
Данный метод позволяет настроить отправляемое пользователю сообщение. В частности, мы можем изменить вызов метода так:
app.UseStatusCodePages("text/plain", "Error. Status code : {0}");
В качестве первого параметра указывается MIME-тип ответа, а в качестве второго — собственно то сообщение, которое увидит пользователь. В сообщение мы можем
передать код ошибки через плейсхолдер «{0}».
Вместо метода app.UseStatusCodePages()
мы также можем использовать еще пару других, которые также обрабатываю ошибки HTTP.
С помощью метода app.UseStatusCodePagesWithRedirects()
можно выполнить переадресацию на определенный метод, который непосредственно обработает статусный код:
app.UseStatusCodePagesWithRedirects("/error?code={0}");
Здесь будет идти перенаправление по адресу «/error?code={0}». В качестве параметра через плейсхолдер «{0}» будет передаваться статусный код
ошибки.
Но теперь при обращении к несуществующему ресурсу клиент получит статусный код 302 / Found. То есть формально несуществующий ресурс будет существовать, просто статусный код 302
будет указывать, что ресурс перемещен на другое место — по пути «/error/404».
Подобное поведение может быть неудобно, особенно с точки зрения поисковой индексации, и в этом случае мы можем применить другой метод
app.UseStatusCodePagesWithReExecute():
app.UseStatusCodePagesWithReExecute("/error", "?code={0}");
Первый параметр метода указывает на путь перенаправления, а второй задает параметры строки запроса, которые будут передаваться при перенаправлении.
Вместо плейсхолдера {0} опять же будет передаваться статусный код ошибки. Формально мы получим тот же ответ, так как так же будет идти перенаправление на путь «/error?code=404». Но теперь браузер получит оригинальный статусный код 404.
Пример использования:
public void Configure(IApplicationBuilder app) { // обработка ошибок HTTP app.UseStatusCodePagesWithReExecute("/error", "?code={0}"); app.Map("/error", ap => ap.Run(async context => { await context.Response.WriteAsync($"Err: {context.Request.Query["code"]}"); })); app.Map("/hello", ap => ap.Run(async (context) => { await context.Response.WriteAsync($"Hello ASP.NET Core"); })); }
Настройка обработки ошибок в web.config
Еще один способ обработки кодов ошибок представляет собой определение и настройка в файле конфигурации web.config элемента
httpErrors. Этот способ в принципе использовался и в других версиях ASP.NET.
В ASP.NET Core он также доступен, однако имеет очень ограниченное действие. В частности, мы его можем использовать только при развертывании на IIS, а также не можем использовать ряд настроек.
Итак, добавим в корень проекта новый элемент Web Configurarion File, который естественно назовем web.config:
Изменим его следующим образом:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <httpErrors errorMode="Custom" existingResponse="Replace"> <remove statusCode="404"/> <remove statusCode="403"/> <error statusCode="404" path="404.html" responseMode="File"/> <error statusCode="403" path="403.html" responseMode="File"/> </httpErrors> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/> </handlers> <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/> </system.webServer> </configuration>
Также для обработки ошибок добавим в корень проекта новый файл 404.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Ошибка 404</title> </head> <body> <h1>Ошибка 404</h1> <h2>Ресурс не найден!</h2> </body> </html>
По аналогии можно добавить файл 403.html для ошибки 403.
Итак, элемент httpErrors имеет ряд настроек. Для тестирования настроек локально, необходимо установить атрибут errorMode="Custom"
.
Если тестирование необязательно, и приложение уже развернуто для использования, то можно установить значение errorMode="DetailedLocalOnly"
.
Значение existingResponse="Replace"
позволит отобразить ошибку по оригинальному запрошенному пути без переадресации.
Внутри элемента httpErrors с помощью отдельных элементов error устанавливается обработка ошибок. Атрибут statusCode
задает статусный код, атрибут path
— адрес url, который будет вызываться, а атрибут responseMode
указывает, как будет обрабатываться ответ вызванному url.
Атрибут responseMode
имеет значение File
, что позволяет рассматривать адрес url из атрибута path как статическую страницу и использовать ее в качестве ответа
Настройки элемента httpErrors
могут наследоваться с других уровней, например, от файла конфигурации machine.config
. И чтобы удалить
все унаследованные настройки, применяется элемент <clear />
. Чтобы удалить настройки для отдельных ошибок, применяется элемент
<remove />
.
Для тестирования используем следующий класс Startup:
public class Startup { public void Configure(IApplicationBuilder app) { app.Map("/hello", ap => ap.Run(async (context) => { await context.Response.WriteAsync($"Hello ASP.NET Core"); })); } }
И после обращения к несуществующему ресурсу в приложении отобразится содержимое из файла 404.html.
Handling 404 Not Found in Asp.Net Core
You might be surprised to find that the default asp.net core mvc templates do not handle 404 errors gracefully resulting in the standard browser error screen when a page is not found. This posts looks at the various methods for handling 404 not found errors in asp.net core.
The Problem
Without additional configuration, this is what a (chrome) user will see if they visit a URL that does not exist:
Fortunately, it is very simple to handle error status codes. We’ll cover three techniques below.
The Solution
In previous versions of Asp.Net MVC, the primary place for handling 404 errors was in the web.config.
You probably remember the <customErrors>
section which handled 404’s from the ASP.NET pipeline as well as <httpErrors>
which was lower level and handled IIS 404’s. It was all a little confusing.
In .Net core, things are different and there is no need to play around with XML config (though you can still use httpErrors in web.config if you are proxying via IIS and you really want to :-)).
There are really two different situations that we need to handle when dealing with not-found errors.
There is the case where the URL doesn’t match any route. In this situation, if we cannot ascertain what the user was looking for, we need to return a generic not found page. There are two common techniques for handling this but first we’ll talk about the second situation. This is where the URL matches a route but one or more parameter is invalid. We can address this with a custom view.
Custom Views
An example for this case would be a product page with an invalid or expired id. Here, we know the user was looking for a product and instead of returning a generic error, we can be a bit more helpful and return a custom not found page for products. This still needs to return a 404 status code but we can make the page less generic, perhaps pointing the user at similar or popular products.
Handling these cases is trivial. All we need to do is set the status code before returning our custom view:
public async Task<IActionResult> GetProduct(int id)
{
var viewModel = await _db.Get<Product,GetProductViewModel>(id);
if (viewModel == null)
{
Response.StatusCode = 404;
return View("ProductNotFound");
}
return View(viewModel);
}
Of course, you might prefer to wrap this up into a custom action result:
public class NotFoundViewResult : ViewResult
{
public NotFoundViewResult(string viewName)
{
ViewName = viewName;
StatusCode = (int)HttpStatusCode.NotFound;
}
}
This simplifies our action slightly:
public async Task<IActionResult> GetProduct(int id)
{
var viewModel = await _db.Get<Product,GetProductViewModel>(id);
if (viewModel == null)
{
return new NotFoundViewResult("ProductNotFound");
}
return View(viewModel);
}
This easy technique covers specific 404 pages. Let’s now look at generic 404 errors where we cannot work out what the user was intending to view.
Catch-all route
Creating a catch-all route was possible in previous version of MVC and in .Net Core it works in exactly the same way. The idea is that you have a wildcard route that will pick up any URL that has not been handled by any other route. Using attribute routing, this is written as:
[Route("{*url}", Order = 999)]
public IActionResult CatchAll()
{
Response.StatusCode = 404;
return View();
}
It is important to specify the Order to ensure that the other routes take priority.
A catch-all route works reasonably well but it is not the preferred option in .Net Core. While a catch-all route will handle 404’s, the next technique will handle any non-success status code so you can do the following (probably in an actionfilter in production):
public async Task<IActionResult> GetProduct(int id)
{
...
if (RequiresThrottling())
{
return new StatusCodeResult(429)
}
if (!HasPermission(id))
{
return Forbid();
}
...
}
Status Code Pages With Re Execute
StatusCodePagesWithReExecute is a clever piece of Middleware that handles non-success status codes where the response has not already started. This means that if you use the custom view technique detailed above then the 404 status code will not be handled by the middleware (which is exactly what we want).
When an error code such as a 404 is returned from an inner middleware component, StatusCodePagesWithReExecute allows you to execute a second controller action to handle the status code.
You add it to the pipeline with a single command in startup.cs:
app.UseStatusCodePagesWithReExecute("/error/{0}");
...
app.UseMvc();
The order of middleware definition is important and you need to ensure that StatusCodeWithReExecute is registered before any middleware that could return an error code (such as the MVC middleware).
You can specify a fixed path to execute or use a placeholder for the status code value as we have done above.
You can also point to both static pages (assuming that you have the StaticFiles middleware in place) and controller actions.
In this example, we have a separate action for 404. Any other non-success status code hits, the Error action.
[Route("error/404")]
public IActionResult Error404()
{
return View();
}
[Route("error/{code:int}")]
public IActionResult Error(int code)
{
// handle different codes or just return the default error view
return View();
}
Obviously, you can tailor this to your needs. For example, if you are using request throttling as we showed in the previous section then you can return a 429 specific page explaining why the request failed.
Conclusion
Handling specific cases of page not found is best addressed with a custom view and setting the status code (either directly or via a custom action result).
Handling more generic 404 errors (or in fact any non-success status code) can be achieved very easily by using the StatusCodeWithReExecute middleware. Together, these two techniques are the preferred methods for handling non-success HTTP status codes in Asp.Net Core.
By adding StatusCodeWithReExecute to the pipeline as we have done above, it will run for all requests but this may not be what we want all of the time. In the next post we will look at how to handle projects containing both MVC and API actions where we want to respond differently to 404’s for each type.
Useful or Interesting?
If you liked the article, I would really appreciate it if you could share it with your Twitter followers.
Share
on Twitter