.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
День 2090. #юмор
👍14
Все зависимости вашего класса определены как интерфейсы. Конкретные реализации интерфейсов (зависимости) внедряются в ваш класс через конструктор. С какими функциями (свойствами и методами) этих зависимостей может работать ваш класс?
#Quiz #BestPractices
Anonymous Quiz
31%
С функциями, определёнными в любом из интерфейсов, которые реализует внедрённая зависимость
13%
Со всеми функциями конкретного типа внедрённой зависимости
53%
Только с функциями интерфейса зависимости
3%
Только с функциями класса зависимости, но не с функциями, определёнными в интерфейсе
День 2091. #ЗаметкиНаПолях
Task.Factory.StartNew и Длительные Асинхронные Задачи
Допустим, вы хотите реализовать шаблон «производитель-потребитель» на основе каналов для асинхронной обработки элементов:
public class Processor
{
private Channel<string> _channel =
Channel.CreateUnbounded<string>();
private Task _task;

public Processor()
{
_task = Task.Factory.StartNew(async () =>
{
await foreach (var log in
_channel.Reader.ReadAllAsync())
{
// Обработка
Console.WriteLine(log);
}
}, TaskCreationOptions.LongRunning);
}

public void ProcessLog(string log)
{
_channel.Writer.TryWrite(log);
}
}

И поскольку вы знаете, что задача обработки должна выполняться в течение всего процесса, вы используете флаг TaskCreationOptions.LongRunning.

Как думаете, есть здесь проблема? На самом деле, да.

Флаг LongRunning сообщает TPL о необходимости иметь выделенный поток для данного обратного вызова вместо получения потока из пула потоков. Но даже если ваша задача семантически долго выполняется, выделенный поток не будет выполняться долго.

Давайте упростим код и добавим немного трассировки:
static void WriteLine(string msg)
{
Console.WriteLine(
$"""
[{Environment.CurrentManagedThreadId}]
[IsThreadPoolThread: {Thread.CurrentThread.IsThreadPoolThread}]
{msg}.
"""
);
}

var task = Task.Factory.StartNew(async () =>
{
WriteLine("Task started");
await Task.Delay(1000);
WriteLine("Task completed");
}, TaskCreationOptions.LongRunning);

Thread.Sleep(100);
Console.WriteLine(
$"Completed: " + task.IsCompleted);

Console.ReadLine();

Вывод:
[4]
[IsThreadPoolThread: False]
Task started.
Completed: True
[6]
[IsThreadPoolThread: True]
Task completed.

Вот что происходит во время выполнения:
- Task started печатается из выделенного потока.
- Task completed печатается из потока пула потоков.

Задача, по-видимому, завершена до выполнения обратного вызова. Это происходит, потому что Task.Factory.StartNew не является асинхронно-дружественным. Фактический тип переменной задачи — Task<Task>, а родительская задача завершается, когда новый поток начинает выполнять обратный вызов, а не когда завершается сам обратный вызов.

Флаг LongRunning учитывается для запуска первого блока асинхронного метода перед первым await. Await приостанавливает выполнение асинхронного метода, а остальная часть метода планируется в потоке пула потоков планировщиком задач по умолчанию.

Теоретически возможно, что обратный вызов, который вы предоставляете StartNew, имеет длительную часть перед первым await, и вы действительно хотите запустить его в выделенном потоке. Если это так, флаг LongRunning допустим, но это очень редкое явление.

Рекомендации для Task.Factory.StartNew и асинхронных делегатов:
1. Избегайте использования Task.Factory.StartNew с асинхронными делегатами. Если необходимо, используйте метод расширения Unwarp, чтобы получить фактическую базовую задачу.
2. Не используйте флаг LongRunning с Task.Factory.StartNew для асинхронных обратных вызовов. Флаг полезен для синхронных методов, которые блокируют поток, но не для асинхронных методов, где продолжение будет запланировано в пуле потоков.

Источник: https://sergeyteplyakov.github.io/Blog/csharp/2024/10/01/Task.Factory.StartNew-and-long-running-async-tasks.html
👍23
День 2092. #ЧтоНовенького
Разбираем Атрибут OverloadResolutionPriority в C# 13

C# 13 добавляет поддержку нового атрибута OverloadResolutionPriority. Этот атрибут является узкоспециализированным, поэтому большинство из вас не будет его использовать. Тем не менее, это приятное дополнение к языку, и о нём стоит знать, если вы пишете библиотеки.

Рассмотрим простой пример:
Sample.Test(); // Вывод: Test 1

static class Sample
{
public static void Test()
=> Console.WriteLine("Test 1");
}

Если вы захотите обновить сигнатуру и добавить новый необязательный параметр:
Sample.Test(); // Вывод: Test 1

