.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
День 1504. #ЗаметкиНаПолях
Использование Нескольких Контекстов EF Core. Окончание
Начало

Ограничения
- Невозможно выполнять соединения между разными экземплярами DbContext, поскольку EF Core не знает, используют ли они одну и ту же базу данных.
- Транзакции будут работать, только если DbContexts используют одну и ту же базу данных. Вы должны создать новую транзакцию и поделиться ею между контекстами, вызвав метод UseTransaction().

Таблица истории миграций
Если вы решите использовать разные схемы для каждого DbContext, вы можете быть неприятно удивлены, узнав, что схема по умолчанию не применяется к таблице истории миграций. Нужно настроить это, вызвав метод MigrationsHistoryTable и указав имя таблицы и схему, в которой будет храниться история миграций для этого контекста:
services.AddDbContext<CatalogDbContext>(opt =>
opt.UseSqlServer(
"CONNECTION_STRING",
o => o.MigrationsHistoryTable(
tableName: "efmigrations",
schema: "catalog")));

services.AddDbContext<OrderDbContext>(opt =>
opt.UseSqlServer(
"CONNECTION_STRING",
o => o.MigrationsHistoryTable(
tableName: "efmigrations",
schema: "order")));

Преимущества использования нескольких контекстов
Использование нескольких DbContexts может дать вашему приложению несколько преимуществ:
- Разделение ответственности
Каждый DbContext может отвечать за определённое подмножество данных приложения, что может помочь организовать код и сделать его более модульным.

- Повышение производительности
Когда вы разделяете доступ к данным на несколько DbContext, приложение может снизить риск конфликтов и улучшить параллелизм, что может повысить производительность.

- Больше контроля и безопасности
Вы можете настроить более детальный контроль доступа (например, ограничив его только чтением) для повышения безопасности приложений. Вы также можете оптимизировать производительность и использование ресурсов.

Источник: https://www.milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application
👍9
День 1505. #BestPractices
20 Вещей, Которым я Научился за 20 Лет. Начало
Учиться у тех, кто был до нас, очень важно для успеха, но мы часто забываем одну важную оговорку. Почти все советы зависят от контекста, но они редко полезны во всех случаях. Без понимания контекста совет не имеет смысла или, что ещё хуже, вреден.

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

1. Я всё ещё очень многого не знаю
«Как можно не знать, что такое Kafka?», «Ты никогда не слышал о Rust?» Большинство из нас слышали такого рода заявления. Многие из нас любят разработку ПО, потому что мы учимся на протяжении всей жизни, и в ПО, независимо от того, в каком направлении вы развиваетесь, есть огромные области знаний, которые развиваются во всех направлениях и расширяются с каждым днём. Вы можете потратить десятилетия карьеры и всё ещё иметь огромный пробел в знаниях по сравнению с кем-то, кто также провел десятилетия в, казалось бы, похожей роли. Чем раньше вы это поймёте, тем скорее сможете избавиться от синдрома самозванца и вместо этого получать удовольствие от обучения и обучения других.

2. Самое сложное в ПО — создавать правильные вещи
Да, это клише, но большинство программистов не верят этому, потому что думают, что это обесценивает их работу. Это не так. Это подчёркивает сложность и иррациональность среды, в которой нам приходится работать. Вы можете создать самую технически впечатляющую вещь в мире, и никто не захочет ей пользоваться. Это происходит постоянно. Разработка ПО — это в основном слушание, и нам часто приходится быть программистом, экстрасенсом и антропологом в одном лице. Инвестирование в этот процесс проектирования, будь то через увлечённых членов команды UX или просто самообразование, принесёт огромные дивиденды. Как реально рассчитать стоимость создания неправильного ПО? Одно точно, что она гораздо выше, чем просто потерянное время программиста.

3. Лучшие программисты думают, как дизайнеры
Великие программисты глубоко задумываются о пользовательском опыте своего кода. Они могут не думать об этом в этих терминах, но будь то внешний API, программный API, пользовательский интерфейс, протокол или любой другой интерфейс; великие инженеры обдумывают, кто будет его использовать, почему он будет использоваться, как он будет использоваться и что важно для этих пользователей. Помнить о потребностях пользователя — это основа хорошего пользовательского опыта.

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

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

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

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

Источник:
https://www.simplethread.com/20-things-ive-learned-in-my-20-years-as-a-software-engineer/
👍27
День 1506. #BestPractices
20 Вещей, Которым я Научился за 20 Лет. Продолжение
Начало

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

8. Каждая система в итоге становится плохой, смиритесь
У Бьерна Страуструпа есть цитата: «Есть только два вида языков: те, на которые люди жалуются, и те, которые никто не использует». Это может быть распространено и на большие системы. Не существует «правильной» архитектуры, вы никогда не спроектируете идеальный интерфейс, тесты всегда будут медленными. Это не повод не улучшать ситуацию, но меньше беспокойтесь об элегантности и совершенстве. Стремитесь к постоянному улучшению и созданию пригодной для жизни системы, в которой вашей команде будет приятно работать и которая будет стабильно приносить пользу.

9. Никто не задаёт достаточно вопросов «почему»
Используйте любую возможность, чтобы подвергнуть сомнению предположения и подходы из серии «всегда так делаем». Приходит новый член команды? Обратите внимание на то, где он путается и какие вопросы задаёт. Есть новый запрос странной функции? Убедитесь, что вы понимаете цель и то, что движет желанием иметь эту функцию. Если вы не получите чёткого ответа, продолжайте спрашивать «почему», пока не поймёте.

