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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2407. #ЧтоНовенького
Самая Мощная Новинка .NET 10

Вам знакомо чувство, когда то, чем вы пользовались годами, всегда казалось… незаконченным? Например, методы-расширения. Они, конечно, крутые, но редко когда по-настоящему нужны. Это костыли, скрывающиеся за маской чистого синтаксиса. C# 14 исправляет это с помощью членов-расширений.

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

Пример 1: Переписывание ToObservable
Если вы использовали MVVM с WPF, Xamarin, MAUI и т.д., вы, вероятно, писали это сотню раз:
public static class ObservableExtensions
{
public static ObservableCollection<T>
ToObservable<T>(this IEnumerable<T> source)
=> new ObservableCollection<T>(source);
}

Это работает, но выглядит странно; это не похоже на расширение существующего типа. А вот то же с использованием члена-расширения:
public static class EnumerableExtensions
{
extension(IEnumerable<T> collection)
{
public ObservableCollection<T> ToObservable()
=> new ObservableCollection<T>(collection);
}
}

Видите разницу? Никакого беспорядка. Никаких странных «статических методов, притворяющихся реальными членами класса». Такое ощущение, что этим поведением владеет IEnumerable<T>.

Синтаксис
public static class NameOfExtensionClass
{
extension(YourType obj)
{
// Методы, свойства,
// операторы и вложенные типы
}
}


Пример 2: Добавляем свойства
Простой способ проверки на пустую коллекцию вместо !col.Any() или col.Count == 0 везде:
public static class CollectionExtensions
{
extension(ICollection<T> collection)
{
public bool IsEmpty => this.Count == 0;
}
}

Использование:
if (myList.IsEmpty)
{
// Наконец-то код читается, как надо
}

То, чего методы расширения никогда не могли сделать чисто, теперь кажется естественной частью системы типов.

Пример 3: Статические Методы-Расширения
Вы можете добавлять статические члены к типам, которыми вы не владеете:
public static class DateTimeExtensions
{
extension(DateTime)
{
public static DateTime UnixEpoch
=> new DateTime(1970, 1, 1);
}
}


Как это работает изнутри
- Они не изменяют исходный тип.
- Компилятор и метаданные рассматривают их как «присоединённые» члены.
- Инструменты (например, IntelliSense) отображают их так, как будто они принадлежат типу.
- В основе всё по-прежнему безопасно и независимо, просто умнее.

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

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

См. также «Используем Расширения C# 14 для Парсинга Enum»

Источник: https://blog.stackademic.com/net-10s-most-powerful-feature-isn-t-what-you-think-7d507dd254dc
👍31
День 2408. #Архитектура #БазыДанных
Postgres Слишком Хорош. Начало

Разные инди-разработчики и основатели стартапов лихорадочно собирают технологические стеки с Redis для кэширования, RabbitMQ для очередей, Elasticsearch для поиска и MongoDB, потому что… так надо? Оказывается, есть слон в комнате, которого никто не хочет замечать: Postgres может буквально всё это. И он делает это лучше, чем вы думаете.

Миф о том, что Postgres не масштабируется
Часто говорят, что Postgres — это «всего лишь РСУБД», и для решения специализированных задач нужны специализированные инструменты. Но Instagram масштабируется до 14 миллионов пользователей на одном экземпляре Postgres. Discord обрабатывает миллиарды сообщений. Notion построил весь свой продукт на Postgres. Только они не используют Postgres так, будто на дворе 2005 год.

1. Системы Очередей
В Postgres есть нативная поддержка LISTEN/NOTIFY, и он может обрабатывать очереди заданий лучше, чем многие специализированные решения:
-- Простая очередь на Postgres
CREATE TABLE job_queue (
id SERIAL PRIMARY KEY,
job_type VARCHAR(50),
payload JSONB,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT NOW(),
processed_at TIMESTAMP
);

