Готовим ASP.NET Core: поговорим подробнее про OWIN и Katana

в 7:56, , рубрики: .net, #aspnetcolumn, ASP, ASP.NET, asp.net 5, asp.net core, Katana, OWIN, безопасность веб-приложений, Блог компании Microsoft, хостинг

Мы рады поделиться с вами очередной статьей из серии статей о платформе ASP.NET Core (ранее ASP.NET 5). В этот раз Вячеслав Бобик — .NET-разработчика из компании Radario, продолжит свой рассказ о платформе рассказом про применение технологий OWIN, Katana и связанные вопросы. Все статьи цикла вы всегда можете найти тут #aspnetcolumn — Владимир Юнев

Немного истории

В далекие времена, когда только появился ASP.NET MVC версии CTP, никто и не задумывался, о кроссплатформености, о том, что было бы здорово запускать приложения, написаные на этом фрейворке, не только на IIS, но и на другом вэб-сервере, и на другой ОС.

Готовим ASP.NET Core: поговорим подробнее про OWIN и Katana - 1

Со временем, богатство фреймворка ASP.NET MVC росло, росла и монолитная библиотека System.Web, от которой зависит фреймфорк и увеличивалась его сложность. В какой-то момент, а именно с четвертой версией, этот фрейворк стал довольно большим, почти прибитым гвоздями к IIS.

С другой стороны, был ASP.NET Web API, который не имел прямых зависимостей на событийную модейль IIS, и мог хостится самостоятельно без IIS(self-hosting). В какой-то момент к ребятам из Microsoft пришло понимание, того, что нужен инстумент позволяющий возможность запускать вэб-приложения написанные на .Net не только на IIS, но и на других вэб-серверах, а так же обеспечивать возможноть гибко встраиваться в процесс обработки запросов. В итогде появилась спецификация OWIN и проект Katana.

Начинаем работать с OWIN

Katana — это набор компонентов для создания и запуска вэб-приложений на hosting abstraction. Hosting abstraction — это OWIN. Главный интерфейс в OWIN называется application delegate или AppFunc.

using AppFunc = Func<IDictionary<string, object>, Task>;

Каждое OWIN приложение должно иметь Startup класс, в котором будет находится определение наших компонентов. Есть несколько способов сообщить о нахождении такого класса нашему приложению.

Первый — создать в нашем приложении класс Startup с методом Configuration, который принимает IAppBuilder

public class Startup {
  public void Configuration(IAppBuilder app) {
    app.Use(...);
    app.Use(...);
  }
}

С помощью таких вызовов app.Use(..) мы можем гибко конфигурировать наш процесс обработки запросов(pipeline).

Второй — пометить специальным аттрибутом:

[assembly: OwinStartup(typeof(App.TestApp))]

В ASP.NET Core поддержка OWIN выполнена на основе проекта Katana, с изменениями и дополнениями. Так IAppBuilderбыл заменен на IApplicationBuilder, но если вы работали с Katana вам не составить труда написать свой OWIN модуль.

Давайте как это принято во всем мире напишем простой hello world модуль. Создадим пустой ASP.NET Core проект. Ваш Startup класс должен выглядеть как-то так:

public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }
        public void Configure(IApplicationBuilder app)
        {
            // Add the platform handler to the request pipeline.
            app.UseIISPlatformHandler();
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }

Если ваш класс выглядит немного иначе, ничего страшного, в примере я использую ASP.NET 5 beta-8 (актуальная версия на момент написания статьи — ВЮ), в ранних версиях Startup можем выглядеть несколько по-другому. Так же установим nuget пакет Microsoft.AspNet.Owin. Для этого зайдем в project.json и в секции dependencies вставим.

Microsoft.AspNet.Owin" : "1.0.0-beta8"

Теперь для примера напишем небольшой метод:

public Task OwinHello(IDictionary<string, object> enviroment)
        {
            var responseText = "Hello via Owin";
            var responseBytes = Encoding.UTF8.GetBytes(responseText);
            var responseStream = (Stream)enviroment["owin.ResponseBody"];
            var responseHeaders = (IDictionary<string, string[]>)enviroment["owin.ResponseHeaders"];
            responseHeaders["Content-Length"] = new string[] { responseBytes.Length.ToString(CultureInfo.InvariantCulture) };
            responseHeaders["Content-Type"] = new string[] { "text/plain" };
            return responseStream.WriteAsync(responseBytes, 0, responseBytes.Length);
        }