10. Лучше избегать программистов 0,1x, чем искать программистов 10x
10-х программист — глупый миф. Идея, что кто-то может сделать за день то, что другой компетентный, трудолюбивый, такой же опытный программист может сделать за 2 недели, глупа. Некоторые пишут в 10 раз больше кода, а затем его приходится исправлять в 10 раз чаще. Кто-то может стать 10x программистом только при сравнении с 0,1x программистом, который тратит время впустую, не спрашивает отзывов, не тестирует свой код, не рассматривает пограничные случаи и т. д. Лучше не допустить в команду программистов 0,1x, чем найти мифического программиста 10x.

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

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

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

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

Источник:
https://www.simplethread.com/20-things-ive-learned-in-my-20-years-as-a-software-engineer/
Автор оригинала: Justin Etheredge
👍14
День 1507. #BestPractices
20 Вещей, Которым я Научился за 20 Лет. Окончание
Начало
Продолжение

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

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

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

17. Держите свои процессы как можно более экономичными
В наши дни все хотят быть гибкими, но быть «гибким» означает строить вещи небольшими порциями, учиться, а затем повторять. Если кто-то пытается втиснуть в этот процесс гораздо больше, чем нужно, то он, вероятно, что-то продаёт. Это не значит, что людям не нужно доверять и не полагаться на них в таких случаях. Но сколько раз вы слышали, как кто-то из крупной компании или крупного проекта хвастался тем, насколько хорош их процесс Scrum? Продолжайте опираться на процесс, пока не поймете, что вам нужно что-то больше.

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

19. Собеседования почти бесполезны в смысле понимания, насколько хорошим членом команды будет человек
Интервью гораздо лучше проводить, пытаясь понять, кто человек такой и насколько он заинтересован в данной области знаний. Пытаться выяснить, насколько хорошим членом команды он будет, бесполезно. То, насколько человек умён или хорошо осведомлён, также не является хорошим показателем того, что он будет отличным членом команды. Никто не скажет вам в интервью, что он будет ненадёжным, грубым или никогда не будет приходить на встречу вовремя. Некоторые говорят, что есть «сигналы» для таких вещей… «Если человек спрашивает об отгулах на первом собеседовании, то это всё, что его волнует!» Но все это ерунда. Если вы используете такие сигналы, вы просто угадываете и отвергаете хороших кандидатов.

20. Всегда стремитесь построить меньшую систему
Есть много вещей, которые будут подталкивать вас к созданию более крупной системы «на всякий случай». Распределение бюджета, неспособность решить, какие функции следует урезать, желание предоставить «лучшую версию» системы. Все эти вещи подталкивают нас к тому, чтобы делать слишком много. Вы должны бороться с этим. Когда вы создаёте систему, вы изучаете проблему так хорошо, что в итоге вы создаёте гораздо лучшую систему, чем вы когда-либо могли бы спроектировать изначально. Это удивительно трудно продать большинству людей.

Буду рад, если в комментариях вы поделитесь мудростью, которую вы почерпнули за свою карьеру.

Источник: https://www.simplethread.com/20-things-ive-learned-in-my-20-years-as-a-software-engineer/
Автор оригинала: Justin Etheredge
👍16
День 1508. #ЗаметкиНаПолях
Создавайте Аварийные Пути в Вашем ПО
Вы должны проверять входные данные, предполагать, что они враждебны, перепроверять их на каждом слое и т. д. Это принципы написания хорошего кода. Когда всё идёт гладко, хочется запретить недопустимые операции и опасные действия. Проблемы возникают, когда что-то идёт не так.

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

Допустим, истекает срок действия корневого сертификата, а значит, аутентификации нет. Вы не можете аутентифицироваться на серверах, потому что срок действия используемого вами сертификата аутентификации также истёк. Вам нужен физический доступ, но дата-центр вас не пустит, так как вы не можете аутентифицироваться. Это не фантастика, это случилось недавно в Facebook (не сертификаты, а плохая конфигурация IP, но суть та же).

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

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

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

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

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

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

Источник: https://ayende.com/blog/198625-A/building-emergency-pathways-in-your-software-never-to-be-used
👍7
This media is not supported in your browser
VIEW IN TELEGRAM
День 1509. #ЧтоНовенького
Публичная Бета-версия GitHub Markdown Helpers
В публичной бета-версии вышли функции-помощники Markdown на основе Slash-команд. Просто введите / в описании/комментариях задачи, пул-реквесте или обсуждений и используйте возникающее диалоговое окно, чтобы выбрать из ряда полезных ярлыков Markdown. См. видео.

На данный момент включено 6 функций:
- Блок кода /code
Подсветка синтаксиса для конкретного языка.
- Подробности /details
Сворачиваемая область.
- Сохранённые ответы /saved-replies
Выбор из сохранённых ответов пользователя.
- Таблица /table
Таблица. Можно выбрать количество строк и столбцов.
- Шаблон /template
Шаблон из репозитория. Эта команда будет работать для шаблонов задач и шаблонов пул-реквестов.
- Список заданий* /tasklist
Вставляет список задач (работает только в описании задачи).
*В настоящее время находятся в закрытой бета-версии.

Документация по Slash-командам находится здесь. А здесь можно поучаствовать в дискуссии и оставить отзыв.

Источник
👍11
День 1510. #ЗаметкиНаПолях
Создание Параметризованных Тестов в xUnit
xUnit использует атрибуты для определения методов тестирования. Атрибут Fact определяет простой тест, а атрибут Theory определяет параметризованный тест. Допустим, у нас есть следующий тест, проверяющий, что парсер email корректно извлекает домен:
public void Should_Return_Domain(
string email, string expectedDomain)
{
var parser = new EmailParser();
var domain = parser.GetDomain(email);
Assert.Equal(domain, expectedDomain);
}

Рассмотрим 4 способа написания параметризованных тестов.
1. InlineData
Простейший способ - вы предоставляете тестовые данные, передавая значения конструктору:
[Theory]
[InlineData("[email protected]", "test.com")]
[InlineData("[email protected]", "github.io")]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }

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

