Для создания небольшого обработчика не обязательно создавать класс. Иногда достаточно метода.
Давайте вместо возврата кода 404 выведем текст: "Извините, но запрошенный адрес не существует". Конечно, это не корректно для реального веб-приложения, но подойдет для тренировочной задачи.
Соглашения для создания функций-обработчика
Конечно, можно создать класс, как это было описано в прошлой части. Однако соглашения Katana позволяют задавать обработчики в виде функции Func<AppFunc, AppFunc>. Она получает делегат AppFunc следующего обработчика в цепочке (next) и должна создать и вернуть свой обработчик AppFunc. Для лучшего понимания сначала рассмотрим полный вариант записи вызова Use():
appBuilder.Use(new Func<AppFunc, AppFunc>(next =>
new AppFunc(env => {
/* Код обработчика */
return next(env); /* или возврат Task и завершение обработки запроса. */
})));
Благодаря возможностям C#, этот код можно записать несколько короче:
appBuilder.Use(new Func<AppFunc, AppFunc>(next =>
env => {
/* Код обработчика */
return next(env); /* или возврат Task и завершение обработки запроса. */
}));
Здесь env все тот же словарь с данными запроса, сервера и объектами для формирования ответа.
Рассматриваемое соглашение позволяет размещать несколько обработчиков в одном классе. Желательно чтобы при этом они были логически связанны. Выглядеть будет это так:
public class CoolModule
{
public Task HandleMain(IDictionary<string, object> env, AppFunc next) { ... }
public Task HandleAddon(IDictionary<string, object> env, AppFunc next) { ... }
}
...
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var coolModule = new CoolModule();
appBuilder.Use(new Func<AppFunc, AppFunc>(next =>
env => coolModule.HandleMain(env, next)));
appBuilder.Use(new Func<AppFunc, AppFunc>(next =>
env => coolModule.HandleAddon(env, next)));
}
}
Здесь сигнатура обработчиков несколько изменена, для передачи в них значения next. Но это возможно, т.к. напрямую они не вызываются и не попадают под действия соглашений Katana.
При разработке реального модуля создание coolModule и вызовы Use() можно разместить в методе-расширении интерфейса IAppBuilder. Тогда сам CoolModule можно не раскрывать и сделать internal. Кроме того, пользователь сможет подключать и настраивать модуль просто вызовом appBuilder.UseCoolAPI(agr):
public static class IAppBuilderExtensionsForCoolModule
{
public static void UseCoolAPI(this IAppBuilder appBuilder, int someAgr)
{
var coolModule = new CoolModule();
appBuilder.Use(new Func<AppFunc, AppFunc>(next =>
env => coolModule.HandleStatic(env, next)));
/* Тут может быть логика подключения разных обработчиков в зависимости от someAgr. */
appBuilder.Use(new Func<AppFunc, AppFunc>(next =>
env => coolModule.HandleCompression(env, next)));
}
}
Подобный подход можно использовать для группы модулей, каждый из которых является классом.
Но достаточно теории – перейдем к практике.
Пишем функцию обработчик
Разместим код создаваемого обработчика непосредственно в методе Configuration():
namespace OwinDemo
{
using System;
using System.Collections.Generic;
using System.Text;
using Owin;
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder.Use(typeof(LoggerModule), "Log Module >");
appBuilder.Use(new Func<AppFunc, AppFunc>(next =>
env => {
var headers = (IDictionary<string, string[]>)env["owin.ResponseHeaders"];
headers.Add("Content-Type", new[] { "text/plain; charset=utf-8" });
var outstream = (System.IO.Stream)env["owin.ResponseBody"];
var buffer = Encoding.UTF8.GetBytes("Извините, но запрошенный адрес не существует.");
return outstream.WriteAsync(buffer, 0, buffer.Length);
}));
}
}
}
Обратите внимание на порядок вызовов Use(). Как уже упоминалось, модули вызываются в том порядке, в котором они были зарегистрированы. Только что созданный обработчик всегда завершает цепочку вызовов. Поэтому если подключить его до LoggerModule, то последний никогда не будет вызван.
Сам код достаточно простой. В нем устанавливается тип содержимого ответа (просто текст) и кодировка (UTF8). После чего выводится заданная строка. Возможные оптимизации и обработка ошибок не приведены для упрощения примера. Также стоит отметить, что созданный код по прежнему полагается только на соглашения и не использует какие-либо внешние дополнительные библиотеки.
И в завершении еще один пример: заменим обработчик по умолчанию на свой. Однако, в этом случае вместо Func<AppFunc, AppFunc> необходим просто AppFunc, ведь передавать запрос дальше некуда:
namespace OwinDemo
{
using System.Collections.Generic;
using System.Text;
using Owin;
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder.Use(typeof(LoggerModule), "Log Module >");
appBuilder.Properties["builder.DefaultApp"] = new AppFunc(
env => {
var headers = (IDictionary<string, string[]>)env["owin.ResponseHeaders"];
headers.Add("Content-Type", new[] { "text/plain; charset=utf-8" });
var outstream = (System.IO.Stream)env["owin.ResponseBody"];
var buffer = Encoding.UTF8.GetBytes("Извините, но запрошенный адрес не существует.");
return outstream.WriteAsync(buffer, 0, buffer.Length);
});
}
}
}
Если запустить проект, то можно убедиться, что оба созданных модуля выполняют свои функции.
А дальше давайте посмотрим, как Katana помогает избежать создания "велосипедов".
Исходный код проекта (C#, Visual Studio 2012):
OwinDemo-part3-2.zip