static class Sample
{
public static void Test(
[CallerMemberName]string name = "")
=> Console.WriteLine("Test 1");
}

Но это ломает бинарный код, поскольку метода без параметров больше не существует. Лучшим решением было бы добавление перегрузки, но это не сработает:
Sample.Test(); // Вывод: Test 1

static class Sample
{
public static void Test()
=> Console.WriteLine("Test 1");

public static void Test(
[CallerMemberName]string name = "")
=> Console.WriteLine("Test 2");
}

Больше нет ломающего изменения. Но приложение выведет на консоль «Test 1». Причина в том, что компилятор предпочитает перегрузку без параметров, а не перегрузку с параметром по умолчанию, поскольку она лучше соответствует вызову.

Обратите внимание, что удаление первой перегрузки сломает бинарный код. Вы не можете это делать в библиотеке. Новый атрибут OverloadResolutionPriority позволяет указать приоритет разрешения перегрузки для компилятора. Компилятор выберет совместимую перегрузку с наивысшим приоритетом.
Sample.Test(); // Вывод: Test 2

static class Sample
{
[OverloadResolutionPriority(-1)]
public static void Test()
=> Console.WriteLine("Test 1");

public static void Test(
[CallerMemberName]string name = "")
=> Console.WriteLine("Test 2");
}

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

Вы также можете использовать этот атрибут для приоритизации некоторых перегрузок, которые требуют неявных преобразований:
Sample.Test(100);  // Вывод: byte
Sample.Test(1000); // Вывод: int

static class Sample
{
public static void Test(int a)
=> Console.WriteLine("int");

[OverloadResolutionPriority(1)]
public static void Test(byte a)
=> Console.WriteLine("byte");
}


Источник: https://www.meziantou.net/understanding-overloadresolutionpriority-attribute-in-csharp-13.htm
👍29
День 2093. #Курсы
Центр Обучения Copilot
Copilot меняет способ, которым много людей выполняют ежедневные задачи. Copilot, как ИИ-помощник, позволяет нам быть более креативными и эффективными в широком спектре видов деятельности.

Когда Microsoft выпустила Copilot в марте 2023 года, у пользователей возникли вопросы. Они хотели узнать, как они могут использовать Copilot, чтобы быть более продуктивными на работе и в повседневной жизни. Им было интересно, как использовать Copilot в приложениях Microsoft 365, таких как Word, PowerPoint, Outlook и Teams, и как использовать Copilot разработчикам, специалистам по данным и специалистам по безопасности.

Чтобы ответить на эти и другие вопросы, в мае 2024 года создан Центр Обучения Copilot.

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

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

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

В ближайшее время Microsoft обещают добавить несколько новых ресурсов, каждый из которых призван ещё больше оптимизировать ваш рабочий процесс и улучшить ваш опыт использования Copilot:
- Новый контент об «агентификации» Copilot. Что такое агенты? Это экземпляры Copilot, которые могут действовать независимо, активизируясь событиями (а не только чатом) что позволяет вам автоматизировать и организовывать сложные, длительные бизнес-процессы с меньшим вмешательством человека. Например, Copilot «приёмщика заказов» может управлять сквозным процессом выполнения заказа: от принятия заказа до его обработки и предоставления разумных рекомендаций и замен для отсутствующих на складе товаров, до доставки клиенту.
- Также обещают добавить больше историй, демонстрирующих, как клиенты и партнёры Microsoft помогают своим клиентам использовать Copilot, особенно в таких специализированных отраслях, как финансы, производство и другие.

Источник: https://techcommunity.microsoft.com/t5/microsoft-learn-blog/get-ai-ready-inside-the-copilot-learning-hub/ba-p/4248103
👍4
День 2094. #ЧтоНовенького
Улучшаем Отладку Коллекций с Помощью Редактируемых Выражений
Мне частенько не хватало в отладчике Visual Studio возможности отладки LINQ-выражений или возможности использовать LINQ, чтобы отфильтровать элементы коллекции. Новая функция редактируемых выражений в отладчике Visual Studio позволяет это делать.

Эта функция позволяет использовать текстовое поле выражений в верхней части диалогового окна визуализатора IEnumerable, добавляя в него желаемые LINQ-выражения. Визуализатор обновляется в режиме реального времени, отражая результаты запроса. Можно легко применять различные фильтры или порядки сортировки к коллекциям в зависимости от ваших потребностей.

В сеансе отладки запустите визуализатор IEnumerable, наведя указатель мыши на переменную коллекции или набора данных в отладчике и щёлкнув на значок увеличительного стекла (см. картинку). Кроме того, вы можете щёлкнуть правой кнопкой мыши по переменной и выбрать View Visualizer (Просмотреть визуализатор) в контекстном меню.

