Andrey on .NET | .NET 7 Preview 4

.NET 7 Preview 4

.NET logoMicrosoft продолжает традицию ежемесячных выпусков предварительных версий .NET 7. Как всегда, кратко рассмотрим, что нового появилось в очередной версии.

Для работы с .NET 7 Preview 4 рекомендуется использовать Visual Studio 17.3 Preview 1. Поддержка Visual Studio for Mac по-прежнему отсутствует.

Где скачать?

.NET 7 Preview 4

Аннотации в Microsoft.Extensions.*

Все библиотеки Microsoft.Extensions.* получили поддержку ссылочных типов, допускающих значение null.

Поддержка OpenTelemetry

Улучшения в очередной раз коснулись OpenTelemetry:

  • Добавлено событие Activity.CurrentChanged, которое можно использовать для получения оповещения о смене текущей Activity.
  • У Activity стали доступны методы EnumerateTagObjects(), EnumerateLinks(), EnumerateEvents() возвращающие Enumerator<T>. Это позволяет перебирать указанные коллекции без дополнительных выделений памяти.

Микросекунды и наносекунды

До .NET 7 минимальной единицей измерения времени был "такт". Это значение, как правило, хранилось в свойстве Ticks.  При этом для получения секунд надо было помнить, что в .NET 1 такт равен 100 наносекундам.

.NET 7 упрощает жизнь разработчикам, добавляя поддержку микросекунд (свойство Microsecond) и наносекунд (Nanosecond) в следующие типы: TimeStamp, DateTime, DateTimeOffset и TimeOnly.

Улучшения System.Text.RegularExpressions

В данной библиотеке, кроме исправления найденных ошибок, появились несколько методов с поддержкой Span<T>:

  • Regex.IsMatch(ReadOnlySpan<char> input) – указывает если ли в строке input хотя бы одно соответствие заданному регулярному выражению.
  • Regex.Count(ReadOnlySpan<char> input) – возвращает число найденных соответствий.
  • Regex.EnumerateMatches(ReadOnlySpan<char> input) – возвращает итератор для перебора всех найденных соответствий.

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

Метрики в Microsoft.Extensions.Caching

В .NET 7 появились метрики для IMemoryCache. Они представлены классом MemoryCacheStatistics, который содержит информацию по попаданиям, промахам, размеру кэша и количеству элементов в нем.

Для включения сбора статистики в метод AddMemoryCache() добавлена опция TrackStatistics, а получить сами данные можно с помощью метода IMemoryCache.GetCurrentStatistics().

Поддержка Tar архивов

Новая библиотека System.Formats.Tar предназначена для чтения и создания архивов в формате tar. Для поддержки данных операций она предоставляет статический класс TarFile.

В .NET 7 Preview 4 реализованы только синхронные методы, но в дальнейшем обещают поддержку асинхронных операций.

Кодогенерация

В .NET 7 Preview 4 содержится ряд исправлений ошибок и улучшений кодогенерации. Полный список можно посмотреть в официальном анонсе.

On Stack Replacement (OSR)

Замена на стеке (On Stack Replacement) позволяет заменить код, который выполняется текущими методами, пока они активны на стеке.

Например, методы, работающие долго, могут быть заменены оптимизированными версиями в процессе их выполнения. Это позволяет существенно (от 10% до 30%) ускорить запуск приложений за счет сокращения времени на первую компиляцию, выполняя её без оптимизации. В дальнейшем OSR может заменить код таких методов на оптимизированные версии непосредственно во время выполнения.

Централизованное управление пакетами NuGet

В .NET 7 "из коробки" поддерживается централизованное управление NuGet пакетами. Это возможность при помощи специального файла задать версии NuGet пакетов, которые будут использоваться во всех проектах текущего решения (solution). Сами проекты по-прежнему определяют необходимые зависимости от NuGet пакетов самостоятельно, но уже без указания конкретных версий. И только при необходимости можно переопределить используемую версию пакета для конкретного проекта.

Критические изменения

