.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
День 1987. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 1: Обзор. Окончание

Начало

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

Стандартный код для «90% случаев» обрабатывает это неявно; если DoFirstAsync или DoSecondAsync выбрасывают OperationCanceledException, то это исключение также распространяется из DoAsync. Никаких изменений в коде в «90% случаев» не требуется:
async Task DoAsync(
int data,
CancellationToken ct)
{
var myVal = await DoFirstAsync(data, ct);
await DoSecondAsync(myVal, ct);
}

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

Исключение из «90% случаев»
Код в «90% случаев» просто принимает параметр CancellationToken и передаёт его дальше. Из этого правила есть одно заметное исключение: не следует передавать токен отмены в Task.Run.

Причина в том, что семантика сбивает с толку. Многие разработчики передают делегат и токен отмены в Task.Run и ожидают, что делегат будет отменён при отмене токена, но этого не происходит. Токен отмены, передаваемый в Task.Run, просто отменяет планирование делегата в пуле потоков; как только этот делегат начинает работать (что происходит практически сразу), этот токен отмены игнорируется.

Вот что пишут многие разработчики, ошибочно ожидая, что // Что-то делаем будет отменено после его запуска:
async Task DoSomethingAsync(CancellationToken ct)
{
await Task.Run(() =>
{
// Что-то делаем
}, ct);

}

Никогда не передавая CancellationToken в Task.Run (который в любом случае игнорируется, если только не возникает серьёзная конкуренция за пул потоков или токен уже не отменен), мы яснее даём понять, что сам делегат должен реагировать на токен:
async Task DoSomethingAsync(CancellationToken ct)
{
await Task.Run(() =>
{
// Что-то делаем
// IDE сообщает, что ct не используется,
// поэтому делегату нужно его использовать.
});

}


Источник: https://blog.stephencleary.com/2022/02/cancellation-1-overview.html
👍20
День 1988. #ЧтоНовенького
Оценка Кода с Помощью .NET Upgrade Assistant
Я уже писал про инструмент Upgrade Assistant ранее. Сегодня посмотрим, какие обновления он получил.

Независимо от того, выполняете ли вы обновление с .NET Framework до .NET 8 или просто между версиями .NET Core (с .NET 6 или 7 до .NET 8 или 9), .NET Upgrade Assistant поможет вам понять, какие изменения потребуются. Он доступен как расширение Visual Studio или как инструмент командной строки. Теперь в рамках обновления вы получите доступ к мощным функциям оценки кода.

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

Каждая проблема в отчете классифицирована по степени серьезности: обязательная проблема, блокирующее обновление, или дополнительная возможность новой версии .NET.

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

Анализ в Visual Studio
После установки расширения Visual Studio запустить инструмент обновления можно, просто щелкнув правой кнопкой мыши на решении и выбрав пункт Upgrade (Обновить).

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

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

Вы можете просмотреть саммари проблем для всего решения или углубиться в представления конкретного проекта. Здесь вы можете просмотреть подробную информацию о каждом инциденте, получить помощь по их устранению, перейти к их местоположению в коде и так далее. Можно просматривать проблемы по типу проблемы или по компоненту, чтобы понять, что создаёт основную часть проблем в отчёте. Отчёты можно сохранять в форматах HTML, CSV и JSON, чтобы делиться с коллегами.

Источник: https://devblogs.microsoft.com/visualstudio/code-assessment-with-net-upgrade-assistant/
👍9
День 1989. #CSharp13
⚡️BREAKING! Типов-Расширений не Будет в C#13
О типах-расширениях я уже рассказывал, их с помпой представили Мэдс Торгенсен и Дастин Кэмбелл аж на Microsoft Build как главную новинку C# 13. Но позавчера в очередном обзоре новинок Кэтлин Доллард, главный программный менеджер .NET, сообщила, что «разработка и реализация потребуют больше времени. Ищите типы расширений в ранних предварительных версиях C#14 (NET 10)».

Подробности проблемы были описаны в саммари встречи о дизайне языка. Идея с генератором кода расширений, использовавшего Unsafe.As, «была отвергнута архитекторами среды выполнения .NET как принципиально небезопасная. Существуют потенциальные проблемы с псевдонимами, которые могут привести к путанице в частях JIT, особенно в редких сценариях оптимизации, и нельзя гарантировать, что они всегда будут безопасно обрабатываться с учётом предполагаемых вариантов использования. Нужно будет научить среду выполнения обрабатывать эти сценарии. Это долгосрочная функция, и нам потребуется время, чтобы опробовать её с реальными пользователями, прежде чем мы будем полностью уверены, что у нас правильный дизайн, поэтому мы хотели вернуться к чертёжной доске.

