.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1514. #ЧтоНовенького
Новинки
ASP.NET Core 8 Превью 2. Продолжение
Начало

3. Новый интерфейс IResettable в ObjectPool
Microsoft.Extensions.ObjectPool обеспечивает поддержку объединения экземпляров объектов в памяти. Приложения могут использовать пул объектов, если выделение или инициализация значений требуют больших затрат.

В превью 2 упрощено использование пула объектов путём добавления интерфейса IResettable. Повторно используемые типы часто необходимо возвращать в состояние по умолчанию между использованиями. Типы IResettable автоматически сбрасываются при возврате в пул объектов.
public class ReusableBuffer : IResettable
{
public byte[] Data { get; }
= new byte[1024 * 1024]; // 1 MB

public bool TryReset()
{
Array.Clear(Data);
return true;
}
}

var bufferPool = ObjectPool.Create<ReusableBuffer>();
var buffer = bufferPool.Get();
try
{
await ProcessDataAsync(buffer.Data);
}
finally
{
// Данные автоматически сбрасываются
bufferPool.Return(buffer);
}

4. Повышение производительности передачи по именованным каналам
В превью 1 была добавлена поддержка использования именованных каналов в Kestrel. Именованные каналы — это популярная технология для построения межпроцессного взаимодействия (IPC) между приложениями Windows. Теперь вы можете создать сервер IPC, используя .NET, Kestrel и именованные каналы.
var builder = 
WebApplication.CreateBuilder(args);
builder.WebHost
.ConfigureKestrel(opts =>
{
opts.ListenNamedPipe("MyPipeName");
});

Подробнее про межпроцессное взаимодействие с помощью gRPC в документации.
В превью 2 улучшена производительность соединения именованного канала. Теперь принимаются параллельные соединения и повторно используются экземпляры NamedPipeServerStream.

Источник: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-2/
👍8
День 1515. #Карьера
Менталитет, Ведущий к Успеху в Разработке ПО
Образ мышления, который вы привносите в свою работу, играет важную роль в определении траектории вашей карьеры. Дружелюбное, но участное отношение, активное стремление поддержать и воодушевить коллег может направить на путь личного роста и профессионального успеха. А негативное мышление - нанести ущерб.

С самого начала нас призывают мыслить творчески и разрабатывать более эффективные или действенные решения, чем традиционные подходы. Эта конкурентность может быть полезной, если направить её в правильное русло, но некоторые разработчики заходят слишком далеко, постоянно пытаясь превзойти своих коллег, отстаивая свою точку зрения или стремясь стать лучше других членов команды. Такое отношение может быть нездоровым и, в конечном счёте, пагубным как для отдельного человека, так и для успеха команды.

Часто разработчик убеждён, что конкретный язык программирования, инструмент или система намного превосходят все остальные. Хотя могут быть случаи, когда это верно, важно сохранять непредвзятость и рассматривать все варианты и их потенциальное влияние на конкретный проект. Как разработчики, мы имеем доступ к широкому спектру технологий, каждая из которых имеет свои преимущества и недостатки. Расширяйте свой набор навыков и знакомьтесь с различными инструментами и технологиями, так как это может повысить вашу ценность и сделать вас более адаптируемым к потребностям проекта.

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

Хорошее эмпирическое правило - всегда учиться и воспринимать всё как возможность. Если вы столкнётесь на работе с чем-то, чему нужно научиться, - отлично, - теперь вам платят за то, что вы учитесь и становились лучше. Если вы хотите заполнить свободное время работой над личным проектом, попробуйте что-то, отличающееся от того, что вы делали в прошлом. Даже если проект будет заброшен или используемые навыки не относятся к вашей профессиональной деятельности, это всё равно увеличит ваше понимание того, что вас интересует, и может помочь в будущем.

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

Ещё одним важным аспектом позитивного отношения в индустрии разработки ПО является мышление роста. Это вера в то, что способности и интеллект можно развить благодаря самоотверженности и упорному труду. Такое мышление поощряет постоянное обучение и совершенствование. В проблемах и неудачах люди с установкой на рост чаще видят возможности для развития.

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

Источник: https://betterprogramming.pub/the-mindset-that-leads-to-success-in-software-development-a6b9ac955b75
👍13
День 1516. #Оффтоп
Прекрасного воскресенья вам. Сегодня поговорим об искуственном интеллекте. А поскольку я сам о нём почти ничего не знаю, порекомендую вам два видео с канала Coputerphile. Это беседы с ютубером, специалистом по искусственному интеллекту, Робертом Майлсом. Кстати, вот его канал https://www.youtube.com/@RobertMilesAI

Итак, первое видео (примерно месячной давности) – про ChatGPT https://youtu.be/viJt_DXTfwA.
Роберт объясняет, что такое ChatGPT, чем он отличается от GPT, как работает, почему иногда выдаёт неверные факты, какие интересные и забавные ситуации возникали при его использовании, а также почему они возникали. Почему нельзя «перетренировывать» модели, и что может получиться, если «перетренировать», например, GitHub Copilot?

Второе видео, вышедшее всего несколько дней назад – про Bing Chat https://youtu.be/jHwHPyWkShk (поиск Bing с прикрученным к нему ChatGPT), который в теории должен был усовершенствовать поиск в интернете, добавив к нему ИИ. Но вдруг пользователи стали сталкиваться со странными, откровенно ложными и даже грубыми ответами. Что пошло не так у Майкрософт, почему они выпустили недоделанный продукт, и можно ли его исправить?
👍4
День 1517. #ЗаметкиНаПолях
Параллельная Публикация Уведомлений в MediatR
MediatR — популярная библиотека с простой реализацией паттерна посредник в .NET. С ростом популярности паттерна CQRS MediatR стала популярной библиотекой для реализации команд и запросов.

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

Нам нужен класс, реализующий INotification:
public record OrderCreated(Guid OrderId) : INotification;

А также реализация соответствующего INotificationHandler:
public class OrderCreatedHandler : 
INotificationHandler<OrderCreated>
{
private readonly INotificationService svc;

public OrderCreatedHandler(
INotificationService service)
{
svc = service;
}

public async Task Handle(
OrderCreated notification,
CancellationToken ct)
{
await svc.SendOrderCreatedEmail(
notification.OrderId,
ct);
}
}

Теперь можно публиковать сообщение с помощью IMediator или IPublisher:
await publisher.Publish(
new OrderCreated(order.Id),
cancellationToken);

