На написание данной статьи-заметки меня сподвигла работа на формой обратной связи, в которой имелась возможность отправки файлов на сервер. Естественным образом захотелось ограничить размер загружаемых файлов со стороны сервера и выдавать пользователю соответствующее сообщение. Хорошая новость заключалась в том, что ASP.NET имеет встроенные средства для такого ограничения. Плохая – нет лёгких путей обработки данной ситуации.
Суть проблемы
В ASP.NET можно задать ограничение на размер запроса в web.config:
<system.web>
<httpRuntime maxRequestLength="1000"/>
Значение задаётся в килобайтах, по умолчанию – 4096 КБ. Очевидно, что для каждого location можно задавать свои ограничения.
Замечание: В IIS 7+ дополнительно существует своя возможность фильтрации запросов:
<system.webServer>
<security>
<requestFiltering>
<!--ограничение на размер запроса в байтах-->
<requestLimits maxAllowedContentLength="30000000"></requestLimits>
Т.ч. менять эти два параметра нужно в паре.
Стандартного способа перехвата и обработки исключения о превышении данного ограничения в ASP.NET нет. Первое, что приходит в голову – это ловить исключение в Global.asax обработчиком события Error. Но это оказалось не кошерным по 2-м причинам:
- Специального типа исключения нет, а выбрасывается общий System.Web.HttpException (обёрнутое, разумеется, в HttpUnhandledException) с сообщением «Maximum request length exceeded.» в английской локали. Т.ч. гарантированного способа получить нашего «клиента» не имеем.
- Опытным путём столкнулся с тем, что к моменту перехвата исключения сервер уже успевает отослать клиенту часть ответа, поэтому осложняется пользование объектом Response.
Решение
Решение подглядел тут. Но, сдаётся мне, что товарищ несколько перемудрил. У меня получилось проще и, на мой взгляд, точнее. В Global.asax в самом начале обработки запроса проверяем его размер и редиректим пользователя, если что на специально подготовленную страничку:
public override void Init()
{
base.Init();
this.BeginRequest += GlobalBeginRequest;
}
private void GlobalBeginRequest(object sender, EventArgs e)
{
var runTime = (HttpRuntimeSection)WebConfigurationManager.GetSection("system.web/httpRuntime");
var maxRequestLength = runTime.MaxRequestLength * 1024;
if (Request.ContentLength > maxRequestLength)
{
// или другой свой код обработки
Response.Redirect("~/filetoolarge/");
}
}
Пара пояснений.
Первое по поводу
var runTime = (HttpRuntimeSection)WebConfigurationManager.GetSection("system.web/httpRuntime");
Первая мысль была запомнить этот параметр в отдельное свойство приложения на стадии его старта. Но это не есть правильно, т.к. у разных директорий на сайте могут быть свои параметры.
Второе про
var maxRequestLength = runTime.MaxRequestLength * 1024;
Я так и не понял, зачем первоисточник пытался вычесть 100 КБ. Ограничение ASP.NET работает на весь запрос в совокупности (тестировалось на ASP.NET 2, .NET 3.5, IIS 6) вместе с данными формы и всеми прикрепленными файлами.
Ну, вот и всё, что я хотел рассказать по этому поводу. Хотя нет. Наверное, нужно добавить, что по-хорошему нужна клиентская валидация, в т.ч. и на размер файла. Для этого нужно использовать сторонние компоненты, т.к. встроенные браузерные контролы загрузки файлов – это отдельный порядочный головняк. Возможно, отдельные реализации умеют решать затронутую проблему комплексно.
Автор: neru