2. MemberData
С помощью MemberData мы можем программно предоставить тестовые данные из статического свойства или члена типа:
[Theory]
[MemberData(nameof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }

public static IEnumerable<object[]>
EmailTestData => new List<object>
{
new object[] { "[email protected]", "test.com" },
new object[] { "[email protected]", "github.io" }
};

Вы указываете имя члена в атрибуте MemberData. Одно ограничение - свойство (или метод) должно возвращать IEnumerable<object[]>, поэтому строгой типизации не существует.

3. ClassData
Позволяет извлечь тестовые данные в отдельный класс. Это полезно для организации тестовых данных отдельно от тестов и упрощает повторное использование. Вы загружаете тестовые данные из класса, который наследуется от IEnumerable<object[]> и реализует метод GetEnumerator:
[Theory]
[ClassData(typeof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }

public class EmailTestData : IEnumerable<object[]>
{
public IEnumerable<object[]> GetEnumerator()
{
yield return new object[] {
"[email protected]", "test.com" };
yield return new object[] {
"[email protected]", "github.io" };
}

IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
};

Этот подход сложен, потому что нужно реализовать интерфейс IEnumerable. И мы всё ещё страдаем от отсутствия безопасности типов.

4. TheoryData
Позволяет реализовать класс для тестовых данных, сохраняя при этом безопасность типов. Вот пример в сочетании с атрибутом ClassData, но его также можно использовать и с MemberData, возвращая TheoryData из свойства или метода:
[Theory]
[ClassData(typeof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }

public class EmailTestData :
TheoryData<string, string>
{
public EmailTestData()
{
Add("[email protected]", "test.com");
Add("[email protected]", "github.io");
}
};

TheoryData – обобщённый класс, который позволяет указывать типы для параметризованного теста. Вы просто вызываете метод Add в конструкторе, чтобы предоставить тестовые данные для одного тестового примера. А добавление дополнительных тестов сводится к многократному вызову метода Add.

Источник: https://www.milanjovanovic.tech/blog/creating-data-driven-tests-with-xunit
👍20
День 1511. #Алгоритмы
Использование Префиксного Дерева для Текстового Поиска
Сегодня рассмотрим алгоритм префиксного дерева (Trie) и способы его использования в C# для эффективного поиска текстовых шаблонов.
Trie (произносится «трай») — это древовидная структура данных, которая часто используется для быстрого хранения и извлечения строк. Она состоит из узлов, представляющих отдельные символы в строке. Корневой узел – пустая строка, а каждый дочерний представляет символ, который может следовать за строкой, представленной его родителем.

Преимущества
1) Быстрое определение, присутствует ли данная строка в наборе строк. Строка представлена в виде уникального пути в дереве, что позволяет легко определить её наличие или отсутствие.
2) Способность быстро находить все строки с заданным префиксом. Все строки с общим префиксом имеют один и тот же путь в дереве до конечной точки префикса.

Рассмотрим слова «cat» и «car».
Сохранение: Корневой узел пустой. Первый символ в обеих строках — «c», поэтому добавляем дочерний узел. Аналогично добавляем дочерний узел для «а». Наконец, добавляем два дочерних узла из узла «a» для представления символов «t» и «r».
Поиск: Теперь можно легко определить, присутствует ли в дереве, например, строка «cab». А подсчёт всех строк с префиксом «ca» — это просто количество дочерних узлов для узла «c» > «a».

Реализуем структуру данных Trie. Это набор узлов, поэтому начнём с создания класса TrieNode:
public class TrieNode
{
public bool IsWord { get; set; }
public Dictionary<char, TrieNode>
Children { get; } = new();
}
TrieNode хранит флаг, указывающий, представляет ли узел конец слова в свойстве IsWord, а также словарь дочерних узлов.

Теперь создадим класс Trie, который хранит ссылку на корневой узел и предоставляет методы для вставки и поиска слов:
public class Trie
{
private readonly TrieNode _root = new();

public void AddWord(string word)
{
var node = _root;
foreach (char c in word)
{
if (!node.Children.ContainsKey(c))
node.Children[c] = new();
node = node.Children[c];
}
node.IsWord = true;
}

public bool Search(string word)
{
var node = _root;
foreach (char c in word)
{
if (!node.Children.ContainsKey(c))
return false;
node = node.Children[c];
}
return node.IsWord;
}
}

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

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

Использование
var trie = new Trie();
var text = "The quick brown fox jumps over the lazy dog";
foreach (var word in text.Split(' '))
trie.AddWord(word);

Console.WriteLine(trie.Search("quick"));

Создаём экземпляр Trie и добавляем каждое слово предложения в него с помощью метода AddWord(). Далее с помощью метода Search() проверяем существование слова.

Источник: https://code-maze.com/csharp-using-trie-class-for-efficient-text-pattern-searching/
👍15
День 1512. #ЧтоНовенького
Новинки
ASP.NET Core 8 Превью 2. Начало
Недавно Microsoft выпустила превью 2 .NET 8, в которую включены несколько улучшений в ASP.NET Core. Вот самые заметные.

1. QuickGrid — высокопроизводительный компонент сетки, который позволяет разработчикам отображать данные в табличном формате с расширенными функциями, такими как сортировка, фильтрация, разбиение по страницам и виртуализация. Чтобы его использовать необходима ссылка на пакет Microsoft.AspNetCore.Components.QuickGrid. Этот компонент ранее был экспериментальным пакетом для .NET 7 и претерпел изменения и улучшения API для версии .NET 8.
Если вы использовали QuickGrid в .NET 7, чтобы обновиться до .NET 8, придётся внести некоторые изменения:
- переименовать атрибут Value в State
- переименовать атрибут IsDefaultSort в InitialSortDirection,
- добавить IsDefaultSortColumn=true,
- удалить атрибут ResizableColumns.
На демо-странице https://aspnet.github.io/quickgridsamples/ можно попробовать QuickGrid в действии.

2. Улучшена производительность Blazor WebAssembly с помощью jiterpreter.
jiterpreter — это новая функция среды выполнения в .NET 8, которая обеспечивает частичную поддержку JIT в интерпретаторе .NET IL для повышения производительности среды выполнения.
Приложения Blazor WebAssembly могут запускать код .NET в браузере благодаря небольшой среде выполнения .NET, реализованной в WebAssembly, которая загружается вместе с приложением. Эта среда выполнения представляет собой интерпретатор .NET IL, который является полностью функциональным и небольшим по размеру, но ему не хватает преимуществ производительности выполнения нативного кода посредством JIT-компиляции. JIT для WebAssembly требует создания новых модулей WebAssembly на лету и создания их экземпляров, что создает проблемы для среды выполнения. Вместо этого приложения Blazor WebAssembly могут выбрать предварительную компиляцию (AOT) в WebAssembly, чтобы повысить производительность во время выполнения, но за счёт гораздо большего размера загрузки. Поскольку некоторые распространённые шаблоны кода .NET несовместимы с AOT, интерпретатор .NET IL по-прежнему необходим в качестве резервного механизма для поддержания полной функциональности.

jiterpreter оптимизирует выполнение байт-кодов интерпретатора, заменяя их крошечными фрагментами кода WebAssembly. Используя интерпретатор в качестве основы, мы можем оптимизировать наиболее важные части приложения без необходимости обрабатывать более сложные или непонятные случаи и без чрезмерного усложнения среды выполнения. Хотя jiterpreter не является полной реализацией JIT, он значительно повышает производительность во время выполнения без увеличения размера и времени сборки AOT. jiterpreter также помогает при использовании AOT, оптимизируя случаи, когда среда выполнения должна отступать от интерпретатора. Таким образом, jiterpreter может значительно ускорить выполнение низкоуровневых операций. В превью 2 .NET 8 jiterpreter автоматически включен.

Разработчики, которые хотят использовать ASP.NET Core с превью 2 .NET 8, должны сначала установить пакет SDK для .NET 8. Пользователям Visual Studio для Windows рекомендуется загрузить последнюю предварительную версию VS 2022. Visual Studio для Mac не поддерживает предварительные версии .NET 8 в настоящее время.

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

Источник:
https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-2/
👍12
День 1513. #ЗаметкиНаПолях
Таймеры в .NET
В .NET есть как минимум 6 разных таймеров! Каждый для своей цели и варианта использования. Рассмотрим различия между ними.

Во-первых, есть специальные таймеры пользовательского интерфейса. Эти таймеры используются для выполнения кода в UI-потоке:
- System.Windows.Forms.Timer
- System.Windows.Threading.DispatcherTimer

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

Для WebForms есть ещё один таймер: System.Web.UI.Timer. Он генерирует событие обратной передачи на сервере. Но не будем об устаревших технологиях)))