Мы видим два основных подхода:
1) Полностью посвятить себя поддержке во время выполнения. Если бы мы хотели пойти по пути поддержки во время выполнения, мы бы не полагались только на Unsafe.As; мы бы обновили среду выполнения, чтобы напрямую разрешить присвоение ref базового типа типу расширения. Это было бы безопаснее на уровне выполнения, поскольку даёт примитив, который можно проверить. Это также может дать лучшую отправную точку для дальнейшей поддержки членов интерфейса. Однако, к сожалению, мы не сможем достаточно обкатать функциональность до выхода .NET 9. Мы не боимся, что обещанные функции не будут выпущены, если они не готовы. Но это, скорее всего, будет означать, что расширения не будут выпущены в стабильной форме, по крайней мере, до выхода .NET 11.

2) Вернуться к рассмотрению расширений как сахара над статическими методами для типов расширений, что очень похоже на современные методы расширения. Здесь возникают повышенные трудности с представлением компилятора: публичная модель (сахар) и внутренняя реализация модели этих типов должны будут конвертироваться между собой. И дальнейшее расширение сахара возможно, но будет иметь некоторые сложности. Также потребуется больше переписывать тела членов, чтобы имитировать семантику методов экземпляра. Преимущества:
- Такая версия может быть выпущена в период с выходом .NET 9, по крайней мере, в предварительной версии.
- Существующие методы расширения фактически могут быть преобразованы в новую форму. Это будет не идеально; в частности, любой класс-держатель методов расширения, имеющий расширения для нескольких типов, скорее всего, не сможет работать.»


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

Что ж, лично я могу только поддержать стремление выпускать только полноценно готовые новые фичи. На примере первичных конструкторов мы уже видели, что получается, когда создатели языка идут по другому пути.

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

Что думаете?

Источники:
-
https://devblogs.microsoft.com/dotnet/csharp-13-explore-preview-features/#update-on-extension-types
-
https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md#extensions
👍13
День 1990. #BlazorBasics
Основы Blazor. Что Такое Blazor? Начало
Blazor — современная технология разработки UI от Microsoft. Он позволяет разрабатывать современные веб-приложения, такие как одностраничные приложения (Single-Page Application - SPA). Blazor - часть платформы .NET, т.е. он имеет доступ к обширной экосистеме .NET: API, языки программирования и среда выполнения.

Технологии
Blazor использует HTML, CSS и C# для создания веб-UI на стороне клиента. Вместо использования специфичных для Microsoft технологий, вроде XAML, для описания UI используются стандартные HTML и CSS. Вместо JavaScript или TypeScript, которые используются в React, Angular, Vue и других веб-фреймворках, Blazor использует C# для реализации поведения приложения.

Модель компонентов
Вот код типичного компонента Blazor:
@page "/counter"
<PageTitle>Счётчик</PageTitle>
<h1>Счётчик</h1>
<p role="status">Счёт: @count</p>
<button class="btn" @onclick="Increment">Увеличить</button>

@code {
private int count = 0;
private void Increment()
{
count++;
}
}

1) Используется стандартный HTML (теги h1, p и button).
2) Код C# либо начинается с символа @, либо заключен в блок кода, начинающийся с @code.
3) Директива @page в первой строке делает этот компонент страницей, доступ к которой возможен по маршруту /counter.
4) Мы определяем свойства в разделе @code компонента, к которым можем получить доступ, используя символ @ перед ним в HTML-шаблоне компонента.
5) Атрибут @onclick, добавленный в HTML-тег кнопки, позволяет связать с кнопкой метод Increment, определённый в блоке кода компонента. При каждом нажатии кнопки метод выполняется.
6) Тег <PageTitle> ссылается на компонент из библиотеки базовых классов. Тот же синтаксис используется при использовании пользовательских компонентов Blazor в вашем проекте.

Как Blazor работает внутри?
В зависимости от модели хостинга (о них в продолжении) Blazor работает по-разному. Вы можете использовать одни и те же компоненты независимо от того, какую модель хостинга вы выберете. Можно создать библиотеку классов, содержащую компоненты Blazor, чтобы использовать их в разных приложениях. Приложение сможет получить доступ к библиотеке классов и использовать компоненты Blazor вне зависимости от модели хостинга.