MediatR вызовет все соответствующие обработчики. До 12й версии MediatR стратегия публикации вызывала каждый обработчик по отдельности. Однако появился новый интерфейс INotificationPublisher, управляющий тем, как вызываются обработчики.

Реализация по умолчанию - ForeachAwaitPublisher:
public class ForeachAwaitPublisher 
: INotificationPublisher
{
public async Task Publish(
IEnumerable<NotificationHandlerExecutor> executors,
INotification notification,
CancellationToken ct)
{
foreach (var e in executors)
{
await e
.HandlerCallback(notification, ct)
.ConfigureAwait(false);
}
}
}
Она вызывает обработчики по одному и завершается неудачей при ошибке в одном из обработчиков.

Но вы также можете использовать TaskWhenAllPublisher (показаны только отличия в реализации метода Publish:
var tasks = executors
.Select(e =>
e.HandlerCallback(notification, ct))
.ToArray();
return Task.WhenAll(tasks);

TaskWhenAllPublisher вызывает все обработчики одновременно и выполняет их все независимо от того, возникали ли в них ошибки. Если вы сохраните задачу, возвращенную TaskWhenAllPublisher, вы можете получить доступ к свойству Task.Exception, содержащему экземпляр AggregateException, и реализовать обработку исключений.

Настройка стратегии публикации происходит в методе AddMediatR.
Если вы хотите использовать стратегию TaskWhenAllPublisher, вы можете:
- указать значение для свойства NotificationPublisher (тогда издатель будет синглтоном),
- указать тип стратегии в свойстве NotificationPublisherType и использовать свойство ServiceLifetime для задания времени жизни:
services.AddMediatR(cfg => {

cfg.NotificationPublisher =
new TaskWhenAllPublisher();

// или
cfg.NotificationPublisherType =
typeof(TaskWhenAllPublisher);
cfg.ServiceLifetime = ServiceLifetime.Transient;
});

Вы также можете реализовать пользовательский экземпляр INotificationPublisher и вместо этого использовать собственную реализацию.

Чем это полезно?
Возможность параллельного запуска обработчиков уведомлений обеспечивает значительное повышение производительности по сравнению с поведением по умолчанию.
Однако обратите внимание, что все обработчики будут использовать одну и ту же область видимости. Если у вас есть экземпляры сервисов, которые не поддерживают конкурентный доступ, вы можете столкнуться с проблемами. К сожалению, одним из таких является EF Core DbContext.

Источник: https://www.milanjovanovic.tech/blog/how-to-publish-mediatr-notifications-in-parallel
👍9
This media is not supported in your browser
VIEW IN TELEGRAM
День 1518. #ЧтоНовенького
Группы Точек Останова
В Visual Studio 17.6 превью 2
появилась новая функция Группы точек останова, позволяющая создавать именованные группы с любым количеством точек останова в них. Так можно настраивать конфигурацию точек и включать/выключать их по отдельности или вместе.

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

Чтобы создать новую группу точек останова, выберите в окне Breakpoints в меню New > Breakpoint Group… (Новая > Группа точек останова…). Затем нужные точки можно либо перетащить в группу или нажать на них правой кнопкой и выбрать “Add to Breakpoint Group” (Добавить в Группу Точек Останова). После этого можно включать/отключать отдельные точки или всю группу (см. видео).

Источник
👍23
День 1519. #Testing
Как Тестировать Контракты HTTP API в .NET
Тесты HTTP API можно называть модульными, интеграционными или компонентными – это не так важно. Важно, чтобы тесты взаимодействовали только с интерфейсом, использующимся в производственном коде.

Если определённая часть вашей системы вызывается только через HTTP API, тест должен делать то же самое. Прямой вызов метода класса контроллера ASP.NET нарушает эту идею.

Убедитесь, что вы проверяете только то, что имеет отношение к конкретному тестовому случаю:
- Если вы ожидаете исключения, убедитесь, что тип исключения правильный, свойства имеют правильные значения, а сообщение соответствует ожиданиям.
- В сообщении об исключении, часто надо проверить только отдельные части. Утверждение WithMessage в Fluent Assertions принимает шаблон сообщения именно по этой причине.
- Если API возвращает конкретный код ошибки HTTP, проверяйте только его и игнорируйте тело.
- Если тест охватывает определённый URL, где имеет значение только определённое свойство результата, игнорируйте остальные.
Это позволяет избегать провалов тестов по несвязанным причинам.

Многие разработчики используют в тестах реальный тип (например, тип DTO) из кода проекта, чтобы сравнивать с ним десериализованный результат, полученный из HTTP API. Обычный аргумент в пользу этого: это удобнее для рефакторинга, так что, например, изменение имени свойства этого типа не нарушит теста.
Но на самом деле это должно ломать тест! Маршрут, заголовки и конкретный JSON, возвращаемый HTTP API, являются контрактом и, следовательно, должны рассматриваться как таковые.

Как быть? Есть два распространённых способа:
- Использовать необработанный JSON. Самый чистый способ, но это сложно, если надо проверить только отдельные части результата.
- Десериализовать результат в анонимный тип определённой структуры. Рассмотрим пример, используя NewtonSoft.Json и Fluent Assertions:
IHost host = GetTestClient();
var response = await host.GetAsync(…);
var body = await
response.Content.ReadAsStringAsync();

var expect = new[] {
new {
State = "Active",
Count = 1
}
}

var actual = JsonConvert
.DeserializeAnonymousType(body, expect);

actual.Should().BeEquivalentTo(expect);

Здесь мы устанавливаем ожидание (expect) с определёнными значениями, а затем используем DeserializeAnonymousType, чтобы NewtowSoft.Json попытался десериализовать JSON в анонимный объект, структура которого определяется объектом expect. BeEquivalentTo использует глубокое сравнение ожидаемого и фактического (actual) объектов.

В System.Text.Json мы можем добиться того же результата:

var actual = JsonSerializer.Deserialize(
body,
expect.GetType(),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});

actual.Should().BeEquivalentTo(expect);

Вы можете инкапсулировать большую часть логики проверки в метод BeEquivalentTo, который принимает множество настроек. Также можно использовать метод Should().BeAs(), предоставляемый библиотекой FluentAssertions.Web.

