.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
День 2018. #BlazorBasics
Основы Blazor. Привязка Данных
Привязка данных используется для синхронизации данных между пользовательским интерфейсом и моделью данных C#. Также она позволяет реагировать на события, которые запускает пользователь или браузер.

1. Одностороннее связывание — связывание свойств
Это самый базовый подход, используемый для вывода содержимого свойства или переменной C# на экран:
<h1>@Title</h1>

@code {
public string Title { get; set; }
}

В этом компоненте в элемент <h1> выводятся данные с помощью символа @, за которым следует имя свойства или переменной. Всякий раз, когда содержимое свойства Title изменяется, UI автоматически обновляется, чтобы отразить новое значение.
Тот же подход можно использовать, чтобы предоставить значение свойства в качестве аргумента для параметра компонента.
@page "/MyPage"
<MyComponent Title="@Name" />

Здесь мы отображаем дочерний компонент MyComponent, задавая его параметру Title значение параметра Name страницы MyPage.

2. Двусторонняя привязка
При использовании двусторонней привязки данных данные передаются из модели данных в UI и обратно:
<input @bind="Username" />

@code {
public string Username { get; set; }
}

В этом компоненте мы привязываем свойство Username к свойству value текстового поля. Синтаксис @bind — это сокращение. Каждый компонент имеет свойство по умолчанию, привязываемое при использовании сокращённого синтаксиса. Мы также могли бы использовать более длинную форму и явно привязаться к свойству value:
<input @bind-value="Username" />

Заметьте, что важно использовать правильный синтаксис. Например, при использовании компонента InputText, Value нужно писать с заглавной буквы, потому что компонент имеет свойство Value:
<InputText @bind-Value="Username" />

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

3. Привязка событий
В дополнение к односторонней и двусторонней привязке данных мы можем привязать метод C# к событию:
<h3>User</h3>

<EditForm Model="@Model" OnSubmit="@Save">
<InputText @bind-Value="Model.Username" />
<input type="submit" value="Submit" />
</EditForm>

@code {
public User Model { get; set; } = new User();
public void Save()
{
// Сохраняем пользователя
}

public class User
{
public string Username { get; set; }
}
}

Здесь мы привязываем:
- свойство Model к параметру Model компонента EditForm,
- свойство Value компонента InputText к свойству Username класса User,
- метод Save к событию OnSubmit компонента EditForm.
Когда пользователь отправляет форму с помощью кнопки отправки, срабатывает событие OnSubmit и вызывается метод Save. В методе Save мы можем получить доступ к имени пользователя с помощью свойства Model и, например, сохранить его в базе данных.

Событие можно связать и с отдельным HTML-элементом:
<input type="button" @onclick="Save" />

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

Урок 18. Чем выше уровень абстракции, тем проще выполнять итерации. Начало

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

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

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

Альтернативный подход — выполнение итераций на более высоком уровне абстракции, чем ПО. Проектное моделирование обеспечивает менее дорогую альтернативу.

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

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

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

Визуальные модели — это средства коммуникации, способы представления знаний и обмена ими. Важно говорить на одном языке. Поэтому при моделировании требований или проекта используйте общепринятые обозначения. Предлагаемую системную архитектуру можно смоделировать в виде простой блок-схемы, но элементы проекта более низкого уровня требуют применения специализированных символов. Наиболее популярным выбором для ООП является унифицированный язык моделирования (Unified Modeling Language, UML). Чтобы исследовать, улучшать и документировать свои идеи, а также делиться ими с другими, используйте готовый стандарт, такой как UML, а не изобретайте собственные обозначения, которые могут быть непонятны другим. Моделирование не может полностью заменить создание прототипов, но любой метод, упрощающий просмотр и изменение проекта на высоком уровне абстракции, поможет вам создавать более качественные продукты.

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

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

Урок 18. Чем выше уровень абстракции, тем проще выполнять итерации. Окончание

Начало

Быстрые визуальные итерации
Пользовательские интерфейсы имеют два уровня проектирования: архитектурное и техническое. Просматривая экран UI, вы видите техническую часть с элементами визуального оформления, особенностями размещения текста, изображений, ссылок, полей ввода и элементов управления. Если нужна бОльшая точность, можно создать подробную структуру экрана или веб-страницы с помощью такого инструмента, как модель «отображение—действие—отклик» (Display-Action-Response, DAR). Однако поэтапное техническое проектирование UI требует изменения отдельных отображаемых элементов. Эти изменения могут быть утомительными, если не использовать эффективный инструмент создания экранов.

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

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍10
День 2021. #ЗаметкиНаПолях #EFCore
Полезные Функции EF Core. Начало
EF Core – мощный инструмент, и знание нескольких ключевых функций может сэкономить вам много времени и избежать разочарований.

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

Допустим, вы хотите получить отдел со всеми командами, сотрудниками и их задачами:
List<Department> departments =
context.Departments
.Include(d => d.Teams)
.ThenInclude(t => t.Employees)
.ThenInclude(e => e.Tasks)
.ToList();