Наконец, 3 таймера, которые не зависят от пользовательского интерфейса.

1. System.Threading.Timer
Самый простой. Он планирует обратный вызов в ThreadPool. Если обработчику требуется больше времени для выполнения, чем интервал, обработчик будет выполнен снова, и вы получите несколько обработчиков, работающих параллельно.
var timer = new System.Threading.Timer(
// обратный вызов может быть выполнен параллельно
// если предыдущий ещё не завершился
// до старта следующего
callback: state => Console.WriteLine("tick"),

// Используется для передачи данных
// в метод обратного вызова,
// чтобы не использовать замыкание
state: null,

// Начать немедленно
dueTime: TimeSpan.Zero,

// Повторять каждую секунду
period: TimeSpan.FromSeconds(1));

// Поставить таймер на паузу
timer.Change(
dueTime: Timeout.Infinite,
period: Timeout.Infinite);

2. System.Timers.Timer
Использует System.Threading.Timer внутри и предоставляет несколько дополнительных функций, таких как AutoReset, Enabled или SynchronizingObject, которые позволяют настроить способ выполнения обратного вызова. Также событие Tick позволяет зарегистрировать несколько обработчиков. Вы также можете изменить обработчик после запуска таймера.
var timer = new System.Timers.Timer(
TimeSpan.FromSeconds(1));

// несколько обработчиков
timer.Elapsed += (sender, e)
=> Console.WriteLine("Handler 1");
timer.Elapsed += (sender, e)
=> Console.WriteLine("Handler 2");

// Останавливаем после 1го выполнения
timer.AutoReset = false;

// Запускаем
timer.Start();

3. System.Threading.PeriodicTimer
Используется в цикле и поддерживает асинхронные обработчики. У него нет события Tick, но он предоставляет метод WaitForNextTickAsync, возвращающий ValueTask<bool>, которая завершается к концу интервала. Булев параметр указывает, был ли таймер удалён. Таким образом, можно использовать его в цикле while и обратные вызовы не могут перекрываться.
using var cts = 
new CancellationTokenSource();
using var timer =
new PeriodicTimer(TimeSpan.FromSeconds(1));

while (
await timer.WaitForNextTickAsync(cts.Token))
{
Console.WriteLine("Tick");
await AsyncOperation();
}

Источник: https://www.meziantou.net/too-many-timers-in-dotnet.htm
👍15
День 1514. #ЧтоНовенького
Новинки
ASP.NET Core 8 Превью 2. Продолжение
Начало

3. Новый интерфейс IResettable в ObjectPool
Microsoft.Extensions.ObjectPool обеспечивает поддержку объединения экземпляров объектов в памяти. Приложения могут использовать пул объектов, если выделение или инициализация значений требуют больших затрат.