Это откроет диалоговое окно визуализатора IEnumerable, в котором вы увидите текстовое поле выражений вверху. Вы можете ввести любое допустимое выражение LINQ в это текстовое поле и нажать <ENTER>, чтобы применить его к коллекции. Визуализатор обновит сетку данных ниже результатами запроса.

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

Источник: https://devblogs.microsoft.com/visualstudio/improve-your-debugger-game-with-editable-expressions/
👍19
День 2095. #ЗаметкиНаПолях #БазыДанных
Типы Ключей

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

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

См. также «Вы Пожалеете, что Использовали Естественные Ключи»

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

Столбец идентификатора клиента может существовать:
- в таблице клиентов как первичный ключ
- в таблице выданных книг как внешний ключ
Столбец идентификатора книги может существовать:
- в таблице книг как первичный ключ
- в таблице выданных книг как внешний ключ

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

Составной (composite) ключ
Это особый тип первичного ключа, который использует содержимое двух или более полей из таблицы для создания уникального значения. Например, номер паспорта не является уникальным, уникальной является комбинация двух полей: серия паспорта и номер паспорта.

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

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

Например, первичным ключом в таблице «товары в заказе» может быть комбинированный ключ, состоящий из идентификатора заказа и идентификатора товара.

Источник: https://www.bbc.co.uk/bitesize/guides/z4wf8xs/revision/4
👍16
День 2096. #ЧтоНовенького
WebStorm и Rider Теперь Бесплатны для Некоммерческого Использования
Думаю, все уже знают об этой новости, но, всё-таки, не могу не упомянуть.

JetBrains объявили об изменении модели лицензирования — WebStorm и Rider теперь бесплатны для некоммерческого использования!

Ранее в этом году бесплатными для некоммерческого использования сделали IDE RustRover и Aqua, теперь распространили эту модель на WebStorm и Rider. Если вы используете эти IDE в некоммерческих целях, таких как обучение, разработка проектов с открытым кодом, создание контента или хобби, теперь вы можете делать это бесплатно.

Для коммерческих проектов ничего не изменится — существующее лицензирование остаётся в силе. Другие IDE JetBrains также не затронуты этим обновлением.

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

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

Важно отметить, что, если вы используете некоммерческую лицензию, вы не можете отказаться от сбора анонимной статистики использования. Они используется для улучшения продуктов JetBrains. Собираемые данные — это исключительно данные об анонимном использовании функций IDE. Они сосредоточены на том, какие действия выполняются и какие типы функциональных возможностей IDE используются.

Источник: https://blog.jetbrains.com/blog/2024/10/24/webstorm-and-rider-are-now-free-for-non-commercial-use/
5👍12
Какой(-ими) IDE вы пользуетесь для .NET разработки?
(возможно несколько ответов, если одна рабочая, другая для пет-проектов)
Anonymous Poll
47%
Rider
51%
Visual Studio
13%
Visual Studio + Resharper
27%
VS Code
1%
Другая (напишу в комментариях)
1%
Только консоль, только хардкор!
👍1
День 2097. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 29. Держитесь подальше от критического пути
Основу планирования проекта составляет определение задач, которые необходимо решить. Многие из этих задач должны выполняться в конкретной последовательности, а часть из них связана друг с другом, поэтому планировщик должен также определить временные зависимости между задачами. Это всё усложняет достижение цели.

Определение критического пути
Планировщики проектов часто рисуют сетевой график (также называемый диаграммой PERT), чтобы показать временные отношения между задачами. На рисунке выше показан такой сетевой график для проекта с шестью задачами, от A до Е. Показана расчётная продолжительность каждой задачи. Сетевые графики содержат гораздо больше информации, например самые ранние и самые поздние даты начала и окончания каждой задачи. Для простоты предположим, что нельзя приступить ни к одной задаче, пока не будут выполнены все предшествующие ей. Т.е. задача Е не может начаться, пока не будут выполнены задачи A, Г и Д.

Если суммировать приблизительную продолжительность задач в различных путях, то можно заметить, что путь ДЕ самый длинный: 5 + 4 = 9 дней (жирные стрелки). Эта последовательность задач является критическим путём и определяет кратчайшую расчётную продолжительность реализации проекта. Если на реализацию какой-либо задачи в критическом пути потребуется больше времени, то дата завершения всего проекта сдвинется на время этой задержки. Новые задачи, добавляемые в критический путь, тоже задерживают завершение проекта.

Задачи, не лежащие на критическом пути, имеют резервное время. Например, задачи A и Г вместе имеют один резервный день: 2 + 2 = 4 дня, что на 1 день меньше, чем продолжительность решения задачи Д, лежащей на критическом пути.