Почему Blazor — это не Silverlight
Silverlight был откровенным провалом. Но не Microsoft отказалась от этой технологии. Это была архитектура, которая оказалась не в том месте и не в то время. Silverlight был основан на плагине для браузера. Плагины для браузера (вроде Flash) были обычным явлением в те времена, но постепенно от них стали отказываться, не в последнюю очередь из-за проблем с безопасностью.

Blazor основан на открытых веб-стандартах, таких как HTML, CSS и WebAssembly. Очень вероятно, что эти технологии будут поддерживаться в течение многих десятилетий. Кроме того, использование открытых стандартов означает, что без каких-либо плагинов или расширений Blazor работает в каждом браузере в любой операционной системе.
Blazor — это подходящее кроссплатформенное решение пользовательского веб-интерфейса .NET (которым всегда хотел быть Silverlight), но сделанное правильно.

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

Источник:
https://www.telerik.com/blogs/blazor-basics-introduction-blazor-development
👍29
День 1991. #BlazorBasics
Основы Blazor. Что Такое Blazor? Окончание
Начало

Модели хостинга
1. Blazor WebAssembly — наиболее известная модель, в которой код .NET запускается в браузере. При первом запросе к сайту браузер загружает приложение, включая среду выполнения .NET. Код приложения написан на C#, а среда выполнения .NET преобразует код C# в двоичный в WebAssembly во время выполнения.
Браузер клиента хранит WebAssembly, среду выполнения .NET и код приложения. Для этой модели первоначальная загрузка среды выполнения и веб-приложения составляет около 1–5МБ. Общий размер зависит от размера вашего приложения.
Преимущество в том, что после загрузки весь код приложения оказывается на устройстве, а код в WebAssembly может выполняться на стороне клиента. Клиент подключается к серверной части только для получения дополнительных данных. Это делает подход Blazor WebAssembly масштабируемым.

2. Blazor Server был первой моделью хостинга, представленной в .NET Core 3. Веб-приложение запускается на сервере, а клиент подключается с помощью соединения SignalR (веб-сокет).
Браузер клиента хранит Blazor.js. Клиент и сервер взаимодействуют через соединение SignalR. Среда выполнения .NET, API и код приложения содержатся на сервере.
Для этого подхода требуется сервер, способный выполнять код .NET, поскольку приложение запускается на сервере, обычно в ASP.NET Core. Масштабируемость имеет свои ограничения, поскольку каждый клиент подключается к серверу, используя постоянное соединение через веб-сокет с помощью SignalR.
Однако он обеспечивает более быструю загрузку и начальный рендеринг страницы, поскольку с сервера клиенту передаются только HTML и CSS, которые появляются на экране. У вас также есть полный доступ к серверным технологиям, поскольку код выполняется на сервере.
Blazor Server зависит от стабильного сетевого подключения.

3. Blazor Hybrid — новейшая модель хостинга, а также самая сложная. Приложение частично работает на нативной платформе. Другая часть запускается в браузере. Идея состоит в том, чтобы объединить веб-технологии для реализации приложения с нативным доступом к API.
Автономные сценарии могут выиграть от гибридного подхода Blazor. Вы также можете использовать нативные компоненты UI для создания своего приложения. Однако развёртывание и реализация гораздо сложнее по сравнению с другими подходами. В превью .NET 9 включён шаблон гибридного приложения Blazor и MAUI, где вы можете посмотреть эту модель в деле.

Что выбрать?
Все модели хостинга имеют свои сильные и слабые стороны. Лучший совет — использовать модель хостинга, которая соответствует нефункциональным требованиям веб-приложения. Руководство в документации Microsoft поможет сделать выбор.
Для внутреннего приложения с известным количеством пользователей я бы использовал Blazor Server. Можно писать как внешний, так и внутренний код в одном проекте. Код работает во всех браузерах, даже в старых, которые не поддерживают WebAssembly.

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

Источник: https://www.telerik.com/blogs/blazor-basics-introduction-blazor-development
👍13
День 1992. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 15. Расстановка приоритетов по децибелам - не лучшая стратегия
Слышали поговорку: «Смазывают то колесо, которое скрипит»? По аналогии тот, кто громче всех отстаивает свои требования, получает наивысший приоритет.

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

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

Методы определения приоритетов
- трехуровневая классификация: высокий, средний и низкий приоритет;
- классификация MoSCoW (Must, Should, Could, Won’t — обязательно, желательно, возможно, не нужно);
- попарное сравнение требований для сортировки по приоритетам;
- упорядочивание приоритетов похожих требований;
- распределение 100 баллов между требованиями в зависимости от приоритета;
- игра в планирование ;
- аналитические методы ранжирования требований на основе их ценности для продукта и стоимости реализации.