Источник: https://www.continuousimprover.com/2023/03/test-http-contracts.html
👍13
День 1520. #ЗаметкиНаПолях
Кэширование Вывода в
ASP.NET Core. Начало
Кэширование выходных данных — это первоклассная функция в ASP.NET Core 7. Раньше его приходилось реализовывать самостоятельно. Рассмотрим особенности кэширования и как его реализовать.

Кэширование вывода — можно применить в ASP.NET Core для кэширования часто используемых данных, в основном для повышения производительности. Предотвратив чрезмерные вызовы ресурсоемких зависимостей (например, БД или сетевых API), мы можем значительно улучшить время отклика, что является одним из ключей к масштабированию приложений.

Прежде всего, кэширование вывода (Output Caching) сильно отличается от кэширования ответов (Response Caching):
- Ответственность: кэширование ответов возлагает ответственность за кэширование на клиентов (или промежуточные прокси-серверы) путем установки заголовков кэша. Кэширование вывода возлагает ответственность на сервер.
- Носитель данных: кэширование ответов хранится в памяти, тогда как кэширование вывода имеет множество параметров для настройки хранилища.
- Удаление: поскольку мы контролируем кэширование вывода на сервере, у нас есть возможность удалить эти записи кэша. При кэшировании ответов это сложно, поскольку мы не контролируем поведение клиентов.
- Повторная проверка: кэширование вывода может возвращать код ответа 304 Not Modified вместо кэшированного тела ответа. Это поможет сэкономить трафик.

При этом одно не лучше другого и каждый вид имеет разные варианты использования.

Настройка кэширования вывода
Давайте начнем с настройки самого простого примера кэширования вывода:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddOutputCache();
var app = builder.Build();

app.MapControllers();
app.UseOutputCache();
app.Run();

Мы добавляем промежуточное ПО для кэширования вывода в коллекцию сервисов и конвейер запросов.

Рассмотрим простейший метод контроллера, возвращающий случайное число:
public IResult Get()
=> Results.Ok(Random.Shared.Next(100));

При каждом обновлении число меняется. Но если добавить к методу атрибут [OutputCache], то при обновлении результат будет оставаться прежним.
По умолчанию ответ кэшируется на 1 минуту.

Политики
Политики кэширования вывода позволяют настроить кэширование и в самом атрибуте, но лучше сделать это централизованно в Program.cs:
builder.Services.AddOutputCache(opt =>
{
opt.AddBasePolicy(c =>
c.Expire(TimeSpan.FromSeconds(5)));
opt.AddPolicy("CacheTenSec", c =>
c.Expire(TimeSpan.FromSeconds(10)));
});

Здесь мы добавляем две политики:
- Базовая политика по умолчанию кэширует на 5 секунд.
- Политика CacheTenSec кэширует на 10 секунд.

Если удалить атрибут [OutputCache] из нашего метода действия, мы увидим, что ответ кэшируется на 5 секунд. Если добавить атрибут с указанием политики:
[OutputCache(PolicyName="CacheTenSec")]
то ответ будет кэшироваться на 10 секунд. Политики кэширования позволяют централизовано определять стратегии кэширования, избавляя методы действий от лишних атрибутов.

Стоит отметить, что конфигурация кэша по умолчанию принимает некоторые смелые решения:
- кэшируются только ответы HTTP 200,
- кэшируются только ответы GET/HEAD,
- не кэшируются ответы с аутентификацией или cookie.
Это можно переопределить с помощью создания пользовательской политики кэширования вывода.

Продолжение следует…

Источник:
https://code-maze.com/aspnet-core-output-caching/
👍17
День 1521. #ЗаметкиНаПолях
Кэширование Вывода в
ASP.NET Core. Продолжение
Начало

Ключи кэша
По умолчанию ключом кэша для конечной точки является полный URL. Если добавить параметры строки запроса, ключ также будет включать их значения и отдельно кэшировать элементы. Если мы хотим иметь больший контроль над тем, как создаётся ключ кэша, можно явно это указать.

1. Вариация по запросу
Изменим политику CacheTenSec:
opt.AddPolicy("CacheTenSec", c =>
c.Expire(TimeSpan.FromSeconds(10))
.SetVaryByQuery("par1"));

Здесь мы указываем, что хотим менять ключ кэша для параметра строки запроса par1. Теперь для каждого уникального значения par1 будет возвращаться свой ответ, кэшированный на 10 секунд. Однако другие параметры запроса на это влиять не будут. Например: www.site.com и www.site.com?par2=123 вернут один и тот же кэшированный ответ. Это может быть полезно, если мы хотим чётко указать, как результаты варьируются в зависимости от параметров.

2. Вариация по заголовку
opt.AddPolicy("CacheTenSec", c =>
c.Expire(TimeSpan.FromSeconds(10))
.SetVaryByQuery("par1")
.SetVaryByHeader("X-Client-Id"));

Здесь мы указываем, что хотим, чтобы кэш варьировался в зависимости от значения заголовка X-Client-Id (браузер клиента). Наиболее распространённый вариант использования вариации по заголовку — во время согласования содержимого - вариация по заголовку Accept. Например, можно по-разному кэшировать ответы JSON и XML из-за разных затрат по обработке этих запросов на сервере.

3. Существует также VaryByValue, более сложная конфигурация, которая позволяет изменять ключ по вычисляемому на сервере значению.

Повторная проверка кэша
До сих пор мы возвращали полное тело кэшированного ответа, даже если оно было идентично предыдущему ответу. Но можно просто проинструктировать клиента, что ответ тот же.
Добавим ещё одну конечную точку, используя минимальные API в Program.cs:
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await context.Response.WriteAsync("hello");
}).CacheOutput();

Здесь мы устанавливаем специальный заголовок ответа, называемый ETag, со значением GUID. Если мы сделаем запрос к URL /etag в Postman, мы увидим в ответе:
- заголовок ETag,
- код ответа 200 (ОК),
- тело ответа «hello».

Добавим в запрос специальный заголовок If-None-Match со значением только что полученного ETag (увеличьте время кэширования в настройках, чтобы успеть это сделать). Теперь мы получим ответ 304 и пустое тело ответа. Это связано с тем, что с помощью заголовка If-None-Match клиент проинструктировал сервер, что у него уже есть копия ответа с таким значением ETag, поэтому, если оно не изменилось, не нужно повторно отправлять содержимое, просто нужно сообщить об этом факте с кодом ответа 304. Это очень мощный способ уменьшить объём обработки как на клиенте, так и на сервере.

