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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2693. #Курсы
День Модернизации с Помощью Агентов в .NET

16 июля 2026г. с 19:00 до 23:00 мск.
Формат: онлайн
Язык: английский

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

19:00 - Modernize .NET Apps with GitHub Copilot (Mika Dumont, Microsoft)
В этом докладе покажут, как модернизация с GitHub Copilot помогает командам оценивать, планировать и выполнять обновления .NET, используя ИИ. Продемонстрируют реальные сценарии миграции, как ИИ сокращает ручные усилия, риски и время. Вы получите практические стратегии, чтобы начать работу уже сегодня.

19:30 - Stop Rewriting. Start with Aspire (David Pine, Microsoft)
Мы рассмотрим процесс внедрения Aspire — aspire init и новый навык агента Aspireify — и позволим ему модернизировать существующее приложение .NET в режиме реального времени.

20:00 - Cold Case or Active Asset? A 2005 WinForms LoB Application (Merrie McGaw, Klaus Loeffelmann, Microsoft)
Приложение непрерывно используется уже почти 20 лет: специализированная бизнес-система, построенная на SQL Server, и .NET Framework 2.0 с использованием ранних версий Windows Forms. Мерри и Клаус исследуют, что необходимо для развития устаревшего приложения WinForms. Только реальный код с реальной историей и прагматичный анализ того, насколько далеко может зайти модернизация.

20:30 - Cloud-Native Data Without Starting Over (Jerry Nixon, Microsoft)
Многие бизнес-приложения тесно связаны с данными, поэтому миграция в облако кажется рискованной, дорогостоящей и сопряженной с трудностями. Но что, если сначала можно перенести БД? Мы рассмотрим практические шаблоны для поэтапной миграции без переписывания кода или архитектурного хаоса. Благодаря стабильным API-слоям, современным шаблонам доступа к данным и новым инструментам мы продемонстрируем, как приложения могут развиваться с помощью стратегий, разработанных для реальных систем, бюджетов и инженерных команд.

21:00 - Modernize Fast, Migrate Faster: .NET Apps to Azure with Modernize CLI (Kaushaya Ganguly, Microsoft)
Мы пройдемся по реальному .NET-приложению с Modernize CLI от начала до конца, а затем используем тот же инструмент для модернизации кодовой базы, чтобы подготовить её к работе в Azure, выделения инфраструктуры Azure и развёртывания в Azure App Service. Рассмотрим пакетную модернизацию для параллельного выполнения одного и того же рабочего процесса во многих репозиториях, интеграцию с конвейером CI/CD для превращения модернизации в повторяемый этап сборки, а также пользовательские навыки для интеграции шаблонов миграции вашей организации, внутренних библиотек и стандартов кодирования.

21:30 - Modernize .NET Apps and Add Agentic Functionality in Minutes (Gaurav Seth, Andrew Westgarth, Jordan Selig, Microsoft)
Модернизируйте устаревшие приложения, разместив их в управляемом экземпляре в Azure App Service без переписывания кода, а затем мгновенно предоставьте к ним доступ как к инструментам, управляемым ИИ-агентами, используя встроенный MCP.

22:00 - App Modernization Done. Now Let's Make It Smarter (Bruno Capuano, Microsoft)
Итак, вы перенесли свое приложение, что дальше? Мы рассмотрим практические способы внедрения ИИ в существующее приложение, размещённое в Azure, с помощью Microsoft Foundry. Рассмотрим доступные сценарии после миграции, такие как анализ журналов приложения, извлечение полезной информации или создание более интеллектуальных отчетов — без необходимости перестраивать всё с нуля.

Регистрация по ссылке: https://developer.microsoft.com/en-us/reactor/events/27243/
👎3👍2
День 2694. #ЗаметкиНаПолях
Планирование Мощностей для API. Начало

Представьте митинг по релизу. Кто-то спрашивает: «Сможет ли API справиться с "чёрной пятницей"?» - тишина. Старший инженер говорит что-то вроде: «Скорей всего. Мы сейчас используем более мощные серверы», - и все переходят к следующему вопросу. Эта фраза — догадка, произнесённая уверенным голосом, а через 3 недели она превращается в инцидент в 2 часа ночи. Планирование мощностей — то, что поможет в этом случае дать реальный ответ. Не ощущение, а измеренное число с указанием условий измерения рядом с ним. Рассмотрим, как это сделать на практике в ASP.NET Core API.

Метрики CPU и памяти обманчивы
Многие планы загрузки ресурсов представляют собой всего два графика: CPU и память. У сервера есть запас по обоим параметрам, поэтому вывод: «всё в порядке». Затем трафик резко возрастает, и API сбоит, в то время как CPU загружен на 40%.

Дело в том, что проблемы, которые действительно возникают первыми, редко проявляются как перегрузка CPU:
1. Исчерпание пула соединений - приложение исчерпывает количество соединений с БД задолго до того, как БД исчерпает ресурсы CPU, и очередь запросов будет ожидать свободного соединения.
2. Голодание пула потоков - несколько блокирующих вызовов под нагрузкой приводят к тому, что пул потоков не может достаточно быстро расти, и задержка резко возрастает, даже если ни один ресурс не выглядит перегруженным.
3. Конкуренция за блокировки - горячая блокировка превращает параллельную работу в непрерывную обработку одного файла.
4. Задержка в очереди - фоновая обработка отстаёт, поэтому запись "успешно выполняется", но пользователь не видит результата в течение 30 секунд.
5. Коллапс задержки p95 - среднее значение по-прежнему выглядит отлично, но 1 запрос из двадцати выдаёт тайм-аут.