Это преобразуется в один SQL-запрос с несколькими JOIN. Предположим, что в отделе много команд, а в каждой команде много сотрудников. В этом случае БД возвращает много строк из каждого подзапроса, что значительно влияет на производительность. Вот как можно избежать этих проблем с производительностью с помощью разделения запросов:
List<Department> departments =
context.Departments
.Include(d => d.Teams)
.ThenInclude(t => t.Employees)
.ThenInclude(e => e.Tasks)
.AsSplitQuery()
.ToList();

При использовании AsSplitQuery EF Core будет выполнять дополнительный SQL-запрос для каждого навигационного свойства коллекции. Однако не злоупотребляйте разделением запросов. Измерьте производительность в каждом случае для вашего конкретного набора данных. Разделённые запросы делают больше обращений к БД, что может быть медленнее, если задержка базы данных высока. Также нет гарантии согласованности между несколькими SQL-запросами.

См. подробнее о разделённых запросах.

2. Массовые обновления и удаления
EF Core 7 добавил два новых API для выполнения массовых обновлений и удалений, ExecuteUpdate и ExecuteDelete. Они позволяют эффективно обновлять большое количество строк за один цикл обращения к БД.

Например, компания решила повысить зарплату на 5% всем сотрудникам в отделе «Продажи». Мы могли бы пройтись по всем сотрудникам и обновить их зарплату:
var salesEmployees = context.Employees
.Where(e => e.Department == "Sales")
.ToList();

foreach (var employee in salesEmployees)
employee.Salary *= 1.05m;

context.SaveChanges();

Этот подход приведёт к множеству запросов в БД, что может быть неэффективным, особенно для больших наборов данных. Мы можем добиться того же за один запрос, используя ExecuteUpdate:
context.Employees
.Where(e => e.Department == "Sales")
.ExecuteUpdate(s =>
s.SetProperty(e => e.Salary, e => e.Salary * 1.05m));

Это приведёт к одному SQL-запросу UPDATE, и прямому изменению зарплаты в БД без загрузки сущностей в память.

Ещё пример. Допустим, платформа электронной коммерции хочет удалить все корзины покупок старше года:
context.Carts
.Where(o => o.CreatedOn < DateTime.Now.AddYears(-1))
.ExecuteDelete();

Однако массовые обновления обходят трекер изменений EF. Это может быть проблемой, о чём см. подробнее.

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

Источник:
https://www.milanjovanovic.tech/blog/5-ef-core-features-you-need-to-know
👍35
День 2022. #ЗаметкиНаПолях #EFCore
Полезные Функции EF Core. Окончание

Начало

3. Чистые SQL-запросы
EF Core 8 добавил новую функцию, которая позволяет нам запрашивать несопоставленные (unmapped) типы. Предположим, мы хотим получить данные из представления БД, хранимой процедуры или таблицы, которые напрямую не соответствуют ни одному из классов сущностей контекста БД. Например, получить сводные данные продаж продукта. В EF Core 8 можно определить простой класс и запросить его напрямую:
public class ProductSummary
{
public int Id { get; set; }
public string Name { get; set; }
public decimal TotalSales { get; set; }
}

var sums = await context.Database
.SqlQuery<ProductSummary>(
@$"""
SELECT p.Id, p.Name,
SUM(oi.Quantity * oi.UnitPrice) AS TotalSales
FROM Products p
JOIN OrderItems oi ON p.ProductId = oi.ProductId
WHERE p.CategoryId = {categoryId}
GROUP BY p.Id, p.Name
""")
.ToListAsync();

Метод SqlQuery возвращает IQueryable, что позволяет встраивать чистые SQL-запросы в LINQ-запросы.

Не забывайте использовать параметризованные запросы, чтобы предотвратить SQL-инъекции. Метод SqlQuery принимает FormattableString, что означает, что вы можете безопасно использовать интерполированную строку. Каждый аргумент преобразуется в параметр SQL.

См. подробнее.

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

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

В мультитенантном приложении часто нужно фильтровать данные на основе текущего пользователя:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
// Связываем продукты с пользователями
public int TenantId { get; set; }
}

protected override void
OnModelCreating(ModelBuilder mb)
{
// Текущий пользователь задаётся из контекста/запроса
mb.Entity<Product>()
.HasQueryFilter(p => p.TenantId == _currentTenantId);
}

// Запросы автоматически фильтруются по пользователю:
var products = context.Products.ToList();

См. подробнее о мультитенантности с помощью EF Core.

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

Источник: https://www.milanjovanovic.tech/blog/5-ef-core-features-you-need-to-know
👍26
День 2023. #ЗаметкиНаПолях
Используем Memory для Эффективного Управления Памятью. Начало

В C# разработчики имеют доступ к мощному API Memory<T>, позволяющему гибко и эффективно работать с памятью.

Мы используем Span для безопасного предоставления непрерывной области памяти. Как и Span<T>, Memory<T> представляет собой непрерывную область памяти. Однако она может находиться в куче, а не только в стеке, как Span.

См. также «Различия Между Span и Memory в C#»

Создание Memory
var numbers = new int[] { 1, 2, 3, 4, 5 };
var memory = new Memory<int>(numbers);

В примере выше memory ссылается на данные в куче, т.к. массивы в C# размещаются в куче.