Окончание следует…

Источник:
https://code-maze.com/aspnet-core-output-caching/
👍9👎1
День 1522. #ЗаметкиНаПолях
Кэширование Вывода в
ASP.NET Core. Окончание
Начало
Продолжение

Удаление кэша
Представим, что данные, которые мы кэшируем, изменились. Здесь у нас два варианта:
- убедиться, что клиенты понимают, что данные могут устареть после определённого периода;
- обновить кэш, удалив элементы.
Первое обычно подходит для коротких периодов кэширования (как в нашей текущей конфигурации), но не для более длительных периодов кэширования.

Добавим ещё одну конечную точку:
[HttpGet("database")]
[OutputCache(PolicyName = "Expensive")]
public string GetDatabase()
=> $"Дорогой вызов БД в: {DateTime.Now:hh:mm:ss}";

И политику (для этой политики добавим тэг tag-expensive:
opt.AddPolicy("Expensive", с =>
с.Expire(TimeSpan.FromMinutes(1))
.Tag("tag-expensive"));

Также добавим тег tag-all в политику по умолчанию:
opt.AddBasePolicy(с =>
с.Expire(TimeSpan.FromSeconds(5))
.Tag("tag-all"));

Для конечной точки /database ответ находится в кэше 1 минуту. Если данные изменились, клиенты всё равно в течение минуты будут получать старые кэшированные значения. Однако мы можем удалить их, используя только что настроенные теги.

Добавим сервисную конечную точку:
[HttpDelete("cache/{tag}")]
public async Task DeleteCache(
IOutputCacheStore cache, string tag)
=> await cache.EvictByTagAsync(tag, default);

Она принимает специальную зависимость IOutputCacheStore и вызывает метод EvictByTagAsync, передавая значение тега из URL. Это удаляет из кэша элементы, соответствующие этому тегу.

Итак, давайте проверим результаты. Используя утилиту, вроде Postman, вызовем сервисную конечную точку:
DELETE …/cache/tag-expensive
Теперь кэш для конечных точек с политикой Expensive очищен.

DELETE …/cache/tag-all
Теперь кэш для конечных точек с политикой по умолчанию очищен.

Это очень мощный инструмент, позволяющий обновлять кэш, когда данные устаревают, а не только по истечении срока кэширования. В нашем случае мы предоставляем эту функциональность через конечную точку, но это не лучшая практика, поскольку мы не должны позволять клиентам очищать кэш. Лучшим решением будет вызов метода EvictByTagAsync во время операции сохранения в БД данных, которые потом будут извлекаться и кэшироваться.

Итого
В этой серии мы обсудили, как настроить OutputCache в ASP.NET. Для большинства сценариев мы использовали значения по умолчанию, включая использование кэша в памяти в качестве хранилища. Однако по мере роста размера приложения и добавления серверов этот вариант становится менее предпочтительным. Есть варианты лучше, включая такой инструмент, как Redis.

Источник: https://code-maze.com/aspnet-core-output-caching/
👍12
День 1523. #ЗаметкиНаПолях
Обработка CancelKeyPress с Помощью CancellationToken
Иногда нужно определить, когда консольное приложение закрывается, чтобы выполнить некоторую очистку. Console.CancelKeyPress позволяет зарегистрировать метод обратного вызова, который выполнится при нажатии Ctrl+C или Ctrl+Break в консоли. Это событие также позволяет предотвратить закрытие приложения, чтобы вы могли выполнить очистку перед завершением работы. Можно использовать Console.CancelKeyPress в паре с CancellationToken для отмены текущих асинхронных операций.

Метод, выполнение которого прерывается:
static async Task DoAsync (
string[] args, CancellationToken ct)
{
try
{
Console.WriteLine("Ожидание…");
await Task.Delay(10_000, ct);
}
catch (OperationCanceledException)
{
Console.WriteLine("Операция отменена");
}
}

Основной текст программы:
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (sender, e) =>
{
// Мы остановим процесс вручную
// с помощью токена отмены
e.Cancel = true;

// … очистка …

// Вызываем отмену на токене
cts.Cancel();
};

await DoAsync(args, cts.Token);

Кроме того, у токена отмены есть метод Register, позволяющий зарегистрировать методы обратного вызова, которые выполнятся в случае отмены токена:
cts.Token.Register(
() => Console.WriteLine("Отмена…"));

Источник: https://www.meziantou.net/handling-cancelkeypress-using-a-cancellationtoken.htm
👍8
День 1524. #ЗаметкиНаПолях
Храните Информацию в Её Высшей Форме. Начало
Может быть несколько представлений некоторой части информации; вы не должны ограничивать себя только одним из них. Вместо этого храните источник этой информации.

Допустим, мы создаём онлайн-кинотеатр и нам нужно хранить продолжительность фильмов в базе. Во внешнем интерфейсе она представлена ​​как «1ч 47мин». Но в каком виде её хранить? В виде строки «1ч 47мин», но что, если мы решим изменить формат на 1:47 или «107 минут»? Поэтому нужно хранить её в форме, которую можно легко преобразовать в любой формат, то есть в виде целого количества минут. Это высшая форма информации о продолжительности фильма.

Этот совет можно перефразировать как: «Храните исходник, а не исполнение.»

Звучит тривиально. Вот более сложный пример. Есть сущность Customer и объект-значение LoyaltyPoints:
public class Customer : Entity 
{
public LoyaltyPoints Points { get; private set; }

public void AddPoints(LoyaltyPoints pts)
=> Points += pts;

public void RedeemPoints(LoyaltyPoints pts)
{
if (Points < 250 || pts > Points)
throw new Exception();
Points -= pts;
}
}

У нас два варианта использования:
1) Когда клиент размещает заказ, объект Order вычисляет баллы лояльности на основе суммы заказа и вызывает AddPoints для клиента.
2) Клиент может использовать баллы лояльности, когда их минимальное значение составляет 250.
Приведённый выше код идеально отвечает этим требованиям.

Допустим, теперь клиент может обновить существующий заказ. Когда товар удаляется из заказа, объект заказа должен рассчитать разницу в баллах лояльности и вычесть её из суммы клиента. Вот три возможных решения:
1) Повторно использовать RedeemPoints для вычитания. Но этот метод проверяет минимальное значение 250 и выдаст исключение для клиента без баллов лояльности.
2) Использовать AddPoints, передав отрицательное число. Но по бизнес-правилам LoyaltyPoints не может быть отрицательным.
3) Ввести отдельный метод, который не проверяет минимум в 250:
public void SubtractPoints(LoyaltyPoints pts)
=> Points -= points;
Но теперь у нас два публичных метода, которые выполняют вычитание, и неочевидно, какой когда использовать. Это является признаком того, что мы раскрываем детали реализации.

