.NET Разработчик
6.69K subscribers
470 photos
4 videos
14 files
2.33K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2667. #ЧтоНовенького #NET11
Улучшения API процессов в .NET 11. Продолжение

Обзор
Проблемы существующего API

Чтобы решить проблему, описанную в предыдущем посте, были добавлены несколько новых функций.

Process.ReadAllText и Process.ReadAllTextAsync
Одновременно обрабатывают стандартный вывод и ошибки, что помогает избежать взаимоблокировок. Они декодируют вывод, используя кодировку, указанную в ProcessStartInfo.Standard[Output/Error]Encoding (или кодировку по умолчанию, если она не указана), и возвращают результат в виде строки. Теперь код с чтением вывода и ошибок процесса стал намного проще:
ProcessStartInfo startInfo = new("dotnet", "--help")
{
RedirectStandardOutput = true,
RedirectStandardError = true
};

using Process p = new() { StartInfo = startInfo };
p.Start();

(string output, string error) = p.ReadAllText();
p.WaitForExit();


Process.RunAndCaptureText и Process.RunAndCaptureTextAsync
Поскольку код выше – очень распространенная практика, эти методы производят запуск процесса, чтение всех выходных данных и ошибок и ожидание завершения процесса в одном вызове:
ProcessTextOutput output = 
Process.RunAndCaptureText("dotnet", ["--help"]);


Process.Run и Process.RunAsync
Не захватывают вывод и ошибки, а просто ждут завершения:
ProcessExitStatus status = Process.Run("dotnet", ["build", "-c", "Release"]);


Process.ReadAllLines и Process.ReadAllLinesAsync
Пригодятся, если нужно получать вывод и ошибки в виде набора строк. Возвращают перечислимый объект ProcessOutputLine:
using Process p = Process.Start(…);
await foreach (var line in p.ReadAllLinesAsync())
{
if (line.StandardError)
Console.ForegroundColor = ConsoleColor.Red;

Console.WriteLine(line.Content);
Console.ResetColor();
}


Process.ReadAllBytes и Process.ReadAllBytesAsync
Позволяют получить вывод и ошибки в виде массивов байт.

Тайм-ауты и отмена
Все вышеупомянутые методы поддерживают тайм-ауты и отмену. Если тайм-аут достигнут или токен отмены отменён до достижения конца потока, методы выбросят исключение TimeoutException или OperationCanceledException соответственно. Методы верхнего уровня RunAndCaptureText[Async] и Run[Async] также попытаются завершить процесс.

Мультиплексирование и другие оптимизации «под капотом»
Новые методы не только проще в использовании, но и быстрее. В фоновом режиме синхронные методы Process.RunAndCaptureText и Process.ReadAll[Bytes/Text] используют мультиплексирование (poll в Unix и WaitForMultipleObjects в Windows) для чтения как из stdout, так и из stderr с использованием одного потока. Они также реализуют ряд других оптимизаций, таких как использование ArrayPool для уменьшения выделения памяти. Асинхронные методы Process.RunAndCaptureTextAsync и Process.ReadAllTextAsync используют асинхронные операции ввода-вывода без блокировки каких-либо потоков.

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

Источник:
https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
👍4
День 2668. #ЧтоНовенького #NET11
Улучшения API процессов в .NET 11. Окончание

Обзор
Проблемы существующего API
Разбор новых методов

Управление жизненным циклом

Process.StartAndForget
Распространено заблуждение, что при очистке переменной процесса, процесс также завершается. Это не так, поскольку Process.Dispose только освобождает ресурсы, связанные с процессом, но не завершает его.

Чтобы упростить запуск процесса без необходимости беспокоиться о его завершении, добавлен метод Process.StartAndForget, который запускает процесс, возвращает его ID и освобождает все связанные с ним ресурсы.
int processId = Process.StartAndForget("notepad.exe");