-- ACID-совместимая обработка заданий
BEGIN;
UPDATE job_queue
SET status = 'processing', processed_at = NOW()
WHERE id = (
SELECT id FROM job_queue
WHERE status = 'pending'
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING *;
COMMIT;

Это обеспечивает обработку «ровно один раз» без дополнительной инфраструктуры.

2. Хранилище Ключ-значение
В Postgres есть JSONB, который выполняет большую часть того, что вам нужно:
-- Альтернатива Redis
CREATE TABLE kv_store (
key VARCHAR(255) PRIMARY KEY,
value JSONB,
expires_at TIMESTAMP
);

-- GIN индекс для запросов к JSON
CREATE INDEX idx_kv_value ON kv_store USING GIN (value);

SELECT * FROM kv_store
WHERE value @> '{"user_id": 12345}';

Оператор @> - секретное оружие Postgres'а. Он быстрее большинства запросов NoSQL, и ваши данные остаются согласованными.

3. Полнотекстовый поиск
Кластеры Elasticsearch дороги и сложны. В Postgres есть встроенный полнотекстовый поиск, который поразительно хорош:
-- Добавляем поиск к любой таблице
ALTER TABLE posts ADD COLUMN search_vector tsvector;

-- Авто-обновляемый индекс поиска
CREATE OR REPLACE FUNCTION update_search_vector()
RETURNS trigger AS $$
BEGIN
NEW.search_vector := to_tsvector('english',
COALESCE(NEW.title, '') || ' ' ||
COALESCE(NEW.content, '')
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Ранжированные результаты поиска
SELECT title, ts_rank(search_vector, query) as rank
FROM posts, to_tsquery('startup & postgres') query
WHERE search_vector @@ query
ORDER BY rank DESC;

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

4. Функции Реального Времени
Postgres LISTEN/NOTIFY обеспечивает обновления в реальном времени без дополнительных сервисов:
-- Уведомления клиентов об изменениях
CREATE OR REPLACE FUNCTION notify_changes()
RETURNS trigger AS $$
BEGIN
PERFORM pg_notify('table_changes',
json_build_object(
'table', TG_TABLE_NAME,
'action', TG_OP,
'data', row_to_json(NEW)
)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Ваше приложение отслеживает эти уведомления и отправляет обновления пользователям.

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

Источник:
https://dev.to/shayy/postgres-is-too-good-and-why-thats-actually-a-problem-4imc
👍50
День 2409. #Архитектура #БазыДанных
Postgres Слишком Хорош. Окончание

Начало

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

Операционные расходы
- Мониторинг, обновления и отладка разных сервисов;
- Разные шаблоны масштабирования и режимы сбоев;
- Необходимость поддержки нескольких конфигураций;
- Раздельные процедуры резервного копирования и аварийного восстановления;
- Разные аспекты безопасности для каждого сервиса.

Сложности разработки
- Разные клиентские библиотеки и шаблоны подключения;
- Координация развёртываний нескольких сервисов;
- Несогласованность данных между системами;
- Сложные сценарии тестирования;
- Разные подходы к настройке производительности.
Если вы размещаете сервер самостоятельно, добавьте управление сервером, исправления безопасности и неизбежные сеансы отладки, когда Redis решает израсходовать всю вашу память, и т.п. Postgres делает всё это в одном сервисе, которым вы уже управляете.

Единая масштабируемая БД
Большинство людей не осознаёт, что один экземпляр Postgres может справиться с огромными объёмами данных. Речь идёт о миллионах транзакций в день, терабайтах данных и тысячах одновременных подключений. Вся магия кроется в архитектуре Postgres. Она невероятно хорошо масштабируется вертикально, а когда вам наконец понадобится горизонтальное масштабирование, у вас есть проверенные решения, такие как:
- Реплики чтения для масштабирования запросов;
- Партиционирование для больших таблиц;
- Организация пулов подключений для конкурентности;
- Логическая репликация для распределённых конфигураций.
Большинство компаний никогда до этого не доходят. Вас, вероятно, устроит один экземпляр, пока вы не начнёте обслуживать миллионы пользователей или сложные аналитические задачи. Сравните это с управлением отдельными сервисами, каждый из которых масштабируется по-разному.

Остановите оверинжиниринг с первого дня
Главная ловушка современной разработки — архитектурная астронавтика. Мы проектируем системы для задач, которых у нас нет, с трафиком, которого мы никогда не видели, для масштаба, которого, возможно, никогда не достигнем.

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

Postgres может быть вашей основной БД, кэшем, очередью, поисковой системой и системой реального времени одновременно. И всё это с поддержкой ACID-транзакций во всех областях:
-- Одна транзакция, несколько операций
BEGIN;
INSERT INTO users (email) VALUES ('[email protected]');
INSERT INTO job_queue (job_type, payload)
VALUES ('send_welcome_email', '{"user_id": 123}');
UPDATE kv_store SET value = '{"last_signup": "2024-01-15"}'
WHERE key = 'stats';
COMMIT;


Итого
Postgres может быть слишком хорош сам по себе. Он настолько мощен, что делает большинство других сервисов ненужными для 90% приложений. Индустрия убедила нас, что нам нужны специализированные инструменты для всего, но, возможно, мы просто усложняем задачу.
Ваш стартап не должен быть витриной распределённых систем. Он должен решать реальные проблемы для реальных людей. Postgres позволяет вам сосредоточиться на этом. Поэтому в следующий раз, когда кто-то предложит добавить Redis «для производительности» или MongoDB «для гибкости», спросите: «Вы попробовали сначала сделать это в Postgres?»

Источник: https://dev.to/shayy/postgres-is-too-good-and-why-thats-actually-a-problem-4imc
👍23
День 2410. #TipsAndTricks
Улучшаем Отладку EF Core с Помощью Тегов Запросов
Отладка запросов к БД в EF Core иногда напоминает поиск иголки в стоге сена. Когда ваше приложение генерирует десятки или сотни SQL-запросов, определить, какое LINQ-выражение сгенерировало тот или иной SQL-запрос, становится настоящей проблемой. К счастью, есть элегантное решение: теги запросов.

Теги запросов
Теги запросов позволяют добавлять пользовательские комментарии к SQL-запросам, сгенерированным LINQ-выражениями. Эти комментарии отображаются непосредственно в сгенерированном SQL-коде, что позволяет легко сопоставлять SQL-запрос с кодом, который его создал. Чтобы использовать эту функцию, необходимо применить метод TagWith к любому объекту IQueryable и передать описательный комментарий:
var оrders = context.Orders
.TagWith("Заказы больше $1000")
.Where(o => o.Total > 1000)
.Include(o => o.Customer)
.ToList();


Это сгенерирует примерно такой SQL-запрос:
-- Заказы больше $1000
SELECT [o].[Id], [o].[CustomerId], [o].[Total], [c].[Id], [c].[Name]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]
WHERE [o].[Total] > 1000.0

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

Вы можете объединить несколько вызовов TagWith для добавления дополнительного контекста, а также добавлять значения переменных во время выполнения:
var userOrders = context.Orders
.TagWith("Запрос с дэшборда")
.TagWith($"User ID: {userId}")
.TagWith($"CorrelationId: {correlationId}")
.Where(o => o.CustomerId == userId)
.OrderByDescending(o => o.OrderDate)
.Take(10)
.ToList();

Результат:
-- Запрос с дэшборда
-- User ID: 12345
-- CorrelationId: 987654321
SELECT TOP(10) [o].[Id], [o].[CustomerId], [o].[OrderDate]
FROM [Orders] AS [o]
WHERE [o].[CustomerId] = 12345
ORDER BY [o].[OrderDate] DESC

*CorrelationId помогает проследить весь путь пользовательского запроса (см. подробнее).

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

Источник: https://dev.to/shayy/postgres-is-too-good-and-why-thats-actually-a-problem-4imc
1👍34
День 2411. #SystemDesign101 #Testing
9 Видов Тестирования API


1. Дымовое (Smoke) тестирование
Проводится после завершения разработки API. Просто проверяется работоспособность API и отсутствие сбоев.

2. Функциональное тестирование
Составление плана тестирования на основе функциональных требований и сравнение результатов с ожидаемыми.

3. Интеграционное тестирование
Тестирование объединяет несколько вызовов API для выполнения сквозных тестов. Тестируются внутрисервисные коммуникации и передача данных.

4. Регрессионное тестирование
Тестирование гарантирует, что исправления ошибок или новые функции не нарушат текущее поведение API.

5. Нагрузочное тестирование
Тестирование производительности приложений путем моделирования различных нагрузок. Затем мы можем рассчитать пропускную способность приложения.

6. Стресс-тестирование
Мы намеренно создаем высокие нагрузки на API и проверяем его работоспособность.

7. Тестирование безопасности
Тестирование API на предмет всех возможных внешних угроз.

8. Тестирование пользовательского интерфейса
Тестирование взаимодействия пользовательского интерфейса с API для обеспечения корректного отображения данных.

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

Источник: https://blog.bytebytego.com/
👍10
День 2412. #Оффтоп
Berghain-Челлендж

Вот такой мистический билборд увидели на улицах Сан-Франциско. Попробуйте расшифровать.

Если у вас (как и у меня) ничего не получилось, то, видимо, вы мало знаете про AI 😊 На самом деле, это набор o200k токенов, которые все вместе складываются в URL listenlabs.ai/puzzle

Там вас ждёт следующий челлендж.
Berghain*-Челлендж
Berghain — ночной техно-клуб в Берлине. Вы — вышибала в ночном клубе. Ваша цель — заполнить заведение N=1000 людьми, соблюдая ограничения, например, «не менее 40% местных жителей Берлина» или «не менее 80% одеты в чёрное». Люди приходят по одному, и вам нужно сразу же решить, впускать их или нет. Ваша задача — заполнить заведение, получив как можно меньше отказов, соблюдая при этом все минимальные требования.

Как это работает
- Люди приходят последовательно с бинарными признаками (например, женщина/мужчина, молодой/старый, постоянный/новичок).
- Вы должны немедленно принимать решения о принятии/отклонении.
- Игра заканчивается, когда:
(a) заведение заполнено (1000 человек).
(b) вы отклонили 20 000 человек.

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

Приз
Человек, занявший первое место в таблице лидеров на 15 сентября в 6:00 по тихоокеанскому времени, станет победителем и получит билет в Berghain за счёт оргазизаторов! Также вы сможете пройти собеседование с Listen.

Все подробности тут.

PS: Если вдруг кто решит принять участие, пожалуйста, отпишитесь в комментариях о результатах.
👍5
День 2413. #BestPractices
Сегодня порекомендую вам для воскресного просмотра выступление Скотта Заубера на недавней NDC Conference в Осло "10 Things I Do On Every .NET App" (10 вещей, которые я делаю в каждом .NET приложении)

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

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

Приятного просмотра.

PS: 20 звёзд, и я разберу все советы на канале😉
31👍27
День 2414. #Книги
«C# Concurrency. Асинхронное программирование и многопоточность» (Добовицки Н. — «Питер», 2026).

Очередная книга, над переводом которой довелось поработать совместно с сообществом DotNetRu Translate.

Многопоточность и асинхронность, наверное, всегда будут главными темами вопросов на собеседованиях, а также рассуждений и споров в командах разработчиков. Тема неисчерпаема, и полностью её понимают, кажется, очень немногие. Эта книга - довольно компактное (всего 270 страниц), но при этом полезное руководство по тому, как работает конкурентность в C#, и как вам работать с ней. Автор неплохо раскрывает все особенности и нюансы. Для начинающих в начале книги на доступных аналогиях из жизни разбираются основы многопоточности и асинхронности. Если для вас async/await до сих пор было магией, из первых глав книги вы узнаете, что ничего волшебного там нет, и как это всё устроено "под капотом". Для опытных разработчиков, уже знакомых с основами, в последующих главах разбираются более продвинутые способы применения. Поэтому независимо от того, только начинаете вы изучение конкурентности, либо просто хотите освежить материал и закрепить знания, книга вам подойдёт.

PS
А ещё внезапно так получилось, что на конференции DotNext в Питере в конце первого дня я поучаствую в проведении BoF-сессии «Перевод IT-книг. От идеи до книжной полки» совместно с представителями издательства «Питер». Приглашаю всех, кому интересно узнать про сообщество переводчиков, зачем вообще мы этим занимаемся, как проходит процесс перевода, какие скиллы требуются, чтобы поучаствовать, и как вообще издаются переводы книг на русском языке. Буду рад всех видеть.
👍37
День 2415. #ЗаметкиНаПолях
Реальная Цена Абстракций в .NET. Начало
Мы, разработчики, любим абстракции. Репозитории, сервисы, конвертеры, обёртки. Они делают наш код «чистым», обещают тестируемость и дают нам ощущение гибкости. Некоторые абстракции оправдывают себя, изолируя реальную волатильность и защищая систему от изменений. Другие же незаметно увеличивают сложность, замедляют внедрение и скрывают проблемы производительности за слоями косвенности. Рассмотрим, когда абстракции приносят дивиденды, а когда они становятся техническим долгом.

Когда абстракции окупаются
Лучшие абстракции изолируют реальную волатильность — те части вашей системы, которые вы действительно ожидаете изменить. Пример: обработка платежей. Ваша бизнес-логика не должна напрямую зависеть от API или SDK платёжной системы. Если вы когда-нибудь перейдёте на другую, вы не хотите, чтобы это повлияло на множество мест вашей кодовой базы. Здесь абстракция имеет смысл:
public interface IPaymentProcessor
{
Task ProcessAsync(
Order order, CancellationToken ct);
}

public class StripePaymentProcessor
: IPaymentProcessor
{
public async Task ProcessAsync(
Order order, CancellationToken ct)
{
// Реализация для Stripe
}
}

Теперь бизнес-логика может сфокусироваться на домене:
public class CheckoutService(
IPaymentProcessor processor)
{
public Task CheckoutAsync(
Order order, CancellationToken ct) =>
processor.ProcessAsync(order, ct);
}

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

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

Большинство команд начинают с чего-то разумного:
public interface IUserRepository
{
Task<IEnumerable<User>> GetAllAsync();
}

Но по мере изменения требований, растёт и интерфейс:
public interface IUserRepository
{
Task<IEnumerable<User>> GetAllAsync();
Task<User?> GetByEmailAsync(string email);
Task<IEnumerable<User>> GetActiveUsersAsync();
Task<IEnumerable<User>> GetUsersByRoleAsync(string role);
Task<IEnumerable<User>> SearchAsync(string keyword, int page, int pageSize);
// ...и т.д.
}

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

Между тем, Entity Framework уже предоставляет всё это через LINQ: строго типизированные запросы, которые напрямую соответствуют SQL. Вместо того, чтобы использовать эту мощь, вы ввели слой косвенности. Паттерн репозитория имел смысл, когда ORM были незрелыми. Сегодня это часто просто формальность.

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

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

Источник:
https://www.milanjovanovic.tech/blog/the-real-cost-of-abstractions-in-dotnet
👍23
День 2416. #ЗаметкиНаПолях
Реальная Цена Абстракций в .NET. Продолжение

Начало

Обёртки Сервисов: контекст имеет значение
Хороший пример
При интеграции с внешними API обёртка действительно полезна, поскольку централизует задачи:
public interface IGitHubClient
{
Task<UserDto?> GetUserAsync(string username);
Task<IReadOnlyList<RepoDto>>
GetRepositoriesAsync(string username);
}

public class GitHubClient(HttpClient httpClient)
: IGitHubClient
{
public Task<UserDto?> GetUserAsync(string username) =>
httpClient
.GetFromJsonAsync<UserDto>($"/users/{username}");

public Task<IReadOnlyList<RepoDto>>
GetRepositoriesAsync(string username) =>
httpClient
.GetFromJsonAsync<IReadOnlyList<RepoDto>>(
$"/users/{username}/repos");
}

Эта обёртка изолирует детали API GitHub. При изменении аутентификации или развитии конечных точек вы обновляете одно место. Вашей бизнес-логике не нужно разбираться с HTTP-заголовками, базовыми URL или JSON-сериализацией.

Плохой пример
Проблемы начинаются, когда мы обёртываем наши собственные стабильные сервисы, не добавляя им ценности:
public class UserService(IUserRepository userRepository)
{
// Просто перенаправляем вызовы
public Task<User?> GetByIdAsync(Guid id)
=> userRepository.GetByIdAsync(id);
public Task<IEnumerable<User>> GetAllAsync()
=> userRepository.GetAllAsync();
public Task SaveAsync(User user)
=> userRepository.SaveAsync(user);
}

Этот UserService только добавляет косвенности. Всё, что он делает, — перенаправляет вызовы в IUserRepository. Он не обеспечивает соблюдение бизнес-правил, не добавляет валидацию, не реализует кэширование и не предоставляет никакой реальной функциональности. Это слой, существующий только потому, что «сервисы — это хорошая архитектура».

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

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

Источник:
https://www.milanjovanovic.tech/blog/the-real-cost-of-abstractions-in-dotnet
👍17👎1
День 2417. #ЗаметкиНаПолях
Реальная Цена Абстракций в .NET. Окончание

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

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

1. Абстрактные политики, а не механизмы
Политики — решения, которые могут измениться: какой платёжный сервис использовать, как обрабатывать кэширование, стратегии повторных попыток для внешних вызовов.
Механизмы — стабильные детали реализации: LINQ в EF Core, конфигурация HttpClient, сериализация JSON.
Абстрагируйте политики, т.к. они обеспечивают гибкость. Не абстрагируйте механизмы — это уже стабильные API, которые редко меняются критически.

2. Дождитесь второй реализации
Если у вас только одна реализация, не поддавайтесь соблазну создать интерфейс. Одна реализация не оправдывает абстрагирование, это преждевременное обобщение, которое добавляет сложности без какой-либо пользы.
// 1: Начинаем с конкретного
public class EmailNotifier
{
public async Task SendAsync(
string to, string subject, string body)
{
// Реализация в SMTP
}
}

// 2: Нужны SMS? Теперь абстрагируемся
public interface INotifier
{
Task SendAsync(string to, string subject, string body);
}

public class EmailNotifier : INotifier { … }
public class SmsNotifier : INotifier { … }

Абстракция возникает естественным образом, когда она действительно нужна. Интерфейс раскрывается через реальные требования, а не воображаемые.

3. Реализации внутри, абстракции на границах
Внутри приложения отдавайте предпочтение конкретным типам. Используйте EF напрямую, настраивайте HttpClient как типизированные клиенты, работайте с сущностями домена. Вводите абстракции только там, где система взаимодействует с внешним миром: внешними API, сторонними SDK, инфраструктурными сервисами. Именно там изменения наиболее вероятны, и там абстракции оправдывают себя.

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

Выявив проблемные абстракции, вот как их безопасно удалить:
1. Определите реальных потребителей. Кому на самом деле нужна абстракция?
2. Встройте интерфейс. Замените абстрактные вызовы конкретными реализациями.
3. Удалите обёртку - ненужные косвенные обращения.
4. Упростите вызывающий код. Воспользуйтесь возможностями конкретного API.

Например, замените репозиторий прямым использованием EF:
// До: Скрыто за репозиторием 
var users = await _userRepo
.GetActiveUsersWithRecentOrders();

// После: Прямой запрос
var users = await _context.Users
.Where(u => u.IsActive)
.Where(u => u.Orders.Any(o => o.CreatedAt > DateTime.Now.AddDays(-30)))
.Include(u => u.Orders.Take(5))
.ToListAsync();

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

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

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

Источник: https://www.milanjovanovic.tech/blog/the-real-cost-of-abstractions-in-dotnet
👍15👎2
День 2418. #ЧтоНовенького
Visual Studio 2026
Вышла Insiders (для предварительной оценки) версия Visual Studio 2026. В этой версии VS ИИ становится неотъемлемой частью рабочего процесса разработчика, улучшена производительность, которая меняет ожидания относительно скорости, а современный дизайн делает рабочую среду более лёгкой и сфокусированной.

Новый канал предварительной оценки (Insiders Channel), представленный Microsoft, призван заменить канал превью (Preview Channel) и позволяет разработчикам получать ранний доступ к новым функциям.

Скачать и попробовать новую VS 2026 можно отсюда, подробные примечания к выпуску тут. Также попробуйте бесплатную версию Copilot, чтобы раскрыть всю мощь ИИ в Visual Studio 2026.

Вот некоторые новинки, которые вы найдёте в новой версии:
1. Интеграция ИИ в разработку
ИИ в Visual Studio 2026 вплетён в повседневный кодинг. Вы заметите это, когда перейдёте к новой кодовой базе: IDE поможет вам понять, что вы видите, предложит типы тестов, которые обычно пишутся в вашем репозитории, и будет поддерживать документацию и комментарии в соответствии с вашим кодом. Вы почувствуете, что «адаптивная вставка» (Shift+Alt+V) станет вашим вариантом вставки кода по умолчанию, потому что редактор адаптирует фрагмент к шаблонам и соглашениям вашего проекта. А когда возникнут вопросы по производительности, вам не придётся гадать — рекомендации основаны на реальных трассировках и проверены бенчмарками.

Аудит кода начинается с чётких, применимых на практике данных о корректности, производительности и безопасности — на вашем компьютере, ещё до того, как вы откроете пул-реквест. Всё это время вы контролируете ситуацию. IDE берёт на себя всю рутинную работу, а за вами остаётся окончательное решение. Результат прост: вы работаете быстрее, а ваш код становится лучше.

2. Производительность
Скорость определяет то, что вы можете создать за день. В VS 2026 операции, которые вы чаще всего запускаете — открытие решений, навигация по коду, сборка и нажатие F5 — стали быстрее. Вы заметите, что первый запуск происходит быстрее, крупные решения — легче, а время между идеей и запуском приложения продолжает сокращаться. Выигрыш заметен на больших кодовых базах и сохраняется как на x64, так и на Arm64, так что мощность вашего компьютера напрямую влияет на скорость разработки.

3. Современный внешний вид
Ясность важна, когда вы работаете в IDE. VS 2026 представляет более чистый и современный дизайн. В результате рабочее пространство ощущается спокойным и продуманным. Вы заметите более четкие линии, улучшенные иконки и оптимальное расположение визуальных элементов. Настройки не перегружены, поэтому настройка IDE в соответствии с вашими предпочтениями выполняется быстро. Управление расширениями упрощено, а множество новых цветовых тем делают вашу среду персонализированной — комфортной для длительных сеансов кодинга и позволяющей сосредоточиться в критические моменты. Этот дизайн учитывает ваше внимание и помогает оставаться сконцентрированным.

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

PS
Поскольку это только предварительная версия, она, конечно, не лишена косяков. У меня, например, часто не подгружалась подсветка синтаксиса и навигация по коду в декомпилированных файлах. Нажимаешь, например, F12 на классе Task, и открывается простой текстовый файл. Поэтому использовать VS 2026 в проде пока рано. Но скорость, по сравнению с VS 2022, действительно впечатляет.

Источник: https://devblogs.microsoft.com/visualstudio/visual-studio-2026-insiders-is-here/
👍13
День 2419. #SystemDesign101 #Docker
9 Рекомендаций по Docker, Которые Стоит Знать


1. Используйте официальные образы
Это обеспечивает безопасность, надежность и регулярные обновления.

2. Используйте конкретную версию образа
Тег по умолчанию (latest) непредсказуем и приводит к непредвиденному поведению.

3. Многоэтапные сборки
Уменьшает размер конечного образа за счет исключения инструментов сборки и зависимостей.

4. Используйте .dockerignore
Исключает ненужные файлы, ускоряет сборку и уменьшает размер образа.

5. Используйте пользователя с минимальными привилегиями
Повышает безопасность за счёт ограничения привилегий контейнера.

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

7. Упорядочивайте задачи для кэширования
Упорядочивайте шаги от наименее к наиболее часто изменяемым для оптимизации кэширования.

8. Маркируйте образы
Это улучшает организацию и помогает в управлении образами.

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

Источник: https://blog.bytebytego.com
👍14
День 2420. #BestPractices
Вещи, Которые Я Делаю в Каждом Проекте .NET. Начало

Как и обещал, разбираю советы Скотта Заубера из его доклада на NDC Conference. Их много, так что эта серия затянется.

Каждая система стремится к запутанности, медленности и сложности. Поддержание простоты, скорости и лёгкости использования — это битва, в которой приходится бороться каждый день.
— Гильермо Раух


И получается это очень просто. Вы видите метод из тысячи строк, и вы думаете: «Мне нужно добавить ещё 4 строки». Какая разница, 1000 строк или 1004. Но так же думали те, кто его редактировал, когда он был 800, 600 и 200 строк. В конце концов, нужно, поставить точку и сказать: «Так, нам нужно это отрефакторить». Потому что, если не следить за этим, то всё быстро выйдет из-под контроля, и тогда, возможно, придётся делать великое переписывание, которое всё исправит. Все же знают, что переписать с нуля - всегда работает )))
Эта цитата о том, что необходимо фокусироваться на поддерживаемости во многих подобных вещах, потому что единственное, что постоянно в ПО, — это изменения. Технологии, требования, бизнес - всё меняется, и мы не можем это контролировать. Поэтому старайтесь оптимизировать приложение с учётом изменяемости.

