Тут на конкурсе задали вопрос: чем отличаются Http Handlers и Http Modules. Ответов полно, дублировать их желания нет. Всегда можно открыть MSDN и почитать про это.
Вкратце, Handler, это обработчик запроса, например, когда обращаемся на /default.aspx, в web.config ищется подходящий обработчик и ему запрос отдаётся на растерзание. Естественно, если вы поглядите в web.config для вашего приложения, вы там скорее всего такого не найдёте. Но если вспомнить иеархию конфигруационных файлов в .NET, то становится ясно, что нужно искать в базовом web.config файле. У меня он находится тут: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config.
Этот зверь в 30Кб, полностью описывает стандартные настройки приложения, которые можно заменить в своём. Там как раз и есть обработчик aspx-файлов:
Собственно он и говорит рабочему процессу, что все подобные запросы должна обрабатывать фабрика с названием PageHandler (фабрика, это небольшой класс, который позволяет уже самостоятельно выбрать нужный Handler, в зависимости от более тонких условий). Также там можно найти другие интересные строчки:
...
<add path="*.soap" verb="*" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>
Например, эти говорят что *.config файлы нельзя показывать (например тот же web.config), а *.soap нужно для Remoting. Естественно, все эти правила можно заменить в своём приложении, да и при этом не забыть, что сам IIS по умолчанию не все расширения отдаёт ASP.NET, и надо добавить соответствующий фильтр в настройках вашего приложения (ну или глобальный, на все файлы).
Про Http Handler's почему-то не получилось коротко, но это я случайно. Дальше я расскажу более интересную вещь. А пока немножко про Http Modules.
Собственно, с ними всё просто, поскольку файл мы уже открыли и видим что за httpHandlers идут httpModules. Они-то и будут будут вызваны HttpApplication во время обработки запроса, при этом они могут сделать что-нибудь полезное не прерывая и не перенаправляя запрос. Например, разобраться с сессией, авторизацией или логгированием. Да мало ли чем, всегда их можно подключить и отключить. Например мы хотим фатальные ошибки приложения сразу отправлять админу по ICQ. Сделали модуль, подключили к нужным приложениям. Всё работает, красота.
Собственно с Handlers и Modules разобрались, переходим к задаче, которую можно элегантно решить с помощью стандартных хендлеров. Задача эта, достаточно частая и состоит в том, чтобы дать возможность пользователям скачивать файлы из нашего приложения, но при этом, как всегда файлы лежат непонятно где и непонятно с каким именем, да и права нужно бы проверить.
Я часто встречал реализацию в виде /getfile.ashx?id=123 — где собственный хендлер как-то пытается отдать файлы. Идея хорошая, но не очень красивая, к тому же приходится рисовать собственный хендлер, отдающий файлы, что как-то неправильно.
Собственно моя идея состоит в том, что нужно использовать существующий DefaultHttpHandler, который и предназначен для отдачи статики, только для этого придётся выполнить пару простых условий:
- Необходим .NET Framework 3.5 SP1, потому что в предыдущих реализация данного хендлера более примитивная.
- Файлы должны быть физически доступны из приложения, т.е. пользователь должен иметь теоретическую возможность их скачать (правда имя может быть любое и лежать они могут как хотят. Для запрета прямой скачки, делаем соответствующие правила в web.config)
Теперь, собственно, осталось придумать по какому пути пользователи будут обращаться к файлам и навесить на этот путь свой простенький хендлер, отнаследованный от DefaultHttpHandler. Можете использовать различные url-rewriting решения, или написать своё. Если вам приходится вешаться на aspx файлы, то можете в фабрике возвращать следующий Handler:
Т.е. практически вручную запустить обработку страницы (с некоторым оговорками, но не о них сейчас речь).
Далее, в своём хендлере делаем следующее:
- В методе BeginRequest определяем "настоящий" путь к файлу по запросу. Например, запрос, /123/myfile.jpg меняем на /files/storedFile_123.dat
- вызываем у контекста (вам его передадут) метод: context.RewritePath с полученным путём.
- Дальше, чтобы не отдавать запрос IIS (а для оптимизации DefaultHttphandler попробует это сделать), что-нибудь пишем в Response, например context.Response.Write(' ').
- Вызываем базовый метод BeginRequest
В общем-то всё. Мы подменили путь и сказали обработать его как следует. С докачкой, и прочими плюшками. Но небольшое уточнение, зачем же не стоит отдавать запрос IISу? На самом деле, в определённых случаях это нужно делать, ибо производительность будет выше, но IIS делает пару нехороший действий:
- Записывает в заголовки Content-Location с настоящим именем файла, т.е. хоть и не страшно, но мы светим то, как у нас хранятся файлы, т.е. неаккуратненько как-то
- Если он не знает расширения файла, то в целях безопасности может его и не отдать (Свойства сервера, типы MIME — тут указаны расширения, которые IIS знает)
Ещё можно обрабатывать ошибки, писать логи и статистику, но это уже тонкости и детали, главное, что достаточно быстро (гораздо быстрее чем читать этот пост ).
Вышесказанное относится к IIS 6.0, в седьмом, возможно всё будет ещё проще, но я с ним мало работал, и в основном в классическом режиме, так что не буду ничего утверждать заранее.