Sunday, December 6, 2015

ASP.NET Architecture. Deep dive

Здравствуйте, уважаемые читатели. Темой сегодняшней статьи будет низкоуровневая архитектура ASP.NET. Наверное, многих интересует принцип работы машины ASP.NET, когда они вводят url адрес в строку браузера. В интернете есть немного информации, которая, в основном, очень устарела, или покрывает нужную область лишь частично. Есть несколько статей, с которыми я детально ознакомился , прежде чем решился на эту статью:
Топ-статья из всех вышеперечисленных – последняя. Даже учитывая тот факт, что год написания этой статьи – 2008, особо ничего не изменилось. Благо, сейчас вы можете все это легко проверить и сами попробовать, так как Microsoft открыла исходники .NET Framework, библиотеки которого вы можете посмотреть онлайн по ссылке referencesource.microsoft.com. В общем, я и смотрел, как это все работает изнутри, с помощью открытых исходников. К сожалению, не существует способа лучше узнать предметную область, чем в нее окунуться. Чем мы, собственно, и займемся сегодня. 
Начнем с краткого описания процессов, которые происходят, когда вы вводите ваш url в строку браузера. Первым делом ваш запрос перехватывается с помощью системной библиотеки http.sys, анализируется и пересылается дальше пулу приложений (каждым пулом приложений управляет процесс w3wp.exe). Дальше наш процесс w3wp.exe изучает URL запроса и загружает ASP.NET ISAPIaspnet_isapi.dll”. ASP.NET ISAPI загружает среду выполнения HTTP
На этом моменте остановимся подробнее. Загрузка среды выполнения HTTP начинается с вызова метода IsapiRuntime.ProcessRequest(), который вызывается с неуправляемого кода. Этот метод запускает метод, который является точкой входа среды выполнения – HttpRuntime.ProcessRequest. Данный метод внутри вызывает метод ProcessRequestInternal, в который передает экземпляр класса HttpWorkerRequest. Класс HttpWorkerRequest, по сути, является рабочей лошадкой HttpContext. Суть метода ProcessRequestInternal – это создать экземпляр HttContext и создает инстанс приложения с пула (это наша точка входа класс HttpApplication). Как это происходит, показано ниже.
Сначала, как мы видим, происходит создание класса HttpContext. Этот класс существует на протяжении всей жизни запроса, и его можно получить через статическое свойство HttpContext.Current. Суть этого объекта заключается в том, что он содержит в себе ссылки и объекты, доступ к которым мы можем получить, пока жив запрос (Response, Request, Cache и т.д.). Как только функция создала HttpContext, в том же методе происходит создание HttpAppication и связывание каждого запроса с данным объектом HttpApplication. Создается этот класс с помощью абстрактной фабрики, которая называется HttpAppicationFactory.
Почему-то многие авторы статей сразу после того как рассказали о том, как создается экземпляр HttpApplication, начинают переходить к конвейеру HTTPкоторый называется так, потому что состоит из модулей HttpModules, которые перехватываю запрос на пути к HttpHandlers. Это основная рабочая лошадка ASP.NET, поскольку благодаря этим модулям ваше приложение и работает. Разъясню, как эти модули инициализируются, чтобы все стало на свои места. 
В методе HttpAppicationFactory.GetApplicationInstance() есть множество разных методов внутри. Но один из них заслуживает отдельного рассмотрения. Это метод HttpApplication.Init. Его суть заключается в том, что он запускает подгруздку HttpModules и подписывается на события с этих модулей с помощью рефлексии. Первый этап – погрузить модули с помощью функции InitModule().
Ниже показано, как эти модули загружаются с config файла.
Небольшое отступление. По умолчанию часть модулей прописана в файле, который называется “machine.config”, плюс ко всему, вы можете сами писать кастомные модули и связывать их через web.config. Чуть позже мы остановимся на этом детальнее. Теперь вторая часть, которую мы упустили, – подписка на события.
Там обычный цикл и куча кода внутри foreach, которая в 90% построена на рефлексии.
Давайте немного подытожим информацию по классу HttpApplication. Первое – этот класс является внешним контейнером для конкретного веб-приложения, который связан с классом, определенным в Global.asax. Этот класс создается через фабрику HttpAppicationFactory и передается сразу запросу. Он также служит как контейнер для предопределенных событий, которые возбуждаются в течении жизни запроса, а также как контейнер для HttpModules.
После того, как мы создали HttpApplication, сразу же вызываем метод ProcessRequest для начала обработки. И от на этом этапе у нас в бой вступают HttpModules.
Задача эти модулей самая различная. Например, аутентификация, управление состоянием и модули кеширования, управление маршрутизацией и другие. И здесь один из самых важных модулей, благодаря которому ваше приложение работает, – это UrlRoutingModule. Его задача является запуск маршрутизации, анализ и парсинг входящего URL и заполнить контекст запроса (request context). Как это работает, показано ниже. У класса Global.asax определен метод
RouteConfig.RegisterRoutes(RouteTable.Routes);
Его задача стоит в том, чтобы построить таблицу маршрутизации и добавить туда нужные пути (System.Web.Routing.RouteTable.Routes). Записи в этой таблице представляют собой шаблон для допустимых Url адресов. Это разные заполнители.
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Смотрим выше на заполнители в параметре url. С каждой записью связан обработчик маршрута – объект, который реализует интерфейс IRouteHandler. У этого интерфейса есть только один метод GetHttpHandler, который возвращает IHttpHandler. Для большинства приложений ASP.NET MVC у нас уже существует обработчик по умолчанию, который называется MvcRouteHandler и знает, как с помощью контекста запроса вызвать соответствующий контролер. Класс MvcRouteHandler возвращает инстанс класса MvcHandler. В этом классе MvcHandler есть метод ProcessRequest, который, используя интерфейс IControllerFactory, создает соответствующий контроллер. По умолчанию интерфейс IControllerFactory реализован в виде класса DefaultControllerFactory. Ниже представлен асинхронный вариант этого метода. Полную реализацию можно посмотреть на github MvcHandler.cs.
Следующее действие заключается в том, что контроллер вызывает метод InvokeAction, передавая детали для выбора метода выполнения. Метод доступен благодаря интерфейсу IActionInvoker. В ASP.NET MVC есть класс, который по умолчанию называется ControllerActionInvoker. У него не особо сложная логика, поэтому вы можете ознакомиться с ней более внимательно. Там есть такой метод, который называется FindAction. Он собирает информацию по всем методам и ищет самый подходящий. Все данные по методам выгребаются через атрибуты, которые получаются через класс ActionDescriptor. После того как метод был найден, в работу вступает DefaultModelBinder, задачей которого является обработка данных полученных с запроса (data type conversion, data validation и т.д.), а начинают работать фильтры ASP.NET MVC. Сначала выполняется фильтр аутентификации (Authentication filters), а затем фильтр авторизации (Authorizations filters). Action фильтры срабатывают перед началом действия (OnActionExecution) и после его выполнения (OnActionExecuted). Затем роль принимают фильтры, задача которых – возвращать результат Result фильтры. Они также представлены двумя методами, один из которых вызывается до начала возврата результата (OnResultExecution), а второй – после (OnResultExecuted). Детальнее по фильтрам исключений можно познакомиться здесь: Action Filtering in ASP.NET MVC Applications
Теперь по поводу самого результата, который может возвращаться конечному пользователю.  Его условно можно разделить на две части: ту, которая рендерит представление (интерфейс IView), и вторая, для которой ничего генерировать не нужно. Все типы доступных возвращаемых, как ActionResult, вы можете найти здесь, а наиболее часто используемые перечислим: ViewResult, PartialViewResult, RedirectResult, RedirectToRouteResult, ContentResult, JsonResult, FilePathResult, FileContentResult и FileStreamResult.
View initialization and rendering
Рассмотрим отдельно обработку и рендеринг представления, потому что это отличается от остального возвращаемого результата. Тип ViewResult и PartialViewResult представляют собой реализацию интерфейса IView и рендерятся соответствующим движком для представлений.
Отображается все наше представление благодаря движку представления, то есть классу, который наследуется от интерфейса IViewEngine.  Например, стандартным движком по умолчанию есть WebFormViewEngine, шаблоны которого являются страницами WebForm (.aspx). Вы можете для примера определить свой собственный движок и использовать его. Пример такого движка вы можете найти по ссылке Creating a custom View Engine for ASP.NET MVC leveraging Text Template (T4) engine for rendering the view. Использовать это дело довольно просто. Для этого нужно открыть файл Global.asax, затем в методе Application_Start добавить следующий код:
protected void Application_Start()
{
    //Remove All View Engine including Webform and Razor
    ViewEngines.Engines.Clear();
    //Register Your Custom View Engine
    ViewEngines.Engines.Add(new CustomViewEngine());
    //Other code is removed for clarity
Здесь первым делом вы чистите старые движки, и вторым шагом добавляете свои.
Ну и напоследок, активизируются разные Razor хелперы, которые вы неоднократно видели, наверное, как, например @Html.LabelFor и т.д. Это актуально не только для Razor движка, просто я его упомянул в качестве примера.

Итоги
В завершение хотелось бы добавить схему, как это все работает.

Схема взята со статьи Detailed ASP.NET MVC Pipeline. На данной схеме не хватает некоторых мелких деталей, но в целом, она достойно демонстрирует принцип работы ASP.NET. Надеюсь, что статья получилась не очень сухой. Буду рад ответить на ваши вопросы.Удачи вам на пути профессионала ASP.NET MVC!

No comments:

Post a Comment