Однако критический путь может измениться. Если на решение задачи А потребовалось 4 дня, что вдвое превышает первоначальную оценку, критическим станет путь АГЕ протяженностью 10 дней (4 + 2 + 4), в результате чего срок окончания проекта на 1 день превысит путь ДЕ.

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

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

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

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

Старайтесь быстро завершать действие, которое, насколько вам известно, может замедлить кого-то ещё и, возможно, весь проект. Старайтесь максимально быстро сойти с критического пути.

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 4.
👍12
День 2098. #Git #TipsAndTricks
Советы по Git: Предыдущие Ветки

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

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

Не зная точного имени ветки, вы можете использовать:
git checkout -

или (начиная с версии Git 2.23):
git switch -


На самом деле, это сокращённая версия следующей команды:
git checkout @{-1}

Эта команда позволяет вернуться к любой предыдущей ветке.

Получение последних 5 использованных веток
Если вы хотите увидеть последние 5 веток, которые вы использовали, вы можете выполнить следующую команду:
git reflog | grep -o 'checkout: moving from [^ ]* to [^ ]*' | awk '{print $NF}' | awk '!seen[$0]++' | head -n 5 

Это работает с git bash и такими ОС, как Linux и MacOS. Идея заключается в использовании reflog для получения истории веток, которые вы извлекали. Команда grep фильтрует вывод, чтобы показывать только ветки, которые вы извлекли. Первая команда awk извлекает имена веток из вывода. Вторая команда awk удаляет дубликаты. Команда head ограничивает вывод последними 5 ветками. Поэтому, если вам нужны только последние 3 ветки, которые вы извлекли, вы можете изменить head -n 5 на head -n 3.

Источник: https://steven-giesel.com/blogPost/bbfb8333-e05a-4de7-88b9-17ac2248d77f/git-tricks-get-the-last-checked-out-branch
👍19
День 2099. #ЗаметкиНаПолях
Реализация Идемпотентных POST-запросов в
ASP.NET Core API
Идемпотентность — важнейшая концепция REST API, которая обеспечивает надёжность и согласованность системы, особенно в распределённых системах, где сетевые проблемы могут привести к повторным запросам. Реализуя идемпотентность, вы предотвращаете дублирование операций, которое может возникнуть из-за повторных попыток клиента.

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

Мы будем использовать стратегию с использованием ключей идемпотентности:
1. Клиент генерирует уникальный ключ для каждой операции и отправляет его в пользовательском заголовке.
2. Сервер проверяет, видел ли он этот ключ раньше:
- ключ новый - обработать запрос и сохранить результат,
- ключ не новый - вернуть сохраненный результат без повторной обработки.
Это гарантирует, что повторные запросы (например, из-за проблем с сетью) будут обработаны на сервере только один раз.

Примечание: если запрос не удается (возвращает 4xx/5xx), мы не кэшируем ответ. Это позволяет клиентам повторять попытки с тем же ключом идемпотентности. Однако это означает, что неудавшийся запрос, за которым следует успешный запрос с тем же ключом, будет успешным — убедитесь, что это соответствует вашим бизнес-требованиям.

Мы можем реализовать идемпотентность для контроллеров с помощью атрибута фильтра действия, реализовав IAsyncActionFilter:
[AttributeUsage(AttributeTargets.Method)]
internal sealed class IdempotentAttribute :
Attribute, IAsyncActionFilter
{
private readonly TimeSpan _cacheMins;

public IdempotentAttribute(int cacheMins = 60)
{
_cacheMins = TimeSpan.FromMinutes(cacheMins);
}

public async Task OnActionExecutionAsync(
ActionExecutingContext ctx,
ActionExecutionDelegate next)
{
// проверяем, передал ли клиент ключ
if (!ctx.HttpContext.Request
.Headers.TryGetValue(
"Idempotence-Key",
out var keyValue) ||
!Guid.TryParse(keyValue, out Guid key))
{
ctx.Result = new BadRequestObjectResult(
"Отсутствует заголовок Idempotence-Key");
return;
}

var cache = ctx.HttpContext
.RequestServices
.GetRequiredService<IDistributedCache>();

// проверяем, обработан ли ключ
var cacheKey = $"Idempotent_{key}";
var cachedResult = await
cache.GetStringAsync(cacheKey);
if (cachedResult is not null)
{
var response = JsonSerializer
.Deserialize<IdempotentResponse>(cachedResult);

var result = new ObjectResult(response.Value) {
StatusCode = response.StatusCode };

ctx.Result = result;

return;
}

// выполняем действие и кэшируем результат
var executedContext = await next();

if (executedContext.Result is
ObjectResult { StatusCode: >= 200 and < 300 } objRes)
{
int status = objRes.StatusCode
?? StatusCodes.Status200OK;
IdempotentResponse response =
new(status, objRes.Value);

await cache.SetStringAsync(
cacheKey,
JsonSerializer.Serialize(response),
new DistributedCacheEntryOptions {
AbsoluteExpirationRelativeToNow = _cacheMins }
);
}
}
}

