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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2502. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

11. Различия между современным .NET и .NET Framework
«Объясните ключевые различия между современным .NET и .NET Framework? Также обсудите ситуации, в которых один вариант может быть более подходящим, чем другой».

Хороший ответ
Современный .NET и .NET Framework — это фреймворки, разработанные Microsoft, но они предназначены для разных потребностей и сценариев.

- Поддержка кроссплатформенности: современный .NET кроссплатформенный, поддерживает Windows, Linux и macOS, что делает его подходящим для приложений, требующих широкого охвата в различных операционных системах. .NET Framework же ограничен Windows.

- Производительность и масштабируемость: современный .NET оптимизирован для производительности и масштабируемости. Он включает в себя такие усовершенствования, как веб-сервер Kestrel и компилятор RyuJIT, которые эффективнее и быстрее аналогов из .NET Framework. Это делает современный .NET идеальным для создания высокопроизводительных и масштабируемых веб-приложений.

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

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

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

Подводя итог, можно выделить следующие сценарии, в которых один вариант может быть более подходящим:
- Современный .NET подходит для новых корпоративных приложений, особенно тех, которые требуют кроссплатформенной функциональности, архитектуры микросервисов или требуют работы в контейнерных средах.
- .NET Framework подойдёт для поддержки существующих приложений, использующих старые библиотеки или специфичные для Windows API, которые не поддерживаются современным .NET.

Часто даваемый неудачный ответ
«NET Core — это просто более новая версия .NET Framework, поэтому всегда лучше использовать .NET Core для всех проектов, поскольку он новее и в конечном итоге заменит .NET Framework.»

Этот ответ чрезмерно упрощает различия и игнорирует конкретные сильные стороны и варианты использования каждого фреймворка:

- Непонимание области применения и вариантов использования: Предложение использовать .NET Core или современный .NET для всех проектов игнорирует сценарии, в которых .NET Framework может по-прежнему быть необходим из-за совместимости со старыми технологиями и широкого использования в корпоративном секторе.

- Игнорирование проблем совместимости: Утверждение, что современный .NET заменит .NET Framework, не учитывает тот факт, что некоторые приложения зависят от функций и библиотек, доступных только в .NET Framework.

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

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👎13👍6
День 2503. #SystemDesign101
8 Ключевых Концепций DDD


1. Предметно-ориентированное проектирование (Domain Driven Design)
Предполагает разработку программного обеспечения посредством моделирования предметной области. Единый язык — одна из ключевых концепций DDD. Модель предметной области — связующее звено между бизнес-доменом и программным обеспечением.

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

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

4. Агрегация
Агрегат — это кластер связанных объектов (сущностей и объектов-значений), которые рассматриваются как единое целое при изменении данных. Обращение ко всем объектам агрегата должно осуществляться только через главный объект – корень агрегата.

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

6. Манипуляции с моделями
В DDD для манипуляций с этими моделями используется ряд объектов, которые действуют как «операторы»:
- фабрики – для создания объектов,
- сервисы – для управления моделями, исполняя бизнес-логику,
- репозитории – для сохранения и извлечения моделей из хранилища.

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

8. Построение модели предметной области
Набор методов для извлечения моделей предметной области из бизнес-знаний.

Источник: https://bytebytego.com/guides/8-key-concepts-in-ddd/
👎9👍6
День 2504. #ЗаметкиНаПолях
Использование Сортируемых UUID/GUID в Entity Framework

В .NET 9 появились методы Guid.CreateVersion7() и Guid.CreateVersion7(DateTimeOffset) для создания сортируемых UUID/GUID по времени их создания. Это может быть особенно полезно в базах данных, где требуется поддерживать хронологический порядок записей (плюс некоторые преимущества в плане производительности). В настоящее время в Entity Framework нет встроенного способа настроить использование этих методов при генерации новых GUID для первичных ключей. Но мы можем сделать это самостоятельно.

Генератор значений
Для начала нам нужно создать пользовательский генератор значений, который будет использовать Guid.CreateVersion7() для генерации новых GUID:
public class UUIDv7Generator 
: ValueGenerator<Guid>
{
public override bool
GeneratesTemporaryValues => false;

public override Guid Next(EntityEntry entry)
=> Guid.CreateVersion7();
}