Memory<T> и асинхронные методы
public static async Task 
ProcessMemoryAsync(Memory<int> memory)
{
await Task.Delay(1000);
for (var i = 0; i < memory.Span.Length; i++)
{
var item = memory.Span[i];
Console.WriteLine(item);
}
}

Здесь мы имитируем асинхронную операцию с помощью Task.Delay(), а затем получаем доступ к данным в памяти с помощью memory.Span и выводим их на консоль. Это было бы невозможно с помощью Span<T>, поскольку мы не можем использовать Span<T> в асинхронном коде из-за того, что это ref struct.

Метод расширения AsMemory()
Метод расширения string.AsMemory() позволяет создавать объект Memory<char> из строки без копирования базовых данных. Это может быть полезно при передаче подстрок методам, которые принимают параметры Memory<T>, без дополнительных выделений памяти:
const string str = "Hello, World!";
var memory = str.AsMemory();
var slice = memory.Slice(7, 5);
Console.WriteLine(slice.ToString());
// World

Мы создаем Memory<char> из строки с помощью AsMemory(), делаем срез этого Memory<char>, чтобы получить новый Memory<char>, представляющий часть исходной строки. И выводим этот срез на консоль, преобразуя его в строку с помощью метода ToString().

Далее рассмотрим продвинутые методы управления памятью.

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

Источник:
https://code-maze.com/csharp-using-memory/
👍15
День 2024. #ЗаметкиНаПолях
Используем Memory для Эффективного Управления Памятью. Окончание

Начало

Модели владения и интерфейс IMemoryOwner
Метод MemoryPool<T>.Rent() возвращает интерфейс IMemoryOwner<T>, который действует как владелец блока памяти. Общий пул позволяет сдавать блок памяти в аренду. Когда память больше не используется, владелец блока несёт ответственность за его утилизацию:
using IMemoryOwner<int> owner = 
MemoryPool<int>.Shared.Rent(16);
var memory = owner.Memory;
for (var i = 0; i < memory.Length; i++)
memory.Span[i] = i;

foreach (var item in memory.Span)
Console.WriteLine(item);

Мы арендуем блок памяти из общего пула с помощью MemoryPool<int>.Shared.Rent(). Он возвращает IMemoryOwner<int>, который владеет арендованным блоком. Затем извлекаем Memory<int>, который представляет этот блок памяти через свойство Memory. Мы используем этот блок для хранения некоторых данных, а затем отображаем эти данные.
Директива using отвечает за утилизацию блока и возвращение его в пул, делая его доступным для последующих вызовов Rent().

Реальный сценарий использования Memory<T> в C#
Создадим метод, который считывает данные из файла в арендованный блок памяти, обрабатывает данные, а затем возвращает память в пул:
public static async Task 
ProcessFileAsync(string path)
{
using var owner =
MemoryPool<byte>.Shared.Rent(4096);
var buffer = owner.Memory[..4096];
await using var stream = File.OpenRead(path);
int bytes;
while ((bytes = await stream.ReadAsync(buffer)) > 0)
{
var data = buffer[..bytes];
for (var i = 0; i < data.Span.Length; i++)
{
var b = data.Span[i];
Console.Write((char)b);
}
Console.WriteLine();
}
}

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

Итого
Благодаря возможности выделения памяти как в стеке, так и в куче, совместимости со строками и пулингу памяти, с помощью Memory<T> разработчики могут оптимизировать использование памяти, повысить производительность приложений и создавать более надёжные и масштабируемые программные решения.

Источник: https://code-maze.com/csharp-using-memory/
👍10
День 2025. #BlazorBasics
Основы Blazor. Маршрутизация и Навигация. Начало
Маршрутизация — ключевая функция любого одностраничного приложения. Она позволяет разработчику организовать веб-сайт, а пользователю — перемещаться по разным страницам.

Когда мы используем директиву @page в компоненте Blazor, компилятор добавляет RouteAttribute во время компиляции, делая компонент доступным в качестве маршрута.

Маршрутизатор в Blazor
При создании проекта Blazor из шаблона в файле App.razor создаётся такой код:
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

Маршрутизатор работает одинаково как в приложениях Blazor Server, так и в WebAssembly. При запуске он считывает сборку приложения (из свойства AppAssembly) и ищет RouteAttribute, применённый к скомпилированным классам компонентов Blazor.

Если маршрут найден, используется компонент RouteView для визуализации представления, связанного с информацией о маршрутизации. Также устанавливается компонент макета по умолчанию - DefaultLayout (он используется для всех страниц, которые явно не указывают макет с помощью директивы @layout).
Если маршрут не найден, отображается простое сообщение об ошибке. Его можно изменить и сделать более дружелюбным к пользователю.

FocusOnNavigate позволяет поместить фокус на элементе HTML после завершения навигации. Можно использовать любой селектор CSS.

Важно: Маршрутизация в Blazor требует, чтобы мы установили тег <base> в разделе заголовка HTML. В шаблоне по умолчанию он устанавливается в файле _Host.cshtml. Измените это значение в зависимости от того, где и как вы размещаете своё приложение.