ProcessStartInfo.KillOnParentExit
Процессы, запущенные родительским процессом, не завершаются автоматически при завершении родительского процесса. Это может привести к появлению «осиротевших» процессов, продолжающих работать в фоновом режиме, что нежелательно во многих сценариях. Для решения этой проблемы добавлено свойство ProcessStartInfo.KillOnParentExit, которое гарантирует завершение дочернего процесса при завершении родительского процесса (включая случаи принудительного завершения и сбоев).

Это достигается за счёт использования специфических для платформы функций, таких как JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE в Windows и PR_SET_PDEATHSIG в Linux и Android. В отличие от других API, поведение немного отличается на разных платформах:
- В Windows используется объект Job, чтобы гарантировать завершение дочернего процесса при завершении родительского процесса. Объекты Job по умолчанию наследуются всеми дочерними процессами, поэтому, если дочерний процесс порождает другой процесс (внука), этот внук также будет завершён при завершении родительского процесса.
- В Linux и Android используется PR_SET_PDEATHSIG для указания сигнала SIGKILL, который ядро отправит дочернему процессу при завершении потока, создавшего процесс. Поскольку как потоки пула потоков, так и пользовательские потоки могут быть завершены в любое время, поддерживается выделенный поток, используемый только для порождения процессов с включенным KillOnParentExit, чтобы гарантировать завершение дочерних процессов при завершении родительского процесса. Таким образом, когда запускается несколько процессов с KillOnParentExit, используется механизм синхронизации, гарантирующий, что выделенный поток запускает только один процесс за раз.

ProcessStartInfo.StartDetached
Свойство ProcessStartInfo.StartDetached позволяет запустить процесс, отсоединённый от родительского процесса, что означает, что он будет продолжать работать, даже если родительский процесс завершится, получит сигнал или будет закрыт терминал. Это достигается с помощью платформенно-специфических функций, таких как флаг DETACHED_PROCESS в Windows и setsid в Unix (PR).

Более того, если StartDetached установлено в true и не указано перенаправление для стандартных обработчиков выводы, они будут перенаправлены на нулевой дескриптор, чтобы избежать ненужного удержания родительских стандартных обработчиков открытыми.

Источник: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
👍2
День 2669. #ЗаметкиНаПолях #Blazor
Создаём Базовый Компонент для Всех Компонентов в Blazor

При разработке Blazor-приложения может понадобиться пользовательский базовый компонент для всех остальных компонентов. Это полезно для совместного использования общих функций, таких как токены отмены, логирование или управление состоянием, во всех компонентах. Вместо добавления @inherits YourBaseComponent в каждый файл Razor, вы можете использовать файл _Imports.razor для глобальной установки базового компонента.

Создадим файл _Imports.razor в папке, к компонентам которой нужно применить базовый компонент. Все файлы Razor в этой папке и её подпапках будут наследовать указанный базовый компонент.
@inherits YourNamespace.CustomComponentBase

Файл _Imports.razor обрабатывается перед любым компонентом Razor в том же каталоге или его подкаталогах. Все компоненты затем автоматически наследуют от CustomComponentBase без необходимости объявлять @inherits в каждом файле.

Пример: CustomComponentBase с CancellationToken
Вот пример базового компонента, который предоставляет CancellationToken всем производным компонентам. Это полезно для отмены асинхронных операций при удалении компонента:
@* CustomComponentBase.razor (Razor) *@
@implements IDisposable

@code {
private readonly CancellationTokenSource
_cts = new CancellationTokenSource();

public CancellationToken
CancellationToken => _cts.Token;

public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}

Теперь все наши компоненты могут получить доступ к свойству CancellationToken без какой-либо дополнительной настройки:
@* MyComponent.razor (Razor) *@
@* Не нужно использовать @inherits, т.к. _Imports.razor импортируется автоматически *@

<h3>Мой компонент</h3>

@code {
protected override async Task OnInitializedAsync()
{
// Используем CancellationToken из базового компонента
await LoadDataAsync(CancellationToken);
}

private async Task LoadDataAsync(CancellationToken ct)
{
// … асинхронный код …
await Task.Delay(1000, ct);
}
}