Критерии выбора приоритетов
1. Бизнес-цели

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

2. Группы пользователей
Удовлетворение требований привилегированных групп пользователей больше способствует успеху бизнеса, чем удовлетворение потребностей других групп.

3. Частота использования
Один из способов оценить частоту использования — изучение особенностей применения приложения. Например, какой процент пользовательских сеансов на сайте авиакомпании связан с бронированием, отменой бронирования, проверкой статуса рейса и т.п.? Обычно (но не всегда) наиболее часто используемые операции будут иметь наивысший приоритет.

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍5
День 1993. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 2: Запрос Отмены.

В прошлый раз мы рассмотрели базовый контракт отмены. Теперь рассмотрим, как создавать токены отмены для запроса отмены.

CancellationTokenSource
Некоторые токены отмены предоставляются используемой вами платформой или библиотекой. Например, ASP.NET предоставит вам CancellationToken, который представляет собой неожиданное отключение клиента. Polly может предоставить вашему делегату токен, который представляет собой более общую отмену (например, срабатывание политики тайм-аута).

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

Каждый CancellationTokenSource управляет своим набором токенов. Каждый токен, созданный из CancellationTokenSource, представляет собой небольшую структуру, которая ссылается на свой CancellationTokenSource. Токен отмены может только отвечать на запросы отмены; CancellationTokenSource необходим для запроса отмены. Поэтому запрашивающий код создает CancellationTokenSource и сохраняет ссылку на него (используя эту ссылку позже для запроса отмены), а отвечающий код просто получает CancellationToken и использует его для ответа на запросы отмены.

Таймауты
Одной из распространённых потребностей отмены является реализация тайм-аута. Решение состоит в том, чтобы иметь таймер, который запрашивает отмену по истечении срока его действия. В CancellationTokenSource такое поведение встроено. Вы можете использовать конструктор CancellationTokenSource, который принимает таймаут, или вызвать CancelAfter для существующего CancellationTokenSource:
async Task DoSomethingWithTimeoutAsync()
{
// Создаём CTS, который отменится через 5 минут
using CancellationTokenSource cts
= new(TimeSpan.FromMinutes(5));

// Передаём токен
await DoSomethingAsync(cts.Token);

// В конце метода CTS уничтожается
// Его токены не могут больше использоваться
}


Очистка
Чтобы избежать утечек ресурсов, важно уничтожать (dispose) экземпляры CancellationTokenSource. Очищается несколько видов ресурсов: во-первых, таймер тайм-аута (если он есть); во-вторых, все «слушатели», прикрепленные к токенам отмены. Эта очистка выполняется при отмене или уничтожении CancellationTokenSource. Вы должны убедиться, что либо одно, либо другое выполнено, чтобы избежать утечек ресурсов.

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

Предупреждение: уничтожение связанных источников CancellationTokenSource.
Вызов Cancel или Dispose очистит все регистрации для конкретного CancellationTokenSource. Однако если этот CancellationTokenSource связан с «родительским» CancellationTokenSource, вызов Cancel для «дочернего» CancellationTokenSource приведёт к отмене регистрации в «родительском» CancellationTokenSource. Вы можете вызвать Dispose (или использовать оператор using) для «дочернего элемента», чтобы очистить регистрацию у родителя этого дочернего элемента, разрывая ссылку.

Эта и другие темы, касающиеся связанных CancellationTokenSource, будут рассмотрены далее в этой серии.

Источник: https://blog.stephencleary.com/2022/03/cancellation-2-requesting-cancellation.html
👍16
День 1994. #ЗаметкиНаПолях
Activator.CreateInstance(Type) Может Возвращать Null

Activator.CreateInstance(Type) позволяет создать экземпляр типа, не зная тип во время компиляции. Например, вы можете захотеть создать экземпляр типа на основе файла конфигурации или ввода пользователя. Всегда, когда вы не можете использовать ключевое слово new, вы можете использовать Activator.CreateInstance(Type).

Большинство людей ожидают, что этот метод вернет не-null объект, поскольку вы явно запрашиваете создание нового экземпляра, но это не всегда так. Для типов Nullable<T> Activator.CreateInstance(Type) возвращает значение null.
object? value = Activator.CreateInstance(typeof(int?));
Console.WriteLine(value is null); // true

