.NET Разработчик
6.7K 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
День 2662. #ЧтоНовенького #NET11
В .NET 11 Исключения Фоновых Сервисов Будут Всплывать
Ошибка, которая давно существовала в .NET: если BackgroundService генерировал исключение после первого await, хост перехватывал его, писал критическое сообщение в лог, а затем корректно завершал работу с кодом результата 0. Т.е. все думали, что процесс успешно завершился. Теперь это исправлено!
 
Проблема
Рассмотрим этот рабочий процесс:
public class Worker : BackgroundService
{
  protected override async Task
   ExecuteAsync(CancellationToken stopToken)
  {
    await Task.Delay(TimeSpan.FromSeconds(1), stopToken);
    throw new Exception("something went wrong");
  }
}

До .NET 11 вывод был таким:
crit: Microsoft.Extensions.Hosting.Internal.Host[10]
      The HostOptions.BackgroundServiceExceptionBehavior is configured to StopHost.
      A BackgroundService has thrown an unhandled exception, and the IHost instance is stopping.
      ...
      System.Exception: something went wrong
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...

Процесс завершён с кодом 0 ??? Критическая ошибка в логе, но код результата всё равно 0.
 
Почему это так работало?
Если метод помечен как асинхронный, исключение перехватывалось функцией TryExecuteBackgroundServiceAsync глубоко внутри инфраструктуры, логировалось как критическое, после чего хост инициировал корректное завершение работы. Корректное завершение работы => код результата 0.
Если метод не помечен как асинхронный и выбрасывалось исключение, то процесс завершался с кодом ошибки.
 
Основная причина в том, что host.RunAsync() возвращает Task (а не Task<int>), и не было механизма для сигнализации о завершении процесса с кодом ошибки для фонового сервиса, который бы указывал на сбой.
 
Исправление в .NET 11
Теперь IHost.RunAsync() и IHost.StopAsync() будут передавать исключение из BackgroundService, а не игнорировать его. Теперь вы получите:
crit: Microsoft.Extensions.Hosting.Internal.Host[10]
      …
      System.Exception: something went wrong
 
Unhandled exception. System.Exception: something went wrong
   at Worker.ExecuteAsync(…)
 
Process finished with exit code 134. (или 1 в Windows)

 
Если вам нужно старое поведение
Вы можете установить для параметра обработки исключений значение «Игнорировать»:
services.Configure<HostOptions>(options =>
{
    options.BackgroundServiceExceptionBehavior = BackgroundServiceExceptionBehavior.Ignore;
});

 
См. оригинальный ишью.
 
Источник: https://steven-giesel.com/blogPost/00fcb870-6bf7-4f97-824f-8eab1b8838be/backgroundservice-exceptions-now-propagate-in-net-11
👍17
День 2663. #ЗаметкиНаПолях #DDD
Что Такое Инварианты? Начало

Во многих «DDD-подобных» кодовых базах .NET бизнес правила разбросаны по обработчикам, валидаторам и контроллерам, и почти не затрагивают саму доменную модель. Каждая копия одного и того же правила со временем немного меняется, и валидность данного объекта начинает зависеть от того, каким путём вызывающий код к нему добрался. Вы, безусловно, можете построить работающую систему таким образом. Но есть более чистый способ, и он начинается с одной идеи.

Что такое инвариант?
Инвариант — это правило об объекте, которое должно оставаться верным до тех пор, пока объект существует.

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

Несколько примеров:
- У курса всегда есть непустое название.
- Итоговая сумма заказа всегда равна сумме стоимостей позиций в нём.
- Подписка находится ровно в одном состоянии: пробная, активная, истекшая или отменённая.

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

Где процедурный код дает сбои
Возьмём простой курс, написанный так, как это до сих пор делают большинство CRUD-подобных .NET-приложений:
public class Course
{
public string Title { get; set; }
public CourseStatus Status { get; set; }
public DateTime? PublishedOn { get; set; }
public decimal Price { get; set; }
}

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

Чтобы данные оставались корректными, правила разбросаны по всему приложению:
- CreateCourseValidator проверяет, не пуст ли заголовок.
- PublishCourseHandler устанавливает статус «Опубликован» и дату PublishedOn и не забывает проверить, не был ли курс уже в статусе «Опубликован».
- ChangePriceHandler проверяет, не отправлен ли курс в архив.
Появляется новая конечная точка, кто-то копирует существующий обработчик, и проверка архивности незаметно исчезает.
Каждое правило находится в месте, которое случайно оказывается на пути запроса. Ничто в самом классе Course не мешает ему перейти в недействительное состояние.

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