Обновлен список критических изменений.

ASP.NET 7 Preview 4

Улучшение производительности HTTP/2

Предыдущие версии ASP.NET Core активно использовали lock при работе с HTTP/2 запросами. С одной стороны это делало код проще, но с другой приводило к увеличению нагрузки на CPU и задержкам из-за ожидания освобождения блокировок. В ASP.NET Core 7 Preview 4 этот код переписан с использованием потокобезопасной очереди, что позволило получить значительный прирост в скорости обработки запросов. Для примера, в тесте с Kestrel + gRPS достигнуто 12-кратное увеличение скорости обработки запросов по сравнению с .NET 6.

Типизированные результаты в минимальных API

Статический класс Microsoft.AspNetCore.Http.Results предоставляет набор методов для создания различных HTTP ответов. Однако, эти методы используют переменную типа object для получения значения. А значит и результат будет использовать этот тип. Например:

return Results.Ok(responseContent);

Данная строка, вне зависимости от типа responseContent, вернет результат типа Ok<object>. Это может быть не критичным для самого приложения, но неудобным для тестирования такого метода и ряде других случаев, так как тип результата всегда будет одинаковый.

В ASP.NET Core 7 Preview 4 появился типизированный вариант – Microsoft.AspNetCore.Http.TypedResults. Например:

var responseContent = new MyConteent(…);
return TypedResults.Ok(responseContent);

Тип результата такого метода будет Ok<MyContent>.

Microsoft.AspNetCore.OpenApi для минимальных API

Новая библиотека предназначена для поддержки OpenAPI спецификации в веб-приложениях с минимальным API. Она содержит метод-расширение WithOpenApi(…), который позволяет создать OpenAPI описание для текущей конечной точки (endpoint). При этом часть информации может быть получена автоматически:

app.MapGet("/users/{id}", (int id) => … )
    .WithOpenApi();

При необходимости описание можно откорректировать или дополнить самостоятельно:

app.MapGet("/users/{id}", (int id) => … )
    .WithOpenApi(op => {
        op.Summary = "Retrieve a user by ID.";
        op.Parameters[0].AllowEmptyValue = false;
        return op;
    });

Метаданные для OpenAPI для минимальных API

В ASP.NET Core 7 появилась возможность определять метаданные, которые будут использоваться для создания описаний классов в документации API. Для этого объявлены два интерфейса:

  • Для классов, описывающих входные параметры и результат обработчиков:
public interface IEndpointMetadataProvider
{
    static abstract void PopulateMetadata(EndpointMetadataContext context);
}
  • Для классов, описывающих входные параметры:
public interface IEndpointParameterMetadataProvider
{
    static abstract void PopulateMetadata(EndpointParameterMetadataContext parameterContext);
}

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

Возврат различных типов из обработчика в минимальных API

ASP.NET Core 7 появился новый универсальный тип результата обработчика:

public class Results<TResult1, TResult2, … TResultN> 
    : IResult, IEndpointMetadataProvider 
{
    … 
}

Он позволяет перечислить все возможные типы возвращаемого значения. Данный класс также реализует IEndpointMetadataProvider , что позволяет использовать метаданные из все указанных типов для создания документации API.

Например:

app.MapGet("/users/{id}", async Results<Ok<User>, NotFound> (int id)
{
    …
    return user is not null
        ? TypedResults.Ok(user)
        : TypedResults.NotFound();
});

Если в проекте используется Swagger, то он отобразит описание для двух типов ответа сервера: Ok<User>, NotFound.

Группы маршрутов в минимальных API

Еще одна новая новинка в ASP.NET Core 7 – группы маршрутов, которые объединяют несколько конечных точек (endpoints), включая их обработчики. Рассмотрим на примере какие возможности они предоставляют:

app.MapGroup("/api").MapV1().RequireAuthorization();
…
public static class V1ApiGroup
{
    public static GroupRouteBuilder MapApiV1(this GroupRouteBuilder group)
    {
         group.GroupPrefix("/v1"); // Общий префикс внутри группы
         group.MapGroup("/users").MapUsersApiV1();
         group.MapGroup("/companies").MapCompaniesApiV1();
         …
         return group;
    }
}