Заметьте, что такое же поведение наблюдается при использовании ключевого слова new:
object? value = new int?();
Console.WriteLine(value is null); // true


Вы можете использовать null-снисходительный оператор (!), чтобы подавить предупреждение о том, что значение может быть null, если вы уверены, что тип не может быть Nullable<T>:
object? value = Activator.CreateInstance(typeof(object))!;
Console.WriteLine(value.GetHashCode());
Console.WriteLine(value is null); // всегда false

Источник:
https://www.meziantou.net/activator-createinstance-type-may-return-null.htm
👍16
День 1995. #ЧтоНовенького
UUID Версии 7 в .NET 9

Спецификация UUID (универсальные уникальные идентификаторы), также известные как GUID (глобальные уникальные идентификаторы), определяет несколько различных версий UUID.

.NET использует UUID версии 4 в методе Guid.NewGuid(). Это, почти полностью случайное (или псевдослучайное) число. Но в превью 7 .NET 9 мы получим UUID версии 7.

Новый API по-прежнему находится внутри System.Guid:
var guid = Guid.CreateVersion7();

Что аналогично:
var guid = 
Guid.CreateVersion7(Guid.CreateVersion7(DateTimeOffset.UtcNow));


Основным преимуществом является перегрузка с меткой времени, включенная в UUID. Вот как устроен UUID версии 7:
| 48-bit timestamp | 12-bit random | 62-bit random |

Длина осталась прежней, но первые 48 бит занимает метка времени. Основное преимущество заключается в том, что вы можете сортировать UUID по времени их создания, что делает их более подходящими для идентификаторов в базах данных, чем UUID версии 4.

Управление меткой времени
Поскольку метод принимает DateTimeOffset, вызывающая функция может использовать TimeProvider для управления UtcNow:
var uuid = Guid.CreateVersion7(timeProvider.GetUtcNow());

timeProvider можно внедрить через DI и эмулировать в сценариях тестирования.

Интересно, что внутренний код вызывает NewGuid(), а затем заменяет его первые байты на метку времени с помощью Unsafe.AsRef. Это делает CreateVersion7() примерно в 2 раза медленнее, чем NewGuid(), но это в любом случае десятки наносекунд.

CreateVersion7 - довольно странное название метода, как по мне. Во-первых, оно какбэ намекает, что должны быть CreateVersion1, …, CreateVersion6, а их нет. А во-вторых, не соответствует методу NewGuid. То есть предполагается, что вы должны разбираться в версиях UUID, чтобы знать, какой версии UUID в NewGuid. Впрочем, возможно (и надеюсь), что к релизу .NET 9 метод переименуют.

Оригинальное предложение и дискуссия: https://github.com/dotnet/runtime/issues/103658

Источник: https://steven-giesel.com/blogPost/ea42a518-4d8b-4e08-8f73-e542bdd3b983/uuid-v7-in-net-9
👍33
День 1996. #ЗаметкиНаПолях
Кастомизируем Swagger UI

Swagger — это набор инструментов для разработки, документирования и использования RESTful API. Он помогает разработчикам более эффективно проектировать, создавать, документировать и использовать API.

Swashbuckle — проект с открытым кодом, который интегрирует Swagger с приложениями .NET. По умолчанию при создании проекта API в Program.cs создаётся код, который добавляет поддержку Swagger вместе со Swagger UI.
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

Этот UI можно обогатить множеством вещей.

IDocumentFilter
С помощью этого интерфейса можно манипулировать и настраивать весь документ Swagger/OpenAPI перед его визуализацией или доставкой клиентам. Он обеспечивает возможность изменять, добавлять или удалять любую часть документа, например пути, операции, схемы и т. д.

Метод Apply предоставляет доступ к объекту OpenApiDocument, который представляет собой документ Swagger, и объекту DocumentFilterContext, который предоставляет дополнительный контекст и информацию.

Например, вам нужно удалить устаревшую конечную точку из Swagger UI, не удаляя её из кода. Если мы пометим какую-то конечную точку как устаревшую (Deprecated), это не скроет её из Swagger UI. Она будет просто перечёркнута и выделена серым цветом.
public class RemoveObsoleteFilter : IDocumentFilter
{
public void Apply(
OpenApiDocument doc,
DocumentFilterContext ctx) {
var obsPaths = doc.Paths
.Where(p => p.Value.Operations
.Any(op => op.Value.Deprecated))
.Select(p => p.Key)
.ToList();

foreach (var path in obsPaths)
doc.Paths.Remove(path);
}
}