NavigationManager
NavigationManager позволяет нам переходить с одной страницы на другую в коде C#.
@page "/list"
@inject NavigationManager Nav
<ul>
<li @onclick="() => NavigateTo(1)">1</li>
<li @onclick="() => NavigateTo(2)">2</li>
</ul>

@code {
public void NavigateTo(int id)
{
Nav.NavigateTo($"item/{id}");
}
}

Мы внедрили NavigationManager на страницу при помощи директивы @inject и использовали его в метода NavigateTo, привязанном к элементам списка через событие @onclick. При навигации с использованием NavigationManager мы можем предоставить различные опции (NavigationOptions). Например, обойти клиентскую маршрутизацию и заставить браузер загрузить новую страницу с сервера с помощью свойства ForceLoad. Или заменить запись истории с помощью свойства ReplaceHistoryEntry.

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

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

Источник:
https://www.telerik.com/blogs/blazor-basics-blazor-routing-navigation-fundamentals
👍9
День 2026. #BlazorBasics
Основы Blazor. Маршрутизация и Навигация. Окончание

Начало

Маршруты
Маршруты страниц Blazor определяются аналогично маршрутам Razor Pages. Они могут включать параметры в фигурных скобках и ограничения параметров, указанные после двоеточия:
@page "/list"
@page "/item/{id}" – параметр id
@page "/item/{id:int}" – целочисленный параметр id

Cтроки запроса
Чтобы компонент страницы мог читать параметр из строки запроса, нужно использовать атрибуты Parameter и SupplyParameterFromQuery:
@page "/search"
<h3>Результаты поиска</h3>
<p>Вы искали: @FirstName @LastName</p>

@code {
[Parameter]
[SupplyParameterFromQuery]
public string FirstName { get; set; }

[Parameter]
[SupplyParameterFromQuery]
public string LastName { get; set; }
}

Когда мы вызываем URL /search?firstname=Jon&lastname=Smith, Blazor назначает обоим свойствам соответствующие части строки запроса. Если вы хотите иметь другое имя для свойства, чем в URL, можно использовать свойство Name атрибута SupplyParameterFromQuery, чтобы определить имя параметра строки запроса.

Компоненты NavLink и NavMenu
Компонент NavLink можно использовать вместо HTML-тега <a>. Он автоматически применяет класс CSS “active” в зависимости от того, соответствует ли его URL текущему URL.

Компонент NavMenu в шаблоне проекта Blazor содержит пример использования компонента NavLink. Вот его упрощённый код:
<nav>
<NavLink href="" Match="NavLinkMatch.All">
Home
</NavLink>
<NavLink href="counter">
Counter
</NavLink>

</nav>

Поведение сопоставления маршрута с адресом ссылки настраивается с помощью перечисления NavLinkMatch:
- All - ссылка активна, если адрес полностью соответствует адресу текущего компонента.
- Prefix - ссылка активна, если адрес соответствует началу адреса текущего компонента (по умолчанию).
Также можно предоставить пользовательский класс CSS для активного NavLink с помощью свойства ActiveClass (по умолчанию — "active").

Индикация загрузки между переходами страниц
Иногда страницы могут загружаться в течение длительного времени. Мы можем использовать компонент Navigating в компоненте Router, чтобы показать пользовательскую разметку во время перехода между страницами:
<Router AppAssembly="@typeof(App).Assembly">

<Navigating>
<p>Loading…</p>
</Navigating>
</Router>

Однако лучшим решением будет загружать данные на странице асинхронно, а переходы между страницами делать как можно более быстрыми.

Итого
Маршрутизация и навигация являются основополагающими при работе с одностраничными приложениями. Blazor обеспечивает простую, но мощную реализацию обоих из коробки. Не нужно выбирать один из нескольких вариантов маршрутизации; всё это включено в Blazor. Тем не менее, он предоставляет много места для пользовательского кода и является очень гибким и расширяемым. Он не только обрабатывает простые варианты использования, но и предоставляет API для сложных сценариев.

Источник: https://www.telerik.com/blogs/blazor-basics-blazor-routing-navigation-fundamentals
👍7
День 2027. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 19. Разрабатывайте продукты так, чтобы их легко было использовать правильно и трудно — неправильно

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

Есть 4 варианта обращения с потенциальными ошибками.

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍13
День 2028. #ЧтоНовенького #CSharp13
Альтернативный Доступ к Коллекциям

До C#13 ref-структуры не могли быть аргументами параметра-типа в обобщённых типах. Теперь в объявление обобщённого типа после ключевого слова where добавлено «анти-ограничение» allows ref struct, которое сообщает, что аргументом для параметра типа может быть ref-структура. Анти-ограничение оно, потому что по факту оно не ограничивает возможный набор параметров типа, а расширяет его. Это анти-ограничение налагает все доступные для ref-структур правила (вроде запрета размещения в куче) на все экземпляры параметра-типа. Смысл этого изменения также в том, чтобы можно было использовать типы Span в обобщённых-алгоритмах.

Например, таблицы поиска (lookup-таблицы) типа Dictionary<TKey,TValue> и HashSet<T> часто используются в качестве своего рода кэша. Однако, так как ключи таблиц обычно строкового типа, раньше приходилось выделять строку, чтобы обратиться в lookup-таблицу по ключу. Теперь упомянутое выше «анти-ограничение» добавляет новые возможности для этих типов коллекций.