Таким образом, вопрос, на который отвечает планирование мощностей, не в том, "есть ли у нас свободные ресурсы CPU". Он более узкий и полезный: «Какой объем трафика может выдержать этот API, прежде чем качество обслуживания пользователей начнёт ухудшаться?»
Все остальное служит для ответа на этот вопрос.

Полезные для измерения показатели
Самая большая ошибка - отслеживание средней задержки. Средние значения скрывают тех, кто действительно страдает. Если средний ответ 80мс, а p99 — 4 секунды, значимая часть пользователей испытывает серьёзные проблемы.

Вот что важно отслеживать (в порядке убывания значимости для выявления реальных проблем):
1. Задержки p95 и p99 - в хвосте задержек источник проблем. p95 — ваш SLO; p99 – насколько всё плохо, в худших случаях.
2. Запросы в секунду (RPS) - для каждого класса конечных точек, а не одно глобальное число. Конечная точка чтения и конечная точка записи не имеют ничего общего.
3. Одновременные пользователи/запросы в процессе выполнения -
сколько запросов одновременно выполняется, что фактически создаёт нагрузку на пулы и потоки.
4. Частота тайм-аутов и частота ошибок - разница между «медленно» и «сломано».
5. Использование пула соединений с БД - наиболее распространённый скрытый потолок в API.
6. Глубина очереди и задержка обработки - для всего асинхронного, насколько отстают фоновые обработчики.

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

Базовый цикл
Планирование мощностей — не документ, который вы пишете один раз. Это цикл, который вы запускаете, и он всегда выглядит одинаково:
1. Сначала определите SLO: «p95 менее 300мс, частота ошибок менее 1%» — это целевой показатель, по которому можно проводить тестирование. Зафиксируйте это число, прежде чем что-либо запускать, иначе вы будете изменять правила игры в зависимости от результатов.

2. Проведите тест на установившееся состояние (steady-state) при нагрузке, которая, по вашему мнению, отражает нормальный трафик. Это точка отсчёта.

3. Проведите тест на пиковую нагрузку с резким и быстрым нарастанием. Steady-state-тест показывает крейсерскую высоту; пиковая нагрузка показывает, что произойдёт, когда начнётся «чёрная пятница».

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

5. Заложите запас прочности, затем публикуйте. После того, как вы окажетесь в пределах SLO, заложите 30-50% запаса сверх ожидаемой пиковой нагрузки и запишите полученное число. Пиковая нагрузка, значения которой никто не знает, равносильна её отсутствию.

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

Источник:
https://thecodeman.net/posts/capacity-planning-for-dotnet-apis-from-guessing-to-measured-scaling
👍6
День 2695. #ЗаметкиНаПолях
Планирование Мощностей для API. Продолжение

Начало

Практика
Проект production-scaling-lab — это небольшой API на ASP.NET Core 8, чтобы наблюдать за этими сбоями. В нём 2 примера конечных точек:
- io-bound – для эмуляции нагрузки на соединения с БД,
- orders – для эмуляции нагрузки на запись.

Нагрузка на соединения
В конечной точке io-bound ожидание заменяет собой вызов нижестоящего узла или медленный запрос — ресурсоёмкая работа, которая незаметно потребляет потоки и соединения под нагрузкой:
app.MapGet("/api/io-bound", 
async (int delayMs, CancellationToken ct) =>
{
var boundedDelay = Math.Clamp(delayMs, 5, 5000);
await Task.Delay(boundedDelay, ct);
return Results.Ok(
new { delayMs = boundedDelay, at = DateTime.UtcNow });
});

Скрипт k6 постепенно увеличивает количество виртуальных пользователей с 200 до 1000 и устанавливает целевой уровень p95. Это базовый тест — он показывает, где начинает расти задержка:
// k6/connections.js
export const options = {
stages: [
{ duration: '30s', target: 200 },
{ duration: '1m', target: 1000 },
{ duration: '30s', target: 0 }
],
thresholds: {
http_req_duration: ['p(95)<400'],
http_req_failed: ['rate<0.01']
}
};