Переопределение базового компонента для конкретных компонентов
Если конкретному компоненту требуется другой базовый компонент или вообще никакой, вы можете объявить @inherits непосредственно в файле этого компонента. Явное объявление имеет приоритет над _Imports.razor:
@* MyComponent.razor (Razor) *@
@inherits ComponentBase

@* Этот компонент будет использовать ComponentBase вместо CustomComponentBase *@


Организация файлов _Imports.razor
Можно иметь несколько файлов _Imports.razor в разных папках, чтобы применять разные базовые компоненты к различным разделам приложения. Приоритет имеет ближайший файл _Imports.razor в иерархии каталогов. Например, /Components/_Imports.razor применяется ко всем компонентам в этой папке /Components/Admin/_Imports.razor применяется специально к компонентам папки Admin. Такой иерархический подход обеспечивает точный контроль над тем, какие компоненты наследуют от каких базовых классов.

Источник: https://www.meziantou.net/blazor-how-to-set-a-base-component-for-all-razor-components-using-viewstart-razo.htm
👍3👎1
День 2670. #ЧтоНовенького #NET11
Сжатие Zstandard в .NET 11
В .NET уже некоторое время существуют DeflateStream, GZipStream, ZLibStream и BrotliStream. В .NET 11 к ним присоединяется новый: ZstandardStream.

Zstandard (или Zstd) — это алгоритм сжатия от Facebook. Разработан для обеспечения коэффициента сжатия, сравнимого с алгоритмом DEFLATE, но более быстрого, особенно для декомпрессии. Он настраивается с уровнями сжатия от -7 (самый быстрый) до 22 (самый медленный по скорости сжатия, но с наилучшим коэффициентом сжатия). Zstd используется в ядре Linux, дампах памяти FreeBSD, AWS Redshift, менеджерах пакетов Fedora и ArchLinux, а также архивах игр Nintendo Switch.

Новый API
Дизайн соответствует существующему шаблону Brotli*, поэтому, если вы использовали BrotliStream или BrotliEncoder, он покажется вам знакомым.

В System.IO.Compression 4 основных новых типа:
- ZstandardStream — обёртка потока,
- ZstandardEncoder/ZstandardDecoder — кодировщик/декодировщик,
- ZstandardDictionary — для эффективного сжатия множества маленьких файлов,
- ZstandardCompressionOptions — настройки: качество, размер окна, контрольная сумма и т.п.

Простейший пример применения:
// сжатие
using var output = new MemoryStream();
using (var zstd =
new ZstandardStream(output, CompressionMode.Compress))
{
await inputStream.CopyToAsync(zstd);
}

// декомпрессия
using var zstd = new
ZstandardStream(compressedStream, CompressionMode.Decompress);
await zstd.CopyToAsync(outputStream);


Если не требуется потоковая обработка, есть API проще:
byte[] src = File.ReadAllBytes("data.bin");

// сжатие
int maxLen = ZstandardEncoder
.GetMaxCompressedLength(src.Length);
var compressed = new byte[maxLen];
ZstandardEncoder.TryCompress(src, compressed, out int bytesWritten);

// декомпрессия
int decompressedSize = ZstandardDecoder.GetMaxDecompressedLength(compressed.AsSpan(0, bytesWritten));
var decompressed = new byte[decompressedSize];
ZstandardDecoder.TryDecompress(compressed.AsSpan(0, bytesWritten), decompressed, out int bytesRead);


Уровни качества
Zstd имеет диапазон качества от ZSTD_minCLevel() (который может быть отрицательным для сверхбыстрых режимов) до 22, по умолчанию 3. CompressionLevel.Fastest - минимальное значение, CompressionLevel.SmallestSize — 22.
var options = new ZstandardCompressionOptions
{
Quality = 6
};

using var zstd = new ZstandardStream(output, options);


