Готуємо ASP.NET Core: поговоримо докладніше про OWIN і Katana

Ми раді поділитися з вами черговій статті з серії статей про платформі ASP.NET Core (раніше ASP.NET 5). В цей раз В'ячеслав Бобик — .NET-розробника з компанії Radario, продовжить свою розповідь про платформі розповіддю про застосування технологій OWIN, Katana та пов'язані питання. Всі статті циклу ви завжди можете знайти тут #aspnetcolumn — Володимир Юнев
Трохи історії
В далекі часи, коли тільки з'явився ASP.NET MVC версії CTP, ніхто і не замислювався, про кроссплатформености, про те, що було б здорово запускати програми, написані на цьому фрейворке, не тільки на IIS, але і на іншому веб-сервері, і на інший ОС.
З часом, багатство фреймворку 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");
});
}
}

Тепер, якщо ми запусти наш проект, то ми повинні побачити вікно для введення логіна і пароля:

Якщо ми введемо вірний логін — habra, пароль — habr, то побачимо Hello World. Ось так, досить не складно можна написати свій
middleware
.

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

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

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

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

Ваш досвід з OWIN:

/>
/>


<input type=«radio» id=«vv71049»
class=«radio js-field-data»
name=«variant[]»
value=«71049» />
Вже використовую
<input type=«radio» id=«vv71051»
class=«radio js-field-data»
name=«variant[]»
value=«71051» />
Цікаво, уважно вивчаю
<input type=«radio» id=«vv71053»
class=«radio js-field-data»
name=«variant[]»
value=«71053» />
Зацікавлений, намагаюся розібратися
<input type=«radio» id=«vv71055»
class=«radio js-field-data»
name=«variant[]»
value=«71055» />
Поки ніякого

Проголосувало 63 людини. Утрималося 9 осіб.


Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.