Добавим фильтр в Program.cs:
builder.Services.AddSwaggerGen(c =>
{
c.DocumentFilter<RemoveObsoleteFilter>();
});

Теперь эта конечная точка не появится в Swagger UI.

Также можно добавить дополнительные метаданные ко всем (или избранным) конечным точкам. Например, добавление стандартного заголовка полезно для ограничения скорости, отслеживания запросов или любых других сквозных задач:
public class CustomHeaderFilter : IDocumentFilter
{
public void Apply(
OpenApiDocument doc,
DocumentFilterContext ctx)
{
foreach (var path in doc.Paths.Values)
{
foreach (var op in path.Operations.Values)
{
foreach (var resp in op.Responses.Values)
{
resp.Headers ??=
new Dictionary<string, OpenApiHeader>();

resp.Headers
.Add("X-Custom-Header",
new OpenApiHeader
{
Description = "Custom header",
Schema = new OpenApiSchema
{
Type = "string"
}
});
}
}
}
}
}

Источник:
https://thecodeman.net/blog
👍22
День 1997. #ЗаметкиНаПолях
Добавляем Аудит NuGet в Проекты .NET.

Аудит безопасности для менеджеров пакетов, таких как NuGet, является критически важным процессом для обеспечения безопасности программных проектов. В NuGet есть функция, которая вам поможет. Она может запустить аудит безопасности с помощью команды dotnet restore, которая сверяет ваши зависимости со списком известных уязвимостей из GitHub Advisory Database.

Если обнаружены уязвимости, вы можете обновить затронутые пакеты до более новой версии, содержащей исправления безопасности, или, если обновления недоступны, проверить, как обезопасить код или использовать альтернативный пакет.

Для аудита NuGet требуется .NET SDK 8.0.100 или более поздней версии. Чтобы включить аудит NuGet для ваших проектов .NET, добавьте в файл проекта следующие свойства:
<Project>
<PropertyGroup>
<NuGetAudit>true</NuGetAudit>
<!-- Прямые и транзитивные пакеты -->
<NuGetAuditMode>all</NuGetAuditMode>
<!-- Уровень уязвимостей для отчёта -->
<NuGetAuditLevel>low</NuGetAuditLevel>

<!-- Отменяем сборку в CI при обнаружении уязвимостей -->
<WarningsAsErrors Condition="$(ContinuousIntegrationBuild) == 'true' OR '$(Configuration)' == 'Release'">
(WarningsAsErrors);NU1900;NU1901;NU1902;NU1903;NU1904
</WarningsAsErrors>
</PropertyGroup>
</Project>

Вы также можете проверить наличие уязвимостей в своих проектах с помощью команды
dotnet list package --vulnerable.


Если вы пока не можете устранить уязвимость, можно отключить предупреждение для каждого пакета, используя NoWarn в элементе PackageReference. Если ссылка транзитивная, можно добавить прямую ссылку на пакет с уязвимостью:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="System.Formats.Asn1" Version="8.0.0" NoWarn="NU1903" />
<PackageReference Include="System.Text.Json" Version="8.0.1" />
</ItemGroup>
</Project>

Если у вас глобально включены WarningsAsErrors в проекте, но вы не хотите такого поведения для уязвимостей, используйте WarningsNotAsErrors:
<Project>
<PropertyGroup>
<WarningsNotAsErrors>NU1901;NU1902;NU1903;NU1904</WarningsNotAsErrors>
</PropertyGroup>
<Project>

Источник: https://www.meziantou.net/enable-nuget-auditing-for-your-dotnet-projects.htm
👍17
День 1998. #УрокиРазработки
Уроки 50 Лет Разработки ПО

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

Призрак разбухания проекта
Разбухание часто называют главной причиной того, почему реализация проекта не укладывается в запланированный график. Но вы не узнаете, имеет ли место разбухание проекта, если нет согласованной точки отсчёта: «Вот что мы намерены сделать в этот период времени».

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

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

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍10
День 1999. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 16. Не задокументировав и не согласовав содержимое проекта, нельзя узнать, увеличивается ли его объём. Окончание

Начало

Это входит в рамки проекта?
Поскольку объём так или иначе будет меняться, в каждом проекте должен быть определён практический процесс управления изменениями. Простое добавление требований в список без их анализа может только навредить. Формальный процесс внесения изменений не должен начинаться, пока команда не установит базовые границы определённого объёма работы. До этого момента требования и объём проекта динамично меняются. Во время выполнения каждого этапа работы контроль над изменениями должен становиться всё более строгим, чтобы повысить вероятность достижения базовых целей в соответствии с графиком.