1. Структура папок
Если вы создаёте новый проект, выбираете, например, MVC и работаете с традиционным серверным приложением (без Blazor, Angular, React), то каждый раз, когда вам нужно добавить новую функцию, вам приходится перемещаться по множеству папок. Нужно зайти в папку контроллеров, в папку представлений, в папку моделей, в CSS, в JavaScript, и т.п., которые разбросаны по всему приложению. Таким образом, область действия вашей функции как бы разбросана по всему проекту. И если вам когда-нибудь понадобится удалить функцию, придётся найти все эти файлы и удалить их. А если нужно добавить новую функцию, вам нужно знать, как разместить логику во всех этих разных папках.

Решение в том, чтобы создавать папки для функций, что упрощает поддержку и приводит к так называемой высокой связности, которая просто означает, что связанные элементы должны оставаться вместе. Вы создаёте функцию управления профилем пользователя? Создайте для неё папку и добавьте туда всё, что имеет отношение к этому: контроллер, модель представления, само представление и т.п. В React и Blazor – то же самое. Все компоненты – и даже тесты этих компонентов – всё в этой папке.

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

2. Предупреждения
Бывало ли, что вы открывали проект, собирали его и получали сотни или даже тысячи предупреждений? Не очень приятно, правда? Так вот, предупреждений быть не должно! Либо это ошибка, которая меня волнует, и я её исправляю, либо я её игнорирую по какой-то причине, например, знаю, что она не появится. Поэтому включите флажок «Рассматривать предупреждения как ошибки». Это особенно актуально для новых проектов. Сделайте это с самого начала, потому что в противном случае у вас будут копиться самые разные предупреждения, и вы не будет знать, какие из них важны, а какие можно проигнорировать». Поэтому постарайтесь разбираться с ними с самого начала.

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

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