Все 3 решения — лишь попытка разобраться с последствиями неправильного дизайна. Нужно хранить исходные данные расчёта, а не его результат. Остаток баллов лояльности - производное от двух частей информации: сколько баллов клиент заработал и сколько он использовал.

Вместо сохранения остатка, нужно хранить два исходных значения и вычислять остаток на лету:
public class Customer : Entity 
{
public LoyaltyPoints Earned { get; private set; }
public LoyaltyPoints Redeemed { get; private set; }

public LoyaltyPoints Points
=> Earned - Redeemed;

public void IncreasePoints(LoyaltyPoints pts)
=> Earned += pts;

public void ReducePoints(LoyaltyPoints pts)
=> Earned -= pts;

}

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

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

Хотя примеры выше различаются, принцип один. Мы вычисляем строку продолжительности фильма на лету из её источника, и также вычисляем оставшиеся баллы из заработанных и использованных. В обоих сценариях мы не замыкаемся на определённом формате. Например, в дополнение к показу покупателю оставшихся баллов система может также отображать общее количество использованных баллов, чтобы показать, сколько он сэкономил.

Окончание следует…

Источник:
https://enterprisecraftsmanship.com/posts/storing-information-in-its-highest-form/
👍19
День 1525. #ЗаметкиНаПолях
Храните Информацию в Её Высшей Форме. Окончание
Начало

Мы можем пойти дальше. Заработанные баллы – это сумма заработанных баллов по всем заказам. А использованные – сумма по всем использованиям. Поэтому мы можем хранить баллы лояльности, полученные/использованные в каждом заказе, в классе Order и список заказов в поле класса Customer:
public class Customer : Entity 
{
public Order[] Orders { get; private set; }

public LoyaltyPoints Earned
=> Orders.Sum(x => x.PointsEarned);

public LoyaltyPoints Redeemed
=> Orders.Sum(x => x.PointsRedeemed);

public LoyaltyPoints Points
=> Earned - Redeemed;

}
Больше нет необходимости увеличивать/уменьшать заработанные баллы, так как они теперь контролируются классом Order.

Однако важно соблюдать баланс. Насколько детальной должна быть исходная информация, зависит от потребностей вашего проекта. В примере выше можно было бы остановиться на двух полях (Earned и Redeemed), если только не появятся новые требования, которые это решение не может удовлетворить.

Баланс здесь заключается между гибкостью с одной стороны, и требованиями к хранилищу, сложностью и производительностью - с другой. Сохранять источник информации - более гибко, но вы заплатите за это дополнительными требованиями к хранилищу, повышенной сложностью и даже снижением производительности:
- Если мы решим предварительно агрегировать данные, мы потеряем в гибкости и потенциально даже теряем ценную информацию (например, с одним полем Points мы уже не можем сказать, сколько всего баллов клиент заработал).
- С другой стороны, мы можем излишне усложнить наше решение, постоянно считая суммы баллов и храня заказы. На практике потребность в дополнительной гибкости может никогда не возникнуть.

Каждый проект отличается и трудно сформулировать общее правило. Можно сказать, что следует хранить источники, пока влияние недостатков от их хранения минимально:
- Для продолжительности фильма нет никакой разницы между сохранением целого числа и строки, так что это не проблема.
- Для баллов лояльности два поля также мало чем отличаются от одного — они оба могут храниться в одной таблице базы данных.
- Сохранение баллов в классе Order может быть уместным, если количество заказов на клиента невелико, и можно сделать Заказ частью агрегата Клиента.
- А если количество заказов на одного клиента велико и нужно сделать Заказ агрегатом, то лучше остановился на решении с двумя полями Earned и Redeemed.

Итого
Храните информацию в ее высшей форме. Другой способ сформулировать это правило: хранить исходник, а не его представление. Однако соблюдайте баланс, следуя этому правилу. Дополнительная гибкость достигается за счёт требований к хранилищу, повышения сложности и снижения производительности.

Источник: https://enterprisecraftsmanship.com/posts/storing-information-in-its-highest-form/
👍12
День 1526. #ЧтоНовенького
Иерархические Данные в Entity Framework Core 8 Превью 2
Пакет EntityFrameworkCore.SqlServer.HierarchyId — это неофициальный способ добавления использования иерархических данных в Entity Framework, который доступен уже несколько лет (версия 1.0.0 пакета доступна с апреля 2020 г.), однако в EF Core 8 preview 2 эта функция имеет официальную реализацию, основанную на этом пакете от сообщества. Новый официальный пакет — Microsoft.EntityFrameworkCore.SqlServer.HierarchyId.

HierarchyId включается путем установки вышеупомянутого пакета и следующего кода в startup приложения:
options.UseSqlServer(
connectionString,
x => x.UseHierarchyId());

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

В самом типе сущности этот тип используется как любой другой тип свойства:
public HierarchyId NodePath { get; set; }

Тип представляет путь сущности в древовидной структуре. Например, для узла с путем /1/2/3:
- / - корень дерева,
- 1 – прародитель узла,
- 2 - родитель узла,
- 3 — идентификатор узла.

Пути /1/2/4 и /1/2/5 являются родственными узлами исходного узла, а /1/3 и /1/4 являются родственными узлам родительского узла. Узлы также можно вставлять между двумя другими узлами, используя десятичные значения. Узел /1/3.5 находится между узлами /1/3 и /1/4.

Хотя этот формат удобочитаем в коде, сам SQL Server использует компактный двоичный формат для хранения этого идентификатора (varbinary).

Тип также имеет некоторые ограничения непосредственно в SQL Server:
- Иерархический идентификатор SQL Server сам по себе не представляет древовидную структуру. Приложение несёт ответственность за присвоение значений hierarchyId так, чтобы отношения между строками в таблице представляли дерево.
- Нет гарантии, что иерархия будет уникальной, поэтому приложения также несут ответственность за обеспечение надлежащего контроля параллелизма.
- Нет ограничений внешнего ключа на значения hierarchyId. Поэтому, например, приложение должно следить за тем, чтобы все потомки узла в иерархии обновляли свои иерархические идентификаторы при удалении их родителя.