export default function () {
const response = http.get('http://localhost:5080/api/io-bound?delayMs=30');
check(response, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}

Запустите его с выводом отчёта и посмотрите на p95:
set K6_WEB_DASHBOARD=true
set K6_WEB_DASHBOARD_EXPORT=report.html
k6 run k6/connections.js

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

Путь записи
Обычно нельзя просто позволять неограниченному количеству записей накапливаться в БД и надеяться на лучшее. В проекте перед конечной точкой order устанавливается шлюз, так что после превышения лимита параллелизма она возвращает 429 вместо того, чтобы зависнуть:
app.MapPost("/api/orders", async (
CreateOrderRequest request,
AppDbContext db,
WriteGate writeGate,
CancellationToken ct) =>
{


if (!await writeGate.TryEnterAsync(ct))
return Results.StatusCode(StatusCodes.Status429TooManyRequests);

try
{

db.Orders.Add(order);

await db.SaveChangesAsync(ct);

}
finally
{
writeGate.Exit();
}
});

Сам шлюз представляет собой обычный SemaphoreSlim с коротким временем ожидания. Если за 250мс не удаётся получить слот, запрос отбрасывается, а не ставится в очередь:
public sealed class WriteGate
{
private readonly SemaphoreSlim _semaphore;
private int _inflight;

public WriteGate(IConfiguration conf)
{
var max = conf.GetValue<int?>("MaxConcurrentWrites") ?? 64;
_semaphore = new SemaphoreSlim(max, max);
}

public int CurrentInflight => _inflight;

public async Task<bool> TryEnterAsync(CancellationToken ct)
{
var acq = await _semaphore.WaitAsync(
TimeSpan.FromMilliseconds(250), ct);
if (acq) Interlocked.Increment(ref _inflight);
return acq;
}

public void Exit()
{
Interlocked.Decrement(ref _inflight);
_semaphore.Release();
}
}

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

Тест на пиковые нагрузки записи увеличивает скорость поступления запросов до 800 в секунду, чтобы найти эту границу:
// k6/write-spike.js
export const options = {
scenarios: {
write_spike: {
executor: 'ramping-arrival-rate',
startRate: 50,
timeUnit: '1s',
preAllocatedVUs: 100,
maxVUs: 800,
stages: [
{ target: 100, duration: '30s' },
{ target: 500, duration: '1m' },
{ target: 800, duration: '30s' },
{ target: 0, duration: '20s' }
]
}
},
thresholds: {
http_req_duration: ['p(95)<700'],
http_req_failed: ['rate<0.02']
}
};

Кроме того, пример API имеет конечную точку с метриками http://localhost:5080/api/metrics, где можно посмотреть общее число заказов, длину очереди на обработку, количество прочитанных заказов, количество обрабатываемых заказов.

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

Источник:
https://thecodeman.net/posts/capacity-planning-for-dotnet-apis-from-guessing-to-measured-scaling
👍3👎2
День 2696. #ЗаметкиНаПолях
Планирование Мощностей для API. Окончание

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

Следим за результатами
- Задержка p95 - Остается ли скорость в выбранных рамках?
- Частота ошибок 429 - Срабатывает шлюз нагрузки, как и было задумано.
- pendingOutbox (длина очереди обработки – в примере выше в http://localhost:5080/api/metrics) - Если это число продолжает расти и никогда не уменьшается, ваши фоновые процессы не справляются со скоростью записи, а это тоже ограничение пропускной способности — только в фоновых сервисах.

Цель здесь никогда не состоит в нулевом количестве ошибок. Цель — предсказуемое снижение производительности — точное знание того, что делает API, когда вы выходите за его предел, чтобы отказ был плавным, а не катастрофическим.

Рекомендуемые пороговые значения
Это отправные точки, а не законы. Ваши показатели зависят от оборудования, запросов и SLO. Но при отсутствии другой информации, вот примерные цифры:
- Менее ~100 запросов в секунду
Пока не стоит обращаться к инфраструктуре. Сначала избавьтесь от N+1 запросов и лишних аллокаций. Большинство API на этом уровне работают медленно из-за кода, а не из-за нагрузок на оборудование.

- ~100-1000 запросов в секунду
Здесь оправдывает себя кэширование, и имеет смысл настройка пула соединений. Правильно определите размер пула и разместите кэш перед наиболее часто выполняемыми операциями чтения.

- 1000+ запросов в секунду на операции записи
Прекратите синхронную запись в БД на пути запроса. Перейдите к выравниванию нагрузки на основе очередей: быстро отвечайте на запросы, обрабатывайте их в фоновом режиме (см. паттерн Outbox в демо-проекте).

- Высокие пиковые нагрузки + строгий уровень SLO
Добавьте явное ограничение скорости. Встроенный ограничитель скорости в ASP.NET Core или шлюз, подобный описанному выше. Определите точку отказа целенаправленно, а не после обнаружения её в проде.

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

Контрольный список по планированию мощностей
1. Для каждого класса конечной точки существует записанный SLO (целевой показатель p95 + бюджет ошибок).
2. Скрипты нагрузочного тестирования находятся в системе контроля версий рядом с кодом и запускаются при каждом значимом изменении.
3. Для каждого класса конечной точки существует известный, задокументированный максимальный безопасный RPS.
4. Существует руководство по масштабированию вверх и вниз — именно при масштабировании вниз скрываются неожиданности.
5. Оповещения срабатывают при p95, частоте тайм-аутов и задержке очереди — а не только по CPU и памяти.
Если хотя бы один из этих параметров отсутствует, вам снова придется гадать при следующем релизе.

Часто задаваемые вопросы
Какой показатель важнее всего?
Задержка p95, почти всегда. Она отражает то, что чувствует реальный пользователь. Сочетайте её с частотой таймаутов и частотой ошибок, чтобы отличать «медленную» работу от «сбоев».

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

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

Источник: https://thecodeman.net/posts/capacity-planning-for-dotnet-apis-from-guessing-to-measured-scaling
День 2696. #ЧтоНовенького
Просмотр Пул-Реквестов в Visual Studio

Интеграция пул-реквестов в VS — одна из самых востребованных функций Git. Разработчики просили о возможности открывать PR, просматривать изменения, обсуждать отзывы и завершать проверку, не переключаясь в браузер. Создавать PR в VS можно с 2024 года. Теперь также можно просматривать, комментировать и утверждать PR из GitHub и Azure DevOps, не выходя из IDE. Обновление доступно в VS 18.7.

Поиск и открытие PR
Вы можете просмотреть список PR для открытого репозитория в окнах Git Repository, Git Changes или из меню Git (см. картинку 1). Если в текущей ветке уже есть активный PR, вы также можете открыть его непосредственно из окна Git Changes.

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

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

Комментирование и обсуждение
Вы можете оставлять комментарии к определённым строкам, отвечать на ветки обсуждений и завершать обсуждения после их окончания. Файлы с активными комментариями отмечаются в списке изменений, поэтому легко определить, где происходят обсуждения. Все синхронизируется между VS и браузером (см. картинку 3).
При проверке PR в отредактированном коде вы можете одним щелчком применить изменение к вашей рабочей копии. Также Copilot может сгенерировать исправление на основе комментария и окружающего кода, чтобы вы могли сразу же оценить и протестировать его.

Утверждение, завершение и слияние
Когда вы готовы принять/отклонить PR, вы можете увидеть необходимую информацию и действовать, не покидая страницу проверки. На вкладке Overview (Обзор) вы можете увидеть проверки статуса, конфликты слияния и информацию о том, нужны ли ещё необходимые подтверждения. Вы можете утвердить PR из представления сравнения изменений, с дополнительными параметрами голосования для PR Azure DevOps.
Вы также можете завершить или слить PR прямо в IDE. Если планы изменятся, вы можете преобразовать его в черновик или закрыть. После открытия PR вы можете пройти весь процесс проверки в одном месте.

Источник:
https://devblogs.microsoft.com/visualstudio/review-pull-requests-without-leaving-visual-studio/
👍6👎1
День 2698. #ЗаметкиНаПолях
Перенос Строки — Это не Только
\r и \n
Когда мы говорим о символах переноса строки, большинство разработчиков думают о \r\n (Windows) и \n (Unix). В большинстве случаев это работает, но это не полная картина.

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

Переносы строк, которые следует знать
Наиболее распространённые символы завершения строки:
\r\n (CRLF, U+000D U+000A)
\n (LF, U+000A)
\r (CR, U+000D)


Unicode Technical Report #18 (RL1.6) также отмечает эти ограничители:
\u0085 (NEL, Next Line)
\u2028 (LS, Line Separator)
\u2029 (PS, Paragraph Separator)

Вы также можете считать эти пробельные символы переносами строки в некоторых контекстах:
\v (VT, Vertical Tab, U+000B)
\f (FF, Form Feed, U+000C)

Поэтому полноценное «разбиение по строкам» не должно подразумевать только CR и LF.

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

Если вы разделяете текст только по символам \r\n или \n, некоторые записи могут остаться объединёнными в одну строку, что может нарушить синтаксический анализ, проверку или формирование отчётов.

Более безопасный синтаксический анализ в .NET
Если вам нужно разделить текст на строки, отдавайте предпочтение шаблону, который обрабатывает последовательности символов новой строки Unicode.
using System.Text.RegularExpressions;

string input = "A\u2028B\u0085C\r\nD";
string[] lines = Regex.Split(input, @"(?>\r\n|[\n\v\f\r\u0085\u2028\u2029])");

// lines = ["A", "B", "C", "D"]

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

Если вы хотите избежать аллокации массива строк и строковой переменной на каждую строку, в .NET 9 представлен Regex.EnumerateSplits:
using System;
using System.Text.RegularExpressions;

ReadOnlySpan<char> input = "A\u2028B\u0085C\r\nD";

foreach (Range split in Regex.EnumerateSplits(input, @"(?>\r\n|[\n\v\f\r\u0085\u2028\u2029])"))
{
ReadOnlySpan<char> line = input[split];
ProcessLine(line);
}

static void ProcessLine(ReadOnlySpan<char> line)
{
// обработка line без аллокаций
}


Для нормализации .NET также предоставляет метод string.ReplaceLineEndings, который полезен, когда необходимо преобразовать все символы конца строки в единое соглашение перед обработкой:
string input = "A\u2028B\u0085C\r\nD";
string normalized = input.ReplaceLineEndings("\n");
string[] lines = normalized.Split('\n');

// normalized = "A\nB\nC\nD"
// lines = ["A", "B", "C", "D"]


Источник:
https://www.meziantou.net/new-lines-are-more-than-r-and-n.htm
👍6👎1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14
День 2699. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

36. Управление состоянием
«Расскажите о различных стратегиях управления состоянием, доступных в приложениях .NET. Приведите примеры сценариев, в которых вы бы использовали каждую стратегию».

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

1. Куки: для хранения пользовательских предпочтений или данных сессии на стороне клиента. Куки легко реализовать в .NET с помощью класса HttpContext:
public void SetCookie(string key, string value, int? expireTime)
{
CookieOptions option = new();

option.Expires = expireTime.HasValue ?
DateTime.Now.AddMinutes(expireTime.Value) :
DateTime.Now.AddMilliseconds(10);

HttpContext.Response.Cookies.Append(key, value, option);
}


2. Скрытые поля: полезны для сохранения состояния на стороне клиента при POST-запросах. Они могут содержать данные, которые не должны быть видимыми или изменяемыми пользователями, но должны сохраняться между запросами.

3. Сессии: данные пользователя хранятся на сервере между HTTP-запросами. В .NET 8 состояние сессии можно включить и получить к нему доступ через HttpContext:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSession(opts =>
{
opts.IdleTimeout = TimeSpan.FromMinutes(10);
opts.Cookie.HttpOnly = true;
opts.Cookie.IsEssential = true;
});

app.UseSession();

// Сохраняем данные в сессию
HttpContext.Session.SetString("SessionKey", "Value");


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

5. Кэш: кэширование данных может значительно повысить производительность приложения за счёт снижения нагрузки на БД. В .NET кэширование данных может быть реализовано с использованием кэша в оперативной памяти, распределённого или гибридного кэша:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHybridCache();

// Сохраняем данные в кэш
cache.Set("CacheKey", "Cached Value");


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

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

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

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

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

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👎6👍2
День 2700. #ЗаметкиНаПолях
Версионирование API Должно Быть Последним Средством. Начало
Про версионирование API уже была серия постов на канале. Но более важный вопрос не как это делать, а когда. Каждая команда разработчиков API в итоге приходит к одному выводу: «Просто создадим версию 2». Звучит ответственно. За исключением того, что теперь нужно поддерживать два API, два набора документации, два варианта поведения и проект миграции, которую клиенты будут откладывать как можно дольше.

Версионирование — это инструмент совместимости, а не стратегия проектирования.

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

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

Это ломает клиента так же, как и удаление конечной точки:
// До
{ "total": 100 }

// После
{ "total": { "amount": 100, "currency": "USD" } }

Вы не изменили путь, не переименовали конечную точку, но всё равно сломали работу клиентов.

Поэтому вместо вопроса: «Должна ли это быть версия 2?», спросите: «Могут ли старый и новый контракты безопасно сосуществовать?»

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

Источник:
https://www.milanjovanovic.tech/blog/api-versioning-should-be-your-last-resort
👍2
День 2701. #ЗаметкиНаПолях
Версионирование API Должно Быть Последним Средством. Продолжение

Начало

Правила совместимости
- Сохраняйте существующие поля и поведение;
- Не превращайте необязательные данные запроса в обязательные;
- Не меняйте то, что делает существующая операция;
- Делайте всё новое в добавление и необязательным по умолчанию.

1. Добавляйте, а не заменяйте
Самое безопасное изменение обычно является аддитивным. Допустим, первоначальный ответ от GET /orders/{id} был таким:
{
"id": "ord_123",
"status": "paid",
"total": 100
}

Вместо замены поля total добавим новое:
{
"id": "ord_123",
"status": "paid",
"total": 100,
"totalMoney": {
"amount": 100,
"currency": "USD"
}
}

Существующие клиенты продолжают использовать total. Новые могут перейти на totalMoney. Помечаем старое поле как устаревшее и удалим его только после реального периода миграции. Иногда некрасивый контракт — это цена совместимости.

2. Делайте клиентов толерантными
Хорошо работающий клиент не должен выдавать ошибку из-за того, что сервер добавил поле, которое он не понимает. Если ответ изменился с:
{
"id": "ord_123",
"status": "paid"
}

на:
{
"id": "ord_123",
"status": "paid",
"estimatedDeliveryDate": "2026-05-29"
}

существующие клиенты должны игнорировать дополнительное свойство и продолжать работу.

В System.Text.Json неизвестные свойства игнорируются по умолчанию. Реальный риск обычно заключается в чрезмерно строгой проверке JSON (об этом позже на канале). Это одна из распространённых проблем. Команды заявляют о желании обратной совместимости, а затем генерируют клиентские модели, которые отклоняют любое неожиданное поле в ответе. Клиенты должны быть достаточно толерантными, чтобы игнорировать то, чего они не понимают.

3. Не меняйте то, что делает существующая операция
Самые опасные критические изменения скрываются в поведении. URL, тело запроса, формат ответа те же. Но то, что делает операция на сервере, отличается. Например, DELETE /orders/{id} сначала реализовывал мягкое удаление, и заказ переходил в «архив», но по-прежнему отображался в отчётах аудита и мог быть восстановлен службой поддержки. Затем команда решила «почистить базу» и изменить поведение на жёсткое удаление. Ни один клиент сразу этого не заметит, но данные теперь «по-тихому» уничтожаются.

Аналогично:
- POST /orders раньше был идемпотентным, а затем незаметно перестал им быть;
- POST /orders/{id}/cancel раньше автоматически возвращал деньги, а затем перестал это делать, потому что «возвраты должны быть отдельным вызовом»;
- PUT /orders/{id} раньше был полной заменой, а теперь стал частичным слиянием;
- Веб-хук раньше срабатывал один раз для каждого заказа, а теперь срабатывает для каждой позиции заказа;
и т.п.

Каждый из этих вариантов сохраняет стабильность URL, метода и структуры JSON, но нарушает все существующие интеграции таким образом, что это не будет видно при сравнении схем. Безопасный шаг тот же: добавлять, а не изменять. Например, параметр в конечной точке для жёсткого удаления DELETE /orders/{id}?purge=true

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

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

Изменения в ответах обычно тщательно проверяются с точки зрения проектирования. Изменения в проверке запросов заслуживают такого же внимания. Главное правило: то, что вы добавляете в контракт, должно быть необязательным, и всё, что было необязательным, должно оставаться необязательным.

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

Источник:
https://www.milanjovanovic.tech/blog/api-versioning-should-be-your-last-resort
👍5
День 2703. #ЗаметкиНаПолях
Версионирование API Должно Быть Последним Средством. Окончание

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

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

Вот плохой путь:
POST /orders?validateOnly=true&includeTaxEstimate=true&reserveInventory=true

Тут нет одной чистой операции, есть несколько рабочих процессов, скрывающихся за одной конечной точкой.
В этом случае лучше добавить новую операцию или ресурс. Это сохраняет стабильность старого контракта, предоставляя новому поведению чистое место. Например:
- POST /orders остаётся простой конечной точкой «разместить заказ».
- POST /orders/quote становится операцией «скажите, сколько это будет стоить».
Это обычно намного дешевле, чем создание /v2/orders и перенос всего остального API вместе с этим.

Устаревание по-настоящему
Это недостающая часть управления изменениями API. Большинство случаев устаревания — это обман. Они существуют в документации, но ничего не происходит в процессе работы. Реальный процесс устаревания должен включать:
- Пометку старого поля или конечной точки как устаревшей в описании OpenAPI;
- Сообщение об устаревании во время выполнения;
- Предоставление пользователям пути миграции;
- Измерение фактического использования перед удалением чего-либо.

Если вы используете HTTP, то сообщения во время выполнения могут быть в заголовке ответа:
Deprecation: true
Sunset: Wed, 31 Dec 2026 23:59:59 GMT
Link: <https://docs.example.com/migrations/orders-total>; rel="deprecation"

Теперь информация об устаревании видна в документации, видна в реальном трафике и связана с фактическим руководством по миграции.

И вот здесь телеметрия имеет значение. Если вы не знаете, какие клиенты всё ещё используют устаревшее поле или конечную точку, вы не управляете изменениями. Вы гадаете. Отслеживайте использование по ID клиента, ключу API, тенанту или имени приложения. Затем дождитесь, пока использование фактически исчезнет, прежде чем что-либо удалять.

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

А обдуманное версионирование означает выбор наименьшего возможного нарушения, которое вы можете оправдать. Иногда это новая форма конечной точки. Иногда это вариант представления. Иногда, особенно для публичных API, это прямое версионирование URL-адресов, поскольку оно явное и легко объяснимое.

Если вы используете версионирование, сочетайте его с реальным процессом устаревания (см. выше). Настоящая работа не в создании версии 2, а в том, чтобы перевести потребителей с версии 1.

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

Источник:
https://www.milanjovanovic.tech/blog/api-versioning-should-be-your-last-resort
👍2
💥Зачем Senior-разработчики каждый год ездят на DotNext?

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

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

В кулуарах встречаются разработчики разных уровней — от Middle до Senior: сравнивают подходы, делятся опытом и обсуждают задачи, которые редко помещаются в обычный рабочий созвон.

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

☄️В этом году среди спикеров — Александр Поломодов, Станислав Выщепан, Андрей Цветцих и другие участники .NET-сообщества, включая представителей DotNetRu и SPBDotNet.

🗓Конференция пройдет 25–26 сентября в Москве + онлайн.

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

🔔А по промокоду: NetDeveloperDiary у подписчиков моего канала есть уникальная возможность получить скидку 15%!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4👎1
День 2703. #ЧтоНовенького
Вышел 5й Превью .NET 11

На прошлой неделе Microsoft выпустили 5ю превью версию .NET 11, которая включает обновления среды выполнения, SDK, библиотек, ASP.NET Core, .NET MAUI, C# и Entity Framework Core.

.NET SDK
1. Ряд улучшений для однофайловых приложений. Разработчики теперь могут разделять код между файлами, используя новую директиву #:ref, которая ссылается на другое однофайловое приложение как на библиотеку, без создания проекта:
// app.cs
#:ref lib.cs

Console.WriteLine(MyLib.Greeter.Greet("World"));

// lib.cs
namespace MyLib;

public static class Greeter
{
public static string Greet(string name)
=> $"Hello, {name}!";
}

Больше инструментов командной строки, включая команды package и NuGet, теперь понимают пути к однофайловым приложениям.

2. Добавлен шаблон сервера Model Context Protocol, поэтому dotnet new mcpserver работает без отдельного пакета.

3. Проекты могут включить проверку, предупреждающую о наличии известных уязвимостей в установленном SDK или о завершении его поддержки:
<PropertyGroup>
<CheckSdkVulnerabilities>true</CheckSdkVulnerabilities>
</PropertyGroup>


4. Консольные и сервисные приложения, ориентированные на net11.0, теперь автоматически включают System.Net.Http.Json при включении неявных операторов using.

C#
1. Закрытый класс, который может быть производным только от класса в рамках одной сборки, что позволяет компилятору проверять, обрабатывает ли switch все случаи:
public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;

static string Describe(GateState state)
=> state switch
{
Closed => "closed",
Open(var percent) => $"{percent}% open"
};


2. Новое объявление объединения создаёт тип значения, который содержит один из фиксированного набора типов вариантов, с поддержкой сопоставления с образцом:
public record class Dog(string Name);
public record class Cat(int Lives);

public union Pet(Dog, Cat);

static string Describe(Pet pet)
=> pet switch
{
Dog(var name) => $"dog: {name}",
Cat(var lives) => $"cat: {lives}"
};


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

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

3. Сортировка и пагинация QuickGrid теперь работают на статически отображаемых страницах.

4. Автономные приложения Blazor WebAssembly получили новый сервер разработки под названием Gateway, который добавляет встроенную маршрутизацию для SPA.

Entity Framework Core
1. Добавлена поддержка однофайловых приложений в инструменте dotnet ef и файл конфигурации для хранения параметров по умолчанию.

2. Новое предупреждение анализатора EF1004 указывает на асинхронные запросы, которые выполняются синхронно.

3. Совместимость с SQL Server 2022 теперь является стандартной, а сгенерированный код C# использует пространства имен с областью видимости файла.

Источник:
https://www.infoq.com/news/2026/06/dotnet-11-preview-5/
👍2
🚀 DDD на C#: от теории к микросервису за 6 недель

Если ты пишешь на C# и в какой-то момент начал чувствовать, что вроде всё работает, но как-то костыльно — это тревожный сигнал.

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

На курсе по Domain-Driven Design и Clean Architecture на C# ты научишься:

— Отделять бизнес-логику от инфраструктуры
— Организовывать код так, чтобы новые требования не приводили к переписыванию половины сервиса
— Писать тесты, которые проверяют поведение системы, а не набор моков
— Подключать HTTP, gRPC и Kafka без изменений в доменной логике
— Строить сервисы, которые проще поддерживать и развивать

За 6 недель ты соберёшь полноценный микросервис на C# с DDD, Kafka, gRPC и Clean Architecture на реальном кейсе диспетчеризации заказов.

👨‍🏫 Автор курса — Кирилл Ветчинкин, архитектор Авито, ex Staff Engineer Купер, ex Head of Backend BCS Broker.

🎁 Первый модуль доступен бесплатно.

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

Посмотри демо-модуль и оцени, насколько этот подход подходит для твоих проектов:
https://microarch.ru/courses/ddd/languages/csharp?utm_source=posev&utm_medium=erid:2VtzqwrtmqB&utm_campaign=1

Реклама. ИП Ветчинкин К.Е. ИНН: 773376451099 Erid: 2VtzqwrtmqB
#реклама
👎2
День 2704. #ЗаметкиНаПолях
Ужесточаем Десериализацию JSON в System.Text.Json. Начало
Рассмотрим следующий JSON:
{"Amount": 100, "Amount": -999}

Два свойства с одинаковым именем. В разделе 4 RFC 8259 говорится, что имена объектов "ДОЛЖНЫ быть уникальными", и предупреждается, что поведение парсера непредсказуемо, если они таковыми не являются. System.Text.Json выбирает разрешительный путь: побеждает последняя запись, без предупреждений, без ошибок.

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

JsonSerializerOptions.Strict
.NET 10 представляет JsonSerializerOptions.Strict, новую настройку наряду с Default и Web. Default отдаёт приоритет обратной совместимости, Web оптимизирует работу для типичных HTTP API, а Strict следует лучшим практикам безопасности:
using System.Text.Json;

string json = """{"Amount": 100, "Amount": -999}""";

try
{
JsonSerializer.Deserialize<Payment>(json,
JsonSerializerOptions.Strict);
}
catch (JsonException ex)
{
Console.WriteLine(ex.Message);
}
// Вывод: Duplicate property 'Amount' encountered during deserialization of type 'Payment'.

public record Payment(int Amount);

В каждой настройке 5 свойств. Рассмотрим их значения, и на что они влияют.

1. AllowDuplicateProperties (Принятие дублирующих свойств)
- Default: true
- Web: true
- Strict: false

Протоколы, использующие многоуровневый анализ JSON (OAuth 2.0, OpenID Connect, подписи веб-хуков), могут быть взломаны, если разные парсеры обрабатывают дублирующиеся входные данные по-разному. Один парсер видит первое значение, другой — последнее.
Эта защита распространяется не только на десериализацию обычных C# объектов. Также можно обнаруживать дубликаты в JsonDocument, JsonNode и Dictionary<string, T>.

2. UnmappedMemberHandling (Обработка несоответствующих членов)
- Default: Skip
- Web: Skip
- Strict: Disallow

Десериализация по умолчанию молча отбрасывает свойства JSON, которые не соответствуют вашему типу .NET. Это удобно во время разработки. Но это опасно на границе доверия.
Это позволяет выявлять класс ошибок, когда клиенты отправляют поля, которые API не должен принимать. Если вы их не отклоняете, вы не знаете об их наличии. UnmappedMemberHandling добавлено в .NET 8.

3. PropertyNameCaseInsensitive (Учёт регистра свойств)
- Default: false
- Web: true
- Strict: false

Web устанавливает свойство в true, чтобы "username" соответствовало свойству C# с именем Username. Это полезно для типичного взаимодействия JavaScript с .NET. Но в сочетании с UnmappedMemberHandling.Disallow в режиме Strict учёт регистра становится точным контрактом: имена свойств JSON должны точно совпадать с именами свойств C#, иначе десериализатор их отклонит.
Если ваши клиенты отправляют данные в формате camelCase, и вам нужна строгая проверка, добавьте [JsonPropertyName("username")] к свойству. Так, контракт будет явно указан в определении типа, а не неявно в параметрах.

4. RespectNullableAnnotations (Учёт nullable-аннотаций)
- Default: false
- Web: false
- Strict: true

Обнуляемые ссылочные типы C# помогают выявлять проблемы с null на этапе компиляции. Но System.Text.Json по умолчанию игнорирует их во время десериализации. Значение null в JSON легко помещается в строковое свойство без каких-либо проблем. RespectNullableAnnotations добавлено в .NET 9. В строгом режиме сериализатор обеспечивает соблюдение ваших nullable-аннотаций на границе десериализации. Если вы объявили string Name (а не string? Name), сериализатор будет придерживаться этого правила.

5. RespectRequiredConstructorParameters (Учёт обязательных параметров конструктора)
- Default: false
- Web: false
- Strict: true

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

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

Источник:
https://duendesoftware.com/blog/20260430-harden-your-dotnet-json-deserialization
👍5
День 2705. #ЗаметкиНаПолях
Ужесточаем Десериализацию JSON в System.Text.Json. Окончание

Начало

Строгий режим и генераторы кода
В генераторах кода нужно вручную настроить все параметры в JsonSourceGenerationOptionsAttribute:
[JsonSourceGenerationOptions(
AllowDuplicateProperties = false,
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
PropertyNameCaseInsensitive = false,
RespectNullableAnnotations = true,
RespectRequiredConstructorParameters = true
)]

Сокращенного обозначения параметра Strict не существует. Каждое свойство устанавливается индивидуально.

Строгий режим в минимальных API ASP.NET Core
В веб-приложении параметры JSON настраиваются один раз, и каждая конечная точка наследует их:
builder.Services.ConfigureHttpJsonOptions(o =>
{
o.SerializerOptions.AllowDuplicateProperties = false;
o.SerializerOptions.UnmappedMemberHandling =
JsonUnmappedMemberHandling.Disallow;
o.SerializerOptions.PropertyNameCaseInsensitive = false;
o.SerializerOptions.RespectNullableAnnotations = true;
o.SerializerOptions.RespectRequiredConstructorParameters = true;
});

// …

var app = builder.Build();

app.MapPost("/payments", (Payment pmt) =>
{
// Если тело запроса нарушает какое-либо
// из требований сериализатора, фреймворк
// вернёт 400 Bad Request без выполнения тела метода
return Results.Ok(pmt);
});

app.Run();


Фреймворк перехватывает исключение JsonException во время привязки модели и возвращает ошибку 400 Bad Request с подробным описанием проблемы. Ваш код конечной точки видит только действительные, полностью привязанные объекты.

Параметры для каждой конечной точки
Если требуется строгая проверка для одних конечных точек, но более мягкий анализ для других, используйте Results.Json с явными параметрами:
app.MapGet("/api/data", () =>
Results.Json(new { message = "Hello" }, JsonSerializerOptions.Strict));

Для десериализации можно брать чистое тело запроса:
app.MapPost("/api/strict",
async (HttpContext context) =>
{
var pmt = await context.Request
.ReadFromJsonAsync<Payment>(JsonSerializerOptions.Strict);
return Results.Ok(pmt);
});


Когда использовать Strict
На границах доверия. Конечные точки токенов, контроллеры API — всё, где вы принимаете JSON от клиента, которого вы не полностью контролируете. Цена — исключение JsonException, когда полезная нагрузка не соответствует вашему контракту.

Если вы получаете JSON от сторонних API с несогласованными схемами, строгий режим отклонит данные, которые вы, возможно, захотите обработать корректно. В таких случаях используйте режим Default или Web и проверяйте после десериализации.

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

Параметр Strict проверяет структурные нарушения контракта. Он не защищает от глубоко вложенного JSON (используйте MaxDepth), слишком больших объёмов данных (обрабатывайте на уровне HTTP с ограничениями размера запроса) или путаницы полиморфных типов. Строгий режим — это один уровень защиты, а не единственный.

Укрепление вашей безопасности
Каждая конечная точка API, принимающая JSON, является границей доверия. Разрешительная десериализация делает эту границу уязвимой. Режим Strict не добавляет новую логику проверки. Он активирует защиту, которая уже существует в System.Text.Json, но отключена по умолчанию для обратной совместимости.

Источник: https://duendesoftware.com/blog/20260430-harden-your-dotnet-json-deserialization
👍1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
День 2706. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

37. Аутентификация и авторизация
«Как бы вы реализовали аутентификацию и авторизацию в приложении ASP.NET Core, используя минимальные API? Приведите пример настройки этих мер безопасности и их применения для обеспечения безопасного доступа к данным».

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

Начнём с настройки необходимых сервисов в Program.cs. Вот пример использования JWT (JSON Web Tokens) для аутентификации:
var builder = WebApplication.CreateBuilder(args);

// Добавляем аутентификацию
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", opts =>
{
opts.Authority = "https://authserver.com";
opts.TokenValidationParameters = new()
{ ValidateAudience = false };
});

// Добавляем авторизацию
builder.Services.AddAuthorization(opts =>
{
opts.AddPolicy("MustBeAdmin", policy =>
policy.RequireClaim("role", "Admin"));
});

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();


Далее определим конечные точки минимального API. Используем настроенные сервисы для защиты этих конечных точек:
app.MapGet("/secure-data", 
[Authorize(Policy="MustBeAdmin")] () =>
{
return "Доступно только админам";
});

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

Настройка аутентификации на основе JWT и определение чётких политик авторизации эффективно защищает конечные точки от несанкционированного доступа.

Часто встречающийся плохой ответ
«Можно использовать базовую аутентификацию (HTTP-заголовок `Authorization: Basic …`) для каждого запроса, проверяя имена пользователей и пароли по БД непосредственно в каждой конечной точке API. Так мы гарантируем аутентификацию каждого вызова без усложнения ситуации с помощью JWT или внешних поставщиков.»

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

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

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

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👎3