Следующий код считает количество различных слов в тексте, используя так называемые альтернативные ключи словаря:
static Dictionary<string, int> 
CountWords(ReadOnlySpan<char> input)
{
Dictionary<string, int> counts =
new(StringComparer.OrdinalIgnoreCase);

Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>>
lookup = counts.GetAlternateLookup<string, int, ReadOnlySpan<char>>();

foreach (Range r in Regex.EnumerateSplits(input, @"\b\W+\b"))
{
ReadOnlySpan<char> word = input[r];
lookup[word] = lookup
.TryGetValue(word, out int count) ? count + 1 : 1;
}

return counts;
}

Сначала мы создаём lookup-словарь counts и указываем, что нам не важен регистр ключей.
Затем, чтобы искать в словаре по spanам, а не по строкам, мы создаём структуру lookup для альтернативного доступа к элементам. Здесь она вот такого длинного типа Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>>. В примере указан тип полностью для демонстрации. В реальном коде это можно заменить на var. Заметьте, что тут используется обобщённый тип AlternateLookup с параметром типа виде ref-структуры ReadOnlySpan<char>.
Далее мы разбиваем текст по «не-словам», используя Regex.EnumerateSplits. В результате получаем перечислитель диапазонов результатов. С его помощью мы делаем срезы исходного текста, которые представляют собой отдельные слова. Это переменная word типа ReadOnlySpan<char>. И вот здесь, чтобы не создавать строк для доступа в словарь counts по строковому ключу, нам и потребуется структура lookup, которая обеспечит доступ к словарю по альтернативному ключу типа ReadOnlySpan<char>. В данном случае мы увеличиваем счётчик для текущего слова.

Теперь мы можем взять текст и посчитать в нём слова:
var text = "<какой-то длинный текст>";
foreach (var word in CountWords(text))
Console.WriteLine(word);


Источник: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13
👍20
День 2029. #Оффтоп
«У Нас Кончились Столбцы» — Лучшая Худшая Кодовая База. Начало
Все мы мечтаем работать с передовыми технологиями, современными системами, чистым и понятным кодом. Однако, если вы приходите в компанию, где кодовой базе исполнилось 10+ лет, вы неизбежно столкнётесь с тем, что взорвёт ваш мозг (не спрашивайте, откуда я знаю). Но эта история поразила даже меня. Текст довольно длинный, будет разбит на несколько постов, но я решил его не сокращать, чтобы не лишать вас удовольствия.

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

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

Вы знаете, что у SQL Server есть ограничение на количество столбцов в таблице? Я тоже не знал. В то время их было 1024, сегодня, похоже, 4096. Само собой, большинству людей это знать не нужно. Мы знали. Причина в том, что в Merchants (наша таблица для хранения информации о клиентах) давно закончились столбцы. Решением стала Merchants2. Таблица с (если правильно помню) 500+ столбцами.

Merchants (и её лучшая подруга Merchants2) были источником жизненной силы системы. Всё крутилось вокруг Merchants так или иначе. Но Merchants не была единственной (или двойной) таблицей. Было много правильно нормализованных таблиц, все с внешними ключами к Merchants. Но одна из них всегда будет занимать особое место в моем сердце, SequenceKey.

SequenceKey
------------
SequenceKey
------------
1251238
------------

Для простоты понимания я воссоздал всю таблицу SequenceKey выше. Да. Вы правильно прочитали, это вся таблица. Таблица с одним ключом и одним значением. Можно сказать, что SequenceKey - идеальная таблица. Что может быть проще?

Но вы можете спросить себя, какое возможное применение может иметь таблица с одним столбцом и строкой? Генерация идентификаторов. Как мне сказали в то время, когда-то давно SQL Server не поддерживал автоинкрементные идентификаторы. Это был принятое, правильное решение. Мои попытки выяснить, правда ли это, были безрезультатными. Но на практике её роль была гораздо большей.

SequenceKey была связующим звеном. В каждой хранимой процедуре, которая создавала новые сущности, вы сначала брали ключ из SequenceKey, увеличивали его. А затем вставляли его в качестве идентификатора в N разных таблиц. Теперь у вас было неявное соединение между всеми этими таблицами сущностей. Если вы видели идентификатор в системе, то была большая вероятность, что связанные таблицы будут иметь строку с точно таким же идентификатором. Честно говоря, довольно умно.

Календарь
База данных может существовать вечно, но наша система входа была ограничена календарём. Я не имею в виду настоящий календарь. Я имею в виду таблицу базы данных под названием calendar. Что она содержала? Заполненный вручную календарь. Когда я спросил нашего местного гуру (которого звали Мунч), он сообщил мне, что, когда календарь заканчивается, мы не можем входить в систему. Это произошло несколько лет назад. Поэтому они заставили стажёра заполнить ещё 5 лет, чтобы убедиться, что этого не произойдет в ближайшее время. Какая система использовала этот календарь? Никто не знал.

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

Источник:
https://jimmyhmiller.github.io/ugliest-beautiful-codebase
Автор оригинала: Jimmy Miller
👍27
День 2030. #Оффтоп
«У Нас Кончились Столбцы» — Лучшая Худшая Кодовая База. Продолжение