Предварительные версии EF8 в настоящее время можно использовать в .NET 6 LTS и .NET 7. Выпуск EF8 согласован со следующей LTS-версией .NET 8, которая запланирована на ноябрь 2023 года.

Источник: https://www.infoq.com/news/2023/04/ef-core-8-preview-2/
👍8
День 1527. #TipsAndTricks #Git
Некоторые Малоизвестные Приемы в Git
Сегодня несколько полезных советов при работе с Git для любителей консоли.

1. Изменение последнего коммита
Когда вы делаете коммит в репозитории Git, вы создаёте новый снимок своего кода. Иногда вы можете обнаружить, что забыли включить файл, допустили опечатку в сообщении коммита или внесли другие небольшие изменения, которые хотели бы включить в последний коммит. Git позволяет изменить последний коммит с помощью флага --amend:
# изменить последний коммит новым сообщением
git commit --amend -m "New message"

# изменить, не меняя сообщения
git commit --amend --no-edit

2. Reflog
Git отслеживает все изменения, которые вы вносите в репозиторий, включая коммиты, слияния и другие операции. Reflog — это журнал всех изменений в репозитории Git, включая все коммиты, изменения веток и другие операции. Вы можете использовать журнал ссылок для восстановления потерянных коммитов, возврата к предыдущему состоянию или отмены перебазирования.
Чтобы просмотреть журнал ссылок, запустите:
git reflog

# возврат к предыдущему состоянию
git reset HEAD@{N}

3. Интерактивное перебазирование
Это мощный инструмент, который позволяет редактировать, изменять порядок или удалять коммиты перед их слиянием с основной веткой. Это особенно полезно, когда вы работаете над функциональной веткой и хотите очистить историю коммитов перед её слиянием с основной веткой:
git rebase -i HEAD~N

# изменить сообщение коммита
pick 1234567 Old message
reword 2345678 New message

# изменить порядок коммитов
pick 1234567 First commit
pick 2345678 Second commit
pick 3456789 Third commit

# удалить коммит
pick 1234567 First commit
drop 2345678 Second commit
pick 3456789 Third commit

4. Git-псевдонимы
Псевдонимы Git позволяют создавать собственные ярлыки для команд Git. Это может сэкономить ваше время и объём ввода, особенно для часто используемых команд:
git config --global alias.ci commit

# использование
git ci -m "Commit message"

5. Git Stash
Git stash позволяет временно сохранять изменения, которые вы ещё не готовы зафиксировать, без создания новой ветки:
git stash

# применить последние сохранённые изменения
git stash apply

# применить выбранные сохранённые изменения
git stash apply stash@{N}

# список всех изменений
git stash list

Больше консольных команд Git в этом посте.

Источник: https://dev.to/atordvairn/some-secret-git-tricks-that-come-in-handy-2k8i
👍17
День 1528. #Карьера
Решение Проблем — Это не Навык

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

Меня всегда беспокоила мысль, что программирование — это просто решение проблем. Я думаю, потому что так обычно говорят в контексте карьерного совета в сфере разработке ПО, и это типичный ответ на вопрос «Как стать лучше в программировании?»

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

Но многие люди, кажется, повторяют, что программирование — это просто решение проблем, потому что легче сказать это, чем дать конкретный совет. Только когда я наткнулся на этот пост Скотта Янга «Cognitive Load Theory», я понял, почему идея о том, что решение проблем является основным мета-навыком программирования, казалась мне подозрительной.

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

Другими словами, чтобы решить задачу в программировании, вам нужно иметь определённые знания в области, позволяющие её решить. Конечно, Шерлок Холмс с его способностью к дедукции и логическому мышлению мог бы придумать что-то близкое к псевдокоду для решения FizzBuzz, но, если он не знает, как писать на языке программирования, он не сможет действительно решить задачу. Ему нужно знать разницу между чётными и нечётными числами (я уверен, что знает), а также специфический синтаксис языка программирования, например операторы if и что делает оператор деления по модулю %.

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

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

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

Источник: https://betterprogramming.pub/problem-solving-isnt-a-skill-a32c22b71602
Автор оригинала: Jay Cruz
👍11👎4
День 1529. #TipsAndTricks
9 Советов по Fluent Assertions, Которые Сэкономят Вам Время. Начало
Хороший программист всегда думает о будущем ПО, о том, как написать код, который будет легко читать и понимать.

Один из лучших способов улучшить читаемость юнит-тестов — использовать Fluent Assertions https://fluentassertions.com/ — набор методов расширения для утверждений, делающий их более читаемыми и понятными. Fluent API означает, что библиотека полагается на цепочку методов. Вы комбинируете несколько методов в одном операторе без необходимости сохранять промежуточные результаты в переменных. Вот несколько примеров.

1. Идентификация субъекта – Be()
Первый пример простой. Мы хотим проверить, равно ли целое число 5:
int number = 5;
number.Should().Be(5);

Можно добавить сообщение. Отличительной особенностью сообщений Fluent Assertions, является то, что вы добавляете объяснение в середину сообщения:
int number = 6;
number.Should().Be(5,
"because that is the correct amount");

В итоге получается следующее сообщение:
Expected number to be 5 because that is the correct amount, but found 6. (Ожидалось, что number будет 5, потому что это правильное количество, но найдено 6.)

2. Основные утверждения
Для всех ссылочных типов:
sut.Should().BeNull();
….NotBeNull();
….BeOfType<Customer>();
// для сравнения используется Equals
….Be(otherCustomer);

Для строк:
"string".Should().BeNullOrEmpty();
….BeNullOrWhiteSpace();
….NotBeNullOrEmpty();
….NotBeNullOrWhiteSpace();

Для логических типов доступны BeTrue и BeFalse.

Для числовых типов:
number.Should().Be(1);
….NotBe(10);
….BePositive();
….BeNegative();
….BeGreaterThanOrEqualTo(88);
….BeGreaterThan(66);
….BeLessThanOrEqualTo(56);
….BeLessThan(61);
….BeInRange(1, 5);
….NotBeInRange(6, 9);