Использование:
public class MyDbContext : DbContext
{
protected override void
OnModelCreating(ModelBuilder mb)
{
mb.Entity<MyEntity>()
.Property(e => e.Id)
.HasValueGenerator<UUIDv7Generator>()
.ValueGeneratedOnAdd();
}
}


И вуаля! Теперь, когда для объектов MyEntity свойство Id будет генерироваться в виде UUID версии 7. Если вы хотите, чтобы появился более удобный способ генерации таких значений в Entity Framework, можете проголосовать за этот тикет.

Источник: https://steven-giesel.com/blogPost/d6150b89-a3ef-407e-add2-7afa4a2a8729/using-sortable-uuid-guids-in-entity-framework
👍28
День 2505. #ЗаметкиНаПолях
Кэширование в Redis с Двойным Ключом. Начало
Кэширование в Redis — это просто… пока вы не осознаете, что вашей сущности нужны два разных ключа поиска: один внутренний и один внешний. Тут всё становится сложнее: если не быть внимательным, приложение может стать жертвой устаревших данных, пропущенных инвалидаций и несогласованного состояния между сервисами.

Проблема
Представьте, что вы разрабатываете типичное приложение. У каждого пользователя есть:
- UserId (GUID, внутренний, неизменяемый)
- Email (используется для входа, внешний, изменяемый)
Теперь рассмотрим, как система взаимодействует с этим профилем пользователя.

Вариант 1. Аутентификация (Email → User)
При входе в систему служба идентификации должна:
1. Найти пользователя по email;
2. Загрузить его учётные данные;
3. Загрузить его профиль (роли, настройки и т.п.)
Для этого требуется быстрый поиск по email. Выполнение этого SQL-запроса в часы пиковых нагрузок может замедлить систему. Redis решает эту проблему.

Но остальная бизнес-логика ведёт себя по-другому…

Вариант 2. Внутренние микросервисы (UserId → Профиль)
Биллинг, уведомления, аналитика и журналы аудита — все они идентифицируют пользователей по внутреннему UserId. Поэтому они ожидают быстрого поиска по UserId.

Если вы кэшируете только по одному ключу:
- UserId - трафик аутентификаций приводит к задержкам,
- Email - внутренние сервисы постоянно используют БД.

Кэширование с двойным ключом
Кэширование с двойным ключом позволяет получить доступ к одной и той же сущности, используя два разных ключа:
- По внутреннему, стабильному ключу (UserId),
- По внешнему, доступному пользователю, ключу (Email).

Но есть и более серьёзная проблема…
Наивный разработчик может сказать: «Просто храните полный JSON объекта под обоими ключами!»

Это сработает, на время. Но,
- если пользователь сменит email, нужно удалить запись со старым ключом;
- приходится удалять два элемента каждый раз при изменении пользовательских данных;
- сбои в работе сети между двумя вызовами StringSetAsync = повреждение кэша;
- вы тратите память, храня дублирующиеся JSON-объекты.

Поэтому профессиональные системы используют другой подход.

Единый источник данных + ключ индекса
Храним полный профиль пользователя ОДИН РАЗ:
user : data : {userId} → JSON


Храним индекс Email → UserId:
user : email : {email} → userId


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

Далее посмотрим, как это реализовать в .NET.

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

Источник:
https://thecodeman.net/posts/dual-key-redis-caching-in-dotnet
1👍17
День 2506. #ЗаметкиНаПолях
Кэширование в Redis с Двойным Ключом. Окончание

Начало

Реализация Двойного Ключа в .NET
1. DTO + Хелпер для ключей Redis
public class UserDto
{
public Guid UserId { get; set; }
public string Email { get; set; }
public string DisplayName { get; set; }
public string TimeZone { get; set; }
}

public static class UserKeys
{
public static string Data(Guid id)
=> $"user:data:{id}";

public static string Email(string email)
=> $"user:email:{email.ToLowerInvariant()}";
}


2. Кэширование пользователя (атомарная запись)
public async Task CacheUserAsync(UserDto user)
{
var dataKey = UserKeys.Data(user.UserId);
var emailKey = UserKeys.Email(user.Email);
var json = JsonSerializer.Serialize(user);
var tran = _db.CreateTransaction();

tran.StringSetAsync(dataKey, json, TimeSpan.FromMinutes(10));
tran.StringSetAsync(emailKey, user.UserId.ToString(), TimeSpan.FromMinutes(10));

await tran.ExecuteAsync();
}