В превью 2 упрощено использование пула объектов путём добавления интерфейса IResettable. Повторно используемые типы часто необходимо возвращать в состояние по умолчанию между использованиями. Типы IResettable автоматически сбрасываются при возврате в пул объектов.
public class ReusableBuffer : IResettable
{
public byte[] Data { get; }
= new byte[1024 * 1024]; // 1 MB

public bool TryReset()
{
Array.Clear(Data);
return true;
}
}

var bufferPool = ObjectPool.Create<ReusableBuffer>();
var buffer = bufferPool.Get();
try
{
await ProcessDataAsync(buffer.Data);
}
finally
{
// Данные автоматически сбрасываются
bufferPool.Return(buffer);
}

4. Повышение производительности передачи по именованным каналам
В превью 1 была добавлена поддержка использования именованных каналов в Kestrel. Именованные каналы — это популярная технология для построения межпроцессного взаимодействия (IPC) между приложениями Windows. Теперь вы можете создать сервер IPC, используя .NET, Kestrel и именованные каналы.
var builder = 
WebApplication.CreateBuilder(args);
builder.WebHost
.ConfigureKestrel(opts =>
{
opts.ListenNamedPipe("MyPipeName");
});

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

Источник: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-2/
👍8
День 1515. #Карьера
Менталитет, Ведущий к Успеху в Разработке ПО
Образ мышления, который вы привносите в свою работу, играет важную роль в определении траектории вашей карьеры. Дружелюбное, но участное отношение, активное стремление поддержать и воодушевить коллег может направить на путь личного роста и профессионального успеха. А негативное мышление - нанести ущерб.

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

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

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

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

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

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

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

Источник: https://betterprogramming.pub/the-mindset-that-leads-to-success-in-software-development-a6b9ac955b75
👍13
День 1516. #Оффтоп
Прекрасного воскресенья вам. Сегодня поговорим об искуственном интеллекте. А поскольку я сам о нём почти ничего не знаю, порекомендую вам два видео с канала Coputerphile. Это беседы с ютубером, специалистом по искусственному интеллекту, Робертом Майлсом. Кстати, вот его канал https://www.youtube.com/@RobertMilesAI

Итак, первое видео (примерно месячной давности) – про ChatGPT https://youtu.be/viJt_DXTfwA.
Роберт объясняет, что такое ChatGPT, чем он отличается от GPT, как работает, почему иногда выдаёт неверные факты, какие интересные и забавные ситуации возникали при его использовании, а также почему они возникали. Почему нельзя «перетренировывать» модели, и что может получиться, если «перетренировать», например, GitHub Copilot?

Второе видео, вышедшее всего несколько дней назад – про Bing Chat https://youtu.be/jHwHPyWkShk (поиск Bing с прикрученным к нему ChatGPT), который в теории должен был усовершенствовать поиск в интернете, добавив к нему ИИ. Но вдруг пользователи стали сталкиваться со странными, откровенно ложными и даже грубыми ответами. Что пошло не так у Майкрософт, почему они выпустили недоделанный продукт, и можно ли его исправить?
👍4
День 1517. #ЗаметкиНаПолях
Параллельная Публикация Уведомлений в MediatR
MediatR — популярная библиотека с простой реализацией паттерна посредник в .NET. С ростом популярности паттерна CQRS MediatR стала популярной библиотекой для реализации команд и запросов.

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

Нам нужен класс, реализующий INotification:
public record OrderCreated(Guid OrderId) : INotification;

А также реализация соответствующего INotificationHandler:
public class OrderCreatedHandler : 
INotificationHandler<OrderCreated>
{
private readonly INotificationService svc;

public OrderCreatedHandler(
INotificationService service)
{
svc = service;
}

public async Task Handle(
OrderCreated notification,
CancellationToken ct)
{
await svc.SendOrderCreatedEmail(
notification.OrderId,
ct);
}
}

Теперь можно публиковать сообщение с помощью IMediator или IPublisher:
await publisher.Publish(
new OrderCreated(order.Id),
cancellationToken);

MediatR вызовет все соответствующие обработчики. До 12й версии MediatR стратегия публикации вызывала каждый обработчик по отдельности. Однако появился новый интерфейс INotificationPublisher, управляющий тем, как вызываются обработчики.

Реализация по умолчанию - ForeachAwaitPublisher:
public class ForeachAwaitPublisher 
: INotificationPublisher
{
public async Task Publish(
IEnumerable<NotificationHandlerExecutor> executors,
INotification notification,
CancellationToken ct)
{
foreach (var e in executors)
{
await e
.HandlerCallback(notification, ct)
.ConfigureAwait(false);
}
}
}
Она вызывает обработчики по одному и завершается неудачей при ошибке в одном из обработчиков.