Решение простое: модель никогда не должна принимать недействительное состояние.

Если у вас есть ссылка на Course, вы можете ей доверять. Вам не нужна проверка if (course.Title is null), не нужно вызывать валидатор и не нужно надеяться, что обработчик до вас выполнил правильную проверку.

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

Источник:
https://www.milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them
👍15👎1
День 2664. #ЗаметкиНаПолях #DDD
Что Такое Инварианты? Окончание

Начало

Модель как источник истины
Три шага к цели
1. Запрет создания недействительных объектов
Курс без названия не должен существовать — делаем это невозможным:
public class Course
{
private Course(CourseId id, string title, Money price)
{
Id = id;
Title = title;
Price = price;
Status = CourseStatus.Draft;
}

public static Result<Course>
Create(string title, Money price)
{
if (string.IsNullOrWhiteSpace(title))
return CourseErrors.TitleRequired;

return new Course(CourseId.New(), title, price);
}

Приватный конструктор со статической фабрикой предоставляет единственный вариант создания объекта, и именно там выполняется валидация. Теперь любой код, работающий с объектом Course, может предполагать, что у него есть допустимое название.
Объекты-значения, такие как Money, применяют ту же идею: Money не может быть отрицательным или обязан иметь тип валюты.

2. Инкапсуляция переходов состояния
Класс должен контролировать, как он изменяется, вместо того чтобы оставлять это на усмотрение его обработчиков. Никаких сеттеров, и каждое изменение проходит через метод, который знает правила:
public Result Publish(IDateTimeProvider clock)
{
if (Status != CourseStatus.Draft)
return CourseErrors.AlreadyPublished;

if (_lessons.Count == 0)
return CourseErrors.CannotPublishWithoutLessons;

Status = CourseStatus.Published;
PublishedOn = clock.UtcNow;
return Result.Success();
}

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

3. Инкапсуляция агрегата
Некоторые правила охватывают несколько сущностей в пределах одного агрегата. Корень агрегата — правильное место для обеспечения их соблюдения.

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

Неправильный подход — предоставлять Lessons как изменяемую коллекцию и полагаться на то, что обработчики будут соблюдать правило везде. Правильный подход — держать коллекцию приватной и принудительно обрабатывать каждое изменение через корень агрегата:
public class Course
{
private readonly List<Lesson> _lessons = [];
public IReadOnlyCollection<Lesson>
Lessons => _lessons.AsReadOnly();

public Result RemoveLesson(LessonId id)
{
if (Status == CourseStatus.Published)
return CourseErrors.CannotModifyPublishedLessons;

var lesson = _lessons.FirstOrDefault(l => l.Id == id);
if (lesson is null)
return CourseErrors.LessonNotFound;

_lessons.Remove(lesson);
return Result.Success();
}
}

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

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

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

Источник:
https://www.milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them
👍9👎1
Please open Telegram to view this post
VIEW IN TELEGRAM
День 2665. #ЧтоНовенького #NET11
Улучшения API процессов в .NET 11. Обзор
Класс System.Diagnostics.Process — это основной способ создания процессов и взаимодействия с ними в .NET. В .NET 11 в него внесли самое масштабное обновление за последние годы. Изменения добавляют высокоуровневые API, которые упрощают запуск процесса и перехват его вывода без взаимоблокировок, предоставляют полный контроль над наследованием дескрипторов и стандартным перенаправлением дескрипторов, вводят функции управления временем жизни, такие как KillOnParentExit, и включают в себя облегченный API на основе SafeProcessHandle, более удобный для оптимизации. В этой серии постов рассмотрим нововведения подробно.

Новые функции
1. Однострочник для выполнения процесса - Process.RunAndCaptureText[Async]
Запускает процесс, перехватывает вывод/ошибку, ожидает завершения — всё за один вызов.

2. Выполнение без перехвата вывода - Process.Run[Async]
Запускает процесс и ожидает завершения без перехвата вывода.

3. Запустить и забыть - Process.StartAndForget
Запускает процесс, возвращает его PID и немедленно освобождает все ресурсы.

4. Перехват вывода без взаимоблокировок - Process.ReadAllText/Bytes/Lines[Async]
Читает stdout и stderr одновременно с использованием мультиплексирования, избегая взаимоблокировок буфера канала.

5. Перенаправление - ProcessStartInfo.Standard[Input/Output/Error]Handle
Перенаправляет стандартные дескрипторы на файлы, каналы, null или любой SafeFileHandle.

6. Контролируемое наследование - ProcessStartInfo.InheritedHandles
Точно указывает, какие дескрипторы наследуются дочерними процессами, предотвращая случайные утечки памяти.

7. Завершение при выходе родительского процесса - ProcessStartInfo.KillOnParentExit
Гарантирует завершение дочерних процессов при выходе родительского процесса (Windows и Linux).

8. Отсоединённые процессы - ProcessStartInfo.StartDetached
Запускает процесс, который сохраняется после выхода родительского процесса, сигнала или закрытия терминала.

9. Легковесный дескриптор процесса - SafeProcessHandle.Start/WaitForExit/Kill/Signal
Удобный для сокращения количества процессов низкоуровневый API для запуска и управления процессами без Process.

10. Подробная информация о завершении процесса - ProcessExitStatus
Сообщает код завершения, сигнал завершения (Unix) и был ли процесс завершён из-за тайм-аута/отмены.

11. Нулевой дескриптор - File.OpenNullHandle()
Открывает дескриптор, который отбрасывает операции записи и возвращает EOF при чтении.

12. Анонимные каналы - SafeFileHandle.CreateAnonymousPipe
Создаёт пару подключённых каналов с дополнительной поддержкой асинхронности.

13. Обработчики консоли - Console.OpenStandard[Input/Output/Error]Handle()
Получает базовый дескриптор операционной системы для стандартных потоков.

14. Определение типа дескриптора - SafeFileHandle.Type
Определяет, является ли дескриптор файлом, каналом, сокетом и т. д.

Далее рассмотрим новые функции подробнее.

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

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

Перехват выходных данных процесса без взаимоблокировок

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

Прежде всего, нужно перенаправить stdout и stderr, чтобы иметь возможность их прочитать. Это делается путём установки свойств RedirectStandardOutput и RedirectStandardError объекта ProcessStartInfo в true. Перед запуском процесса создаются два выделенных канала (для вывода и для ошибок). Дочерний процесс записывает данные в свои stdout и stderr, как обычно, но вместо вывода в консоль данные записываются в каналы.
ProcessStartInfo startInfo = new("dotnet", "--help")
{
RedirectStandardOutput = true,
RedirectStandardError = true
};

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

Каналы имеют ограниченный размер буфера (обычно 4КБ в Windows и 64КБ в Unix). Когда производитель (в нашем случае, дочерний процесс) записывает данные в канал, они сохраняются в буфере до тех пор, пока их не прочтёт потребитель (родительский процесс). Если производитель записывает больше данных, чем размер буфера, а потребитель одновременно не читает из канала, производитель будет заблокирован на операции записи, ожидая, пока потребитель прочитает из канала и освободит место в буфере.
Если потребитель ожидает завершения работы производителя (например, вызывая WaitForExit) без чтения из канала, он будет заблокирован, как только производитель заполнит буфер:
p.Start();
p.WaitForExit();

var output = p.StandardOutput.ReadToEnd();
var error = p.StandardError.ReadToEnd();

Перемещение process.WaitForExit(); в конец тоже не помогает. ReadToEnd — блокирующий вызов: он читает до тех пор, пока поток не достигнет конца файла (EOF), что происходит только тогда, когда дочерний процесс закрывает свою часть канала (обычно при завершении). Поэтому в приведённом выше коде мы сначала блокируемся на stdout до завершения дочернего процесса, и только затем начинаем читать stderr. Если дочерний процесс записывает в stderr больше, чем может вместить буфер канала, он блокируется на своей записи — и мы застреваем во взаимоблокировке.

Причина в том, что мы читаем два потока последовательно, а не одновременно. Чтобы избежать этого, нужно одновременно очищать и стандартный вывод, и ошибку. До сих пор у нас было два варианта:
1. Использовать асинхронные операции чтения для StandardOutput и StandardError
p.Start();

var outTask = p.StandardOutput.ReadToEndAsync();
var errTask = process.StandardError.ReadToEndAsync();

await Task.WhenAll(outTask, errTask, p.WaitForExitAsync());

var output = await outTask;
var error = await errTask;


2. Использовать события OutputDataReceived и ErrorDataReceived
События возникают при записи строки в stdout и stderr соответственно:
StringBuilder stdOut = new(), stdErr = new();

p.OutputDataReceived += (sender, e) => stdOut.AppendLine(e.Data);
p.ErrorDataReceived += (sender, e) => stdErr.AppendLine(e.Data);

p.Start();

p.BeginOutputReadLine();
p.BeginErrorReadLine();

p.WaitForExit();

Существующие API не оптимальны с точки зрения простоты и производительности.

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

Источник:
https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
👍8
День 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
👍3