- оба ключа обновляются совместно;
- исключён риск частичных записей;
- JSON хранится 1 раз.

3. Поиск по UserId
public async Task<UserDto?> 
GetByIdAsync(Guid userId)
{
var json = await _db.StringGetAsync(
UserKeys.Data(userId));

return json.IsNullOrEmpty
? null
: JsonSerializer.Deserialize<UserDto>(json);
}


4. Поиск по Email
public async Task<UserDto?>
GetByEmailAsync(string email)
{
var id = await _db.StringGetAsync(UserKeys.Email(email));
if (string.IsNullOrEmpty(id)) return null;

var userId = Guid.Parse(id);
return await GetByIdAsync(userId);
}


5. Безопасная обработка изменений email
public async Task UpdateEmailAsync(
Guid userId,
string oldEmail,
string newEmail)
{
var oldKey = UserKeys.Email(oldEmail);
var newKey = UserKeys.Email(newEmail);

var tran = _db.CreateTransaction();

// ... обновляем данные пользователя

tran.KeyDeleteAsync(oldKey);
tran.StringSetAsync(newKey, userId.ToString());

await tran.ExecuteAsync();
}

- старый индекс удаляется, добавляется новый;
- ключ данных не изменяется;
- нет дублирования JSON;
- нет несогласованного состояния кэша.

Если вы не будете использовать кэширование с двойным ключом, то рано или поздно, вы столкнетесь с…
- устаревшими данными в кэше;
- неработающим входом в систему (изменён email, но кэш не обновился);
- внутренними микросервисами, возвращающими устаревшие значения;
- «фантомными пользователями» в логах;
- и т.п.
Большинство этих ошибок никогда не проявятся в процессе разработки — только в рабочей среде под реальной нагрузкой.

Шаблон кэширования с двойным ключом универсален для современных систем:
1. Электронная коммерция
- ProductId → данные
- Артикул → ProductId

2. CMS
- ContentId → данные
- Slug → ContentId

3. IoT
- DeviceId → данные
- MAC-адрес/Серийный номер → DeviceId

Итого
Кэширование Redis с двойным ключом — не оптимизация, а фундаментальная архитектура для современных систем .NET, использующих Redis. Его следует использовать, когда:
- сущность имеет несколько идентификаторов;
- один или несколько из этих идентификаторов изменяемы;
- необходим быстрый поиск из разных контекстов;
- нужно избежать дублирования JSON в Redis;
- важна согласованность кэша под нагрузкой.

Правильный шаблон:
- один канонический ключ данных;
- несколько легковесных ключей индекса;
- атомарные обновления для обеспечения согласованности.
Так вы избежите почти всех проблем с несогласованностью кэша еще до их появления.

Источник: https://thecodeman.net/posts/dual-key-redis-caching-in-dotnet
👍13
День 2507. #ЧтоНовенького #NET11
Помощник по MIME-типам в .NET 11

Как обычно, сразу за выходом новой версии .NET, сразу начинаем обсуждать потенциальные новинки следующей версии. .NET 11 обещают выход новых хелпер-методов: MediaTypeMap.GetMediaType и MediaTypeMap.GetExtension, которые значительно упростят веб-разработку!

MediaTypeMap.GetMediaType
Основная идея заключается в получении MIME-типов из указанного файла или расширения:
using System.Net.Mime;

// text/css
_ = MediaTypeMap.GetMediaType("myfile.css");

// application/json
_ = MediaTypeMap.GetMediaType("resource.json");
_ = MediaTypeMap.GetMediaType("rEsOuRCe.JsOn");

// null, т.к. smith – неизвестное расширение
_ = MediaTypeMap.GetMediaType("jon.smith");

// Он также работает с расширениями или с целыми путями
// text/css
_ = MediaTypeMap.GetMediaType("/home/user/myfile.css");
_ = MediaTypeMap.GetMediaType("css");


Мы также можем использовать «обратный поиск» с помощью MediaTypeMap.GetExtension:
using System.Net.Mime;

// ".pdf"
_ = MediaTypeMap.GetExtension("application/pdf");

// ".jpg"
_ = MediaTypeMap.GetExtension("image/jpeg");