3. BeEquivalentTo — сравнение графов объектов
BeEquivalentTo — позволяет сравнить, имеют ли два объекта одинаковые свойства с одинаковыми значениями. Два объекта не обязательно должны быть одного типа. Вот примечание к этому методу от авторов:
Объекты эквивалентны, когда оба графа объектов имеют свойства с одинаковыми именами и одинаковыми значениями, независимо от типа этих объектов. Два свойства также равны, если один тип может быть преобразован в другой, и результат равен. Тип свойства коллекции игнорируется до тех пор, пока коллекция реализует System.Collections.Generic.IEnumerable’1 и все элементы коллекции структурно одинаковы. Обратите внимание, что фактическое поведение определяется глобальными значениями по умолчанию, управляемыми FluentAssertions.AssertionOptions.

Довольно часто встречаются классы с одинаковыми свойствами. Это особенно актуально, когда методы API обычно принимают DTO в качестве параметра:
customer.Should()
.BeEquivalentTo(customerDto);

Разница между Be и BeEquivalentTo в том, что Be сравнивает два объекта на основе реализации System.Object.Equals(System.Object). BeEquivalentTo сравнивает свойства и требует, чтобы свойства имели одинаковые имена, независимо от фактического типа свойств.

4. Цепочки утверждений - And
Чтобы связать несколько утверждений, вы можете использовать ограничение And. Пример:
collectionToTest.Should().Contain("first")
.And.HaveElementAt(2, "third");

Продолжение следует…

Источник: https://methodpoet.com/fluent-assertions/
👍17
День 1530. #TipsAndTricks
9 Советов по Fluent Assertions, Которые Сэкономят Вам Время. Продолжение
Начало

5. Методы расширения коллекции
var coll = new List<string>
{ "first", "second" };
var coll2 = new List<string>
{ "first", "second" };

Методов расширения для коллекций множество. Думаю, имена говорят сами за себя:
coll.Should().NotBeEmpty();
….HaveCount(2);
….Equal(coll2);
….Equal("first", "second");
….BeEquivalentTo(coll2);
….NotBeEquivalentTo(
new List<string> { "1", "2" });

….OnlyHaveUniqueItems();
….HaveCountGreaterThan(1);
….HaveCountGreaterThanOrEqualTo(2);
….HaveCountLessThanOrEqualTo(5);
….HaveCountLessThan(5);
….NotHaveCount(1);

….StartWith("first");
….StartWith(
new List<string> { "first" });
….EndWith("second");
….EndWith(
new List<string> { "first", "second" });
….Contain("first")
.And.HaveElementAt(1, "first");

….BeEmpty();
….BeNullOrEmpty();
….NotBeNullOrEmpty();
….ContainInOrder(
new List<string> { "first", "second" });
….NotContainInOrder(
new List<string> { "1", "2", "3" });

….BeInAscendingOrder();
….BeInDescendingOrder();
….NotBeInAscendingOrder();
….NotBeInDescendingOrder();

6. Утверждения даты и времени
DateTime date = new DateTime(2022, 2, 7);

date.Should().Be(7.February(2022).At(0,0));
….BeAfter(6.February(2022).At(23,59));
….BeBefore(8.February(2022).At(0,1));
….BeSameDateAs(7.February(2022));

….NotBe(6.February(2022));
….NotBeAfter(8.February(2022).At(10, 28));
….NotBeBefore(1.January(2022));
….NotBeSameDateAs(27.February(2022));

7. Область действия утверждений - AssertionScope
Вы можете использовать AssertionScope для объединения нескольких утверждений в одно исключение. Это делает код чище и позволяет проверить сразу все утверждения, вместо завершения теста после первой неудачи:
int numberOfDocuments = 5;
using (new AssertionScope())
{
numberOfDocuments.Should().Be(6);
"First Name".Should().Be("Last Name");
}

В примере выше будут отображены оба сбоя и возникнет исключение в момент удаления AssertionScope:
Expected numberOfDocuments to be 6, but found 5.
Expected string to be "Last Name" with a length of 9, but "First Name" has a length of 10, differs near "Fir" (index 0).
(Ожидалось, что numberOfDocuments будет равно 6, но найдено 5.
Ожидалось, что строка будет «Last Name» длиной 9, но «First Name» имеет длину 10 и отличается рядом с «Fir» (индекс 0)
).

Окончание следует…

Источник:
https://methodpoet.com/fluent-assertions/
👍10
День 1531. #TipsAndTricks
9 Советов по Fluent Assertions, Которые Сэкономят Вам Время. Окончание
Начало
Продолжение

8. Проверка исключений
Теперь проверим, что метод выдаёт исключение:
Action act = () => sut.BadMethod();
act.Should().Throw<ArgumentException>();

И наоборот:
Action act = () => sut.GoodMethod();
act.Should()
.NotThrow<NullReferenceException>();

Также поддерживаются асинхронные версии:
Func<Task> act = () => sut.iss.onethodAsync();

await act.Should()
.ThrowAsync<ArgumentNullException>();

await act.Should().NotThrowAsync();

9. Создание утверждений
Вы можете написать свои утверждения, которые проверяют ваши классы и терпят неудачу, если условие не выполняется. Следующее утверждение ищет символ @ в поле адреса электронной почты. Переменная email представляет собой строку, поэтому создадим метод расширения для StringAssertions:
public static class EmailStringExtensions
{
public static AndConstraint<StringAssertions>
ContainAtSign(
this StringAssertions email,
string because = "",
params object[] becauseArgs)
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.ForCondition(email.Subject.Contains('@'))
.FailWith(
"Expected email to contain @{reason}.");

return new AndConstraint<StringAssertions>(email);
}
}

Использование:
string email = "[email protected]";
email.Should()
.ContainAtSign("because that’s valid email");

Также можно создавать утверждения для ваших классов, наследуя от ReferenceTypeAssertions.

Итого
Недостаточно знать, как писать юнит-тесты, необходимо писать читаемые тесты. Один из лучших способов — использовать Fluent Assertions. Эта библиотека позволяет писать чётко определённые утверждения, которые позволяют любому, кто читает ваши тесты, понять, что именно они тестируют. Я представил лишь небольшую часть функциональных возможностей Fluent Assertions, их гораздо больше (см. документацию).

Источник: https://methodpoet.com/fluent-assertions/
👍19
День 1532. #ЧтоНовенького
Улучшения Аутентификации и Идентификации в
ASP.NET Core 8
Команда ASP.NET Core совершенствует проверку подлинности, авторизацию и управление идентификацией в .NET 8.

