Создание асинхронных модулей и HTTP-обработчиков
171ASP.NET --- ASP.NET Web Forms 4.5 --- Создание асинхронных модулей и HTTP-обработчиков
В этой статье мы продолжим разрабатывать проект 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, как показано на рисунке:
Опять-таки, не забывайте, что асинхронные методы не улучшают показатели производительности отдельных запросов - они просто позволяют освободить поток запроса на время ожидания поступления данных от удаленного веб-сервера.