// null для неизвестных
_ = MediaTypeMap.GetExtension("madeup/mimetype");
_ = MediaTypeMap.GetExtension("/some/path/basic.css");

Заметьте, что известные расширения начинаются с точки.

См. также:
Обсуждение на GitHub: https://github.com/dotnet/runtime/issues/121017
Полный пул-реквест: https://github.com/dotnet/runtime/pull/121776

Источник: https://steven-giesel.com/blogPost/8f41d316-f6ac-4712-bf11-ea36b7c2f2e6/mime-type-helper-in-net-11
4👍5
День 2508. #ЗаметкиНаПолях
DbContext не Является Потокобезопасным. Распараллеливаем Запросы EF Core Правильно. Начало
Все мы создавали конечные точки вроде «Панель управления для мониторинга» или «Сводка по пользователю». Это конечная точка, которой необходимо получить несколько совершенно не связанных между собой наборов данных, чтобы составить полную картину для пользователя. Например, последние 50 заказов, текущие логи состояния системы, настройки профиля пользователя и, возможно, количество уведомлений.

Итак, вы пишете код стандартным способом:
var orders = await GetRecentOrdersAsync(id);
var logs = await GetSystemLogsAsync();
var stats = await GetUserStatsAsync(id);

return new DashboardDto(orders, logs, stats);

Это чисто, читаемо, работает. Но есть проблема. Если GetRecentOrdersAsync занимает 300мс, GetSystemLogsAsync — 400мс, а GetUserStatsAsync — 300мс, данные будут загружаться целую секунду (300+400+300).

В распределённой системе задержка ухудшает пользовательский опыт. Поскольку эти наборы данных не связаны между собой, мы должны иметь возможность выполнять их параллельно. Тогда общее время составило бы только длительность самого медленного запроса (400 мс). Это 60% улучшение производительности просто за счёт изменения способа выполнения кода. Но наивный подход с EF Core не сработает.

Проблема с Task.WhenAll
Самая распространённая ошибка – обернуть эти вызовы репозитория в задачи и ожидать их выполнения одновременно:
//  НЕ ДЕЛАЙТЕ ТАК
public async Task<DashboardData>
GetDashboardData(int id)
{
// Методы используют один _dbContext
var orders = _repo.GetOrdersAsync(id);
var logs = _repo.GetLogsAsync();
var stats = _repo.GetStatsAsync(id);

await Task.WhenAll(orders, logs, stats);

return new DashboardData(
orders.Result,
logs.Result,
stats.Result);
}

Если вы выполните это, вы столкнётесь с таким исключением: «A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe.» («Вторая операция в этом контексте началась до завершения предыдущей. Обычно это происходит из-за того, что разные потоки используют один и тот же экземпляр DbContext, однако потокобезопасность членов экземпляра не гарантируется.»)

Почему это происходит?
DbContext в EF Core не является потокобезопасным. Это объект с состоянием, предназначенный для управления одной единицей работы. Он поддерживает «отслеживание изменений» для контроля за загруженными сущностями и инкапсулирует единственное базовое соединение с БД.

Протоколы баз данных (например, TCP-поток для PostgreSQL или SQL Server) обычно синхронны на уровне соединения. Вы не можете отправить два разных SQL-запроса по одному и тому же каналу в одну и ту же миллисекунду. При использовании Task.WhenAll несколько потоков пытаются одновременно захватить это единственное соединение, и EF Core вмешивается, чтобы сгенерировать исключение и предотвратить повреждение данных.

Таким образом, у нас возникает дилемма: нам нужна скорость параллелизма, но DbContext вынуждает нас использовать последовательное выполнение.

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

Источник:
https://www.milanjovanovic.tech/blog/dbcontext-is-not-thread-safe-parallelizing-ef-core-queries-the-right-way
👍30
День 2509. #ЗаметкиНаПолях
DbContext не Является Потокобезопасным. Распараллеливаем Запросы EF Core Правильно. Окончание

Начало

Решение
Начиная с .NET 5, EF Core предоставляет решение для этого сценария: IDbContextFactory<T>. Вместо внедрения экземпляра контекста с ограниченной областью видимости (который существует на протяжении всего HTTP-запроса), вы внедряете фабрику, которая позволяет создавать легковесные, независимые экземпляры DbContext там, где это нужно.