Источник:
https://youtu.be/SvcRvolP2NE
👍27
День 2421. #BestPractices
Вещи, Которые Я Делаю в Каждом Проекте .NET. Продолжение

Начало

3. Логирование
Конкретная библиотека не имеет значения: Serilog, NLog или log4net. Но не используйте их напрямую. Используйте везде ILogger, чтобы единственное место, которое бы знало о конкретной его реализации – был файл program.cs. По возможности используйте структурное логирование. Также помните о том, что каждый журнал должен содержать некоторую ключевую информацию, например, ID пользователя или ID корреляции. Так вы сможете отслеживать как один запрос проходит через разные сервисы, и что там происходит. Иногда полезно включать короткий sha Git-коммита, чтобы если появляется ошибка, и вы её исправляете, вы могли сравнить, получаем ли мы больше исключений в последней версии, чем получали раньше?

Также важно разделять, как вы логируете нужную информацию. Например, в системе управления пользователями нужно регистрировать, кто внёс каждое изменение. Иногда мы думаем: «Есть же логгер, запишем в лог». Скорей всего, это не лучшее место. Рассмотрим о логи, метрики и аудиты.

Логи должны быть ориентированы на разработчиков. Вы не должны их передавать бизнесу. Это исключения с трассировками стека, ответы от внешних API, разные этапы процесса обработки и т.п.
Кроме того, многие путают уровни логирования. Например, сваливают всё в Information. Вы вызываете 3 API и хотите логировать их ответы. Это информация уровня отладки (Debug). А Information – это результат запроса: мы обработали столько-то записей за такое-то время, т.е. одна запись на запрос пользователя.
Используйте предупреждения (Warning) вместо ошибок (Error). Логи уровня предупреждения работают так: если у вас одно предупреждение, это не страшно, но если их много, то что-то не так. Классический пример: пользователь вводит неверный пароль и блокирует свою учётную запись на пять минут. Одно событие – не важно, но если их тысячи за короткий промежуток времени, возможно, происходит что-то не то (например, кто-то пытается проникнуть в систему).
Ошибки (Error) — это необработанные исключения. Многие просто игнорируют ошибки в логах. Не стоит так делать. Нужно выяснить первопричину и определить, волнует ли нас это. Может это предупреждение, а может реальная ошибка в коде. Постарайтесь очистить ошибки из лога, потому что худший человек, которому стоит сообщать о проблемах, — это ваши пользователи.
И, наконец, критический уровень (Critical) используется, когда приложение не может загрузиться. Например, нужно сначала обратиться к БД или к Azure Key Vault, чтобы извлечь секреты. Если это не удаётся, приложение не может работать.