Но вы также можете использовать TaskWhenAllPublisher (показаны только отличия в реализации метода Publish:
var tasks = executors
.Select(e =>
e.HandlerCallback(notification, ct))
.ToArray();
return Task.WhenAll(tasks);

TaskWhenAllPublisher вызывает все обработчики одновременно и выполняет их все независимо от того, возникали ли в них ошибки. Если вы сохраните задачу, возвращенную TaskWhenAllPublisher, вы можете получить доступ к свойству Task.Exception, содержащему экземпляр AggregateException, и реализовать обработку исключений.

Настройка стратегии публикации происходит в методе AddMediatR.
Если вы хотите использовать стратегию TaskWhenAllPublisher, вы можете:
- указать значение для свойства NotificationPublisher (тогда издатель будет синглтоном),
- указать тип стратегии в свойстве NotificationPublisherType и использовать свойство ServiceLifetime для задания времени жизни:
services.AddMediatR(cfg => {

cfg.NotificationPublisher =
new TaskWhenAllPublisher();

// или
cfg.NotificationPublisherType =
typeof(TaskWhenAllPublisher);
cfg.ServiceLifetime = ServiceLifetime.Transient;
});

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

Чем это полезно?
Возможность параллельного запуска обработчиков уведомлений обеспечивает значительное повышение производительности по сравнению с поведением по умолчанию.
Однако обратите внимание, что все обработчики будут использовать одну и ту же область видимости. Если у вас есть экземпляры сервисов, которые не поддерживают конкурентный доступ, вы можете столкнуться с проблемами. К сожалению, одним из таких является EF Core DbContext.

Источник: https://www.milanjovanovic.tech/blog/how-to-publish-mediatr-notifications-in-parallel
👍9
This media is not supported in your browser
VIEW IN TELEGRAM
День 1518. #ЧтоНовенького
Группы Точек Останова
В Visual Studio 17.6 превью 2
появилась новая функция Группы точек останова, позволяющая создавать именованные группы с любым количеством точек останова в них. Так можно настраивать конфигурацию точек и включать/выключать их по отдельности или вместе.

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

Чтобы создать новую группу точек останова, выберите в окне Breakpoints в меню New > Breakpoint Group… (Новая > Группа точек останова…). Затем нужные точки можно либо перетащить в группу или нажать на них правой кнопкой и выбрать “Add to Breakpoint Group” (Добавить в Группу Точек Останова). После этого можно включать/отключать отдельные точки или всю группу (см. видео).

Источник
👍23
День 1519. #Testing
Как Тестировать Контракты HTTP API в .NET
Тесты HTTP API можно называть модульными, интеграционными или компонентными – это не так важно. Важно, чтобы тесты взаимодействовали только с интерфейсом, использующимся в производственном коде.

Если определённая часть вашей системы вызывается только через HTTP API, тест должен делать то же самое. Прямой вызов метода класса контроллера ASP.NET нарушает эту идею.

Убедитесь, что вы проверяете только то, что имеет отношение к конкретному тестовому случаю:
- Если вы ожидаете исключения, убедитесь, что тип исключения правильный, свойства имеют правильные значения, а сообщение соответствует ожиданиям.
- В сообщении об исключении, часто надо проверить только отдельные части. Утверждение WithMessage в Fluent Assertions принимает шаблон сообщения именно по этой причине.
- Если API возвращает конкретный код ошибки HTTP, проверяйте только его и игнорируйте тело.
- Если тест охватывает определённый URL, где имеет значение только определённое свойство результата, игнорируйте остальные.
Это позволяет избегать провалов тестов по несвязанным причинам.

Многие разработчики используют в тестах реальный тип (например, тип DTO) из кода проекта, чтобы сравнивать с ним десериализованный результат, полученный из HTTP API. Обычный аргумент в пользу этого: это удобнее для рефакторинга, так что, например, изменение имени свойства этого типа не нарушит теста.
Но на самом деле это должно ломать тест! Маршрут, заголовки и конкретный JSON, возвращаемый HTTP API, являются контрактом и, следовательно, должны рассматриваться как таковые.

Как быть? Есть два распространённых способа:
- Использовать необработанный JSON. Самый чистый способ, но это сложно, если надо проверить только отдельные части результата.
- Десериализовать результат в анонимный тип определённой структуры. Рассмотрим пример, используя NewtonSoft.Json и Fluent Assertions:
IHost host = GetTestClient();
var response = await host.GetAsync(…);
var body = await
response.Content.ReadAsStringAsync();

var expect = new[] {
new {
State = "Active",
Count = 1
}
}

var actual = JsonConvert
.DeserializeAnonymousType(body, expect);

actual.Should().BeEquivalentTo(expect);

Здесь мы устанавливаем ожидание (expect) с определёнными значениями, а затем используем DeserializeAnonymousType, чтобы NewtowSoft.Json попытался десериализовать JSON в анонимный объект, структура которого определяется объектом expect. BeEquivalentTo использует глубокое сравнение ожидаемого и фактического (actual) объектов.

В System.Text.Json мы можем добиться того же результата:

var actual = JsonSerializer.Deserialize(
body,
expect.GetType(),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});

actual.Should().BeEquivalentTo(expect);

Вы можете инкапсулировать большую часть логики проверки в метод BeEquivalentTo, который принимает множество настроек. Также можно использовать метод Should().BeAs(), предоставляемый библиотекой FluentAssertions.Web.

Источник: https://www.continuousimprover.com/2023/03/test-http-contracts.html
👍13
День 1520. #ЗаметкиНаПолях
Кэширование Вывода в
ASP.NET Core. Начало
Кэширование выходных данных — это первоклассная функция в ASP.NET Core 7. Раньше его приходилось реализовывать самостоятельно. Рассмотрим особенности кэширования и как его реализовать.

Кэширование вывода — можно применить в ASP.NET Core для кэширования часто используемых данных, в основном для повышения производительности. Предотвратив чрезмерные вызовы ресурсоемких зависимостей (например, БД или сетевых API), мы можем значительно улучшить время отклика, что является одним из ключей к масштабированию приложений.

Прежде всего, кэширование вывода (Output Caching) сильно отличается от кэширования ответов (Response Caching):
- Ответственность: кэширование ответов возлагает ответственность за кэширование на клиентов (или промежуточные прокси-серверы) путем установки заголовков кэша. Кэширование вывода возлагает ответственность на сервер.
- Носитель данных: кэширование ответов хранится в памяти, тогда как кэширование вывода имеет множество параметров для настройки хранилища.
- Удаление: поскольку мы контролируем кэширование вывода на сервере, у нас есть возможность удалить эти записи кэша. При кэшировании ответов это сложно, поскольку мы не контролируем поведение клиентов.
- Повторная проверка: кэширование вывода может возвращать код ответа 304 Not Modified вместо кэшированного тела ответа. Это поможет сэкономить трафик.

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