Примечание: хотя использование фабрики является наиболее чистым подходом для внедрения зависимостей, вы также можете вручную создать экземпляр контекста (используя var context = new AppDbContext(options)), если у вас есть доступ к DbContextOptions.

Сначала нужно зарегистрировать фабрику в Program.cs:
// Это регистрирует фабрику как Singleton
// Также регистрирует AppDbContext как Scoped
builder.Services.AddDbContextFactory<AppDbContext>(
opts =>
{
opts.UseNpgsql(
builder.Configuration.GetConnectionString("db"));
});

Теперь изменим конечную точку, используя фабрику контекстов. Внутри нашего метода мы запускаем отдельную задачу для каждого запроса. Внутри каждой задачи создаём отдельный контекст, выполняем запрос, а затем немедленно освобождаем его:
using Microsoft.EntityFrameworkCore;

public class DashboardService(
IDbContextFactory<AppDbContext> ctxFactory)
{
public async Task<DashboardDto>
GetDashboardAsync(int id)
{
var orders = GetOrdersAsync(id);
var logs = GetSystemLogsAsync();
var stats = GetUserStatsAsync(id);

await Task.WhenAll(orders, logs, stats);

return new DashboardDto(
await orders,
await logs,
await stats
);
}

private async Task<List<Order>>
GetOrdersAsync(int id)
{
// Создаём контекст для операции
await using var ctx =
await ctxFactory.CreateDbContextAsync();

return await ctx.Orders
.AsNoTracking()
.Where(o => o.UserId == id)
.ToListAsync();
}
// остальные методы аналогичны
}


Ключевые особенности
1. Изоляция
Каждая задача получает свой собственный DbContext. Т.е. у каждой задачи есть собственное соединение с БД. Конфликтов нет.
2. Освобождение ресурсов
Использование await критически важно. Как только запрос завершится, мы хотим освободить этот контекст и вернуть соединение в пул.

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

Компромиссы и заключение
IDbContextFactory устраняет разрыв между архитектурой EF Core, основанной на единице работы, и реальностью современных требований к параллельной обработке. Он позволяет выйти за рамки принципа «один запрос — один поток» без ущерба для безопасности.

Однако используйте этот шаблон с осторожностью:
1. Истощение пула соединений
Один HTTP-запрос теперь одновременно занимает 3 соединения с БД вместо 1. При высокой степени параллелизма вы можете легко исчерпать пул соединений.
2. Накладные расходы на контекст
Если ваши запросы выполняются очень быстро (например, простой поиск по ID), накладные расходы на создание нескольких контекстов и задач могут сделать параллельную версию медленнее, чем последовательную.

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

Источник: https://www.milanjovanovic.tech/blog/dbcontext-is-not-thread-safe-parallelizing-ef-core-queries-the-right-way
👍22
День 2510. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

12. Кроссплатформенные возможности
«Расскажите о кроссплатформенных возможностях современной платформы .NET. Объясните, как эти возможности приносят пользу разработке ПО, и приведите пример сценария, в котором кроссплатформенная поддержка имеет решающее значение».

Хороший ответ
«.NET Core и более поздние версии .NET — это значительный шаг вперёд в экосистеме .NET, прежде всего благодаря своим кроссплатформенным возможностям, которые позволяют разработчикам создавать и запускать приложения на Windows, Linux и macOS.

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

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

- Упрощение разработки: Инструменты и библиотеки современного .NET поддерживают среды разработки на всех платформах, что упрощает процесс разработки. Разработчики могут использовать свои предпочтительные инструменты и среды независимо от целевой платформы развертывания.

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

Например, компания может разрабатывать пакет офисных приложений, который должен быть доступен на ПК, Mac и серверах под управлением Linux. Используя .NET и Avalonia, его можно создать 1 раз и развернуть на всех этих платформах с минимальными изменениями. Это не только ускоряет процесс разработки, но и гарантирует, что все пользователи имеют одинаковую функциональность и пользовательский опыт независимо от их операционной системы. Хотя, стоит отметить, что пока не все платформы для UI-разработки в .NET полностью кроссплатформенны. Например, .NET MAUI пока не поддерживает Linux.

По сути, кроссплатформенные возможности .NET обеспечивают единый подход к разработке приложений, что особенно полезно в современном разнообразном технологическом ландшафте.»