internal sealed class IdempotentResponse
{
[JsonConstructor]
public IdempotentResponse(
int status, object? value)
{
StatusCode = status;
Value = value;
}

public int StatusCode { get; }
public object? Value { get; }
}

Теперь можно применить атрибут к методу контроллера:
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpPost]
[Idempotent(cacheMins: 60)]
public IActionResult Create(
[FromBody] CreateOrderRequest request)
{
// обработка заказа…

return CreatedAtAction(nameof(GetOrder),
new { id = orderDto.Id }, orderDto);
}
}


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

Источник
👍28
День 2100. #ЗаметкиНаПолях
Реализация Идемпотентных POST-запросов в
ASP.NET Core API. Окончание
Начало

Идемпотентность в минимальных API
В минимальных API аналогично реализуем IEndpointFilter:
internal sealed class IdempotencyFilter(
int cacheMins = 60) : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext ctx,
EndpointFilterDelegate next)
{
// получаем ключ из запроса
if (!ctx.HttpContext.Request
.Headers.TryGetValue(
"Idempotence-Key",
out var keyValue) ||
!Guid.TryParse(keyValue, out Guid key))
{
return Results.BadRequest(
"Отсутствует заголовок Idempotence-Key");
}

var cache = ctx.HttpContext.RequestServices
.GetRequiredService<IDistributedCache>();

// проверяем, обработан ли ключ
var cacheKey = $"Idempotent_{key}";
var cachedResult = await
cache.GetStringAsync(cacheKey);
if (cachedResult is not null)
{
var resp = JsonSerializer
.Deserialize<IdempotentResult>(cachedResult)!;
return new IdempotentResult(
resp.StatusCode, resp.Value);
}

object? result = await next(ctx);

// выполняем действие и кэшируем результат
if (result is IStatusCodeHttpResult
{
StatusCode: >= 200 and < 300
} statusResult
and IValueHttpResult valueResult)
{
var status = statusResult.StatusCode
?? StatusCodes.Status200OK;
IdempotentResult resp =
new(status, valueResult.Value);

await cache.SetStringAsync(
cacheKey,
JsonSerializer.Serialize(resp),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(cacheMins)
});
}

return result;
}
}


Создаём свой тип результата:
[method: JsonConstructor]
internal sealed class IdempotentResult(
int status, object? value) : IResult
{
public int StatusCode { get; } = status;
public object? Value { get; } = value;

public Task ExecuteAsync(HttpContext ctx)
{
ctx.Response.StatusCode = StatusCode;
return ctx.Response
.WriteAsJsonAsync(Value);
}
}

Теперь применим фильтр к конечной точке минимальных API:
app.MapPost("/api/orders", CreateOrder)
.RequireAuthorization()
.WithOpenApi()
.AddEndpointFilter<IdempotencyFilter>();


Ещё одним вариантом является реализация логики идемпотентности в промежуточном ПО.

Замечания
1. Длительность кэширования — сложная задача. Разумное время - от нескольких минут до 24–48 часов в зависимости от вашего конкретного варианта использования.
2. Между проверкой и установкой значения кэша существует небольшое окно состояния гонки. Это может быть проблемой, особенно в API с высоким трафиком. Потокобезопасная реализация с использованием распределённой блокировки отлично работает. Она контролирует ситуацию, когда одновременно поступает несколько запросов. Но это должно быть редким явлением.
3. Для распределённого кэша идеально подходит Redis. Кроме того, он обрабатывает распределенную блокировку.
4. Что делать, если клиент повторно использует ключ идемпотентности с другим телом запроса? Вернуть ошибку. Можно хэшировать тело запроса и сохранять его с ключом идемпотентности. Если хеш нового запроса с тем же ключом отличается от сохранённого, возвращается ошибка. Это предотвращает неправильное использование ключей идемпотентности и поддерживает целостность вашего API.

Источник: https://www.milanjovanovic.tech/blog/implementing-idempotent-rest-apis-in-aspnetcore
👍10👎1
День 2101. #ЗаметкиНаПолях
Использование Singleton в Многопоточных Сценариях. Начало

Основная идея паттерна Одиночка (Singleton) в том, что это класс, предназначенный для создания только одного экземпляра, и он обычно предоставляет простой способ доступа к этому экземпляру.

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

Рассмотрим варианты его реализации.