public static class UsersApiV1
{
    public static GroupRouteBuilder MapUsersApiV1(this GroupRouteBuilder group)
    {
        group.MapGet("/", Get);
        group.MapGet("/{id}", Get);
        group.MapPost("/", Create);
        group.MapPut("/{id}", Update);
        group.MapDelete("/{id}", Delete);
        return group;
    } 

    public static async Task<Ok<User[]>> GetAll(…) { … }
    public static async Task<Results<Ok<User>, NotFound>> Get(…) { … }
    public static async Task<Created<User>> Create(…) { … }
    public static async Task<Results<NoContent, NotFound>> Update(…) { … }
    public static async Task<Results<NoContent, NotFound>> Delete(…) { … }
}

public static class CompaniesApiV1 { … }
…

Можно сразу заметить тот факт, что использование групп позволяет удобно структурировать код. В данном примере отдельные класс UsersApiV1 и подобные отвечают за близкие по ответственности конечные точки, в V1ApiGroup содержится описание всего API, а само приложение только подключает API, добавляя нужные настройки. Кроме того, в данном примере продемонстрирован один из подходов к реализации версионности в минимальных API при помощи групп.

Обратите внимание, что группы могут быть вложенными.

Также группы позволяют настраивать поведение сразу всех конечных точек, содержащихся в них. Так в первой строке примера добавляется авторизация на весь API V1.

Клиентский результат в SignalR

ASP.NET Core 7 теперь поддерживает сценарий, когда сервер при помощи SignalR отправляет сообщение клиенту и ожидает ответ. Для этого есть два способа:

  1. Использовать новый метод ISingleClientProxy.InvokeAsync(…).
  2. Определить в интерфейсе, описывающем клиента, нужный метод с возвращаемым значением.
public interface IClient
{
    Task GetValue();
}

public class AppHub : Hub<IClient>
{
    public async Task WaitForMessage(string connectionId)
    {
        int val = await Clients.Single(connectionId).GetValue();
    }
}

Клиенту необходио подписаться на соответствующее событие, используя метод On(…), и вернуть значение: 

hubConnection.on("GetValue", async (message) => {
    const promise = new Promise((resolve, reject) => { … });
    return promise;
});

Стоит отметить, что данная возможность временно не поддерживается в Azure.

Транскодирование JSON в gRPC

Веб-приложения, созданные при помощи ASP.NET Core 7, могут разрешить обращаться к своим gRPC сервисам при помощи обычных REST вызовов с JSON. При этом все необходимые преобразования делаются "под капотом" ASP.NET Core и нет необходимости отдельно создавать REST API.

Использование Program.Main

При создании нового ASP.NET Core проекта теперь можно сгенерировать классический Program.Main(), вместо выражений верхнего уровня, появившихся в C# 10.

Ограничение числа запросов

Еще одна новая возможность ASP.NET Core 7 – ограничение числа запросов. В проект она подключается следующим образом:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseRateLimiter(…);

Лимиты могуть быть настроены как на весь API приложения, так и на его отдельные части.

Entity Framework 7 Preview 4

В новой предварительной версии исправлены найденные ошибки. Кроме того, команда Entity Framework работает над новыми возможностями библиотеки, но в этот раз стала доступна только одна из них.

Предположим, что есть определенная сущность (entity) которая используется как ключ и для которой описано преобразование в скалярный тип. Это вполне типичный сценарий при использовании DDD подхода в разработке. Раньше в таком случае было невозможно использовать автоматическое генерирование значений. Теперь же это ограничение снято. 

Дополнительные ссылки

Официальные анонсы от Microsoft можно найти по следующим ссылкам:

Кроме того, доступна следующая дополнительная информация:

Комментарии (1) -

Алексей 10.09.2022 13:32:46

Спасибо! Интересно было узнать о оновых возможностях

Добавить комментарий