OWIN и Katana. Часть 3.1 – Класс как модуль OWIN

Рассмотрим первый вариант создания модулей OWIN – в виде класса.

Соглашение для создания класса-модуля

В Katana нет интерфейса, который были бы должны реализовывать модули. Вместо него используется соглашение, что класс модуля должен иметь:

  • произвольное имя;
  • конструктор со следующими параметрами:
    • обязательный AppFunc, в который передается следующий обработчик в цепочке конвейера;
    • опциональные аргументы любого типа в любом количестве;
  • метод Task Invoke(IDictionary<string, object> environment), в котором должна содержаться вся логика модуля. В завершении работы необходимо:
    • или вызвать следующий обработчик;
    • или вернуть результат в виде экземпляра Task, тем самым завершив обработку запроса.

Также необходимо зарегистрировать класс в качестве модуля в методе Configuration() класса Startup. При запуске веб-приложения, Katana создаст его экземпляр, который и будет использоваться для обработки всех поступающих запросов.

Пример реализации модуля

Напишем простой модуль, который будет выводить отладочную информацию и не прерывать обработку запроса. Разумеется, основной целью будет показать создание класса, передачу в его конструктор параметров и обработку исключительных ситуаций.

Код будет выглядеть следующим образом (файл LoggerModule.cs):

namespace OwinDemo
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Threading.Tasks;

    using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;

    public class LoggerModule
    {
        private readonly AppFunc _next;
        private readonly string _prefix;

        public LoggerModule(AppFunc next, string prefix)
        {
            if (next == null)
                throw new ArgumentNullException("next");

            if (string.IsNullOrEmpty(prefix))
                throw new ArgumentException("prefix can't be null or empty");

            this._next = next;
            this._prefix = prefix;
        }

        public Task Invoke(IDictionary<string, object> env)
        {
            try {
                Debug.WriteLine("{0} Request: {1}", this._prefix, env["owin.RequestPath"]);
            }
            catch (Exception ex) {
                var tcs = new TaskCompletionSource<object>();
                tcs.SetException(ex);
                return tcs.Task;
            }

            return this._next(env);
        }
    }
}

Рассмотрим данный код подробнее:

  • Как и определено в соглашении, конструктор класса получает:
    • делегат next, представляющий собой следующий обработчик в цепочке;
    • дополнительный параметр prefix, который будет использоваться в отладочной строке.
  • Метод Invoke() содержит логику модуля. Ключи словаря env описаны в спецификации OWIN.
  • Блок try-catch здесь представлен для демонстрации обработки исключений. В случае их возникновения, они перехватываются и возвращаются в экземпляре класса Task. Это остановит обработку запросов, а клиент получит соответствующее сообщение. Для проверки работоспособности блока можно, например, внести ошибку в имя ключа owin.RequestPath.

Обратите внимание, что для создания модуля не потребовалось никаких дополнительных библиотек. Достаточно просто следовать заявленному соглашению.

Остается только зарегистрировать класс в цепочке обработки сообщений:

namespace OwinDemo
{
    using Owin;

    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            appBuilder.Use(typeof(LoggerModule), "Log Module >");
        }
    }
}

При этом обязательно передать значения параметров, которые требует конструктор класса мо��уля. В данном случае это строка "Log Module >" для prefix.

Можно запустить проект и убедиться в его работоспособности: в окне Output будут выводиться сообщения, начинающиеся со значения prefix и содержащие запрошенный путь. При этом, поскольку других модулей нет, сам запрос закончится выводом ошибки 404 (т.к. сработает модуль Katana по умолчанию – NotFound).

В следующей части посмотрим на еще один способ создания модулей.


Исходный код проекта (C#, Visual Studio 2012): OwinDemo-part3-1.zip

Комментарии (6) -

Дмитрий 03.10.2013 22:44:48

Сделал пробное консольное приложение. Использую для хостинга Microsoft.Owin.Host.HttpListener. В классе модуля OWIN есть следующий код:

        public Task Invoke(AppEnv env)

        {
            Task t = new Task(worker, env);
            t.Start();
            return t;
        }

        private void worker(object state)
        {
            AppEnv env = (AppEnv)state;

            Console.WriteLine("Начало: {0}", Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(10000);
            Console.WriteLine("Конец: {0}", Thread.CurrentThread.ManagedThreadId);

            OwinResponse response = new OwinResponse(env);
            response.Write("Test");
        }


Запускаю приложение. В браузере почти одновременно открываю 3 идентичные страницы. В итоге получаю следующий вывод:

Начало: 12
Конец: 12
Начало: 9
Конец: 9
Начало: 15
Конец: 15

Запросы обрабатываются последовательно, это видно и в браузере не вооруженным глазом - 10 секунд первая страница, еще через 10 - вторая, еще через 10 - третья.

Почему так происходит?

P.S. Андрей, спасибо за ваши старания.

@ Дмитрий: В данном коде не вижу ничего странного. Я даже попробовал его запустить из любопытства. Возможно проблема в другой части приложения.

Дмитрий 07.10.2013 3:54:40

@ Andrey:
Спасибо за ответ. Как оказалось, проблемы и не было - фаерфокс не хотел одновременно отсылать запросы с одинаковым url, как только в url на разных закладках были внесены небольшие изменения (чтобы фаерфокс не считал их одинаковыми) так все заработало как надо.

Сотлкнулся с еще одной проблемой. В модуле OWIN я хочу взаимодействовать с неуправляемыми ресурсами, и мне необходимо их освободить после завершения работы приложения. Ничего похожего кроме ключа окружения host.OnAppDisposing я не нашел. Интерфейс IDisposable для модуля OWIN тоже не помогает. Как определить момент для освобождения неуправляемых ресурсов для модуля OWIN?

@ Дмитрий: IMHO как правило, такие ресурсы лучше получать по необходимости и освобождать сразу после окончания работы с ними. Т.е. внутри Invoke.

Такой вопрос: могу ли я при помощи owin-модулей ловить глобальные эксепшны (как Application_Error в Global.asax)? Отслеживал Request и Response в объекте env, никакой информации об эксепшне не нашел, в то время как метод Application_Error ошибку словил..

@ massimo: как пример из самого OWIN-
katanaproject.codeplex.com/.../...ageMiddleware.cs

Если кратко, то первым в цепочке ставим middleware с
try {
    await _next(environment);
}
catch (Exception ex) {
    // тут разбираемся с ошибкой
}

Добавить комментарий