и добавим вызов нашего метода OwinHello в метод Configure.

app.UseOwin(pipeline => pipeline(next => OwinHello));

Теперь наш Startup класс будет выглядеть так:

public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }
        public void Configure(IApplicationBuilder app)
        {
            // Add the platform handler to the request pipeline.
            app.UseIISPlatformHandler();
            app.UseOwin(pipeline => pipeline(next => OwinHello));

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World");
            });
        }
        public Task OwinHello(IDictionary<string, object> enviroment)
        {
            var responseText = "Hello Owin";
            var responseBytes = Encoding.UTF8.GetBytes(responseText);
            var responseStream = (Stream)enviroment["owin.ResponseBody"];
            var responseHeaders = (IDictionary<string, string[]>)enviroment["owin.ResponseHeaders"];
            responseHeaders["Content-Length"] = new string[] { responseBytes.Length.ToString(CultureInfo.InvariantCulture) };
            responseHeaders["Content-Type"] = new string[] { "text/plain" };
            return responseStream.WriteAsync(responseBytes, 0, responseBytes.Length);
        }
    }

Если сейчас запустить проект, то в браузере будет "Hello Owin". Возможно, у читателя может возникнуть вопрос: "А почему вывелась раза Hellow Owin без Hello World?". Все потому, что согласно спецификации, изменения хедеров, статус кода, тела запроса и т.д., возможны только до первой записи в тело запроса(response body stream).

Для доступа к внутренностям запроса как раз и используюся ключи owin.ResponseBody и owin.ResponseHeaders. Весь набор ключей так же можно смотреть в спецификации. С выполнением запроса разобрались, но у нас есть два разных метода, для использования owin, Run и Use, которые, на первый взгляд, делают одно и тоже. Это не совсем так.

Run — в соглашениях описывается так, что этим методом стоит пользоваться, только тогда, когда мы хотим добавить наш middleware в конец обработки запроса(pipeline), соответственно после Run ничего не будет вызвано.

Давайте теперь напишем что-нибудь посложнее, например компонент с простой базовой авторизацией. Ребята из команды ASP.NET для более лучших сложных случаев, рекомендуют оформлять middleware отдельным классом, а вызовы своих компонентов оформлять в виде метода расширения(extension method).

Создадим класс BasicAuthMiddleware с единственным методом Invoke

//Не пытайтесь повторить это в production
public class BasicAuthMiddleware
    {
        private readonly RequestDelegate _next;
        public BasicAuthMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            if (context.Request.Headers["Authorization"] == "Basic aGFicmE6aGFicg==")
            {
                context.Response.StatusCode = 200;
                await _next.Invoke(context);
            }
            else
            {
                context.Response.StatusCode = 401;
                context.Response.Headers.Add("WWW-Authenticate", "Basic realm="localhost"");
            }
        }
    }

Теперь напишем простой метод расширения. Создадим класс BasicAuthMiddlewareExtension

public static class BasicAuthMiddlewareExtension
    {
        public static IApplicationBuilder UseBasicAuth(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<BasicAuthMiddleware>();
        }
    }

Подключим наш компонент в Startup классе:

public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseIISPlatformHandler();

            app.UseBasicAuth();

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World");
            });
        }
    }

Теперь, если мы запусти наш проект, то мы должны увидеть окно для ввода логина и пароля:

Готовим ASP.NET Core: поговорим подробнее про OWIN и Katana - 2

Если мы введем верный логин — habra, пароль — habr, то увидим Hello World. Вот так, довольно не сложно можно написать свой middleware компонент.

Заключение

В данной статье мы познакомились с основами OWIN. Увидели насколько легко и просто подключать и создать свои компоненты, встраивать их в pipeline запроса, а так же написали свой модуль следуя рекомендациям Microsoft.

Авторам

Друзья, если вам интересно поддержать колонку своим собственным материалом, то прошу написать мне на vyunev@microsoft.com для того чтобы обсудить все детали. Мы разыскиваем авторов, которые могут интересно рассказать про ASP.NET и другие темы.

Готовим ASP.NET Core: поговорим подробнее про OWIN и Katana - 3

Об авторе

Бобик Вячеслав Борисович,
Разработчик .NET в Radario

Молодой .Net программист, с 3 годами опыта. Разработчик на ASP.NET MVC, автор приложений для Windows и Windows phone.

Автор: Microsoft

Источник

Поделиться новостью

* - обязательные к заполнению поля