Стоит подумать, как долго хранятся логи. Azure Log Analytics по умолчанию хранит только 30 дней. И если вы пытаетесь сохранить историческую информацию, например: «кто что изменил?», то ваш ответ бизнесу «Мы храним только 30 дней логов» вряд ли будет удовлетворительным. Аналогично, многие системы записывают логи в буфер и потом скидывают на диск. Так что, если отключится питание, последние логи потеряются. Это нормально, если логи используются только как описано выше, и не хранят важной для бизнеса информации.

Метрики делятся на 2 типа:
- уровня приложения: процессор, сеть, память, глубина очереди и т.п.
- бизнес-метрики: сколько размещено заказов, сколько раз нажимали эту кнопку, или сколько просмотрели эту страницу.
Как долго нужно хранить эти данные? Это должен быть разговор с бизнесом.

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

Поэтому, когда кто-то говорит: «Нам нужно логировать это в следующий раз», - подумайте, какое хранилище данных использовать и на какой срок нужны эти данные.

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

Источник:
https://youtu.be/SvcRvolP2NE
👍21
День 2422. #BestPractices
Вещи, Которые Я Делаю в Каждом Проекте .NET. Продолжение

1-2
3

4. Задаём авторизацию глобально
Если вы используете, например, контроллеры, вам нужно не забыть добавить атрибут Authorize во всех контроллерах и действиях, которые вы хотите защитить (а это обычно большинство). Либо нужно не забыть наследовать от какого-нибудь базового контроллера, у которого есть этот атрибут. Но проблема в том, что если вы забудете, то этот метод/контроллер полностью открыты для внешнего мира.