Настройка кэширования вывода
Давайте начнем с настройки самого простого примера кэширования вывода:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddOutputCache();
var app = builder.Build();

app.MapControllers();
app.UseOutputCache();
app.Run();

Мы добавляем промежуточное ПО для кэширования вывода в коллекцию сервисов и конвейер запросов.

Рассмотрим простейший метод контроллера, возвращающий случайное число:
public IResult Get()
=> Results.Ok(Random.Shared.Next(100));

При каждом обновлении число меняется. Но если добавить к методу атрибут [OutputCache], то при обновлении результат будет оставаться прежним.
По умолчанию ответ кэшируется на 1 минуту.

Политики
Политики кэширования вывода позволяют настроить кэширование и в самом атрибуте, но лучше сделать это централизованно в Program.cs:
builder.Services.AddOutputCache(opt =>
{
opt.AddBasePolicy(c =>
c.Expire(TimeSpan.FromSeconds(5)));
opt.AddPolicy("CacheTenSec", c =>
c.Expire(TimeSpan.FromSeconds(10)));
});

Здесь мы добавляем две политики:
- Базовая политика по умолчанию кэширует на 5 секунд.
- Политика CacheTenSec кэширует на 10 секунд.

Если удалить атрибут [OutputCache] из нашего метода действия, мы увидим, что ответ кэшируется на 5 секунд. Если добавить атрибут с указанием политики:
[OutputCache(PolicyName="CacheTenSec")]
то ответ будет кэшироваться на 10 секунд. Политики кэширования позволяют централизовано определять стратегии кэширования, избавляя методы действий от лишних атрибутов.

Стоит отметить, что конфигурация кэша по умолчанию принимает некоторые смелые решения:
- кэшируются только ответы HTTP 200,
- кэшируются только ответы GET/HEAD,
- не кэшируются ответы с аутентификацией или cookie.
Это можно переопределить с помощью создания пользовательской политики кэширования вывода.

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

Источник:
https://code-maze.com/aspnet-core-output-caching/
👍17
День 1521. #ЗаметкиНаПолях
Кэширование Вывода в
ASP.NET Core. Продолжение
Начало

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

1. Вариация по запросу
Изменим политику CacheTenSec:
opt.AddPolicy("CacheTenSec", c =>
c.Expire(TimeSpan.FromSeconds(10))
.SetVaryByQuery("par1"));

Здесь мы указываем, что хотим менять ключ кэша для параметра строки запроса par1. Теперь для каждого уникального значения par1 будет возвращаться свой ответ, кэшированный на 10 секунд. Однако другие параметры запроса на это влиять не будут. Например: www.site.com и www.site.com?par2=123 вернут один и тот же кэшированный ответ. Это может быть полезно, если мы хотим чётко указать, как результаты варьируются в зависимости от параметров.

2. Вариация по заголовку
opt.AddPolicy("CacheTenSec", c =>
c.Expire(TimeSpan.FromSeconds(10))
.SetVaryByQuery("par1")
.SetVaryByHeader("X-Client-Id"));

Здесь мы указываем, что хотим, чтобы кэш варьировался в зависимости от значения заголовка X-Client-Id (браузер клиента). Наиболее распространённый вариант использования вариации по заголовку — во время согласования содержимого - вариация по заголовку Accept. Например, можно по-разному кэшировать ответы JSON и XML из-за разных затрат по обработке этих запросов на сервере.

3. Существует также VaryByValue, более сложная конфигурация, которая позволяет изменять ключ по вычисляемому на сервере значению.

Повторная проверка кэша
До сих пор мы возвращали полное тело кэшированного ответа, даже если оно было идентично предыдущему ответу. Но можно просто проинструктировать клиента, что ответ тот же.
Добавим ещё одну конечную точку, используя минимальные API в Program.cs:
app.MapGet("/etag", async (context) =>
{
var etag = $"\"{Guid.NewGuid():n}\"";
context.Response.Headers.ETag = etag;
await context.Response.WriteAsync("hello");
}).CacheOutput();

Здесь мы устанавливаем специальный заголовок ответа, называемый ETag, со значением GUID. Если мы сделаем запрос к URL /etag в Postman, мы увидим в ответе:
- заголовок ETag,
- код ответа 200 (ОК),
- тело ответа «hello».

Добавим в запрос специальный заголовок If-None-Match со значением только что полученного ETag (увеличьте время кэширования в настройках, чтобы успеть это сделать). Теперь мы получим ответ 304 и пустое тело ответа. Это связано с тем, что с помощью заголовка If-None-Match клиент проинструктировал сервер, что у него уже есть копия ответа с таким значением ETag, поэтому, если оно не изменилось, не нужно повторно отправлять содержимое, просто нужно сообщить об этом факте с кодом ответа 304. Это очень мощный способ уменьшить объём обработки как на клиенте, так и на сервере.

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

Источник:
https://code-maze.com/aspnet-core-output-caching/
👍9👎1
День 1522. #ЗаметкиНаПолях
Кэширование Вывода в
ASP.NET Core. Окончание
Начало
Продолжение

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

Добавим ещё одну конечную точку:
[HttpGet("database")]
[OutputCache(PolicyName = "Expensive")]
public string GetDatabase()
=> $"Дорогой вызов БД в: {DateTime.Now:hh:mm:ss}";

