-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgetting-started-with-katana.html
418 lines (374 loc) · 68.1 KB
/
getting-started-with-katana.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
<!DOCTYPE html>
<html lang="ru">
<head>
<title>tkirill's blog</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="./theme/css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="./theme/css/blog.css" />
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-49219502-1', 'tkirill.org');
ga('send', 'pageview');
</script>
<link rel="stylesheet" type="text/css" href="./theme/css/pygments-friendly.css" />
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<h1><a href=".">tkirill's blog</a></h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<hr>
</div>
</div>
<div class="row">
<div class="col-md-8">
<p class="article-date">
Ср. 12 Март 2014
</p>
<h2>
<a href="./getting-started-with-katana.html" rel="bookmark" title="Permalink to Приступая к работе с Katana Project">Приступая к работе с Katana Project</a>
</h2>
<div class="article-content">
<p>В 2002, когда впервые был выпущен <span class="caps">ASP</span>.<span class="caps">NET</span>, времена были другие. Интернет всё ещё был в относительно младенческом состоянии, около 569 миллионов пользователей проводили в среднем 46 минут в день на приблизительно 3 миллионах сайтов. Те же самые измерения, проведённые всего десятью годами позже, показывают приблизительно 2.27 миллиардов пользователей, в среднем тратящими ежедневно 4 часа на 555 миллионах сайтов (см. <a href="http://bit.ly/MY7GzO">bit.ly/MY7GzO</a>).</p>
<p>Этот рост, конечно же, привёл к соответствующим изменениям в потребностях разработчиков в смысле фреймворков, инструментов и рантаймов, которые они используют для разработки и запуска веб-приложений. Современные веб-приложения должны развиваться быстро, используя фишки из множества разных компонентов и фреймворков, а также использовать малое количество ресурсов, чтобы эффективно работать в больших облачных системах.</p>
<p>Главным мотивом для Katana Project является обеспечение того, чтобы <span class="caps">ASP</span>.<span class="caps">NET</span> мог ответить этим текущим и будущим потребностям.</p>
<h2>Что такое Katana?</h2>
<p>Проект Katana на самом деле были начат не в Microsoft, а в open-source проекте Open Web Interface for .<span class="caps">NET</span> (<span class="caps">OWIN</span>), спецификации, которая определяет взаимодействие между веб-сервером и компонентами приложения (см. <a href="http://owin.org">owin.org</a>). Так как цель спецификации – стимулировать обширную и живую экосистему .<span class="caps">NET</span>-серверов и программных компонентов, то всё взаимодействие между сервером и приложением сводится к небольшому набору типов и единственной функции, известной как application delegate, или AppFunc:</p>
<div class="highlight"><pre><span></span><span class="k">using</span> <span class="nn">AppFunc</span> <span class="p">=</span> <span class="n">Func</span><span class="p"><</span><span class="n">IDictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">>,</span> <span class="n">Task</span><span class="p">>;</span>
</pre></div>
<p>Каждый компонент <span class="caps">OWIN</span>-приложения предоставляет серверу application delegate. Затем компоненты сцепляются вместе в конвеер, в который <span class="caps">OWIN</span>-сервер посылает запросы. Все компоненты в конвеере должны быть асинхронными, чтобы эффективно использовать ресурсы, и это отражено в application delegate, возвращающим Task.</p>
<p>Все состояния, включая состояния приложения, запроса, сервера и так далее, содержатся в <code>IDictionary<string, object></code>, передаваемом в application delegate. Эта структура данных, известная как environment dictionary, передаётся от компонента к компоненту по ходу того, как запрос проходит через конвеер. Не отменяя того, что любые key/value данные могут быть вставлены в environment dictionary, спецификация <span class="caps">OWIN</span> определяет ключи для некоторых ключевых элементов <span class="caps">HTTP</span>.</p>
<table>
<thead>
<tr>
<th>Ключ</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr>
<td>“owin.RequestBody”</td>
<td><code>Stream</code>-объект с телом запроса, если оно есть. В случае, когда его нет, в качестве заглушки используется <code>Stream.Null</code>.</td>
</tr>
<tr>
<td>“owin.RequestHeaders”</td>
<td><code>IDictionary<string, string[]></code> с заголовками запроса.</td>
</tr>
<tr>
<td>“owin.RequestMethod”</td>
<td>Строка, содержащая <span class="caps">HTTP</span>-метод запроса (например, <span class="caps">GET</span> или <span class="caps">POST</span>).</td>
</tr>
<tr>
<td>“owin.RequestPath”</td>
<td>Строка, содержащая путь запроса. Путь должен указываться относителено “корня” application delegate.</td>
</tr>
<tr>
<td>“owin.RequestPathBase”</td>
<td>Строка, содержащая часть пути запроса, соответствующую “корню” application delegate.</td>
</tr>
<tr>
<td>“owin.RequestProtocol”</td>
<td>Строка, содержащая название и версию протокола (например, <span class="caps">HTTP</span>/1.0 или <span class="caps">HTTP</span>/1.1).</td>
</tr>
<tr>
<td>“owin.RequestQueryString”</td>
<td>Строка, содержащая query string из урла <span class="caps">HTTP</span>-запроса, без “?” в начале (например, foo=bar&baz=quux). Может быть пустой строкой.</td>
</tr>
<tr>
<td>“owin.RequestScheme”</td>
<td>Строка, содержащая схему <span class="caps">URI</span>, использующуюся в запросе (например, <span class="caps">HTTP</span> or <span class="caps">HTTPS</span>).</td>
</tr>
</tbody>
</table>
<p>Закрепление базового набора key-value пар в environment dictionary даёт авторам множества разных фреймворков и компонентов возможность взаимодействовать внутри конвеера <span class="caps">OWIN</span> без навязывания соглашений о строгой модели .<span class="caps">NET</span>-объекта, вроде HttpContextBase в <span class="caps">ASP</span>.<span class="caps">NET</span> <span class="caps">MVC</span> или HttpRequestMessage/HttpResponseMessage in <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span>.</p>
<p>Эти два элемента — application delegate и environment dictionary — и образуют спецификацию <span class="caps">OWIN</span>. Katana — это набор <span class="caps">OWIN</span>-компонентов и фреймворков, созданных и распростроняемых Microsoft.</p>
<p>Компоненты Katana можно отобразить в виде стека:</p>
<p><img alt="Katana architectural stack" src="http://i.msdn.microsoft.com/dn451439.Dierking_Figure2_hires%28en-us,MSDN.10%29.png" /></p>
<p>Стек состоит из следующих слоёв:</p>
<dl>
<dt>Host</dt>
<dd>Процесс, который запускает приложение и который может быть чем угодно: от <span class="caps">IIS</span> или отдельного исполняемого файла до вашей единственной и неповторимой программы. Host отвечает за запуск, загрузку остальных компонентов <span class="caps">OWIN</span> и корректную остановку.</dd>
<dt>Server</dt>
<dd>Ответственен за привязку к <span class="caps">TCP</span>-порту, создание environment dictionary и передачу запросов в <span class="caps">OWIN</span>-конвеер.</dd>
<dt>Middleware</dt>
<dd>Под это название попадают все компоненты, обрабатывающие запросы в <span class="caps">OWIN</span>-конвеере. Middleware может разнится от несложного компонента для сжатия до целого фреймворка вроде <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span>, хотя с точки зрения сервера это всегда будет ни больше ни меньшев как просто компонент, который предоставляет application delegate.</dd>
<dt>Application</dt>
<dd>Это ваш код. Так как Katana — это не замена для <span class="caps">ASP</span>.<span class="caps">NET</span>, а новый способ объединять и запускать компоненты, то существующие <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> и SignalR приложения останутся без изменений, эти фреймворки умеют встраиватся в конвеер <span class="caps">OWIN</span>. Фактически, для таких приложений Katana будет видна всего лишь как небольшой конфигурационный класс.</dd>
</dl>
<p>Архитектурно Katana разделена так, что каждый слой может быть легко заменён, часто без перекомпиляции кода. При обработке <span class="caps">HTTP</span>-запроса слои работают вместе примерно так:</p>
<p><img alt="katana-data-flow" src="http://i.msdn.microsoft.com/dn451439.Dierking_Figure3_hires(en-us,MSDN.10).png" /></p>
<h2>Создание современного веб-приложения с Katana</h2>
<p>Современные веб-приложения зачастую используют 4 вещи:</p>
<ol>
<li>Серверная генерация разметки</li>
<li>Раздача статичных файлов</li>
<li>Web <span class="caps">API</span> для обработки <span class="caps">AJAX</span> запросов</li>
<li>Messaging в реальном времени</li>
</ol>
<p>Для того, чтобы создать приложение со всеми этими возможностями, требуется несколько разных фреймворков, специально приспособленных для соответствующей функциональности. Однако, составление приложения из таких фреймворков часто может оказаться трудным, и сейчас это требует хостинга различных частей приложения под <span class="caps">IIS</span> с возможной изоляцией их друг от друга с помощью applications и virtual directories.</p>
<p>В противоположность этому, Katana предлагает вам собрать современное веб-приложение, используя широкий набор разных веб-технологий, и затем захостить это приложение любым желаемым способом, выставив его под одним <span class="caps">HTTP</span> endpoint. Это даёт несколько преимуществ:</p>
<ul>
<li>Простой деплой: он включает в себя единственное приложение вместо отдельных приложений для каждой технологии.</li>
<li>Вы можете добавить другие возможности, например, аутентификацию, которая может быть применена ко всем нижележащим компонентам в конвеере.</li>
<li>Разные компоненты, неважно, от Microsoft или сторонние, могут работать с одним и тем состоянием запроса через environment dictionary.</li>
</ul>
<p>Теперь я пройдусь по примеру приложения из области, с который вы должны быть знакомы: баг-трекинг. Приложение будет показывать набор багов в разных состояниях - отложен, в работе и сделан - и позволит мне перемещать баги между ними. Также, так как одновременно работать с багом может множество разных людей, то приложение будет обновлять все браузеры в реальном времени при изменении состояния бага. Вот что я буду использовать для построения приложения:</p>
<ul>
<li>Nancy (<a href="http://nancyfx.org">nancyfx.org</a>) для серверной генерации разметки и раздачи статики.</li>
<li><span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> (<a href="http://asp.net/web-api">asp.net/web-api</a>) для обработки <span class="caps">AJAX</span>-запросов.</li>
<li>SignalR (<a href="http://signalr.net">signalr.net</a>) для обмена сообщениями в реальном времени.</li>
</ul>
<p>Я не собираюсь тратить много времени на вёрстку и клиентские скрипты, поэтому дополнительно я буду использовать Knockout.js для отделения <span class="caps">HTML</span>-разметки от данных, поступающих из <span class="caps">API</span> и SignalR.</p>
<p>Главное, о чём стоит помнить - это то, что я собираю все эти фреймворки в один <span class="caps">OWIN</span>-конвеер, так что если мне станут доступны новые возможности, то я добавлю их в приложение простой вставкой их в конвеер.</p>
<h2>Приступаем к работе</h2>
<p>Одна из целей Katana — позволить вам лучше контролировать возможности, добавленные в ваше приложение (и, следовательно, ваши затраты в смысле ресурсов на обработку каждого запроса). Помня об этом, я начну создавать новый пустой <span class="caps">ASP</span>.<span class="caps">NET</span> Web app проект в Visual Studio 2013:</p>
<p><img alt="begin-creating-project" src="http://i.msdn.microsoft.com/dn451439.Dierking_Figure%204_hires(en-us,MSDN.10).png" /></p>
<p>Шаблоны Web-проектов, даже пустые, помогают в том, что по-умолчанию складывают скомпилированные сборки прямо в каталог /bin вместо /bin/debug, что часто встречается в проектах других типов. Стандартный Katana-хост ищет сборки в каталоге /bin. Вы можете создать приложение, основанное на Katana, в виде библиотеки классов, но тогда вам придётся либо поменять свойства проекта так, чтобы они подходили под эту структуру, либо предоставить собственный специфический загрузчик приложений, который сможет отыскать сборки и типы в другой структуре каталогов.</p>
<p>Далее я сделаю серверную генерацию разметки с помощью веб-фреймворка Nancy.</p>
<p>Лаконичный синтаксис Nancy позволяет легко и быстро строить <span class="caps">HTTP</span>-сайты и сервисы. Но что более важно для этого упражнения — это то, что, подобно <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span>, Nancy не зависит от System.Web.dll и приспособлен для запуска в конвеере <span class="caps">OWIN</span>. Фреймворки вроде <span class="caps">ASP</span>.<span class="caps">NET</span> <span class="caps">MVC</span> имеют зависимости от System.Web.dll (на момент написания этой статьи), что делает их менее годными для хостинга вне <span class="caps">IIS</span>.</p>
<p>Зачастую, когда вы добавляете новый функционал в приложение, вы начинаете с установки NuGet пакета (вы можете прочитать подробнее про NuGet на <a href="http://docs.nuget.org">docs.nuget.org</a>). На момент написания многие используемые здесь пакеты находились в пре-релиз версиях, поэтому проверьте, что отображение пре-релиз пакетов включено в диалоговом окне NuGet.</p>
<p>Добавить Nancy в приложение я могу простой установкой одноимённого NuGet-пакета. Однако, так как я хочу ещё запускать Nancy в конвеере <span class="caps">OWIN</span>, я установлю пакет Nancy.Owin (<a href="http://nuget.org/packages/nancy.owin">nuget.org/packages/nancy.owin</a>). Он установит пакет Nancy как зависимость и предоставит дополнительные хелперы для настройки Nancy в конвеере <span class="caps">OWIN</span>.</p>
<p>Затем мне нужно создать модуль Nancy (похож на контроллер из Model-View-Controller, или <span class="caps">MVC</span>) для обработки запросов, а также view для отображения чего-нибудь в браузере. Вот код модуля (HomeModule.cs):</p>
<div class="highlight"><pre><span></span><span class="k">public</span> <span class="k">class</span> <span class="nc">HomeModule</span> <span class="p">:</span> <span class="n">NancyModule</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nf">HomeModule</span><span class="p">()</span> <span class="p">{</span>
<span class="n">Get</span><span class="p">[</span><span class="s">"/"</span><span class="p">]</span> <span class="p">=</span> <span class="n">_</span> <span class="p">=></span> <span class="p">{</span>
<span class="kt">var</span> <span class="n">model</span> <span class="p">=</span> <span class="k">new</span> <span class="p">{</span> <span class="n">title</span> <span class="p">=</span> <span class="s">"We've Got Issues..."</span> <span class="p">};</span>
<span class="k">return</span> <span class="n">View</span><span class="p">[</span><span class="s">"home"</span><span class="p">,</span> <span class="n">model</span><span class="p">];</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Как вы можете видеть, модуль говорит, что запросы на корень приложения (“/”) должны быть обработаны анонимным делегатом, определённым в соответствующей лямбде. Эта функция создаёт модель с заголовком страницы и говорит Nancy отрендерить view “home”, передавая в неё модель. View, показанная ниже, вставляет заголовок из модели и в заголовок страницы, и в тег h1:</p>
<div class="highlight"><pre><span></span><span class="cp"><!DOCTYPE html></span>
<span class="p"><</span><span class="nt">html</span> <span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>@Model.title<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>@Model.title<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span><span class="p">></span>Backlog<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">"bugs"</span> <span class="na">id</span><span class="o">=</span><span class="s">"backlog"</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">></span>a bug<span class="p"></</span><span class="nt">li</span><span class="p">></span>
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span><span class="p">></span>Working<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">"bugs"</span> <span class="na">id</span><span class="o">=</span><span class="s">"working"</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">></span>a bug<span class="p"></</span><span class="nt">li</span><span class="p">></span>
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span><span class="p">></span>Done<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">"bugs"</span> <span class="na">id</span><span class="o">=</span><span class="s">"done"</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">></span>a bug<span class="p"></</span><span class="nt">li</span><span class="p">></span>
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</pre></div>
<p>За более подробной информацией об этих листингах загляните, пожалуйста, в документацию Nancy.</p>
<p>Теперь, когда у меня есть базовая функциональность Nancy, мне нужно организовать конвеер <span class="caps">OWIN</span> и встроить в него модуль Nancy. Для этого мне нужно установить хост Katana и серверные компоненты, а затем написать немного кода для связки конвеера <span class="caps">OWIN</span> и Nancy.</p>
<p>В качестве хоста Katana и серверных компонентов я буду использовать <span class="caps">IIS</span> Express и System.Web, так как они имеют родную поддержку в Visual Studio и вдовабок позволяют использовать удобную кнопку F5 при разработке приложения. Я добавлю System.Web-хост в проект с помощью установки NuGet-пакета Microsoft.Owin.Host.SystemWeb (<a href="http://bit.ly/19EZ2Rw">bit.ly/19EZ2Rw</a>).</p>
<p>Стандартные компоненты Katana используют разные соглашения по загрузке и запуску <span class="caps">OWIN</span>-приложений, включая startup class. Когда хост Katana загружает <span class="caps">OWIN</span>-приложение, он находит и запускает startup class согласно следующих правил (в порядке приоритета):</p>
<ul>
<li>Если Web.config содержит настройку с ключом “owin:AppStartup” в appSettings, то загрузчик возьмёт указанное значение. Оно должно быть валидным именем .<span class="caps">NET</span>-типа.</li>
<li>Если сборка содержит аттрибут <code>[assembly: OwinStartup(typeof(MyStartup))]</code>, загрузчик возьмёт указанный в нём тип.</li>
<li>Если никакое из этих условий не сработает, то загрузчик пройдётся по всем загруженным сборкам в поиска типа Startup, у которого есть метод с сигнатурой <code>void Configure(IAppBuilder app)</code>.</li>
</ul>
<p>Для этого примера я позволю загрузчику просканировать сборки в поисках класса. Однако, будет разумным использовать appSettings или атрибут сборки во избежание ненужного сканирования в случае, когда в вашем проекте много типов и сборок.</p>
<p>Я создам startup class, который будет инициализировать мой <span class="caps">OWIN</span>-конвеер и добавлять Nancy как компонент конвеера. Я создаю новый класс Startup и добавляю конфигурационный метод следующим образом:</p>
<div class="highlight"><pre><span></span><span class="k">public</span> <span class="k">class</span> <span class="nc">Startup</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Configuration</span><span class="p">(</span><span class="n">IAppBuilder</span> <span class="n">app</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">app</span><span class="p">.</span><span class="n">UseNancy</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>UseNancy — это extension method, доступный из NuGet-пакета Nancy.Owin. Многие библиотеки middleware предоставляют похожие удобные extension methods, упрощающие процесс настройки, но вам никто не запрещает добавлять middleware с помощью метода Use у IAppBuilder.</p>
<p>На данном этапе вы можете запустить проект в Visual Studio использую F5 и посмотреть, что он хоть пока что и не заставляет ронять челюсть на пол, но вы уже имеете полно-функциональное веб-приложение. Сейчас конвеер <span class="caps">OWIN</span> состоит из одного компонента, Nancy, как это показано ниже:</p>
<p><img alt="OWIN pipeline with Nancy" src="http://i.msdn.microsoft.com/dn451439.Dierking_Figure6_hires(en-us,MSDN.10).png" /></p>
<h2>Добавляем данные с помощью <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span></h2>
<p>Пока что <span class="caps">HTML</span> view состоит в основном из статичной разметки. Сейчас я выдам пользователям реальные баги для работы. Во многих современных веб-приложениях задача по доставке данных в клиентский браузер перешла от фреймворков для серверной генерации разметки (вроде Nancy) к отдельному <span class="caps">API</span>. В этом случае браузер сразу же после загрузки <span class="caps">HTML</span>-страницы выполняет JavaScript, который данные из <span class="caps">API</span> и динамически строит <span class="caps">HTML</span>-разметку.</p>
<p>Я начну с разработки <span class="caps">API</span> на фреймворке <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span>. Как обычно, первым делом нужно установить NuGet-пакет. Пакет этот называется Microsoft.AspNet.WebApi.Owin (<a href="http://bit.ly/1dnocmK">bit.ly/1dnocmK</a>) и он позволит легко встроить <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> в мой <span class="caps">OWIN</span>-конвеер. Фреймворк <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> установится как зависимость. После установки, я создам простое <span class="caps">API</span>:</p>
<div class="highlight"><pre><span></span><span class="k">public</span> <span class="k">class</span> <span class="nc">BugsController</span> <span class="p">:</span> <span class="n">ApiController</span>
<span class="p">{</span>
<span class="n">IBugsRepository</span> <span class="n">_bugsRepository</span> <span class="p">=</span> <span class="k">new</span> <span class="n">BugsRepository</span><span class="p">();</span>
<span class="k">public</span> <span class="n">IEnumerable</span><span class="p"><</span><span class="n">Bug</span><span class="p">></span> <span class="n">Get</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">_bugsRepository</span><span class="p">.</span><span class="n">GetBugs</span><span class="p">();</span>
<span class="p">}</span>
<span class="na"> [HttpPost("api/bugs/backlog")]</span>
<span class="k">public</span> <span class="n">Bug</span> <span class="nf">MoveToBacklog</span><span class="p">([</span><span class="n">FromBody</span><span class="p">]</span> <span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">bug</span> <span class="p">=</span> <span class="n">_bugsRepository</span><span class="p">.</span><span class="n">GetBugs</span><span class="p">().</span><span class="n">First</span><span class="p">(</span><span class="n">b</span><span class="p">=></span><span class="n">b</span><span class="p">.</span><span class="n">id</span><span class="p">==</span><span class="n">id</span><span class="p">);</span>
<span class="n">bug</span><span class="p">.</span><span class="n">state</span> <span class="p">=</span> <span class="s">"backlog"</span><span class="p">;</span>
<span class="k">return</span> <span class="n">bug</span><span class="p">;</span>
<span class="p">}</span>
<span class="na"> [HttpPost("api/bugs/working")]</span>
<span class="k">public</span> <span class="n">Bug</span> <span class="nf">MoveToWorking</span><span class="p">([</span><span class="n">FromBody</span><span class="p">]</span> <span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">bug</span> <span class="p">=</span> <span class="n">_bugsRepository</span><span class="p">.</span><span class="n">GetBugs</span><span class="p">().</span><span class="n">First</span><span class="p">(</span><span class="n">b</span> <span class="p">=></span> <span class="n">b</span><span class="p">.</span><span class="n">id</span> <span class="p">==</span> <span class="n">id</span><span class="p">);</span>
<span class="n">bug</span><span class="p">.</span><span class="n">state</span> <span class="p">=</span> <span class="s">"working"</span><span class="p">;</span>
<span class="k">return</span> <span class="n">bug</span><span class="p">;</span>
<span class="p">}</span>
<span class="na"> [HttpPost("api/bugs/done")]</span>
<span class="k">public</span> <span class="n">Bug</span> <span class="nf">MoveToDone</span><span class="p">([</span><span class="n">FromBody</span><span class="p">]</span> <span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">bug</span> <span class="p">=</span> <span class="n">_bugsRepository</span><span class="p">.</span><span class="n">GetBugs</span><span class="p">().</span><span class="n">First</span><span class="p">(</span><span class="n">b</span> <span class="p">=></span> <span class="n">b</span><span class="p">.</span><span class="n">id</span> <span class="p">==</span> <span class="n">id</span><span class="p">);</span>
<span class="n">bug</span><span class="p">.</span><span class="n">state</span> <span class="p">=</span> <span class="s">"done"</span><span class="p">;</span>
<span class="k">return</span> <span class="n">bug</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>В <span class="caps">API</span> содержится метод для получения набора багов из репозитория, а также ещё несколько методов для перемещения багов между состояниями. Гораздо больше информации по <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> можно найти на <a href="http://asp.net/web-api">asp.net/web-api</a></p>
<p>Теперь, когда у меня есть контроллер <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span>, мне нужно добавить его к своему существующему <span class="caps">OWIN</span>-конвееру. Для этого я просто добавляю следующие строчки в метод Configuration в моём startup class:</p>
<div class="highlight"><pre><span></span><span class="kt">var</span> <span class="n">config</span> <span class="p">=</span> <span class="k">new</span> <span class="n">HttpConfiguration</span><span class="p">();</span>
<span class="n">config</span><span class="p">.</span><span class="n">MapHttpAttributeRoutes</span><span class="p">();</span>
<span class="n">config</span><span class="p">.</span><span class="n">Routes</span><span class="p">.</span><span class="n">MapHttpRoute</span><span class="p">(</span><span class="s">"bugs"</span><span class="p">,</span> <span class="s">"api/{Controller}"</span><span class="p">);</span>
<span class="n">app</span><span class="p">.</span><span class="n">UseWebApi</span><span class="p">(</span><span class="n">config</span><span class="p">);</span>
</pre></div>
<p>Как и в случае с Nancy, пакет <span class="caps">OWIN</span> для <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> предоставляет extension method UseWebApi, который способствует простой интеграции <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> в мой существующий <span class="caps">OWIN</span>-конвеер. Теперь он состоит из двух компонентов, <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> и Nancy, как показано ниже:</p>
<p><img alt="owin-pipeline-two-components" src="http://i.msdn.microsoft.com/dn451439.Dierking_Figure8_hires(en-us,MSDN.10).png" /></p>
<p>Если запрос, попавший в конвеер, подойдёт под одно из правил в роутинге <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span>, то <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> обработает его и сгенерирует ответ. В противном случае запрос продолжит своё движение по конвееру, где его поджидает Nancy. Если ни один из компонентов конвеера не сможет обработать какой-нибудь запрос, то стандартные компоненты Katana вернут <span class="caps">HTTP</span> 404.</p>
<p>Сейчас у меня есть работающее <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> приложение, но к нему никто не обращается из домашней view. Поэтому я добавлю код по получению данных из <span class="caps">API</span> и генерации списка багов в каждом из состояний: отложено, в работе и сделано. Для этой задачи я использую преимущества Knockout.js, JavaScript Model-View-ViewModel (<span class="caps">MVVM</span>) библиотеки. Больше информации о Knockout вы можете найти на <a href="http://knockoutjs.com">knockoutjs.com</a>.</p>
<p>Для того, чтобы динамически создавать <span class="caps">HTML</span>-разметку на клиенте с использованием Knockout, первым делом мне нужно затянуть все баги из <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> и создать viewModel, которую Knockout привяжет к <span class="caps">HTML</span> элементам.</p>
<div class="highlight"><pre><span></span><span class="p"><</span><span class="nt">script</span><span class="p">></span>
<span class="nx">$</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">viewModel</span><span class="p">;</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">getJSON</span><span class="p">(</span><span class="s1">'/api/bugs'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">model</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span>
<span class="nx">viewModel</span> <span class="o">=</span> <span class="p">{</span>
<span class="nx">backlog</span><span class="o">:</span> <span class="nx">ko</span><span class="p">.</span><span class="nx">observableArray</span><span class="p">(</span>
<span class="nx">model</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">element</span><span class="p">.</span><span class="nx">state</span> <span class="o">===</span> <span class="s1">'backlog'</span><span class="p">;</span> <span class="p">})),</span>
<span class="nx">working</span><span class="o">:</span> <span class="nx">ko</span><span class="p">.</span><span class="nx">observableArray</span><span class="p">(</span>
<span class="nx">model</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">element</span><span class="p">.</span><span class="nx">state</span> <span class="o">===</span> <span class="s1">'working'</span><span class="p">;</span> <span class="p">})),</span>
<span class="nx">done</span><span class="o">:</span> <span class="nx">ko</span><span class="p">.</span><span class="nx">observableArray</span><span class="p">(</span>
<span class="nx">model</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">element</span><span class="p">.</span><span class="nx">state</span> <span class="o">===</span> <span class="s1">'done'</span><span class="p">;</span> <span class="p">})),</span>
<span class="nx">changeState</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">bug</span><span class="p">,</span> <span class="nx">newState</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">self</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">'/api/bugs/'</span> <span class="o">+</span> <span class="nx">newState</span><span class="p">,</span> <span class="p">{</span> <span class="s1">''</span><span class="o">:</span> <span class="nx">bug</span><span class="p">.</span><span class="nx">id</span> <span class="p">},</span> <span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">){</span>
<span class="nx">self</span><span class="p">.</span><span class="nx">moveBug</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="nx">moveBug</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">bug</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Remove the item from one of the existing lists</span>
<span class="p">...</span>
<span class="c1">// Add bug to correct list</span>
<span class="k">this</span><span class="p">[</span><span class="nx">bug</span><span class="p">.</span><span class="nx">state</span><span class="p">].</span><span class="nx">push</span><span class="p">(</span><span class="nx">bug</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="nx">ko</span><span class="p">.</span><span class="nx">applyBindings</span><span class="p">(</span><span class="nx">viewModel</span><span class="p">);</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="p"></</span><span class="nt">script</span><span class="p">></span>
</pre></div>
<p>Как только viewModel создана, Knockout может динамически создавать и обновлять содержимое <span class="caps">HTML</span> через привязку viewModel к <span class="caps">HTML</span> элементам, к которым добавлены специальные атрибуты. Например, список отложенных багов может быть сгенерирован из viewModel с помощью таких атрибутов:</p>
<div class="highlight"><pre><span></span><span class="p"><</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span><span class="p">></span>Backlog<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">"bugs"</span> <span class="na">id</span><span class="o">=</span><span class="s">"backlog"</span> <span class="na">data-bind</span><span class="o">=</span><span class="s">"foreach:backlog"</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">></span>
[<span class="p"><</span><span class="nt">span</span> <span class="na">data-bind</span><span class="o">=</span><span class="s">"text: id"</span><span class="p">></</span><span class="nt">span</span><span class="p">></span>] <span class="p"><</span><span class="nt">span</span> <span class="na">data-bind</span><span class="o">=</span><span class="s">"text: title"</span><span class="p">></</span><span class="nt">span</span><span class="p">></span>:
<span class="p"><</span><span class="nt">span</span> <span class="na">data-bind</span><span class="o">=</span><span class="s">"text: description"</span><span class="p">></</span><span class="nt">span</span><span class="p">></span>
<span class="p"><</span><span class="nt">ul</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"#"</span> <span class="na">data-bind</span><span class="o">=</span><span class="s">"click: $root.changeState.bind($root, $data, 'working')"</span><span class="p">></span>Move to working<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"#"</span> <span class="na">data-bind</span><span class="o">=</span><span class="s">"click: $root.changeState.bind($root, $data, 'done')"</span><span class="p">></span>Move to done<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
<span class="p"></</span><span class="nt">li</span><span class="p">></span>
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
</pre></div>
<h2>Добавление моментальных оповещений об изменениях</h2>
<p>Сейчас у меня есть полностью рабочее одностраничное веб-приложение. Пользователи могут заходить на домашнуюю страницу и перемещать баги между разными состояниями. Более того, технологии, на которых всё это работает, Nancy и <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span>, работают вместе в одном <span class="caps">OWIN</span>-конвеере.</p>
<p>Но я собираюсь пойти дальше и сделать так, чтобы разные пользователи могли в реальном времени видеть обновления, сделанные другими пользователями. Для этого я буду использовать библиотеку SignalR, которая предоставляет клиентское и серверное <span class="caps">API</span> для обмена сообщениями между браузером и веб-сервером в реальном времени. SignalR тоже написан с учётом работы в <span class="caps">OWIN</span>-конвеере, поэтому добавить его к моему существующему приложению будет легче пареной репы.</p>
<p>Я буду использовать одну из возможностей SignalR под названием Hubs. В двух словах, Hub позволяет клиентам и серверам вызывать методы друг друга. Отличную вводную статью по SignalR можно посмотреть на <a href="http://bit.ly/14WIx1t">bit.ly/14WIx1t</a>. В моём приложении, <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span> при заросе на изменение состояния бага будет обновлять его и затем рассылать обновлённый баг через SignalR Hub во все клиентские браузеры, подсоединённые к приложению.</p>
<p>Я начну с созданию Hub на сервере. Я не использую никаких других возможностей SignalR и поэтому мой Hub будет состоять всего лишь из такого пустого класа:</p>
<div class="highlight"><pre><span></span><span class="na">[HubName("bugs")]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">BugHub</span> <span class="p">:</span> <span class="n">Hub</span>
<span class="p">{</span>
<span class="p">}</span>
</pre></div>
<p>Для того, чтобы что-нибудь послать в Hub из <span class="caps">ASP</span>.<span class="caps">NET</span> Web <span class="caps">API</span>, мне для начала нужно получить экземпляр его runtime context. Я могу сделать это, добавив следующий код в конструктор BugsController:</p>
<div class="highlight"><pre><span></span><span class="k">public</span> <span class="nf">BugsController</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_hub</span> <span class="p">=</span> <span class="n">GlobalHost</span><span class="p">.</span><span class="n">ConnectionManager</span><span class="p">.</span><span class="n">GetHubContext</span><span class="p"><</span><span class="n">BugHub</span><span class="p">>();</span>
<span class="p">}</span>
</pre></div>
<p>Затем я могу разослать обновлённый баг ко всем подсоединённым клиентским бразуером из какого-нибудь метода MoveToXX:</p>
<div class="highlight"><pre><span></span><span class="n">_hub</span><span class="p">.</span><span class="n">Clients</span><span class="p">.</span><span class="n">All</span><span class="p">.</span><span class="n">moved</span><span class="p">(</span><span class="n">bug</span><span class="p">);</span>
</pre></div>
<p>На домашней странице, после добавления нескольких JavaScript библиотек SignalR, я могу подсоединиться к bugsHub и начать ждать сообщений об изменениях:</p>
<div class="highlight"><pre><span></span><span class="nx">$</span><span class="p">.</span><span class="nx">connection</span><span class="p">.</span><span class="nx">hub</span><span class="p">.</span><span class="nx">logging</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">bugsHub</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">connection</span><span class="p">.</span><span class="nx">bugs</span><span class="p">;</span>
<span class="nx">bugsHub</span><span class="p">.</span><span class="nx">client</span><span class="p">.</span><span class="nx">moved</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">item</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">viewModel</span><span class="p">.</span><span class="nx">moveBug</span><span class="p">(</span><span class="nx">item</span><span class="p">);</span>
<span class="p">};</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">connection</span><span class="p">.</span><span class="nx">hub</span><span class="p">.</span><span class="nx">start</span><span class="p">().</span><span class="nx">done</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'hub connection open'</span><span class="p">);</span>
<span class="p">});</span>
</pre></div>
<p>Заметьте, что когда я получаю с сервера запрос через функцию moved, я вызываю метод moveBug у viewModel также, как я делал это в обработчике клика на элемент списка. Разница в том, что все клиентские браузеры могут одновременно обновить свои viewModel, так как этот вызов производится через SignalR. Вы можете убедиться в этом, открыв два окна браузера. Изменения, сделанные в одном окне, отобразятся в другом.</p>
<p>Как я говорил, добавление SignalR в <span class="caps">OWIN</span>-конвеер тривиально. Я просто добавляю следующий код в метод Configuration у startup class:</p>
<div class="highlight"><pre><span></span><span class="n">app</span><span class="p">.</span><span class="n">MapSignalR</span><span class="p">();</span>
</pre></div>
<p>Всё это создаёт такой конвеер:</p>
<p><img alt="owin-pipeline-three-elements" src="http://i.msdn.microsoft.com/dn451439.Dierking_Figure11_hires(en-us,MSDN.10).png" /></p>
<h2>Переходим к self-host</h2>
<p>На текущий момент у меня есть работающее приложение по управлению багами, и, хотя ему всё ещё не хватает нескольких важных возможностей, оно уже может делать пару интересных вещей. Я постепенно добавлял к приложению функциональные блоки, используя как Microsoft-, так и сторонние middleware компоненты. Справедливо заметить, что многое из этого можно было сделать и с помощью <span class="caps">ASP</span>.nET HttpModules и HttpHandlers. Так чего же я действительно достиг, кроме более простого, ориентированного на код подхода для составления компонентов конвеера вместе?</p>
<p>Для ответа на этот вопрос нужно вспомнить общую схему архитектуры Katana из начала статьи. До этого момента, я работал только с верхними двумя уровнями стека Katana. Однако, все эти слои могут быть с лёгкостью заменены, включая сервер и хост.</p>
<p>Чтобы продемонстрировать это, я возьму весь свой конвеер, выкину <span class="caps">IIS</span> и System.Web.dll и поставлю всё поверх простого, легковесного <span class="caps">HTTP</span>-сервера, который будет хоститься с помощью OwinHost.exe, входящего в состав Katana. Self-hosting может оказаться полезным в разных сценариях, от случаев, когда на машине разработчика нет Web-сервера, до боевого деплоя на shared-хостинге, который использует изоляцию процессов и не даёт доступа к Web-серверу.</p>
<p>Начну я с установки следующих NuGet пакетов:</p>
<ul>
<li>Microsoft.Owin.Host.HttpListener (<a href="http://bit.ly/153aIca">bit.ly/153aIca</a>)</li>
<li>OwinHost (<a href="http://bit.ly/162Uzj8">bit.ly/162Uzj8</a>)</li>
</ul>
<p>Затем я сделаю ребилд приложения. Заметьте, что не обязательно делать ребилд для того, чтобы запустить приложения поверх новых сервера и хоста. Единственным требованием является наличие их файлов в каталоге /bin, а ребилд — всего лишь удобный способ скопировать эти файлы в /bin.</p>
<p>После того, как пакеты установлены и файлы скопированы, я открываю командную строку, перехожу в корневой каталог веб-проекта и, как это показано ниже, запускаю OwinHost.exe из каталога packages:</p>
<div class="highlight"><pre><span></span>..\packages\OwinHost.2.0.0\tools\OwinHost.exe
</pre></div>
<p><img alt="calling OwinHost.exe" src="http://i.msdn.microsoft.com/dn451439.Dierking_Figure%2012_hires(en-us,MSDN.10).png" /></p>
<p>По-умолчанию, OwinHost.exe запустится, загрузит сервер Microsoft.Owin.Host.HttpListener и начнёт слушать порт 5000. Я могу зайти на http://localhost:5000 и убедиться в том, что приложение работает.</p>
<p>Более того, практически всё настройки могут быть изменены через аргументы командной строки. Например, если вы хотите слушать на другом порту, передайте -p 12345. Если вы хотите использовать совершенно другой сервер, передайте -s your.custom.server.assembly. Сила архитектуры Katana — модульность. Как только появилось что-то новое на каком-то уровне стека, оно может быть без промедлений затянуто в работающее приложение. А так как контракт между всеми компонентами стека — это всего лишь application delegate, то темп этих изменений может быть гораздо большим, чем это возможно сейчас.</p>
<h2>Просто начните использовать</h2>
<p>Katana 2.0 будет выпущена вместе с Visual Studio 2013. В новой версии сконцентрировались на двух главных вещах:</p>
<ul>
<li>Предоставление основных инфраструктурных компонентов для self-hosting.</li>
<li>Предоставление богатого набора middleware для аутентификации, включая социальные провайдеры, такие как Facebook, Google, Twitter и Microsoft Account, а также провайдеры для Windows Azure Active Directory, куков и federation (<em>наверное, речь об этом: <a href="http://msdn.microsoft.com/en-us/library/ms730908%28v=vs.110%29.aspx">msdn.microsoft.com/en-us/library/ms730908%28v=vs.110%29.aspx</a> —- прим. перевод.</em>).</li>
</ul>
<p>Как только Katana 2.0 зарелизится, сразу же начнётся работа над следующим набором компонентов Katana. Детали и приоритеты всё ещё определяются, но вы можете повлиять на обсуждение через заполнение issues на <a href="http://katanaproject.codeplex.com">katanaproject.codeplex.com</a>. Наконец, весь код из статьи доступен на <a href="http://bit.ly/1alOF4m">bit.ly/1alOF4m</a>.</p>
<hr />
<p><em>Howard Dierking работает на должности program manager в команде Windows Azure Frameworks and Tools, где он сосредоточен на <span class="caps">ASP</span>.<span class="caps">NET</span>, NuGet и Web <span class="caps">API</span>. Ранее Dierking был редактором <span class="caps">MSDN</span> Magazine, а также занимался программой сертификации разработчиков для Microsoft Learning. До Microsoft он 10 лет был разработчиком и архитектором приложений с уклоном на распределённые системы.</em></p>
<hr />
<p><strong>От переводчика</strong>
В заключение хочу поделиться собственными мыслями по поводу Katana. В первую очередь меня заинтересовало то, как в Katana используется <span class="caps">ASP</span>.<span class="caps">NET</span> Identity, новая система аутентификации и авторизации, появившаяся вместе с <span class="caps">MVC</span> 5 и остальными октябрьскими релизами в 2013 году. Identity чертовски просто встраивается в конвеер:</p>
<div class="highlight"><pre><span></span><span class="n">appBuilder</span><span class="p">.</span><span class="n">UseGoogleAutentification</span><span class="p">()</span>
</pre></div>
<p>Но есть и подозрительные моменты. Конечно, сложно спорить с Howard Dierking, управляющим разработкой фреймворков, про которые написано столько книг и статей, что отдельным достижением является прочтение хотя бы половины из них. Но, с высоты моего скромного опыта, по крайней мере один из аргументов в пользу Katana воспринимается не так однозначно — возможность собрать все сервисы в одно приложение и деплоить всё сразу. Ведь известен совершенно противоположный подход — разделить приложение на отдельные сервисы и получить возможность независимо и постепенно выкладывать обновления на боевую площадку.</p>
<p>Также, могут возникнуть сложности с поиском места в коде, отвечающим за ту или иную функциональность. Когда запрос может быть обработан кучей разных компонентов и то, какой из них всё-таки отработает, зависит от порядка в конфигурации конвеера и специфичных для каждого компонента настройках, вроде конфига путей в Web <span class="caps">API</span> — искать можно долго.</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<hr>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p class="pelican-credits">Powered by <a href="http://docs.getpelican.com/">Pelican</a></p>
</div>
</div>
</div>
</body>
</html>