Начало

Сотрудники
Каждое утро в 7:15 таблица сотрудников удалялась. Все данные полностью исчезали. Затем в таблицу загружался csv-файл из adp. В это время мы не могли войти в систему. Иногда этот процесс давал сбой. Но это был не конец процесса. Данные нужно было реплицировать в штаб-квартиру. Поэтому электронное письмо отправлялось человеку, который каждый день нажимал кнопку, чтобы скопировать данные.

Заменяющая база данных
Вы могли бы подумать: неужели никому не пришло в голову почистить эту БД? Сделать её более удобной для работы? Да. Была копия БД. Данные в этой копии отставали примерно на 10 минут. Синхронизация работала только в одну сторону, но эта база данных была нормализована. Насколько нормализована? Чтобы перейти от продавца к его номеру телефона, требовалось соединить 7 таблиц.

Цифры продаж
У каждого продавца была квота, которую ему нужно было выполнить в месяц, называемая «победой». Таблицы, в которых хранились эти данные (не финансовые данные, а способ учёта продаж), были невероятно сложными. Каждый день назначенное задание выясняло, какие строки были добавлены и обновлены, и синхронизировало их с какой-то системой в штаб-квартире. Это не было проблемой, пока один продавец не понял, что можно попросить вручную изменить эти записи.

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

Кодовая база
Но что такое БД без кодовой базы. Какая же это была великолепная кодовая база. Когда я пришёл, всё было в Team Foundation Server. Если вы не в курсе, это централизованная система управления исходным кодом от Microsoft. Основная кодовая база, с которой я работал, была наполовину VB, наполовину C#. Она работала на IIS и использовала состояние сеанса для всего. Что это означало на практике? Если вы переходили на страницу по пути A или пути B, вы видели на ней совсем разные вещи.

Но описать эту кодовую базу как просто половина VB, половина C# - это не сказать ничего. Наверное, все существующие тогда JS-фреймворки были добавлены в этот репозиторий. Как правило, с некоторыми пользовательскими изменениями, которые автор посчитал необходимыми. В частности, knockout, backbone и marionette. Ну и конечно, было немного jquery и плагинов jquery.

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

Жесткие диски Гилфойла
Манч (да, это было его настоящее имя) держал жёсткий диск Гилфойла в RAID-массиве на своём столе много лет после того, как Гилфойл ушёл из компании. Почему? Потому что Гилфойл был известен тем, что не коммитил код. Более того, он создавал случайное одноразовое приложение Windows для одного пользователя. Поэтому не было редкостью, когда пользователь приходил к нам с отчётом об ошибке для приложения, которое существовало только на жёстком диске Гилфойла, а мы понятия не имели о его существовании.

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

Источник:
https://jimmyhmiller.github.io/ugliest-beautiful-codebase
Автор оригинала: Jimmy Miller
👍22
День 2031. #Оффтоп
«У Нас Кончились Столбцы» — Лучшая Худшая Кодовая База. Окончание

Начало
Продолжение

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

Попутно я узнал, как мыслит Гилфойл. Приложение доставки извлекало всю БД, фильтровало по дате и сохраняло все заказы после даты запуска приложения. Оно полагалось на SOAP-сервис. Нет, сервис был чистой функцией. Все побочные эффекты были на клиенте. Там я обнаружил иерархию в 120 классов с различными методами и 10-уровневое наследование. Единственная проблема… ВСЕ МЕТОДЫ БЫЛИ ПУСТЫМИ.

В конце концов я узнал, что это было необходимо для создания структуры, к которой он мог бы затем применить рефлексию. Рефлексия позволила бы ему создать строку с разделителями (структура которой управлялась базой данных), которую он мог бы отправлять через сокет. Оказывается, всё это в итоге отправлялось в Kewill (сервис, который общался с перевозчиками). Почему происходила ошибка? Kewill повторно использовал 9-значные числа каждый месяц, а кто-то отключил задание cron, которое удаляло старые заказы.

Прекрасный беспорядок
Ещё много можно рассказать об этой кодовой базе. Например, о команде супер-старших разработчиков, которые переписывали всё это, не делая коммитов кода в течение 5 лет. Или о консультантах Red Hat, создавших одну БД, чтобы управлять всеми. Или об улучшении Джастином страницы поиска продавцов. Она была точкой входа во всё приложение. Каждый представитель службы поддержки клиентов звонил продавцу и вводил либо его ID, либо название, чтобы найти его информацию. Это приводило на огромную страницу с любой информацией, которая вам могла бы понадобиться, и всеми ссылками, которые вы могли бы захотеть посетить. Но она была чертовски медленной.

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

Два способа разделения
Почему Джастин смог это сделать? Потому что у проекта не было генерального плана, формата API, документации и т.п. Система была полным и абсолютным хаосом. Никто не мог его исправить, поэтому никто и не пытался. Что мы делали?

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

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

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

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