Когда кто-то предлагает новое требование, следует задать вопрос: «Вписывается ли оно в объём?» Есть три возможных ответа.

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

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

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

Расплывчатые требования = расплывчатый объём проекта
Расплывчатое описание требований чревато нечётким пониманием объёма проекта. Из-за двусмысленности одна сторона может думать, что определённая функция явно входит в рамки проекта, а другая – что нет. Расплывчатые термины, такие как «поддержка», «улучшение», «и т. д.», по сути являются неопределёнными.

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍3
День 2000.
Вот добрался до очередного юбилея. А вас почти 5,2К. Это немного, но это честная работа ценю каждого и стараюсь для каждого.

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

Спасибо, что читаете, ещё больше спасибо, что лайкаете, комментируете и шерите. А если вдруг кто захочет поддержать, можно это сделать на Boosty, Patreon или просто закинуть на пивко кофе.
👍83
День 2001. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 3: Обнаружение Отмены. Начало

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

В контракте отмены есть способ сообщить об этом: методы, принимающие CancellationToken, по соглашению должны выдавать исключение OperationCanceledException при их отмене. Это верно для всех методов BCL и должно быть так и в вашем коде.

Ответ на отмену
Самый распространённый сценарий обнаружения отмены — не обрабатывать исключение при отмене. Обычно исключение OperationCanceledException просто игнорируется:
async Task TryDoSomethingAsync()
{
using CancellationTokenSource cts = new();

// Создаём что-то, что может отменять cts

try
{
await DoAsync(cts.Token);
}
catch (Exception ex) when
(ex is not OperationCanceledException)
{
// Обработка исключения
}
}

Код выше обработает непредвиденные ошибки, но проигнорирует исключения отмены.

Если код должен сделать что-то другое при отмене, можно обработать это в блоке catch:
async Task DoSomethingAsync()
{
using CancellationTokenSource cts = new();

// Создаём что-то, что может отменять cts

try
{
await DoAsync(cts.Token);
}
catch (OperationCanceledException)
{
// Обработка отмены
}
catch (Exception ex)
{
// Обработка исключения
}
}

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

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

Источник:
https://blog.stephencleary.com/2022/03/cancellation-3-detecting-cancellation.html
👍8
День 2002. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 3: Обнаружение Отмены. Окончание

Начало

TaskCanceledException
Существует ещё один тип исключения для отмены: TaskCanceledException. Он выбрасывается некоторыми API вместо OperationCanceledException.

Как правило, рекомендуется не использовать TaskCanceledException. Некоторые API просто вызывают OperationCanceledException, даже если они имеют дело с отменёнными задачами. А поскольку TaskCanceledException является производным от OperationCanceledException, код обработчика исключений отмены может просто перехватывать OperationCanceledException, игнорировать TaskCanceledException, и это будет работать везде.

Внимание: не перехватывайте TaskCanceledException. Вместо этого перехватывайте OperationCanceledException.

OperationCanceledException.CancellationToken
OperationCanceledException имеет свойство CancellationToken. Это токен, вызвавший отмену. Это в том случае, если он установлен. Не все API устанавливают это значение для выбрасываемых ими исключений.

Если вашему коду необходимо определить, отменяет ли операцию он сам или это делает какой-то другой код, у вас может возникнуть соблазн использовать это свойство. Но лучше игнорировать его. Когда используются связанные токены отмены (об этом далее в этой серии), вполне возможно, что токен в этом свойстве на самом деле не является основной причиной отмены:
async Task DoSomethingAsync()
{
// Плохой код, не делайте так
using CancellationTokenSource cts = new();

// Создаём что-то, что может отменять cts

try
{
await DoAsync(cts.Token);
}
catch (OperationCanceledException ex)
when (ex.CancellationToken == cts.Token)
{
// Обработка отмены «нашим» токеном
}
}

В коде выше есть проблема: в зависимости от реализации DoAsync возможно, что отмена cts приведёт к тому, что DoAsync выдаст исключение OperationCanceledException, но токен, на который ссылается это исключение, будет отличаться от токена cts.

Если вам действительно необходимо выполнить специальную обработку случая, когда происходит отмена через конкретный токен, попробуйте что-то вроде этого:
async Task DoSomethingAsync()
{
using CancellationTokenSource cts = new();

// Создаём что-то, что может отменять cts

try
{
await DoAsync(cts.Token);
}
catch (OperationCanceledException ex)
when (cts.IsCancellationRequested)
{
// Обработка отмены «нашим» токеном
}
}

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

