Как мы уже знаем, все данные запроса передаются в компоненты middleware через объект HttpContext
(контекст запроса), который, помимо прочих полезных свойств, содержит также свойство HttpResponse
, представляющее собой объект, содержащий данные ответа клиенту. Разберемся с этим объектом более детально.
Свойства HttpResponse
Для начала, посмотрим, какие свойства нам доступны у HttpResponse
:
Body |
Возвращает или задает тело ответа в виде объекта класса Stream . |
Body |
Возвращает объект типа PipeWriter для записи тела ответа |
Content |
Возвращает или задает значение заголовка Content-Length ответа. |
Content |
Возвращает или задает значение заголовка Content-Type ответа. |
Cookies |
Возвращает объект , который можно использовать для управления файлами cookie для этого ответа. |
Has |
Возвращает true , если отправка ответа уже началась |
Headers |
Возвращает заголовки ответа. |
Http |
Возвращает объект HttpContext , связанный с этим объектом HttpResponse . |
Status |
Возвращает или задает код ответа HTTP. |
Попробуем использовать эти свойства для формирования ответа клиенту. В предыдущей части мы создали простой конвейер запроса, который выводил на страницу приветствие и текущую дату. Одна из первых проблем, с которыми сталкиваются разработчики, которые только начинают знакомиться с web-приложениями в C# (и не только) — это корректное отображение символов, например, кириллицы на страницах. Иначе говоря — проблема работы с кодировкой символов.
Например, перепишем наше приложение следующим образом:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); //первый middleware app.Use(async (context, next) => { await context.Response.WriteAsync("<html><body> Привет! <br>"); await next.Invoke(); }); //второй middleware app.Run(async context => { await context.Response.WriteAsync($"Текущая дата: {DateTime.Now.ToShortDateString()}.</body></html>"); }); app.Run(); }
Теперь запустим приложение и увидим, что все русские символы превратились в «кракозябры»:
Чтобы указать клиенту кодировку символов, необходимо задать заголовок ответа ContentType
. Сделать это можно несколькими способами. Первый способ:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); //первый middleware app.Use(async (context, next) => { //если отправка ответа ещё не началась if (context.Response.HasStarted == false) { //указываем значение заголовка context.Response.ContentType = "text/html; charset=utf-8"; } await context.Response.WriteAsync("<html><body> Привет! <br>"); await next.Invoke(); }); //второй middleware app.Run(async context => { await context.Response.WriteAsync($"Текущая дата: {DateTime.Now.ToShortDateString()}.</body></html>"); }); app.Run(); }
Второй способ:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); //первый middleware app.Use(async (context, next) => { //если отправка ответа ещё не началась if (context.Response.HasStarted == false) { //указываем значение заголовка context.Response.Headers.ContentType = "text/html; charset=utf-8"; } await context.Response.WriteAsync("<html><body> Привет! <br>"); await next.Invoke(); }); //второй middleware app.Run(async context => { await context.Response.WriteAsync($"Текущая дата: {DateTime.Now.ToShortDateString()}.</body></html>"); }); app.Run(); }
Во втором случае мы воспользовались свойством Response.Headers.ContentType
вместо более короткой записи Response.ContentType
. В любом случае таким образом мы записываем в ответ заголовок Content-Type. Также, перед записью заголовка проверяется началась ли уже отправка ответа клиенту. Делается это по следующей причине: ASP.NET Core не буферизирует текст ответа HTTP. При первом написании ответа:
- Заголовки отправляются вместе с этим фрагментом текста клиенту.
- Изменить заголовки ответов больше невозможно.
Например, если мы напишем так:
app.Use(async (context, next) => { await context.Response.WriteAsync("<html><body> Привет! <br>"); //пробуем изменить заголовок context.Response.Headers.ContentType = "text/html; charset=utf-8"; await next.Invoke(); });
то не только не отправим клиенту заголовок Content-Type
, но и подобная запись вызовет ошибку «Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR», которую можно увидеть в консоли браузера:

Таким образом, заголовки ответа должны отправляться клиенту первыми, до того, как мы начнем отправлять тело ответа и, следовательно, проверка начала отправки ответа клиенту будет не лишней (даже обязательной). Также, одним из предпочтительных вариантов работы с заголовками ответа является использование делегата, запускаемого непосредственно перед отправкой ответа клиенту. Делается это следующим образом:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); //первый middleware app.Use(async (context, next) => { context.Response.OnStarting(() => { //пешем все заголовки за раз context.Response.ContentType = "text/html; charset=utf-8"; //....Прочие заголовки, которые нам будут нужны //устанавливаем статусный код ответа context.Response.StatusCode = 200; return Task.CompletedTask; }); await context.Response.WriteAsync("<html><body> Привет! <br>"); await next.Invoke(); }); //второй middleware app.Run(async context => { await context.Response.WriteAsync($"Текущая дата: {DateTime.Now.ToShortDateString()}.</body></html>"); }); app.Run(); }
Здесь метод OnStarting()
добавляет делегат, который запускается непосредственно перед отправкой клиенту заголовков ответов. Такой подход позволяет, во-первых, сразу записать все заголовки, а, во-вторых, в этом случае не обязательно знать, какой следующий middleware в конвейере.
Методы расширения WriteAsync и WriteAsJsonAsync
У HttpResponse
также имеется ряд методов расширения, которые позволяют формировать ответ клиенту. Так, с одним из методов расширения мы уже познакомились — это метод WriteAsync()
, который мы использовали в наших middleware:
await context.Response.WriteAsync("<html><body> Привет! <br>");
Вторая версия этого метода позволяет определить кодировку символов:
await context.Response.WriteAsync("<html><body> Привет! <br>", System.Text.Encoding.UTF8);
Этот способ не избавляет нас от необходимости указания заголовка Content-Type
.
Не менее полезным может оказаться метод WriteAsJsonAsync()
, который позволяет отправлять клиенту объекты сериализованные в JSON. Например,
public class Person { public int Age { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } app.Run(async context => { Person person = new() { Age = 25, FirstName = "Вася", LastName = "Пупкин" }; await context.Response.WriteAsJsonAsync(person); });
здесь мы отправляем клиенту объект класса Person
в формате JSON. В браузере это будет выглядеть следующим образом:
Используя переопределенные версии метода, мы можем, также, настроить опции сериализации объекта. Вот как может выглядеть полные исходный код приложения, отправляющего клиенту ответ в формате JSON c настроенными опциями сериализации
using System.Text.Json; namespace MyWebSite { public class Person { public int Age { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.Run(async context => { JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true, AllowTrailingCommas = true, }; Person person = new() { Age = 25, FirstName = "Вася", LastName = "Пупкин" }; await context.Response.WriteAsJsonAsync(person, typeof(Person), jsonSerializerOptions, "text/plain; charset=utf-8"); }); app.Run(); } } }
Итого
Сегодня мы изучили некоторые возможности формирования ответов клиенту: научились отправлять клиенту различные заголовки и коды статуса, записывать тело ответа как в обычном текстовом формате и html, так и формировать ответ в формате JSON. В следующей части разберемся с объектом запроса.