Решение этой проблемы — так называемая резервная политика (FallbackPolicy) в ASP.NET Core. Резервная политика работает так: если вы не укажете другую политику, эта будет срабатывать каждый раз. В контейнере DI вы просто добавляете авторизацию и устанавливаете эту резервную политику:
bulder.Services.AddAuthorization(opts =>
{
opts.FallbackPolicy = new
AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});

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

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

Fluent Validation позволяет создавать бизнес-правила, которые легко читать даже бизнесу. Их также легко тестировать. Сложные правила создавать тоже просто. Например, в США, вы не можете пользоваться медицинской страховкой родителей после 26 лет. И вот правило для этого:
public class InsuranceValidator 
: AbstractValidator<Insurance>
{
public InsuranceValidator()
{
RuleFor(model => model.Age)
.Must(age => age < 26)
.When(model => model.IsDependent)
.WithMessage(“A dependent must be younger than 26”);
}
}

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

6. Заголовки сервера
По умолчанию ASP.NET Core в ответ добавляет HTTP-заголовок с именем сервера. Если вы используете Kestrel, это значение будет Kestrel. Зачем он это делает? Скорей всего, в Microsoft просто хотят знать, сколько сайтов используют ASP.NET Core. В .NET Framework было ещё хуже, потому что выдавались и версии MVC и .NET. Но это раскрывает хакерам, что вы используете. Т.е. им не нужно проверять ваш сайт на уязвимости Java или Python, только на уязвимости, специфичные для .NET. Удаление заголовка не избавит вас от атак, но увеличит количество времени, которое атакующий потратит. Если у вас есть публичный веб-сайт, он гарантированно подвергался различным атакам. Удалить заголовок легко, просто добавьте эту строку:
builder.WebHost.UseKestrel(opts => opts.AddServerHeader = false);


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