Внимание: не используйте OperationCanceledException.CancellationToken. Он может работать не так, как вы ожидаете.

Источник: https://blog.stephencleary.com/2022/03/cancellation-3-detecting-cancellation.html
👍12
День 2003. #ЧтоНовенького #CSharp13
Regex.EnumerateSplits
В 6м превью .NET 9 представлен новый метод класса Regex, который позволяет разбивать входные данные по шаблону без выделения памяти.

Класс Regex предоставляет метод Split, по концепции аналогичный методу String.Split. С помощью String.Split вы предоставляете один или несколько разделителей (символов или строк), и реализация разбивает входной текст по этим разделителям. Вот пример использования String.Split:
foreach (var s in "Hello, world! How are you?".Split('w'))
{
Console.WriteLine(s);
}

который выводит:
Hello,
orld! Ho
are you?


Regex предоставляет аналогичный метод Split, но вместо указания разделителя в виде символа или строки он указывается как шаблон регулярного выражения:
foreach (var s in Regex.Split(
"Hello, world! How are you?", "[aeiou]"))
{
Console.WriteLine(s);
}

Вывод:
H
ll
, w
rld! H
w
r
y

?


Однако Regex.Split принимает в качестве входных данных только строки и не поддерживает входные данные в виде ReadOnlySpan<char>, а также выводит полный набор результатов в виде массива строк, что требует выделения как массива строк для хранения результатов, так и выделения строк для каждого результата. В .NET 9 новый метод EnumerateSplits позволяет выполнять ту же операцию, но с входными данными в виде ReadOnlySpan<char> и без какого-либо выделения памяти. Он принимает ReadOnlySpan<char> и возвращает перечислитель (ValueSplitEnumerator) результатов в виде диапазона (Range). В следующем примере показано использование Regex.EnumerateSplits:
ReadOnlySpan<char> input =
"Hello, world! How are you?";
foreach (Range r in
Regex.EnumerateSplits(input, "[aeiou]"))
{
Console.WriteLine($"{input[r]}");
}

Вывод будет такой же, как в предыдущем примере, но без выделения памяти. Здесь r – диапазон, например, {0..1} для первого результата, и на консоль выводится срез входных данных, соответствующий диапазону (это результат типа ReadOnlySpan<char>, поэтому он помещён в интерполированную строку для использования в Console.WriteLine.

Источник: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview6/libraries.md
👍21
День 2004. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Проектирование. Начало

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

Суть проектирования ПО в том, чтобы связать требования с фрагментами кода, но переход даже от чётко описанных требований к конкретным элементам проектного решения не является ни простым, ни очевидным. Одни виды деятельности, например проектирование БД, носят систематический и аналитический характер. Другие более органичны: проектное решение выкристаллизовывается постепенно, по мере того как разработчики исследуют переход от задачи к решению. А проектирование UX/UI включает художественно-творческие подходы, основанные на глубоком понимании человеческого фактора.

Виды проектирования ПО
- архитектурное,
- техническое (низкоуровневое),
- проектирование БД,
- проектирование UX/UI.

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

1. Архитектурное проектирование
Определяет структуру системы и её компоненты, или архитектурные элементы. Элементы состоят из модулей кода, которые могут быть объединены в несколько взаимосвязанных подсистем в крупном продукте. Архитектурное проектирование предусматривает разделение системы на компоненты, определение обязанностей каждого компонента и назначение конкретных требований соответствующим компонентам, а также определение интерфейсов между компонентами.

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

3. Проектирование БД
На этом этапе определяются сущности и отношения между ними, а также элементы данных каждой сущности, их типы, свойства и логические связи, определяются CRUD (create, read, update, delete) процедуры.

4. Проектирование UX/UI
Проектирование пользовательского опыта само по себе является обширной дисциплиной. Архитектура пользовательского интерфейса определяет диалоговые элементы (точки взаимодействия пользователя с системой) и пути навигации между ними, повторяющие сценарии решения пользовательских задач. При техническом проектировании UI учитывается специфика взаимодействий пользователя с продуктом, в том числе продумываются эскизы экрана, внешний вид, элементы управления и свойства отдельных текстовых блоков, графики, поля ввода и экраны вывода результатов. Оба этапа проектирования UI — архитектурное и техническое — определяют воспринимаемую пользователем простоту обучения и использования или в совокупности — удобство использования (UX).

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍2