Предпосылки к переменам
Новость о выходе ASP.NET vNext в сети распространилась достаточно быстро вместе с анонсом наиболее интересных новшеств, предложенных в обновлении. ASP.NET MVC 6 получил новое имя и позиционируется как что-то действительно новое в среде web фреймворков Microsoft (MS). Для того чтобы наиболее полно можно было понимать важность заявленных изменений vNext, вспомним особенности текущей реализации веб приложений на платформе .NET.
Типичное ASP.NET MVC приложение состоит из Global.asax файла, в котором мы расширяем класс HttpApplication (System.Web) и таким образом добавляем желаемое поведение обработки Http запросов путем конфигурирования отдельных составляющих компонентов MVC. В классическом ASP.NET MVC класс Router (System.Web.Routing) является неотъемлемым звеном обработки Http запросов, путем перенаправления их на нужный HttpHandler – в стандартном поведении (MvcRouteHandler) который создает фабрику контроллеров и запускает дальнейший pipe обработки запросов. Эти компоненты являются заведомо тесно привязанными к веб-серверу от MS – Internet Information Services (IIS), использующий HttpHandlers и HttpContext (доступ к параметрам http запроса/ответа) для обработки запросов. Полученное в наследие от ASP.NET Web Forms повсеместное применение этих компонентов в MVC предполагало тесную интеграцию с IIS, что сделало невозможным использование альтернативного веб-сервера для
Попытка исправить ситуацию была предпринята в рамках развития проекта Web Api. Во время миграции с фреймворка MS WCF разработчики отказались от подхода к построению RESTful сервисов на базе концепции «привязок-контрактов», в пользу более удобному MVC, что предопределило много общего и созвучное название — ASP.NET Web Api. На самом же деле разработчики нового фреймворка смогли сделать его с оглядкой на описанные выше недостатки ASP.NET MVC. В Web Api не использовали этих самых компонентов System.Web напрямую благодаря адаптерам для HttpContext.Request, HttpContext.Response – абстракциям c обновленной структурой HttpRequestMessage, HttpResponseMessage, в которые HttpControllerHandler (точка входа в Web Api приложение) производил преобразование указанных объектов. Так же была устранена и зависимость от HttpHandler’ов IIS, путем обработки запросов через цепочку вложенных друг в друга HttpMessageHandlers (System.Net.Http), на базе которых и был реализован обновленный стек (filters, dispatchers, delegating handlers, controllers).
Все эти изменения позволили сделать Web Api приложение независимым от хоста и развивать в рамках OWIN (Open Web Interface) спецификации. Ключевым стало понятие middleware – компоненты веб приложения (в IIS – HttpModules) и application delegate, который вместо HttpContext, взаимодействует с куда более абстрактным набором параметров запроса/ответа в хеш-мапе вида IDictionary<string,object>.
Несмотря на все преимущества, гибкость, улучшенную расширяемость и целый ряд исправлений архитектурных недостатков Web Api все же не мог стать полноценной заменой MVС будучи ориентированным на Single Page Applications(SPA). В то же время один фреймворк ничем не мешал другому. Более того, они отлично уживались рядом в одном веб-приложении в случае использования IIS для их
Шаблон проекта ASP.NET vNext
Первое, что бросается в глаза в новом веб-приложении — отсутствие файлов конфигурации web.config, packages.config и даже AssemblyInfo.cs.
В структуре решения имеем только два файла:
- project.json – файл конфигурации для проекта, где находятся зависимости с указанием версий подключаемых сборок, параметры компиляции проекта, метаданные, перечень зависимостей для развертывания в режимах .NET и Core CLR.
- Startup.cs – входная точка, класс веб-приложения.
Сейчас заглянув в свойства проекта VS мы не увидим того изобилия опций, которыми прежде были снабжены проекты ASP.NET. Всего доступны две вкладки: General – конфигурация пространства имен и типа приложения при построении; на вкладке Debugging – номер порта, на котором будет доступно наше приложение.
Далее интересней, собственно файл конфигурации project.json проекта. Выглядит он приблизительно следующим образом.
Первичный набор предусматривает наличие всего двух ключей.
- dependencies – указание зависимостей для проекта получаемых из сборок, NuGet Packages и даже исходников на диске;
- configurations – перечень зависимостей при построении проекта для конкретной целевой среды: .NET или Core CLR.
Столь кардинальные изменения в структуре проекта обусловлены использованием нового компилятора Roslyn, который позволяет в runtime компилировать исходный код в IL и выполнять его. Возможности Roslyn легли в основу и новой среды выполнения K Language Runtime (KLR). На данный момент для нее предусмотрено две опции запуска проекта в режиме .NET Desktop, либо — Core CLR (в alpha версии режим доступен только в KLR из под Win 8). Собрать проект можно так же используя K Versions Manager (KVM)/K Packages Manager (KPM) — консольные утилиты поставляемые с K, что позволяют самостоятельно управлять установленным версиями KRE(K Runtime Engine), распаковывать приложения на основании project.json и запускать их. Для более детальной информации стоит ознакомиться с ресурсом github.com/aspnet/home.
Структура веб-приложения ASP.NET vNext
Единственный файл исходного кода в проекте — Startup.cs, содержит ссылку на пространство имен Microsoft.AspNet.Builder и класс, структурой схожей с OWIN based приложениям. В нем один метод — Configure с входным параметром типа IBuilder. Проект компилируется и запускается, но ничего не возвращает в качестве ответа (а точнее возвращает «403-ий», поскольку IIS не находя обработчика запроса пытается получить доступ к файловой системе), что логично, поскольку мы не добавили никакого middleware.
На www.asp.net/vnext нам предлагают вызвать статический extension метод (UseWelcomePage), чтобы добавить простейший Request Handler, но он нам мало интересен, так как скрывает всю специфику работы интерфейса IBuilder. Попробуем «копнуть» глубже взглянув на его определение:
using System;
namespace Microsoft.AspNet.Builder
{
public interface IBuilder
{
IServiceProvider ApplicationServices { get; set; }
IServerInformation Server { get; set; }
RequestDelegate Build();
IBuilder New();
IBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}
}
Для того что бы сориентироваться в коде, необходимо вспомнить, что понятие middleware в OWIN напрямую относиться к Application Delegate, который получает перечень параметров Http запроса и выполняет над ним какое-то преобразование, возвращая Task.
Func<IDictionary<string, object>, Task>
Для OWIN мы также имели статический Extensions method на уровне интерфейса IAppBuilder, который позволял добавлять middleware используя более удобную структурированную версию контекста — IOwinContext. В vNext он немного преобразился, поскольку подразумевает работу с HttpContext напрямую. Последний теперь находиться в пространстве имен Microsoft.AspNet.Http, и не имеет ничего общего с HttpContext из System.Web, как это было в случае с оберткой HttpContextBase в ASP.NET MVC:
public delegate Task RequestDelegate(HttpContext context);
Таким образом все понемногу становиться на свои места — RequestDelegate в vNext и есть тем самым OWIN Application Delegate’ом.
Для добавления нового middleware в оригинальной реализации метода Use мы передаем generic делегат, содержащий в качестве параметра вложенный делегат типа RequestDelegate. Внешний Generic делегат ссылается на весь дальнейший pipeline обработки http запроса, RequestDelegate – выполняет работу Http Handler’a.
Попробуем взглянуть на пример с добавлением модулей — это должно немного прояснить ситуацию.
public void Configure(IBuilder app)
{
app.Use(next =>
httpContext =>
httpContext.Response.WriteAsync("Executed before all followers." + Environment.NewLine)
.ContinueWith(task => next(httpContext)));
app.Use(next =>
httpContext =>
next(httpContext)
.ContinueWith(task =>
httpContext.Response.WriteAsync("Executed after all followers." + Environment.NewLine)));
app.Use((httpContext, next) => httpContext.Response.WriteAsync("Hello world middleware." + Environment.NewLine));
}
Как видим, при конфигурации middleware мы имеем поведение во многом схожее с Delegating Handlers Web Api, где мы можем гибко контролировать последовательность выполнения из текущего модуля всего дальнейшего pipeline (параметр next). Достаточно просто добавить обработку Http запроса внутри лямбда-выражений передаваемых в Use.
Что же касается непосредственно OWIN, vNext является полностью совместимым с данным интерфейсом. В пространстве имен Microsoft.AspNet.Builder находиться класс статических методов расширений для IBuilder, которые создают адаптеры перехода от Owin Application Delegate к vNext RequestDelegate.
using AddMiddleware = Action<Func<Func<IDictionary<string, object>, Task>, Func<IDictionary<string, object>, Task>>>;
public static AddMiddleware UseOwin(this IBuilder builder)
{
....
}
В своем приложении мы можем использовать AddMiddleware для взаимодействия между OWIN Environment dictionary и vNext HttpContext, полагаясь на внутреннею реализацию платформы. Расширение позволит достаточно легко использовать OWIN-компоненты в новой версии фреймворка.
Из важных особенностей стоит отметить, что в отличие от OWIN AppBuilder’a, vNext IBuilder интерфейс содержит и встроенный сервис-локатор ApplicationServices инициализируемый приложением веб-сервером. В данном свойстве предоставляется доступ Web Host Application Level Services.
vNext HttpContext
Со структурой приложения немного разобрались, что же представляет из себя обновленный vNext HttpContext. Как и ожидалось, сам класс контекста, Request и Response объявлены абстрактными. HttpContext в реализации поставляемого «из кробки» хоста Microsoft.AspNet.Hosting инстанцируется объектом DefaultHttpContext перед каждым вызовом цепочки middleware. Контекст имеет метод Abort, реализует интерфейс IDisposable и предоставляет два сервис локатора ApplicationServices и RequestServices с соответствующим жизненным циклом контейнеров, что явно говорит в пользу более удобного формирования Structure Map на уровне приложения по сравнению с предыдущими версиями ASP.NET MVC.
В vNext добавлен новый механизм расширяемости контекста путем использования HttpFeatures. На уровне интерфейса контекста он предусматривает два набора методов для получения и добавления расширений (через запаковку в object и generic wrapper):
public abstract object GetFeature(Type type);
public abstract void SetFeature(Type type, object instance);
public virtual T GetFeature<T>()
{
return (T)GetFeature(typeof(T));
}
public virtual void SetFeature<T>(T instance)
{
SetFeature(typeof(T), instance);
}
В обновленной версии ASP.NET данный подход применяется для динамического формирования контекста начиная от первого обращения к HttpContext, где инициализируются Host Application/Server Specific Features и вплоть до уровня использования отдельными middleware компонентами параметров запроса, аутентификации, кеширования, работы с веб-сокетами. В базовой реализации Request практически полностью использует интерфейсы, определенные в Microsoft.AspNet.Builder, для получения составляющих именно из HttpFeatures. Формирование контекста таким образом позволяет скрывать за конкретным интерфейсом отдельного компонента специфическую для веб-сервера реализацию функциональности на уровне хоста не ограничиваясь базовой структурой HttpContex.
vNext MVC
Наиболее ожидаемым нововведением с точки зрения построения самого веб-приложения безусловно есть объединение функциональности ASP.NET MVC и Web Api в рамках одного фреймворка, что позволит использовать общий pipeline для обработки Http запросов.
Добавление поведения MVC в vNext предполагает подключение и конфигурацию двух модулей middleware:
- vNext Dependency Resolver с перечнем сервисов формирующих стек vNext MVC.
- Router middleware — модуль MVC роутера.
public void Configure(IBuilder app)
{
app.UseServices(services => services.AddMvc());
app.UseMvc(router =>
router.MapRoute("DefaultHome",
"{controller}/{action}/{id?}",
defaults: new { Controller = "Home", Action = "Index" }));
}
Первый из них — набор сервисов, предоставляющий реализацию стандартных MVC компонентов и используемый на протяжении всего pipeline для обработки запроса на уровне отдельных абстракций. Речь идет о поставщиках Action контекста, фабрики контроллеров, привязки модели, предоставления View Engine и т.д. Описание сервисов с маппингами DI доступно для ознакомления в классе Microsoft.AspNet.Mvc.MvcServices.cs.
Второй — RouterHandler, который представлен в vNext интерфейсом IRouter с асинхронным методом RouteAsync:
public interface IRouter
{
Task RouteAsync(RouteContext context);
string GetVirtualPath(VirtualPathContext context);
}
По умолчанию интерфейс реализует класс MvcRouteHandler. В отличии от предыдущих версий роутинга основанных на специфической для фреймворка функциональности (в MVC – HttpHandler, Web Api – DelegatingHandler), в этот раз в нем предусмотрен только вызов отдельных методов интерфейсов полученных из сервис локатора с набором компонентов MVC. Именно этот класс получает ActionContext и запускает дальнейший pipeline обработки запроса, используя IActionInvoker сервис в связке с ControllerFactory. Основное преимущество от применения такого подхода очевидно, он предоставляет необходимую универсальность для слияния фреймворков, которая отсутствовала прежде.
Еще одним положительным изменением в новой редакции MVC стал полностью асинхронный режим выполнения методов сервисов. Подобным образом был реализован pipe в Web Api и позволил полностью вынести из рабочего пула потока веб-сервера операции связанные с ожиданием ресурсов. MVC до текущего момента предусматривал «из коробки» только асинхронные контроллеры.
За основную рабочую единицу обработки Http запроса в vNext был взят Controller из MVC. Для пост-процессинга и сериализации результатов выполнения действий используется хорошо знакомый интерфейс IActionResult. Функционал MVC в этом отношении концептуально остался прежним. Что же касается основных расширений, хорошо зарекомендовавших себя в Web Api, то они на данный момент не были добавлены в pipeline vNext. Как например механизм определения форматера путем использования Content Negotiation сервисов. В случае возвращения экземпляра класса не реализующего IActionResult из действия контроллера используется JSON-форматер.
Механизм конфигурации приложения был сильно модифицирован в связи с переходом на обновленный шаблон проекта. Теперь вместо прежнего web.config применяется config.json, представляющий иерархическую структуру пар ключ-значение.
Непосредственно на уровне кода, предоставлен набор утилит из пространством имен Microsoft.Framework.ConfigurationModel, производящий слияние ключей из различных источников конфигурации, например, параметров запуска приложения-хоста, переданных в консоли K; пар определенных в файлах project.json, config.json веб-приложения.
Про каждый из компонентов, а также и те, что не были упомянуты, можно написать отдельный обзор и наверняка он еще будет не один. Учитывая, что и перечень функциональных составляющих нового фреймворка еще не утвержден окончательно, ограничимся базовым пониманием работы его основных компонентов.
Итоги
После ознакомления с apha версией ASP.NET MVC vNext можно действительно говорить, что перемены разработчиков ожидают достаточно кардинальные. Cвязанные они не только с основательным рефакторингом ASP.NET, но и с адаптацией фреймворка под обновленный Runtime.
На уровне приложения изменение внутренних концепций достаточно ограниченное и будет интуитивно понятным для разработчиков, имевших дело с OWIN и предыдущими версиями ASP.NET MVC.
С точки зрения самого фреймворка наиболее важным есть переход на открытый веб интерфейс и четкое разграничение ответственности между хостом и веб-приложением. Качественное обновление состава абстракций и добавление гибких возможностей расширения, открывают новые перспективы при разработке веб-приложений.
Полезные ресурсы:
www.asp.net/vnext
github.com/aspnet/
blogs.msdn.com/b/webdev/archive/2014/06/03/asp-net-vnext-in-visual-studio-14-ctp.aspx
www.asp.net/aspnet/overview/owin-and-katana
blogs.msdn.com/b/dotnet/archive/2014/05/12/the-next-generation-of-net-asp-net-vnext.aspx
Автор: dotnetdonik