Источник:
https://youtu.be/SvcRvolP2NE
👍25
День 2423. #BestPractices
Вещи, Которые Я Делаю в Каждом Проекте .NET. Продолжение

1-2
3
4-6

7. Не внедряйте IOptions
Проблема с параметрами IOptions в том, что вам нужна зависимость от Microsoft.Extensions. И если вы используете IOptions в других проектах ниже по стеку вызовов, придётся добавлять зависимость во все эти проекты. И тестировать параметр типа IOptions может быть непросто. Кроме того, везде приходится использовать .Value вместо просто имени параметра:
public class MyController(
IOptions<AppSettings> appSettings)
{
private readonly AppSettings _appSettings =
appSettings.Value;

}

Решение - регистрируйте класс IOptions напрямую, так вы получите нужный класс значений в DI контейнере, который уже можно внедрять:
services.Configure<AppSettings>(
Configuration.GetSection("AppSettings"));
services.AddSingleton(s =>
s.GetRequiredService<IOptions<AppSettings>>().Value);



public class MyController(AppSettings appSettings)
{

}

Если вы хотите, чтобы параметры изменялись без перезагрузки приложения, это тоже будет работать. Регистрируете значения как AddScoped и используйте IOptionsSnapshot<T> вместо IOptions<T>.

8. Запахи кода
Во-первых, возьмите за правило располагать «счастливый путь» в конце метода. Если вы смотрите на незнакомый код, как быстро узнать, что происходит, когда всё идёт хорошо? Проще всего – когда нужно всегда смотреть в конец.
Чтобы этого добиться, используйте технику раннего возврата. Вместо нескольких вложенных if-else, просто возвращайте ошибку (или любой другой результат), если что-то идёт не так. Таким образом, в начале метода следуют необходимые проверки, которые завершаются ранним возвратом, если они не проходят, а в конце (если все проверки прошли) – возврат результата, когда всё хорошо:
public ActionResult SendMessage(Message msg)
{
if(!ModelState.IsValid())
return View(msg);

if(emailSender.Send(msg) != Result.OK)
{
_logger.Log(…);
return LocalRedirect("/message/error");
}

return LocalRedirect("/message/success");
}

Это не только позволяет быстро найти «счастливый путь», но и избавляет от избыточной вложенности конструкций if.

