Давайте рассмотрим основные принципы создания собственных модулей OWIN.
Создаем проект
В качестве сервера вновь будет использоваться IIS. Поэтому создадим проект OwinDemo на базе шаблона пустого ASP.NET веб-приложения (ASP.NET Empty Web Application).
Для того, чтобы была возможность запускать создаваемые модули установим OWIN для IIS:
PM> Install-Package Microsoft.Owin.Host.SystemWeb
Attempting to resolve dependency 'Owin (≥ 1.0)'.
Подождите… Устанавливается библиотека Owin? Но если OWIN это только спецификация, то откуда взялась dll и что в ней содержится? Сейчас разберемся.
Библиотека OWIN
Внутри сборки Owin.dll находится определение единственного интерфейса:
namespace Owin
{
public interface IAppBuilder
{
IDictionary<string, object> Properties { get; }
object Build(Type returnType);
IAppBuilder New();
IAppBuilder Use(object middleware, params object[] args);
}
}
IAppBuilder предназначен для создания конфигурации конвейера обработки запросов. При запуске веб-приложения с помощью параметра middleware метода Use() указываются используемые модули. В общем случае, выглядеть это будет так:
appBuilder.Use(authModule);
appBuilder.Use(staticHtmlModule, arguments);
appBuilder.Use(userModule, arg1, arg2);
Обратите внимание, что рассматриваемая сборка не содержит реализации IAppBuilder.
Точка конфигурирования конвейера
Реализация Katana базируется на соглашениях. Одно из них уже было упомянуто в предыдущей части: при запуске веб-приложения, Katana создает экземпляр класса Startup и вызывает метод Configuration(). В него передается экземпляр класса, реализующего IAppBuilder, что позволяет определить конфигурацию конвейера обработки запросов.
Создадим указанный класс в проекте OwinDemo (файл Startup.cs):
namespace OwinDemo
{
using Owin;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
}
}
}
Правила создания конвейера
Конвейер обработки запросов является реализацией шаблона "Цепочка ответственностей".
Модули получают пришедший запрос по очереди, порядок которой определяется порядком их регистрации в методе Configuration(). При этом в им передаются:
- делегат AppFunc, которому можно передать управление дальше по цепочке;
- словарь IDictionary<string, object> с данными запроса, окружения сервера и объектами для формирования ответа.
После получения данных каждый обработчик может:
- проигнорировать и передать управление дальше (пример: модуль WebAPI получил запрос на вывод страницы сайта);
- обработать и передать управление дальше (пример: ведение журнала запросов);
- обработать и завершить цепочку вызовов (пример: модуль WebAPI получил запрос к Контроллеру);
- сообщить о критической ошибке (исключении), тем самым прервав цепочку вызовов аварийно.
Легко заметить, что нет никакой гарантии обработки запроса хотя ��ы одним модулем. Поэтому появляется необходимость в обработчике по умолчанию. Katana вполне логично в этой роли использует класс NotFound, который всегда устанавливает код ответа равным 404 (Page not found).
Разумеется, в методе Configuration() можно определить свой обработчик по умолчанию:
appBuilder.Properties["builder.DefaultApp"] = defaultModule;
где defaultModule – модуль (класс или функция), который будет получать все необработанные запросы.
Очень важен порядок в котором зарегистрированы, а значит и будут вызываться, модули. Например, расположим блок записи в журнал в конце конвейера. Тогда запрос до него может просто не дойти, поскольку выполняющийся перед ним код может завершить цепочку вызовов или выбросить исключение. Поэтому подобные обработчики необходимо размещать одними из первых. И наоборот, есть обработчики, которые должны выполняться последними. Пример: упомянутый выше NotFound.
В следующей части перейдем к непосредственному написанию модулей.