Часто встречающийся неверный ответ
«NET работает одинаково на всех платформах, поэтому не нужно делать ничего особенного, чтобы ваше приложение работало на Windows, Linux или macOS».

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

- Чрезмерное упрощение процесса разработки: Утверждение о том, что ничего особенного делать не нужно, не учитывает необходимость тестирования и потенциальных корректировок для различных сред, чтобы обеспечить согласованное поведение и производительность на разных платформах.

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

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍6
День 2511. #ЗаметкиНаПолях
Работа с Git в Visual Studio 2026. Начало

Вы — .NET разработчик, у вас и так напряжённое утро, а тут ещё прилетает задача в трекере: «Конечная точка авторизации выдает ошибку 500 при высокой нагрузке». Вам нужно исправить ошибку, проверить код в отдельной ветке у коллеги, при этом поддерживая чистоту репозитория. Инструменты Git в Visual Studio превращают эту повседневную рутину с Git — создание тематических веток, сохранение изменений, коммиты и обработка пул-реквестов — в плавный и быстрый процесс.

Создание тематической ветки для исправления ошибки
Ваш репозиторий открыт в VS: View -> Git Repository (Вид -> Репозиторий Git), и вы находитесь в ветке main. Чтобы изолировать исправление ошибки, щёлкните правой кнопкой мыши по ветке main в окне репозитория Git, выберите New Local Branch From (Создать локальную ветку из) и назовите её fix/auth-race-1, связав её с задачей в трекере. Вы мгновенно переключитесь на новую ветку, и строка состояния Git в Visual Studio 2026 покажет вашу ветку и статус синхронизации, так что вы никогда не потеряетесь. См. картинку 1.

Вы обновляете AuthService.cs исправлением состояния гонки… И тут прилетает сообщение от ПМ: «нужно срочно проверить код в ветке фичи для презентации». Время отложить вашу работу без риска её потерять.

Сохранение изменений для переключения между задачами
Вы находитесь в процессе исправления ошибки, у вас есть несохраненные изменения, и вы еще не готовы к коммиту. В окне Git Changes - View -> Git Changes (Вид -> Изменения Git) - выберите изменённые файлы, нажмите Stash All (Сохранить все) и добавьте примечание: «WIP: Auth fix before demo.» («В Процессе: исправление авторизации перед демо фичи»). Visual Studio сохранит все изменения, оставив вашу рабочую директорию чистой. См. картинку 2.

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

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

Восстановление изменений и проверка кода
Переключитесь обратно на ветку fix/auth-race-1. Быстрое переключение веток в Visual Studio позволяет вам продолжить работу без задержек. Чтобы восстановить свою работу, перейдите в окно Git Changes, откройте вкладку Stash (Сохранённые изменения), щелкните правой кнопкой мыши и выберите Pop Stash (Восстановить изменения). Ваши изменения будут восстановлены.

Перед коммитом хорошо бы проверить код и выявить любые недочеты. В окне Git Changes нажмите кнопку Copilot Code Review (Проверка кода с помощью Copilot). Copilot просканирует ваши изменения и оставит комментарии в редакторе. Предложения можно применить одним щелчком мыши. См. картинку 3.

Для большей уверенности откройте Copilot Chat - View -> Copilot Chat (Вид -> Чат Copilot), введите «#changes Check security» («#changes Проверить безопасность») и получите советы по безопасности, например: «Add rate limiting for brute-force protection.» («Добавьте ограничение запросов для защиты от перебора паролей»). Так вы выявляете больше проблем на ранних этапах, что делает ваш будущий пул-реквест более чистым.

После доработки кода вы готовы зафиксировать изменения.

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

Источник:
https://devblogs.microsoft.com/visualstudio/streamlining-your-git-workflow-with-visual-studio-2026/
👍12
День 2512. #ЗаметкиНаПолях
Работа с Git в Visual Studio 2026. Окончание
Начало

Коммит
Код готов, поэтому пришло время сделать коммит. В окне Git Changes нажмите кнопку Generate Commit Message (Сгенерировать сообщение коммита). Оно отформатируется в соответствии со стандартами вашей команды (подробнее об этом тут) и сэкономит вам несколько драгоценных минут. Можете отредактировать сообщение или принять его и отправить изменения с помощью кнопки Commit. См. картинку 1.