Для аутентификации в ASP.NET Core сейчас есть несколько вариантов:
1. ASP.NET Core Identity.
2. Azure Active Directory (Azure AD).
3. Различные сторонние решения в виде пакетов, контейнеров и облачных сервисов.

Сегодня существуют ограничения на использование ASP.NET Core Identity в SPA-приложениях. Традиционный способ настройки страниц, связанных с идентификацией, заставляет приложение вернуться к серверным веб-страницам, а также использовать внешние (сторонние) пакеты для поддержки аутентификации на основе токенов.

Поэтому планируется устранить зависимость от Duende IdentityServer из шаблонов SPA в .NET 8. IdentityServer остается отличным вариантом для self-hosting сценариев и остаётся бесплатным, если вы соответствуете требованиям community edition. Но существует множество других вариантов, включая OpenIddict или Keycloak. Теперь шаблон приложения будет содержать ссылку на страницу документации с пояснением вариантов реализации аутентификации. Кстати, про Keycloack вчера был доклад на Ozon Tech Meetup.

Многим не нужны сложности OAuth/OpenID Connect, а просто нужна возможность проверки личности пользователя через вход в систему и доступ к ресурсам на основе разрешений. Эта поддержка встроена в платформу ASP.NET Core Identity с момента её выпуска. Она обеспечивает стандартную аутентификацию на основе файлов cookie.

Вот области, которые планируется улучшить в ASP.NET Core 8:
1. Расширить существующую аутентификацию на основе cookie для поддержки настройки в приложениях SPA. Сейчас, чтобы настроить вход в систему, нужно переопределить серверные страницы Identity. Это приводит к непоследовательному взаимодействию с клиентами: переходу от одностраничного веб-приложения к серверному. Команда добавит конечные точки API, которые позволят разработчикам «оставаться внутри SPA».
2. Добавить поддержку аутентификации на основе токенов. Хотя существующее решение на основе cookie работает, решения для аутентификации на основе токенов в наши дни стали гораздо более гибкими. Аутентификация на основе токенов в основном будет соответствовать возможностям и функциональности cookie-решения, но инкапсулировать данные аутентификации в токен, что позволит работать в сценариях, где файлы cookie неуместны или неоптимальны.

Кроме того, для .NET 8 планируется переработка документации: создание единой точки для изучения доступных вариантов, .NET Auth, которая объединит ссылки на учебные пособия и примеры и будет содержать конкретные рекомендации. Например, SPA без внешних зависимостей предъявляет требования к аутентификации отличные от бизнес-приложения с серверной частью, базой данных, сторонними API-зависимостями и входом через социальные сети.

Всё это находится в стадии подготовки, а пока разработчики предлагают вам внести вклад в процесс улучшения аутентификации в ASP.NET Core, пройдя опрос, который должен занять у вас не более 10 минут.

Источник: https://devblogs.microsoft.com/dotnet/improvements-auth-identity-aspnetcore-8/
👍19
День 1533. #Курсы
.NET Day
Сегодня пройдёт онлайн митап .NET Day. Желающие узнать что-то новое, регистрируйтесь здесь. А вот программа митапа.

11:00 – 11:45 (Мск.)
Райнер Стропек «Изучение новейших возможностей .NET и C# через создание игры»
Быть в курсе событий важно для разработчиков ПО, но это не должно быть скучно. На этом занятии Райнер Стропек продемонстрирует интересные возможности последних выпусков .NET и C#, создав небольшую игру на основе Skia. Вы узнаете о новостях, связанных с десериализацией JSON, сопоставлением по шаблону, записями, file-scoped типами и многими другими новыми функциями языка. На этом сеансе будет только код, без слайдов. Райнер предполагает, что у вас уже есть хорошие навыки работы с C# и .NET, и вы хотели бы узнать о последних изменениях в платформе.

12:00 – 12:45 (Мск.)
Флориан Раппл «Микрофронтенды с Blazor»
Микрофронтенды стали полезным инструментом для разбиения пользовательского интерфейса на более мелкие фрагменты, которые могут разрабатываться и развёртываться независимыми командами. В настоящее время приложения Blazor по-прежнему разрабатываются в основном в виде монолита. Хотя библиотеки и компоненты могут разрабатываться независимо, их развёртывание по-прежнему осуществляется централизованно. В долгосрочной перспективе это становится узким местом. В этом докладе эксперт по микрофронтендам Флориан Раппл представляет устоявшуюся архитектуру для создания модульных фронтенд-приложений. Он покажет, как эту архитектуру можно реализовать на Blazor для создания динамичного взаимодействия с пользователем.

13:00 – 13:45 (Мск.)
Денни Деклерк «Основы специальных возможностей»
Вы хотите научиться делать доступные веб-сайты для всех людей в мире, включая миллиард людей с ограниченными возможностями, с помощью Blazor? Тогда этот доклад определённо стоит посмотреть. Денни Деклерк расскажет, как создавать интерфейсы PWA и веб-сайтов с помощью Blazor, как сделать веб-сайты, соответствующие WCAG, действительными для всех известных групп инвалидов. Помимо Blazor, вы познакомитесь с основами семантического HTML и ARIA, узнаете о важности ALT-текста для изображений, использовании цвета, веб-сайтах доступных для программ чтения с экрана, а также о веб-сайтах, которые должны быть понятными и не сбивать пользователей с толку.

14:00 – 14:45 (Мск.)
Саймон Пейнтер «По Орегонскому пути с функциональным C#»
В 1971 году трое студентов подумали, что могут оживить лекцию по истории, создав компьютерную игру, в которую студенты могли бы играть, и после нескольких дней работы на HP Time Share BASIC они придумали то, что оказалось важной вехой в истории компьютерных игр - Oregon Trail. Однако в этом докладе акцент будет сделан не столько на исторических компьютерных играх, сколько на .NET и функциональном программировании. Задача, которую автор доклада поставил перед собой, состоит в том, чтобы переработать Oregon Trail на C#, используя следующие ограничения:
- Почти 100% покрытие модульными тестами,
- Никакие переменные не могут менять состояние после его установки,
- Никаких операторов (for, foreach, if, where и т. д.), если только их буквально невозможно избежать.
Кроме того, Саймон продемонстрирует несколько приёмов, которые может предложить функциональное программирование, например функции высшего порядка, функциональные потоки с простыми монадами и хвостовую рекурсию.

Кстати, об этом и о других предстоящих событиях я узнал из календаря предстоящих мероприятий, любезно предоставленного PVS-Studio.
👍5