И некоторые не жёсткие правила, а просто предупредительные сигналы о запахах кода:
- метод длиннее 20 строк,
- класс длиннее 200 строк,
- использование областей (#region).
Это всё сигналы о том, что метод или класс можно разбить на части.

9. Новые файлы решений
Это относительно новая функция. Технически она всё ещё в стадии превью версии, но сейчас довольно стабильна. Если коротко, новые файлы .slnx – это старый добрый XML, они гораздо короче и понятнее. Обновить файл решения можно, просто перейдя в папку решения и выполнив:
dotnet sln migrate

Затем не забудьте удалить старый файл .sln. Он больше не нужен, но утилита его не удаляет.

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

Источник:
https://youtu.be/SvcRvolP2NE
👍21
День 2424. #BestPractices
Вещи, Которые Я Делаю в Каждом Проекте .NET. Окончание

1-2
3
4-6
7-9

10. HTTP-заголовки безопасности
Они сообщают браузеру о необходимости применения дополнительных правил. Это означает, что вы можете предотвратить определённые типы атак, такие как «человек посередине», кликджекинг, кросс-скриптинг и многие другие. Однако будьте осторожны. Можно, например, запретить загрузку JavaScript отовсюду, кроме вашего домена, но если вы при этом используете CDN, то ваше приложение сломается. Есть стратегии по постепенному внедрению HTTP-заголовков в существующее приложение. Если ваше приложение сканируется на безопасность внешними аналитиками, они всегда проверяют это. Есть публичные сайты, вроде securityheaders.com, которые просканируют ваш сайт и сообщат о состоянии дел. Примерно половина веб-сайтов вообще не имеет заголовков безопасности. И только у 10% рейтинг A или A+. У Эндрю Лока есть NuGet-пакет для реализации некоторых заголовков безопасности.

11. Валидация при сборке
Эта функция проверяет, что ваш DI контейнер настроен правильно. Синглтоны зависят только от синглтонов, а не принимают scoped-объекты. Иначе возникает проблема захвата зависимости. ASP.NET Core обнаруживает это, в режиме локальной разработки, но не в других средах. Но вы можете заставить это работать всегда:
builder.Host.UseDefaultHostProvider(config =>
{
config.ValidateOnBuild = true;
});


12. Автоматизированные тесты
Надеюсь, не нужно объяснять, почему нужно писать тесты? Здесь рассмотрим один нюанс. Слышали выражение «чеховское ружьё»? Если кратко, Чехов писал, что если в главе 1 у вас на стене висит ружьё, то в последующих главах оно должно выстрелить. Иначе нечего его изначально «вешать». То есть, у всего должно быть предназначение. Вот как это касается тестов.
[Fact]
public void EmptyLastNameValidationTest()
{
var customer = new Customer
{
FirstName = "John",
LastName = "",
Address = "123 1st Street",
Born = new DateOnly(2000, 1, 1)
}

var result = new CustomerValidator()
.Validate(customer);

result.Errors.Should().Contain(
e => e.ErrorMessage == "Last Name is required");
}

Что здесь не так? Имя, дата рождения и адрес не имеют значения для этого теста. Фамилия — единственное, что мы проверяем. Мы можем вынести ненужные конструкторы в общий код:
[Fact]
public void EmptyLastNameValidationTest()
{
_customer.LastName = "";

var result = _customerValidator
.Validate(_customer);

result.Errors.Should().Contain(
e => e.ErrorMessage == "Last Name is required");
}

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

13. Централизованное управление пакетами
Проблема, которую решает централизованное управление пакетами, в том, что у вас может быть один пакет в нескольких проектах в решении, и может быть раздражающим поддерживать их синхронизацию. В .NET 6 добавили эту функцию, с помощью которой вы можете создать directory.packages.props в корне и определить там пакеты и версии, которые вам нужны, а затем вы удаляете все версии пакетов в файлах .csproj проектов, и все проекты используют одну версию пакета (хотя, её можно переопределить в любом проекте при необходимости).

Источник: https://youtu.be/SvcRvolP2NE
👍17
День 2425. #ЗаметкиНаПолях
Тестирование Текущего Времени с Помощью TimeProvider и FakeTimeProvider
В тестировании сложно использовать то, что зависит от конкретных данных. Представьте себе файловую систему: для корректной работы тестов необходимо убедиться, что файловая система структурирована именно так, как вы ожидаете. Похожая проблема возникает и с датами: если вы создаёте тесты, основанные на текущей дате, при следующем запуске они не пройдут. Нужно найти способ абстрагировать эти функции, чтобы сделать их пригодными для использования в тестах. Сегодня рассмотрим класс TimeProvider, как его использовать и как его имитировать.

Раньше: интерфейс вручную
Раньше самым простым способом абстрагирования управления датами было ручное создание интерфейса или абстрактного класса для доступа к текущей дате:
public interface IDateTimeWrapper
{
DateTime GetCurrentDate();
}

И стандартная его реализация, использующая дату и время в UTC:
public class DateTimeWrapper : IDateTimeWrapper
{
public DateTime GetCurrentDate()
=> DateTime.UtcNow;
}

Или аналогичный подход с абстрактным классом:
public abstract class DateTimeWrapper
{
public virtual DateTime GetCurrentDate() => DateTime.UctNow;
}

Затем нужно просто добавить его экземпляр в движок DI, и всё готово. Единственная проблема - придётся делать это для каждого проекта, над которым вы работаете.

Сейчас: класс TimeProvider
Вместе с .NET 8 команда .NET выпустила абстрактный класс TimeProvider. Помимо предоставления абстракции для локального времени, он предоставляет методы для работы с высокоточными временными метками и часовыми поясами. Важно отметить, что даты возвращаются как DateTimeOffset, а не как экземпляры DateTime. TimeProvider поставляется «из коробки» с консольным приложением .NET:
DateTimeOffset utc = TimeProvider.System.GetUtcNow();
Console.WriteLine(utc);

DateTimeOffset local = TimeProvider.System.GetLocalNow();
Console.WriteLine(local);

А если вам нужно использовать внедрение зависимостей, нужно внедрить его как синглтон:
builder.Services.AddSingleton(TimeProvider.System);

// Использование
public class Vacation(TimeProvider _time)
{
public bool IsVacation
=> _time.GetLocalNow().Month == 8;
}


Тестируем TimeProvider
Мы можем использовать NuGet-пакет Microsoft.Extensions.TimeProvider.Testing, который предоставляет класс FakeTimeProvider, выступающий в качестве заглушки для абстрактного класса TimeProvider. Используя класс FakeTimeProvider, вы можете установить текущее время UTC и местное время, а также настроить другие параметры, предоставляемые TimeProvider:
[Fact]
public void WhenItsAugust_ShouldReturnTrue()
{
// Arrange
var fakeTime = new FakeTimeProvider();
fakeTime.SetUtcNow(
new DateTimeOffset(2025, 8, 14,
22, 24, 12, TimeSpan.Zero));
var sut = new Vacation(fakeTime);

Assert.True(sut.IsVacation);
}

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

Источник: https://www.code4it.dev/csharptips/timeprovider-faketimeprovider/
🔥23👍1
День 2426. #TipsAndTricks
Перемещение Файлов и Папок в Корзину в .NET
При работе с файлами и папками в приложениях .NET в Windows может потребоваться перемещать элементы в корзину вместо их безвозвратного удаления (File.Delete, Directory.Delete). Это позволит пользователям восстановить случайно удалённые элементы. Вот как это можно сделать с помощью API Windows Shell:
static void MoveToRecycleBin(string path)
{
if (!OperatingSystem.IsWindows()) return;

var shellType = Type.GetTypeFromProgID(
"Shell.Application", throwOnError: true)!;
dynamic shellApp =
Activator.CreateInstance(shellType)!;

// https://learn.microsoft.com/en-us/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants?WT.mc_id=DT-MVP-5003978
var recycleBin = shellApp.Namespace(0xa);

// https://learn.microsoft.com/en-us/windows/win32/shell/folder-movehere?WT.mc_id=DT-MVP-5003978
recycleBin.MoveHere(path);
}


Теперь можно использовать этот метод для перемещения файлов и папок в Корзину:
MoveToRecycleBin(@"C:\path\to\file.txt");
MoveToRecycleBin(@"C:\path\to\directory");


Источник: https://www.meziantou.net/moving-files-and-folders-to-recycle-bin-in-dotnet.htm
👍26