Создание пул-реквеста и просмотр изменений прямо в IDE
Ваша работа готова к проверке! После отправки изменений вы увидите сообщение в окне Git Changes, и можете щелкнуть по ссылке, чтобы создать пул-реквест. Либо вы можете перейти в окно Git Repository, щёлкнуть правой кнопкой мыши по ветке fix/auth-race-1 и выбрать Create Pull Request (Создать пул-реквест). В пользовательском интерфейсе создания пул-реквеста выберите коллег в качестве рецензентов, щелкнув в поле в блоке Reviewers (Рецензенты) и выбрав их имена из выпадающего списка. Поскольку есть только один коммит, VS автоматически подтягивает сообщение коммита Copilot в описание пул-реквеста. Вы также можете сгенерировать описание пул-реквеста, чтобы убедиться, что оно соответствует стандартам вашей команды. Нажмите Create (Создать), и запрос будет отправлен на проверку. См. картинку 2.

Рецензенты могут легко просмотреть пул-реквест прямо в Visual Studio, найдя его в окне Git Repository. См. картинку 3.

Рецензенты могут открыть пул-реквест и добавлять комментарии непосредственно в Visual Studio. Markdown-разметка отображается прямо в окне сравнения изменений, в виде цепочки комментариев, что делает их понятными. См. картинку 4.

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

Итого
Инструменты Git в Visual Studio — быстрое переключение веток, визуальное сравнение изменений, коммиты Copilot, проверка кода с помощью ИИ, комментарии прямо в коде — экономят ваше время в повседневной работе. Работа с ветками, сохранение изменений и пул-реквесты стали проще, чем когда-либо, позволяя вам сосредоточиться на коде. Проверка кода с помощью ИИ перед отправкой изменений позволяет выявлять ошибки на ранних этапах.

Источник: https://devblogs.microsoft.com/visualstudio/streamlining-your-git-workflow-with-visual-studio-2026/
1👍7
День 2513. #ЗаметкиНаПолях
Не Используйте JSON-Сериализатор Напрямую в Коде. Начало

Вероятно, в большинстве приложений вам придётся работать с JSON. Запросы/ответы или настройки приложения — вам всё равно придётся обрабатывать JSON.

И System.Text.Json, и Newtonsoft.Json предоставляют статические классы для сериализации и десериализации, поэтому очень заманчиво использовать их как есть:
using System.Text.Json; 

Person person = new();
var json = JsonSerializer.Serialize(person);

Однако использование этих статических классов приводит к ряду проблем, таких как:
- Жёсткая связь с реализацией. Если позже вы захотите перейти с System.Text.Json на Newtonsoft.Json или настроить поведение, это может привести к значительному рефакторингу;
- Отсутствие внедрения зависимостей. Гораздо удобнее, когда класс явно объявляет свои зависимости через конструктор. Со статическими сериализаторами это неочевидно или даже невозможно;
- Сложность модульного тестирования. Если класс имеет жёсткую зависимость от статического сериализатора, его сложно изолировать и использовать в качестве заглушки во время тестирования.

Поэтому существуют некоторые подходы, помогающие избежать этих проблем. Один из них — создать абстракцию над сериализатором и использовать её единообразно во всей системе:
interface ISerializer
{
Task<string> SerializeAsync(object obj, Type type);
Task<object> DeserializeAsync(string json, Type type);
}


Для более удобного использования можно создать обобщённый метод расширения:
static class SerializerExtensions
{
extension(ISerializer serializer)
{
public Task<string>
SerializeAsync<TValue>(TValue value)
{
return serializer
.SerializeAsync(value, typeof(TValue));
}

public async Task<TValue>
DeserializeAsync(string json)
{
return (TValue)await serializer
.DeserializeAsync(
json,
typeof(TValue));
}
}
}

Теперь регистрируем сериализатор в DI-контейнере и используем его там, где нам нужно, как в этом примере:
class Handler(ISerializer serializer)
{
public Task<Result> InvokeAsync(Command command)
{
return serializer.DeserializeAsync(command.Json);
}
}


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

Источник:
https://medium.com/@denmaklucky/dont-use-a-json-serializer-directly-in-your-code-instead-do-this-72f8eff3fdc5
1👎17👍12