Источник: https://jimmyhmiller.github.io/ugliest-beautiful-codebase
Автор оригинала: Jimmy Miller
👍17
День 2032. #ЧтоНовенького #CSharp13
Флаги функций с поддержкой тримминга
Два новых атрибута позволяют определять флаги функций, которые можно использовать для включения/отключения областей функциональности, а также для автоматического включения/отключения функций при тримминге или AOT-компиляции.

FeatureSwitchDefinitionAttribute
Атрибут FeatureSwitchDefinition может быть использован, чтобы флаг функции после сборки определялся как константа.
public class Feature
{
[FeatureSwitchDefinition("Feature.IsSupported")]
internal static bool IsSupported =>
AppContext.TryGetSwitch("Feature.IsSupported", out bool enabled)
? enabled
: true;

internal static void Implementation() => …;
}

Здесь мы извлекаем значение IsSupported из конфигурации сборки. В данном случае оно определено в файле проекта (.csproj):
<ItemGroup>
<RuntimeHostConfigurationOption Include="Feature.IsSupported"
Value="false" Trim="true" />
</ItemGroup>

Мы добавляем RuntimeHostConfigurationOption с именем нужной функции (соответствующим параметру атрибута) и булевым значением, включена она или нет.

При сборке с включённым триммингом недосягаемый код, находящийся под флагом, удаляется. Когда приложение собирается с такой настройкой в файле проекта, Feature.IsSupported расценивается как константа false и Feature.Implementation удаляется из сборки.

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

FeatureGuardAttribute
Атрибут FeatureGuard можно использовать для свойства флага функции в качестве защитной конструкции для кода, аннотированного атрибутами RequiresUnreferencedCode, RequiresAssemblyFiles или RequiresDynamicCode:
public class Feature
{
[FeatureGuard(typeof(RequiresDynamicCodeAttribute))]
internal static bool IsSupported =>
RuntimeFeature.IsDynamicCodeSupported;

[RequiresDynamicCode("Feature requires dynamic code support.")]
internal static void DynamicImplementation()
=> …; // Использует dynamic
}

Здесь RuntimeFeature определяет API, которые доступны в среде выполнения. В данном случае свойство IsDynamicCodeSupported показывает, поддерживается ли в среде выполнения динамический код.
Поскольку свойство IsSupported возвращает false, когда динамический код не поддерживается, его можно использовать в качестве защиты для методов, которым требуется динамический код во время выполнения.
if (Feature.IsSupported)
Feature.DynamicImplementation();

Атрибут FeatureGuard сообщает об этом анализатору тримминга и инструментам тримминга. Поэтому, если мы в файле проекта укажем
<PublishAot>true</PublishAot>

то Feature.DynamicImplementation будет удалён при AOT-публикации.

Источник: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview4/runtime.md
6👍7
День 2033. #BlazorBasics
Основы Blazor. (Ре-)Рендеринг Компонентов. Начало

Рендеринг компонентов — основная механика приложений Blazor, превращающая компоненты C# в HTML и CSS. Методы жизненного цикла позволяют нам выполнять пользовательский код синхронно или асинхронно.

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

Методы жизненного цикла компонентов Blazor
Процесс рендеринга не может быть прерван; в противном случае UI бы «завис». Поэтому компоненты Blazor используют методы жизненного цикла, чтобы позволить выполнять пользовательский код в течение их жизненного цикла.

Компонент Blazor впервые отрисовывается после создания его экземпляра при включении в качестве дочернего компонента см. диаграмму ниже.

Метод SetParametersAsync
Это первый метод обратного вызова в жизненном цикле компонента. Он получает параметры, предоставленные родительским компонентом. Переопределяя SetParametersAsync, мы можем использовать пользовательский код для изменения значений, предоставляемых родительским компонентом. По умолчанию параметры будут установлены в свойства, декорированные атрибутом Parameter. Если мы хотим использовать параметры компонента по-другому, мы можем использовать пользовательский код и переопределить метод SetParametersAsync, чтобы определить желаемое поведение.
@code {
// параметры компонента

public override async Task
SetParametersAsync(ParameterView pv)
{
// пользовательский код

await base.SetParametersAsync(pv);
}
}


Методы OnInitialized и OnInitializedAsync
После того, как компонент Blazor завершает выполнение метода SetParametersAsync и, следовательно, все параметры из родительского компонента установлены, вызываются методы жизненного цикла OnInitialized и OnInitializedAsync. Это идеальное место для загрузки данных из БД и назначения их свойствам, используемым в шаблоне компонента.
@code {
private string? message;

[Parameter]
public string Name { get; set; }

protected override void OnInitialized()
{
message = $"Привет, {Name}!";
}
}

Как показано в этом примере, мы также можем использовать значения, параметров компонента в синхронном методе жизненного цикла OnInitialized.

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

Источник:
https://www.telerik.com/blogs/blazor-basics-blazor-component-re-rendering
1👍8
Родительский компонент запускает создание дочернего компонента. Метод жизненного цикла SetParametersAsync и методы OnInitialized(Async) запускаются только для первой отрисовки. Метод OnParametersSet(Async) запускается, когда родительский компонент предоставляет другие значения параметров своему дочернему компоненту. Метод StateHasChanged может использоваться разработчиком для запуска повторной отрисовки компонента вручную.
👍8
День 2034. #BlazorBasics
Основы Blazor. (Ре-)Рендеринг Компонентов. Окончание