Использование в ASP.NET Core
// Program.cs
builder.Services.AddResponseCompression(x =>
{
x.EnableForHttps = true;
x.Providers.Add<ZstandardCompressionProvider>();
});


ZstandardCompressionProvider автоматически подключает заголовок согласования содержимого encoding: zstd. Распаковка запроса также осуществляется простым способом через IDecompressionProvider.

Сжатие по словарю
При сжатии большого количества небольших, похожих данных (ответы JSON API, сериализованные события, блоки конфигурации) общий словарь может улучшить коэффициент сжатия.
// обучаем словарь на примерах
byte[] allSamples = …; // примеры всех сжимаемых объектов
int[] lengths = …; // длина каждого примера

using ZstandardDictionary dict = ZstandardDictionary.TrainFromSamples(allSamples, lengths, maxDictionarySize: 100_000);

// Сжатие по словарю — обе стороны должны использовать один словарь
using var encoder = new ZstandardEncoder(dict);
encoder.Compress(payload, compressed, out int consumed, out int written, isFinalBlock: true);

Храните словарь в месте, доступном как производителю, так и потребителю. Рекомендуемый размер — до 100 КБ, обучение проводится на репрезентативных данных, размер которых примерно в 100 раз превышает размер сжимаемых данных.

Источник:
https://steven-giesel.com/blogPost/6066abb6-640a-4225-ac33-3f4d5a1a1d16/zstandard-compression-in-net-11
👍5
День 2671. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

34. Лучшие практики RESTful-сервисов
«Давайте обсудим лучшие практики проектирования и реализации RESTful-сервисов в приложениях .NET. Приведите примеры того, как эти практики обеспечивают эффективные и поддерживаемые API-сервисы».

Хороший ответ
Разработка RESTful-сервисов требует соблюдения определённых принципов, обеспечивающих масштабируемость, удобство сопровождения и интуитивность этих сервисов. При реализации RESTful API в приложениях .NET можно следовать нескольким передовым практикам:
1. Правильное использование HTTP-методов: Каждый HTTP-метод (GET, POST, PUT, DELETE и т. д.) имеет определённое семантическое значение и должен использоваться в соответствии с представляемым действием. Например, GET следует использовать для получения данных, и он должен быть идемпотентным, то есть не изменять состояние сервера.

2. Именование ресурсов: Ресурсы должны быть названы логично и последовательно, как правило, с использованием существительных, представляющих сущности. Пути URI должны отражать иерархию ресурсов. Например:
GET /api/customers - получить список клиентов,
GET /api/customers/{id} - получить клиента по ID,
POST /api/customers - создать клиента,
PUT /api/customers/{id} - обновить данные клиента,
DELETE /api/customers/{id} - удалить данные клиента.

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

4. Правильное использование кодов состояния: HTTP предоставляет ряд кодов ответа, указывающих на успех или неудачу запросов. Правильное использование этих кодов, таких как 200 OK, 404 Not Found и 500 Internal Server Error, помогает клиенту корректно обрабатывать ответы.

5. Версионирование: По мере развития API версионирование становится критически важным для поддержания совместимости с существующими клиентами, позволяя при этом вносить улучшения и изменения. Этого можно достичь с помощью пути URI, строки запроса или пользовательских заголовков, например: /api/v1/customers, /api/v2/customers

6. Безопасность: Реализация аутентификации и авторизации, Обеспечение шифрования данных при передаче (HTTPS) является основополагающим принципом безопасности RESTful API.

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

Часто встречающийся неверный ответ
«Надо просто убедиться, что API работает хорошо и возвращает правильные данные. Конкретные HTTP-методы или коды состояния, которые вы используете, не так уж важны, главное, чтобы клиент получал то, что ему нужно».

Почему это неверно
- Недостаточное понимание принципов REST: ответ демонстрирует непонимание фундаментальных принципов REST, таких как важность использования правильных HTTP-методов и кодов состояния, которые определяют RESTful-сервисы.

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

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

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

Источник:
https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md