1. Базовый — не потокобезопасный
Это канонический пример реализации паттерна Singleton.
public sealed class Singleton
{
private static Singleton instance = null;

private Singleton() {}

public static Singleton Instance
{
get
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}

Это не потокобезопасно. Может случиться так, что два потока одновременно проверят экземпляр на null и получат true. В результате каждый создаст экземпляр, что противоречит правилу наличия только одного экземпляра Singleton. Также возможно, что экземпляр будет создан до того, как произойдёт эта проверка, но другие потоки могут не увидеть этот новый экземпляр сразу, если не предпринять определённые шаги, чтобы убедиться, что они это сделают.

2. Используем lock
Сначала поток блокирует общий объект, а затем проверяет, был ли уже создан экземпляр. Если нет, он продолжает и создаёт экземпляр. Этот процесс гарантирует, что все чтения будут происходить после того, как будет получена блокировка, а все записи будут происходить до того, как она будет освобождена. Так только один поток сможет создать экземпляр, потому что пока один поток находится в процессе его создания, никакой другой поток не может войти в эту часть кода. К тому времени, как второй поток доберётся туда, первый уже создаст экземпляр, поэтому проверка на null вернёт false.
public sealed class Singleton
{
private static Singleton instance = null;
private static readonly object lck = new object();

Singleton(){}

public static Singleton Instance
{
get
{
lock (lck)
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
}

Недостатком этого метода является то, что он может замедлить работу, поскольку требует блокировки каждый раз, когда требуется экземпляр.
А нужно ли блокировать каждый раз? Нет.

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

Источник:
https://thecodeman.net/posts/how-to-use-singleton-in-multithreading
👍5
День 2102. #ЗаметкиНаПолях
Использование Singleton в Многопоточных Сценариях. Окончание

Начало

3. Блокировка с двойной проверкой
Первая проверка: первая проверка на null. Если экземпляр не пуст, он возвращается. Эта проверка позволяет избежать накладных расходов на блокировку в большинстве вызовов после инициализации экземпляра.
Блокировка: если экземпляр null, получается блокировка.
Вторая проверка: внутри блокировки выполняется вторая проверка, т.к. другой поток мог инициализировать экземпляр между первой проверкой и получением блокировки. Если экземпляр всё ещё null после второй проверки, он создаётся.
public sealed class Singleton
{
private static Singleton instance = null;
private static readonly object lck = new object();

Singleton() {}

public static Singleton Instance
{
get
{
if (instance == null)
{
lock (lck)
{
if (instance == null)
instance = new Singleton();
}
}
}
return instance;
}
}

Это лучшее решение? Не совсем. Решение сложное и может вызвать проблемы, если его неправильно использовать.

4. Singleton с Lazy
Ленивая инициализация: класс Lazy автоматически обрабатывает ленивую инициализацию экземпляра Singleton. Экземпляр не создаётся, пока он действительно не понадобится. Lazy гарантирует, что экземпляр создаётся потокобезопасным способом, поэтому не нужно использовать блокировки или дважды проверять блокировку. Кроме того, код проще и удобнее для обслуживания, поскольку сложность синхронизации потоков инкапсулирована в классе Lazy.
public class Singleton
{
private static readonly Lazy<Singleton>
instance = new Lazy<Singleton>(
() => new Singleton());

private Singleton() {}

public static Singleton Instance
=> instance.Value;
}

Ключевые преимущества использования Lazy для реализации Singleton:
• Потокобезопасность: Lazy обрабатывает все сложности потокобезопасности, снижая риск ошибок.
• Производительность: как правило, более эффективно, так как не требует явной блокировки (которая может быть затратной).
• Простота: код намного чище и понятнее, что повышает удобство обслуживания.

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

Источник: https://thecodeman.net/posts/how-to-use-singleton-in-multithreading
👍28
День 2103. #ЧтоНовенького
Улучшения Problem Details в
ASP.NET 9
В ASP.NET 8 улучшили обработку исключений, представив IExceptionHandler. В ASP.NET 9 промежуточное ПО обработчика исключений было улучшено для обеспечения большей гибкости и контроля над кодом статуса ответа при использовании Problem Details.

Problem Details становится стандартизированным способом представления информации об ошибках в ответе HTTP API. ASP.NET уже поддерживает Problem Details, но вам нужно включить его. Это можно сделать, зарегистрировав промежуточное ПО Problem Details:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddProblemDetails();

var app = builder.Build();

// Возвращаем страницы с ответом при неудаче
// По умолчанию возвращается пустое тело со статусом
app.UseStatusCodePages();

// Переводим ошибки в ответы Problem Details
app.UseExceptionHandler();

app.Run();

AddProblemDetails в сочетании с UseStatusCodePages достаточно для возврата ответов Problem Details для конечных точек, которые возвращают пустой неуспешный ответ (например, BadRequest). Если конечная точка возвращает неуспешный ответ с телом, это тело будет возвращено. Метод UseExceptionHandler преобразует исключения времени выполнения, возникшие при выполнении конечной точки, в ответы соответствующие формату Problem Details:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "An error occurred while processing your request.",
"status": 500,
"traceId": "00-f942c075462cb925f9f1820ce659036a-9a6b9ad75bfcbf22-00"
}

Без UseExceptionHandler API просто вернёт статус 500 без тела.

Ответы Problem Details можно настраивать, реализовав IExceptionHandler, чтобы предоставить больше информации пользователю. В ASP.NET 9 этот процесс упрощён.

Новое свойство конфигурации StatusCodeSelector делает пользовательскую реализацию обработчика исключений практически ненужной (в большинстве случаев). Вы можете использовать её для более сложных сценариев или для регистрации исключения. StatusCodeSelector позволяет изменять код состояния по умолчанию (500) на основе исключения:
// …
app.UseExceptionHandler(new ExceptionHandlerOptions
{
StatusCodeSelector = ex => ex switch {
UserNotAllowedException =>
StatusCodes.Status401Unauthorized,
UserNotFoundException =>
StatusCodes.Status403Forbidden,
NotImplementedException =>
StatusCodes.Status501NotImplemented,
_ => StatusCodes.Status500InternalServerError
}
});
// …

Так, если приложение выбросит ошибку UserNotFoundException, клиенту вернётся состояние 403 (Запрещено).

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

Итого
StatusCodeSelector позволяет легко менять код состояния по умолчанию (500) на основе типа исключения. Поскольку StatusCodeSelector принимает исключение, другие свойства исключения также могут использоваться для определения кода состояния.

Источник: https://timdeschryver.dev/blog/taking-a-look-at-the-problem-details-enhancements-in-aspnet-9
👍27
День 2104. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 30. Задание либо полностью выполнено, либо не выполнено
Заявление, что программа или задача готова на 90%, является чем-то вроде корпоративной шутки. Также говорят, что первая половина проекта потребляет 90% ресурсов, а вторая — оставшиеся 90%. Такое оптимистичное, но вводящее в заблуждение заявление не позволяет оценить, когда данная часть работы завершится на самом деле. Если вы говорите: «Я закончил всё, кроме...» — значит вы ещё не закончили.

Что означает «готово»?
Что мы имеем в виду, когда говорим, что какая-то часть проекта готова? Приступая к проекту, команда должна перечислить критерии, с помощью которых будут определяться завершённость конкретного элемента работы, задачи или итерации. Контрольные списки помогают установить минимальный объём работы, которую необходимо выполнить, чтобы считать конкретную задачу завершённой. Например, программный компонент полностью реализован, протестирован, интегрирован в продукт и задокументирован, то есть потенциально может быть выпущен для использования клиентами. Отдельные единицы работы, такие как реализация пользовательских историй, либо выполнены на 100%, либо не выполнены; промежуточного состояния не существует.

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

Первый шаг к решению проблемы определения готовности — разбить большие задачи на несколько маленьких подзадач по 4-6 рабочих часов. Эта величина позволяет понять всё, что нужно сделать, чтобы выполнить задачу. Полезно определять их как действия, которые нельзя логически разделить на более мелкие части. Если у вас есть предыдущий опыт выполнения какого-либо действия, вы наверняка сможете точно оценить, сколько времени понадобится для его выполнения. Для менее знакомых, более неопределённых или более сложных действий лучше подойдёт разделение на более мелкие подзадачи.

Следите за своим прогрессом по этим мелким задачам в бинарной форме: «готово/не готово». Не бывает частичной готовности. Готовность незавершённой задачи — 0. Прогресс в большой задаче определяется количеством составляющих её и полностью готовых подзадач. Такое отслеживание прогресса более информативно, чем попытки угадать выполненный процент большого и, возможно, неверно определённого объёма работы.

Отслеживание по статусу требований
Другой вариант мониторинга хода выполнения проекта — отслеживание статуса требований. Каждое требование, добавленное в объём работ, имеет статус в данный момент времени, например: «предложено», «одобрено», «реализовано», «проверено», «отложено» и «удалено». При таком подходе запланированный объём работ считается завершённым, когда каждое добавленное в него требование имеет один из трёх статусов:
- «проверено» (полностью реализовано и протестировано);
- «отложено» (отложено для реализации позднее);
- «удалено» (больше не планируется к реализации).

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 4.
👍8
День 2105. #ЧтоНовенького
Upgrade Assistant Теперь Поддерживает Обновление до CPM
.NET Upgrade Assistant помогает обновлять ваши решения до новых версий .NET. Про его возможности я уже писал тут и тут. Недавно в него добавили новый тип обновления до Централизованного Управления Пакетами (Central Package Management).

Независимо от того, обновляетесь ли вы с .NET Framework до .NET 8 или просто между версиями .NET, .NET Upgrade Assistant поможет понять, какие изменения необходимы, и автоматизирует многие из изменений. Он доступен как расширение Visual Studio или как инструмент командной строки.

Новейшая версия Upgrade Assistant представляет новый тип обновления, позволяющий вам преобразовать всё решение или выбранный набор проектов для использования Nuget Central Package Management (CPM).

Обновление в Visual Studio
Если Upgrade Assistant установлен как расширение в Visual Studio, щёлкните правой кнопкой мыши на узел проекта в обозревателе решений и выберите Upgrade (Обновить). Вы заметите новую функцию обновления проекта — Nuget Central Package Management (CPM).

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

Далее будут предложены некоторые параметры настройки. Если вы также хотите преобразовать выбранные проекты в стиль SDK (используемый, начиная с .NET Core), вы можете установить флажок для этого параметра. По умолчанию веб-проекты не будут преобразованы в стиль SDK. Рекомендуется обновлять эти веб-проекты отдельно, но, если вы хотите включить их сюда, есть возможность сделать это, установив флажок «include web projects to SDK conversion» (включить преобразование веб-проектов в SDK).
Также будут предложены параметры управления пакетами в CPM. Рекомендуется (и по умолчанию включено) транзитивное закрепление (transitive pinning) и указан предлагаемый путь к папке, где будут храниться все централизованные версии пакетов. Кроме того, вы можете добавить JSON-файл конфигурации для определения этих параметров. Вот пример такого файла:
{
"upgrade": {
"settings": {
"cpm": {
"convertProjectsToSdkStyle": false,
"includeWebProjects": false,
"useTransitivePinning": true,
"rootPath": "somePath"
}
}
}
}


Обновление в CLI
Вызовите Upgrade Assistant в командной строке, используя следующую команду в папке решения:
upgrade-assistant upgrade

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

Источник: https://devblogs.microsoft.com/dotnet/dotnet-upgrade-assistant-cpm-upgrade/
👍11
День 2106. #ЗаметкиНаПолях
Не Внедряйте Сторонние Зависимости. Используйте Декораторы. Начало

Когда дело касается обычной сквозной функциональности вроде ведения журнала, отказоустойчивости или кэширования, лучшим решением обычно является применение паттерна Декоратор.

Часто можно видеть, как внедрение зависимости (DI) используется для внедрения, скажем, интерфейса журнала или классов пайплайнов Polly для отказоустойчивости в код приложения. Однако, лучшим решением было бы использование Декораторов.
Вот упрощённый пример:
public class MyApi
{
private ResiliencePipeline _pipeline;
private IService _svc;

public MyApi(
ResiliencePipelineProvider<string> prv,
IService service)
{
_pipeline = prv.GetPipeline("retry-pipeline");
_svc = service;
}

public List<string> GetSomething(
QueryByAttribute query)
{
var result =
_pipeline.Execute(() =>
svc.RetrieveMultiple(query));

return result.Entities
.Cast<string>().ToList();
}
}

Как протестировать такой класс? Да, в методе GetSomething всего пара строк, но это упрощённый код, можно предположить, что метод гораздо длиннее.

Класс MyApi связан с Polly, т.к. ResiliencePipeline определён в этой библиотеке. Связанность — главная причина спагетти-кода. Чтобы писать устойчивый код, вы должны осознавать степень связанности. Самый несвязанный код — это код, который вы можете легко удалить. Polly — прекрасная библиотека, и всё сказанное далее скорее относится ко всем сторонним зависимостям.

Также это не означает, что нельзя использовать высококачественные сторонние библиотеки, такие как Polly. Не стоит становиться жертвой синдрома «not invented here».

Когда дело доходит до классических сквозных проблем, паттерн Декоратор обычно предпочтительнее с точки зрения дизайна, чем внедрение проблемы в код приложения. Приведённый выше пример выглядит безобидным, но представьте, что вы внедряете ResiliencePipeline, логгер, возможно, сервис кэширования… И реальный код приложения в итоге тонет в «коде инфраструктуры».

Дело не в том, что мы не хотим иметь эти сторонние зависимости, а в том, что мы хотим переместить их в другое место.

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

Источник:
https://blog.ploeh.dk/2024/09/02/keeping-cross-cutting-concerns-out-of-application-code/
👍19👎1
Вот и настал день, когда больше нельзя оставаться в стороне. Надо определиться с выбором. Будущее зависит от вас.
Anonymous Poll
68%
Табы
32%
Пробелы