Начало

Методы OnParametersSet и OnParametersSetAsync
SetParametersAsync вызывается только при первом рендеринге компонента Blazor. А методы OnParametersSet и OnParametersSetAsync запускаются всякий раз, когда родительский компонент повторно отрисовывается, предоставляя другие значения для параметров дочернего компонента.

Переопределение этих методов предоставляет нам доступ к обновлению значений в компоненте на основе нового набора параметров.
@code {
private string? message;

[Parameter]
public string Name { get; set; }

protected override void OnParametersSet()
{
message = $"Привет, {Name}!";
}
}

После завершения методов OnParametersSet или OnParametersSetAsync запускается автоматическая повторная отрисовка компонента.

Метод StateHasChanged
Мы можем вызвать метод StateHasChanged класса ComponentBase, чтобы сообщить компоненту Blazor об изменении состояния компонента. Он запустит повторную отрисовку компонента, которая вызовет все применимые методы жизненного цикла в процессе отрисовки.

Не следует вызывать метод StateHasChanged в следующих сценариях:
- Обработка событий. Неважно, являются ли события синхронными или асинхронными. Класс ComponentBase запускает отрисовку для большинства обработчиков событий.
- Реализация методов жизненного цикла, таких как OnParametersSetAsync или OnInitialized (синхронных или асинхронных). Класс ComponentBase запускает отрисовку для большинства событий жизненного цикла.

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

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

Асинхронные и синхронные методы жизненного цикла
Для синхронного кода родительские компоненты всегда визуализируются перед дочерними компонентами. Асинхронный код более сложныq. При использовании асинхронных методов жизненного цикла порядок завершения родительского и дочернего компонента не является детерминированным, поскольку он зависит от кода инициализации. Однако Blazor обеспечивает доступность и корректное обновление параметров компонента при визуализации дочернего компонента.

Используйте синхронные методы жизненного цикла, когда не нужно ожидать асинхронных операций. Когда же нужно вызвать БД или использовать другие асинхронные операции, используйте асинхронные методы.

Итого
Компоненты Blazor используют методы жизненного цикла для предотвращения блокировки пользовательского интерфейса и позволяют разработчикам выполнять пользовательский код:
- SetParametersAsync позволяет выполнять пользовательский код, обрабатывающий параметры, предоставленные родительским компонентом, только для первого рендеринга компонента.
- OnInitialized и OnInitializedAsync часто используются для загрузки данных из БД или для инициализации свойств, отображаемых как часть шаблона компонента.
- OnParametersSet и OnParametersSetAsync вызываются, когда родительский компонент повторно отрисовывается, предоставляя другой параметр дочернему компоненту.
- Если UI не отображает ожидаемое значение на экране, вы можете вызвать StateHasChanged в соответствующем месте кода.

Источник: https://www.telerik.com/blogs/blazor-basics-blazor-component-re-rendering
👍8
День 2035. #ЧтоНовенького
Прекращается Поддержка BinaryFormatter
Начиная с .NET 9, BinaryFormatter больше не будет включён в среду выполнения. Его API все ещё присутствует, но реализация всегда будет выдавать исключение PlatformNotSupportedException, независимо от типа проекта. Установка существующего сейчас флага обратной совместимости больше недостаточна для использования BinaryFormatter.

Есть два варианта решения:
1. Настоятельно рекомендуется отказаться от BinaryFormatter из-за связанных ним рисков безопасности и выбрать другой сериализатор.

2. Продолжить использовать BinaryFormatter. Для этого нужно добавить неподдерживаемый пакет совместимости System.Runtime.Serialization.Formatters, который заменяет реализацию, вызывающую исключение в среде выполнения, но подвержен описанным ниже уязвимостям.

Проблема с BinaryFormatter
Дело в том, что любой десериализатор, позволяющий иметь во входных данных информацию о создаваемых объектах, является проблемой безопасности. BinaryFormatter, включённый в первый выпуск .NET Framework в 2002 году, является таким десериализатором. Из-за известных рисков эта функциональность была исключена из .NET Core 1.0. Но в отсутствие чёткого пути миграции на что-то более безопасное, спрос среди клиентов привёл к тому, что BinaryFormatter был обратно включен в .NET Core 2.0. С тех пор команда .NET шла по пути к его удалению, постепенно отключая его по умолчанию в разных типах проектов, но позволяя потребителям включать его с помощью флагов, если было необходимо для обратной совместимости.

Миграция
Выбор другого сериализатора возможен, только если вы контролируете как производителя, так и потребителя сериализованных данных. Если это ваш случай, то в зависимости от ваших потребностей рекомендуются следующие сериализаторы:
- System.Text.Json (JSON)
- DataContractSerializer (XML)
- MessagePack (binary)
- protobuf-net (binary)

Если вы не контролируете производителя, либо десериализуете данные, которые были сериализованы ранее и сохранены (и нет возможности их преобразовать) вы можете использовать новый API для чтения данных BinaryFormatter без выполнения универсальной и уязвимой десериализации с помощью NuGet-пакета System.Formats.Nrbf.

Источник: https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-migration-guide/
👍9