Создание асинхронных модулей и HTTP-обработчиков

171

В этой статье мы продолжим разрабатывать проект AsyncApp, который начали ранее, и добавим в него асинхронные модули и HTTP-обработчики.

Асинхронные модули

Можно создать модули, которые используют асинхронные методы, хотя этот процесс значительно отличается от создания асинхронных веб-форм. В целях демонстрации мы добавили в проект файл класса под названием AsyncModule.cs с содержимым, представленным в примере ниже:

using System.Net;

using System.Web;



namespace AsyncApp

{

    public class AsyncModule : IHttpModule

    {

        public void Init(HttpApplication app)

        {

            EventHandlerTaskAsyncHelper helper

                = new EventHandlerTaskAsyncHelper(async (src, args) =>

                {

                    if (app.Context.Request.Path == "/DisplayItemValue.aspx")

                    {

                        string content = await new

                           WebClient().DownloadStringTaskAsync("http://professorweb.ru");

                        ((HttpApplication)src).Context.Items["length"] = content.Length;

                    }

                });

            app.AddOnBeginRequestAsync(helper.BeginEventHandler, helper.EndEventHandler);

        }



        public void Dispose()

        {

            // Освобождать нечего

        }

    }

}

Конструктору класса EventHandlerTaskAsyncHelper мы передали лямбда-выражение для соответствия методу, определенному в классе HttpApplication, который применяет шаблон асинхронного программирования, являющийся предшественником класса Task и ключевых слов async и await. Конструктор EventHandlerTaskAsyncHelper - это делегат, принимающий аргументы типа object и EventArgs, которые представляют собой стандарт при обработке событий жизненного цикла ASP.NET.

В классе HttpApplication определен набор методов, позволяющих обрабатывать события жизненного цикла асинхронным образом. Методы именуются согласно шаблону AddOn<Событие>Async - мы используем метод AddOnBeginRequestAsync(), который соответствует событию BeginRequest. Это событие обрабатывается за счет запроса контента URL вида "http://professorweb.ru" и добавления длины ответа в коллекцию HttpContext.Items.

Мы не хотим выполнять асинхронную операцию для каждого запроса, поэтому проверяем, какой путь запрашивается, и применяем класс WebClient только в случае запроса веб-формы DisplayItemValue.aspx (которая вскоре будет добавлена).

Ситуация, когда HTTP-запросы необходимо делать внутри модуля, встречается редко - более распространен сценарий с потенциально длинным или сложным запросом к базе данных.

Модуль должен быть зарегистрирован в файле Web.config:

<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <system.web>

    <compilation debug="true" targetFramework="4.5.1" />

    <httpRuntime targetFramework="4.5.1" />

  </system.web>

  <system.webServer>

    <modules>

      <add name="asyncModule" type="AsyncApp.AsyncModule"/>

    </modules>

  </system.webServer>

</configuration>

Чтобы отобразить значение, сохраненное в коллекции Items, мы создали простую веб-форму по имени DisplayItemValue.aspx, контент которой приведен в примере ниже. Это веб-форма, соответствующая оператору if в классе модуля, а ее запрос приводит к тому, что модуль выполняет асинхронную операцию:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="DisplayItemValue.aspx.cs" 

    Inherits="AsyncApp.DisplayItemValue" %>



<!DOCTYPE html>

<html>

<head runat="server">

    <title></title>

</head>

<body>

    Длина загруженной страницы: <%= Context.Items["length"] %> байт

</body>

</html>

Мы воспользовались фрагментом кода, внутри которого читается значение из коллекции HttpContext.Items, и включили его в ответ, отправляемый браузеру. Запустив приложение и запросив веб-форму DisplayItemValue.aspx, можно получить результат, показанный на рисунке ниже:

Отображение результата, сгенерированного асинхронным модулем

Помните, что мы не улучшаем показатели производительности одиночного запроса - сгенерировать ответ для веб-формы DisplayItemValue.aspx невозможно до тех пор, пока не будут получены данные в модуле и не пройдет остаток последовательности обработки запроса. Обрабатывая событие BeginRequest асинхронно, мы возвращаем поток запроса в пул, так что он может применяться для обслуживания других запросов, пока ожидается поступление данных от удаленного веб-сервера.

Создание асинхронных обработчиков

В дополнение к веб-формам и модулям возможно также создание обработчиков, выполняющих работу асинхронным образом. Нам часто приходилось создавать асинхронные веб-формы и периодически - асинхронные модули, но никогда не возникала необходимость в построении асинхронного обработчика. С учетом этого мы представляем этот прием только ради полноты, а не потому, что мы сочли его полезным.

В примере ниже приведено содержимое добавленного к проекту файла класса AsyncHandler.cs с определением обработчика:

using System.Net;

using System.Threading.Tasks;

using System.Web;



namespace AsyncApp

{

    public class AsyncHandler : HttpTaskAsyncHandler

    {

        public override async Task ProcessRequestAsync(HttpContext context)

        {

            string webResponse

                = await new WebClient().DownloadStringTaskAsync("http://professorweb.ru");

            context.Response.ContentType = "text/plain";

            context.Response.Write(string.Format("Длина загруженной страницы: {0}",

                webResponse.Length));

        }

    }

}

Базовый класс HttpTaskAsyncHandler делает процесс создания асинхронного обработчика простым - нужно только переопределить метод ProcessRequestAsync() и добавить собственный код. В примере с помощью класса WebClient запрашивается URL вида "http://professorweb.ru", как это было в предшествующих примерах. Обработчик понадобится зарегистрировать в файле Web.config, что иллюстрируется в примере ниже:

<?xml version="1.0" encoding="utf-8"?>

<configuration>

  ...

  <system.webServer>

    ...

    <handlers>

      <add name="asyncHandler" type="AsyncApp.AsyncHandler"

          verb="*" path="AsyncHandler"/>

    </handlers>

  </system.webServer>

</configuration>

Мы зарегистрировали обработчик так, что он реагирует на запрос к URL вида /AsyncHandler, как показано на рисунке:

Генерация ответа из асинхронного метода в обработчике

Опять-таки, не забывайте, что асинхронные методы не улучшают показатели производительности отдельных запросов - они просто позволяют освободить поток запроса на время ожидания поступления данных от удаленного веб-сервера.

Пройди тесты
Лучший чат для C# программистов