И политику (для этой политики добавим тэг tag-expensive:
opt.AddPolicy("Expensive", с =>
с.Expire(TimeSpan.FromMinutes(1))
.Tag("tag-expensive"));

Также добавим тег tag-all в политику по умолчанию:
opt.AddBasePolicy(с =>
с.Expire(TimeSpan.FromSeconds(5))
.Tag("tag-all"));

Для конечной точки /database ответ находится в кэше 1 минуту. Если данные изменились, клиенты всё равно в течение минуты будут получать старые кэшированные значения. Однако мы можем удалить их, используя только что настроенные теги.

Добавим сервисную конечную точку:
[HttpDelete("cache/{tag}")]
public async Task DeleteCache(
IOutputCacheStore cache, string tag)
=> await cache.EvictByTagAsync(tag, default);

Она принимает специальную зависимость IOutputCacheStore и вызывает метод EvictByTagAsync, передавая значение тега из URL. Это удаляет из кэша элементы, соответствующие этому тегу.

Итак, давайте проверим результаты. Используя утилиту, вроде Postman, вызовем сервисную конечную точку:
DELETE …/cache/tag-expensive
Теперь кэш для конечных точек с политикой Expensive очищен.

DELETE …/cache/tag-all
Теперь кэш для конечных точек с политикой по умолчанию очищен.

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

Итого
В этой серии мы обсудили, как настроить OutputCache в ASP.NET. Для большинства сценариев мы использовали значения по умолчанию, включая использование кэша в памяти в качестве хранилища. Однако по мере роста размера приложения и добавления серверов этот вариант становится менее предпочтительным. Есть варианты лучше, включая такой инструмент, как Redis.

Источник: https://code-maze.com/aspnet-core-output-caching/
👍12
День 1523. #ЗаметкиНаПолях
Обработка CancelKeyPress с Помощью CancellationToken
Иногда нужно определить, когда консольное приложение закрывается, чтобы выполнить некоторую очистку. Console.CancelKeyPress позволяет зарегистрировать метод обратного вызова, который выполнится при нажатии Ctrl+C или Ctrl+Break в консоли. Это событие также позволяет предотвратить закрытие приложения, чтобы вы могли выполнить очистку перед завершением работы. Можно использовать Console.CancelKeyPress в паре с CancellationToken для отмены текущих асинхронных операций.

Метод, выполнение которого прерывается:
static async Task DoAsync (
string[] args, CancellationToken ct)
{
try
{
Console.WriteLine("Ожидание…");
await Task.Delay(10_000, ct);
}
catch (OperationCanceledException)
{
Console.WriteLine("Операция отменена");
}
}

Основной текст программы:
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (sender, e) =>
{
// Мы остановим процесс вручную
// с помощью токена отмены
e.Cancel = true;

// … очистка …

// Вызываем отмену на токене
cts.Cancel();
};

await DoAsync(args, cts.Token);

Кроме того, у токена отмены есть метод Register, позволяющий зарегистрировать методы обратного вызова, которые выполнятся в случае отмены токена:
cts.Token.Register(
() => Console.WriteLine("Отмена…"));

Источник: https://www.meziantou.net/handling-cancelkeypress-using-a-cancellationtoken.htm
👍8
День 1524. #ЗаметкиНаПолях
Храните Информацию в Её Высшей Форме. Начало
Может быть несколько представлений некоторой части информации; вы не должны ограничивать себя только одним из них. Вместо этого храните источник этой информации.

Допустим, мы создаём онлайн-кинотеатр и нам нужно хранить продолжительность фильмов в базе. Во внешнем интерфейсе она представлена ​​как «1ч 47мин». Но в каком виде её хранить? В виде строки «1ч 47мин», но что, если мы решим изменить формат на 1:47 или «107 минут»? Поэтому нужно хранить её в форме, которую можно легко преобразовать в любой формат, то есть в виде целого количества минут. Это высшая форма информации о продолжительности фильма.

Этот совет можно перефразировать как: «Храните исходник, а не исполнение.»

Звучит тривиально. Вот более сложный пример. Есть сущность Customer и объект-значение LoyaltyPoints:
public class Customer : Entity 
{
public LoyaltyPoints Points { get; private set; }

public void AddPoints(LoyaltyPoints pts)
=> Points += pts;

public void RedeemPoints(LoyaltyPoints pts)
{
if (Points < 250 || pts > Points)
throw new Exception();
Points -= pts;
}
}

У нас два варианта использования:
1) Когда клиент размещает заказ, объект Order вычисляет баллы лояльности на основе суммы заказа и вызывает AddPoints для клиента.
2) Клиент может использовать баллы лояльности, когда их минимальное значение составляет 250.
Приведённый выше код идеально отвечает этим требованиям.

Допустим, теперь клиент может обновить существующий заказ. Когда товар удаляется из заказа, объект заказа должен рассчитать разницу в баллах лояльности и вычесть её из суммы клиента. Вот три возможных решения:
1) Повторно использовать RedeemPoints для вычитания. Но этот метод проверяет минимальное значение 250 и выдаст исключение для клиента без баллов лояльности.
2) Использовать AddPoints, передав отрицательное число. Но по бизнес-правилам LoyaltyPoints не может быть отрицательным.
3) Ввести отдельный метод, который не проверяет минимум в 250:
public void SubtractPoints(LoyaltyPoints pts)
=> Points -= points;
Но теперь у нас два публичных метода, которые выполняют вычитание, и неочевидно, какой когда использовать. Это является признаком того, что мы раскрываем детали реализации.

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

Вместо сохранения остатка, нужно хранить два исходных значения и вычислять остаток на лету:
public class Customer : Entity 
{
public LoyaltyPoints Earned { get; private set; }
public LoyaltyPoints Redeemed { get; private set; }

public LoyaltyPoints Points
=> Earned - Redeemed;

public void IncreasePoints(LoyaltyPoints pts)
=> Earned += pts;

public void ReducePoints(LoyaltyPoints pts)
=> Earned -= pts;

}

Одно поле не передавало должным образом значение метода SubtractPoints. Это может означать любой из двух вариантов использования:
- снятие заработанных баллов,
- добавление использованных баллов.

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

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

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

Источник:
https://enterprisecraftsmanship.com/posts/storing-information-in-its-highest-form/
👍19