День 2762. #Карьера
Основные Правила Разработки ПО. Окончание
Начало
Продолжение
21. Никогда не доверяйте пользовательскому вводу
Пользователи допускают ошибки, а злоумышленники активно пытаются взломать вашу систему. Никогда не доверяйте пользовательскому вводу, будь то из форм, API, при загрузке файлов или в параметрах URL.
Проверяйте все входные данные: типы, форматы, диапазоны и допустимые значения. Очищайте данные, чтобы предотвратить атаки инъекцией. Используйте параметризованные запросы, кодирование и проверенные библиотеки безопасности, а не разрабатывайте собственные решения.
22. Ошибки должны приводить к громкому и немедленному сбою
Тихие сбои — кошмар отладки. Когда что-то идёт не так, сделайте это очевидным. Сообщайте об ошибках быстро, громко и предоставляйте чёткие сообщения об ошибках, которые помогут быстро выявить проблему.
Не игнорируйте исключения. Не возвращайте null при сбое. Не продолжайте работу, записав в лог. Когда не выполняется предусловие, данные невалидны, необходимый сервис недоступен, остановите выполнение и чётко сообщите о проблеме.
Хорошая обработка ошибок подразумевает быстрое выявление и устранение неполадок с чёткими сообщениями, предоставлением контекста о том, что пошло не так и почему, а также упрощением отслеживания источника ошибки. Это экономит часы отладки.
23. Сначала измерьте, потом оптимизируйте
Преждевременная оптимизация — корень всех зол. Разработчики часто тратят время на оптимизацию кода, который на самом деле не является узким местом, усложняя его без существенного повышения производительности.
Перед оптимизацией профилируйте приложение. Выявите реальные, а не предполагаемые узкие места. Вы можете обнаружить, что медленная часть вовсе не там, где вы думали. Оптимизируйте проблемные области и снова проведите измерения, чтобы проверить улучшение.
Не игнорируйте производительность полностью. Пишите достаточно эффективный код изначально, но не жертвуйте ясностью и удобством сопровождения ради микрооптимизаций. Знайте, что вы оптимизируете: задержку, пропускную способность, память или время разработки.
24. Меняйте по одной вещи за раз
При отладке или оптимизации сопротивляйтесь желанию изменять несколько вещей одновременно. Так вы не будете знать, какое именно изменение исправило ошибку или улучшило производительность. Внесите одно изменение, измерьте результат, а затем продолжайте. Такой дисциплинированный подход может показаться медленнее, но он экономит время, предоставляя чёткие причинно-следственные связи. Вы построите мысленную модель того, что действительно важно, и избежите разочарования от отмены целого ряда изменений, потому что одно из них сломало что-то другое.
Аналогично при рефакторинге изменяйте либо структуру, либо поведение, но не то и другое одновременно. При развертывании выпускайте по одной функции за раз, чтобы изолировать проблемы.
25. Создавайте API, которые легко использовать правильно и сложно неправильно
Хороший дизайн предотвращает ошибки до их возникновения. При проектировании функции, класса или интерфейса сервиса подумайте о том, как они будут использоваться и как их можно использовать неправильно.
Используйте типы для обеспечения ограничений (обязательность, допустимые диапазоны, разрешённые состояния). Сделайте недопустимые состояния непредставимыми. Используйте чёткие имена, указывающие на назначение и поведение. Предоставляйте хорошие значения по умолчанию. Сделайте распространённые случаи простыми, а сложные — возможными. Возвращайте содержательные ошибки, которые помогут разработчикам правильно использовать API.
Отдавайте предпочтение композиции перед наследованием. Композиция более гибкая и создаёт меньше проблем со связностью. Минимизируйте связь между компонентами и максимизируйте их согласованность. Каждый модуль должен иметь чёткое назначение.
26. Будьте хорошим человеком важнее технических навыков
Разработка — командный вид спорта. Техническое мастерство ничего не значит, если с вами трудно работать, вы неэффективно общаетесь или не поддерживаете своих коллег.
Будьте добры, терпеливы, делитесь заслугами, признавайте ошибки, помогайте другим расти, больше слушайте, уважайте разные точки зрения и стили работы. У каждого есть трудности, которых вы не видите. Ваше отношение и навыки сотрудничества важнее для вашей карьеры, чем любые технические навыки.
Поддерживайте коллег, когда у них возникают трудности. Отмечайте их успехи. Создавайте среду, где люди чувствуют себя комфортно, задавая вопросы, признавая, что чего-то не знают, или высказывая свои опасения. Это укрепляет доверие и побуждает людей сообщать о проблемах на ранних стадиях, а не скрывать их.
27. Думайте о последствиях, а не только о решениях
Большинство инженеров оптимизируют решения. Архитекторы оптимизируют последствия. Обе роли важны, но они решают разные классы проблем.
Сеньоры задаются вопросом: «Как построить это хорошо?» Архитекторы: «Что произойдёт, если мы ошибаемся?» Этот сдвиг в перспективе меняет то, что вы оптимизируете. Вместо того чтобы фокусироваться на локальной производительности или качестве реализации, минимизируйте возможный ущерб, оптимизируйте обратимость и долгосрочные эксплуатационные расходы.
Ваш прогресс как архитектора – это учитывание последствий необратимых решений, компромиссов в выборе инструментов, ограничений в выборе возможностей и побочных эффектов. Уменьшайте сложность не только внутри функции, но и в командах и организации в целом.
Архитектура — это не рисование диаграмм. Это выбор того, с какими последствиями вы готовы смириться, и принятие ответственности за этот выбор.
Итого
Это не строгие законы, а рекомендации, основанные на опыте, ошибках и извлечённых уроках. Возьмите то, что вам близко, адаптируйте к своему контексту и помните, что разработка ПО — это в равной степени код, люди и коммуникация.
Лучшие инженеры — не те, кто пишет самый умный код, а те, кто приносит пользу, эффективно сотрудничает, постоянно учится и делает лучше всех вокруг себя. Развивайте свои принципы, основываясь на собственном опыте. Каждый проект, каждая команда и каждое испытание учат вас чему-то новому.
Источник: https://www.meziantou.net/essential-rules-of-software-engineering.htm
Основные Правила Разработки ПО. Окончание
Начало
Продолжение
21. Никогда не доверяйте пользовательскому вводу
Пользователи допускают ошибки, а злоумышленники активно пытаются взломать вашу систему. Никогда не доверяйте пользовательскому вводу, будь то из форм, API, при загрузке файлов или в параметрах URL.
Проверяйте все входные данные: типы, форматы, диапазоны и допустимые значения. Очищайте данные, чтобы предотвратить атаки инъекцией. Используйте параметризованные запросы, кодирование и проверенные библиотеки безопасности, а не разрабатывайте собственные решения.
22. Ошибки должны приводить к громкому и немедленному сбою
Тихие сбои — кошмар отладки. Когда что-то идёт не так, сделайте это очевидным. Сообщайте об ошибках быстро, громко и предоставляйте чёткие сообщения об ошибках, которые помогут быстро выявить проблему.
Не игнорируйте исключения. Не возвращайте null при сбое. Не продолжайте работу, записав в лог. Когда не выполняется предусловие, данные невалидны, необходимый сервис недоступен, остановите выполнение и чётко сообщите о проблеме.
Хорошая обработка ошибок подразумевает быстрое выявление и устранение неполадок с чёткими сообщениями, предоставлением контекста о том, что пошло не так и почему, а также упрощением отслеживания источника ошибки. Это экономит часы отладки.
23. Сначала измерьте, потом оптимизируйте
Преждевременная оптимизация — корень всех зол. Разработчики часто тратят время на оптимизацию кода, который на самом деле не является узким местом, усложняя его без существенного повышения производительности.
Перед оптимизацией профилируйте приложение. Выявите реальные, а не предполагаемые узкие места. Вы можете обнаружить, что медленная часть вовсе не там, где вы думали. Оптимизируйте проблемные области и снова проведите измерения, чтобы проверить улучшение.
Не игнорируйте производительность полностью. Пишите достаточно эффективный код изначально, но не жертвуйте ясностью и удобством сопровождения ради микрооптимизаций. Знайте, что вы оптимизируете: задержку, пропускную способность, память или время разработки.
24. Меняйте по одной вещи за раз
При отладке или оптимизации сопротивляйтесь желанию изменять несколько вещей одновременно. Так вы не будете знать, какое именно изменение исправило ошибку или улучшило производительность. Внесите одно изменение, измерьте результат, а затем продолжайте. Такой дисциплинированный подход может показаться медленнее, но он экономит время, предоставляя чёткие причинно-следственные связи. Вы построите мысленную модель того, что действительно важно, и избежите разочарования от отмены целого ряда изменений, потому что одно из них сломало что-то другое.
Аналогично при рефакторинге изменяйте либо структуру, либо поведение, но не то и другое одновременно. При развертывании выпускайте по одной функции за раз, чтобы изолировать проблемы.
25. Создавайте API, которые легко использовать правильно и сложно неправильно
Хороший дизайн предотвращает ошибки до их возникновения. При проектировании функции, класса или интерфейса сервиса подумайте о том, как они будут использоваться и как их можно использовать неправильно.
Используйте типы для обеспечения ограничений (обязательность, допустимые диапазоны, разрешённые состояния). Сделайте недопустимые состояния непредставимыми. Используйте чёткие имена, указывающие на назначение и поведение. Предоставляйте хорошие значения по умолчанию. Сделайте распространённые случаи простыми, а сложные — возможными. Возвращайте содержательные ошибки, которые помогут разработчикам правильно использовать API.
Отдавайте предпочтение композиции перед наследованием. Композиция более гибкая и создаёт меньше проблем со связностью. Минимизируйте связь между компонентами и максимизируйте их согласованность. Каждый модуль должен иметь чёткое назначение.
26. Будьте хорошим человеком важнее технических навыков
Разработка — командный вид спорта. Техническое мастерство ничего не значит, если с вами трудно работать, вы неэффективно общаетесь или не поддерживаете своих коллег.
Будьте добры, терпеливы, делитесь заслугами, признавайте ошибки, помогайте другим расти, больше слушайте, уважайте разные точки зрения и стили работы. У каждого есть трудности, которых вы не видите. Ваше отношение и навыки сотрудничества важнее для вашей карьеры, чем любые технические навыки.
Поддерживайте коллег, когда у них возникают трудности. Отмечайте их успехи. Создавайте среду, где люди чувствуют себя комфортно, задавая вопросы, признавая, что чего-то не знают, или высказывая свои опасения. Это укрепляет доверие и побуждает людей сообщать о проблемах на ранних стадиях, а не скрывать их.
27. Думайте о последствиях, а не только о решениях
Большинство инженеров оптимизируют решения. Архитекторы оптимизируют последствия. Обе роли важны, но они решают разные классы проблем.
Сеньоры задаются вопросом: «Как построить это хорошо?» Архитекторы: «Что произойдёт, если мы ошибаемся?» Этот сдвиг в перспективе меняет то, что вы оптимизируете. Вместо того чтобы фокусироваться на локальной производительности или качестве реализации, минимизируйте возможный ущерб, оптимизируйте обратимость и долгосрочные эксплуатационные расходы.
Ваш прогресс как архитектора – это учитывание последствий необратимых решений, компромиссов в выборе инструментов, ограничений в выборе возможностей и побочных эффектов. Уменьшайте сложность не только внутри функции, но и в командах и организации в целом.
Архитектура — это не рисование диаграмм. Это выбор того, с какими последствиями вы готовы смириться, и принятие ответственности за этот выбор.
Итого
Это не строгие законы, а рекомендации, основанные на опыте, ошибках и извлечённых уроках. Возьмите то, что вам близко, адаптируйте к своему контексту и помните, что разработка ПО — это в равной степени код, люди и коммуникация.
Лучшие инженеры — не те, кто пишет самый умный код, а те, кто приносит пользу, эффективно сотрудничает, постоянно учится и делает лучше всех вокруг себя. Развивайте свои принципы, основываясь на собственном опыте. Каждый проект, каждая команда и каждое испытание учат вас чему-то новому.
Источник: https://www.meziantou.net/essential-rules-of-software-engineering.htm
👍6👎1
День 2763.
DotNext 2026
В этом году в очередной раз поеду на конференцию DotNext. На этот раз простым слушателем. Она пройдёт в Москве 25 и 26 сентября. В этом году её сдвинули с обычных четверга-пятницы на пятницу-субботу, так что отпроситься с работы стало проще 😉
Сильно изменилась и программа (расписание, кстати, тут). Раньше основной фокус докладов был на практических нюансах разработки. Всегда можно было найти в расписании доклады по DDD, EF, перекладыванию JSON-чиков, асинхронной коммуникации в приложении или особенностях БД (чаще всего Postgres). Некоторые даже упрекали конференцию в некотором застое, мол, доклады почти одинаковые из года в год. Так вот, в этот раз это совсем не так. AI ворвался на DotNext с ноги, даже создали отдельный топик «Practical AI», и он второй по популярности из 5 топиков программы. Я, например, для себя отметил «Когда код пишут не только люди: как меняется разработка в эпоху agentic development lifecycle» от Дмитрия Иванова и «Как закрывать весь SDLC с помощью агента» от Павла Федотовского.
Но это не значит, что ИИ захватил всё.
Больше всего докладов в топике «Архитектура». Некоторые будут без записи, поэтому, наверное, их стоит посетить лично, а остальные посмотреть потом. Из интересного, на мой взгляд, тут мастер-класс «Распилим монолит» от Алексея Лосева, «Модульность без микросервисов в ASP.NET из коробки» от Станислава Выщепана (да, темы схожие, но тут «у кого чего болит» - у меня на работе «big pile of mud» в каком-то смысле, который надо распиливать) и доклад о DomainEvents в DDD от Дениса Цветцих.
Топик «Internals» вобрал в себя доклады про «кишочки». Там, например, можно послушать Марка Шевченко про «Размеченные объединения в C# 15» или Юрия Малича про сравнение производительности и подводные камни Native AOT и JIT. Я скорей всего схожу на доклад Николая Савенко «Как выбрать инструмент для трейсов в .NET?»
Топик «Best practices» никуда не делся. Здесь меня заинтересовали доклады «Очень быстрые интеграционные тесты на EF Core + PostgreSQL» Сергея Булавского и про практику применения появившихся в C# 14 Extension Members от Виктора Дзицкого.
Ну и наконец, пара докладов есть в топике «Расширяем горизонты». Тут можно отвлечься от технических деталей и послушать о том, реально ли сделать свою IDE или почему в космосе пока нет дата-центров.
В общем, посмотреть и послушать много чего есть (я перечислил от силы четверть). И в этом году докладов стало больше. Если раньше я иногда терялся, на какой доклад пойти, когда их было 3 параллельно, то теперь их стало 4 в каждом слоте! А даже если не понравится совсем ничего из предложенного, можно посетить стенды партнёров конференции и поучаствовать в конкурсах, прикупить (или выиграть) мерча, а возможно и найти новое место работы.
Так что приезжайте тоже, скучно не будет. Ну и, конечно, если встретите меня, подходите, рад буду со всеми пообщаться в офлайне.
PS: если решили купить билет, напишите в личку 😉
DotNext 2026
В этом году в очередной раз поеду на конференцию DotNext. На этот раз простым слушателем. Она пройдёт в Москве 25 и 26 сентября. В этом году её сдвинули с обычных четверга-пятницы на пятницу-субботу, так что отпроситься с работы стало проще 😉
Сильно изменилась и программа (расписание, кстати, тут). Раньше основной фокус докладов был на практических нюансах разработки. Всегда можно было найти в расписании доклады по DDD, EF, перекладыванию JSON-чиков, асинхронной коммуникации в приложении или особенностях БД (чаще всего Postgres). Некоторые даже упрекали конференцию в некотором застое, мол, доклады почти одинаковые из года в год. Так вот, в этот раз это совсем не так. AI ворвался на DotNext с ноги, даже создали отдельный топик «Practical AI», и он второй по популярности из 5 топиков программы. Я, например, для себя отметил «Когда код пишут не только люди: как меняется разработка в эпоху agentic development lifecycle» от Дмитрия Иванова и «Как закрывать весь SDLC с помощью агента» от Павла Федотовского.
Но это не значит, что ИИ захватил всё.
Больше всего докладов в топике «Архитектура». Некоторые будут без записи, поэтому, наверное, их стоит посетить лично, а остальные посмотреть потом. Из интересного, на мой взгляд, тут мастер-класс «Распилим монолит» от Алексея Лосева, «Модульность без микросервисов в ASP.NET из коробки» от Станислава Выщепана (да, темы схожие, но тут «у кого чего болит» - у меня на работе «big pile of mud» в каком-то смысле, который надо распиливать) и доклад о DomainEvents в DDD от Дениса Цветцих.
Топик «Internals» вобрал в себя доклады про «кишочки». Там, например, можно послушать Марка Шевченко про «Размеченные объединения в C# 15» или Юрия Малича про сравнение производительности и подводные камни Native AOT и JIT. Я скорей всего схожу на доклад Николая Савенко «Как выбрать инструмент для трейсов в .NET?»
Топик «Best practices» никуда не делся. Здесь меня заинтересовали доклады «Очень быстрые интеграционные тесты на EF Core + PostgreSQL» Сергея Булавского и про практику применения появившихся в C# 14 Extension Members от Виктора Дзицкого.
Ну и наконец, пара докладов есть в топике «Расширяем горизонты». Тут можно отвлечься от технических деталей и послушать о том, реально ли сделать свою IDE или почему в космосе пока нет дата-центров.
В общем, посмотреть и послушать много чего есть (я перечислил от силы четверть). И в этом году докладов стало больше. Если раньше я иногда терялся, на какой доклад пойти, когда их было 3 параллельно, то теперь их стало 4 в каждом слоте! А даже если не понравится совсем ничего из предложенного, можно посетить стенды партнёров конференции и поучаствовать в конкурсах, прикупить (или выиграть) мерча, а возможно и найти новое место работы.
Так что приезжайте тоже, скучно не будет. Ну и, конечно, если встретите меня, подходите, рад буду со всеми пообщаться в офлайне.
PS: если решили купить билет, напишите в личку 😉
👍7
Посещаете ли вы профессиональные конференции (выберите наиболее подходящий вам ответ)?
Anonymous Poll
16%
да, интересно послушать доклады
1%
да, так развиваю сеть полезных контактов
10%
только если отправляет/оплачивает работодатель
2%
смотрю онлайн (по билету)
52%
нет, смотрю доклады, когда выкладывают в общий доступ
19%
нет, не интересно совсем
День 2764. #ЧтоНовенького #NET11
Вышли Превью 6 и 7 .NET 11
Посмотрим на самые интересные новинки в библиотеках.
1. Асинхронная валидация в DataAnnotations
System.ComponentModel.DataAnnotations теперь поддерживает асинхронную валидацию. Правило валидации, требующее операций ввода-вывода — поиск в базе данных, вызов удалённого API — теперь может выполняться без блокировки потока.
Способы выражения асинхронного правила:
- Наследовать от AsyncValidationAttribute и переопределить его метод IsValidAsync.
- Реализовать IAsyncValidatableObject в модели.
- Вызвать новые методы Validator.ValidateObjectAsync, TryValidateObjectAsync, ValidatePropertyAsync и ValidateValueAsync.
Microsoft.Extensions.Options получает соответствующую поддержку: конфигурация приложения может проверяться асинхронно, в том числе при запуске, с помощью нового IAsyncStartupValidator.
2. System.Text.Json сериализует типы-объединения
Сериализатор распознаёт объединение через новый тип контракта JsonTypeInfoKind.Union, читает и записывает активный вариант и поддерживает как сериализатор на основе рефлексии, так и генератор исходного кода. Новые API JsonUnionAttribute, JsonUnionCaseInfo и классификаторы типов (JsonTypeClassifier и JsonSerializerOptions.TypeClassifiers) позволяют настраивать способ обнаружения и именования вариантов.
3. Настройка трассировки действий с помощью правил
Новый API настройки трассировки в Microsoft.Extensions.Diagnostics позволяет включать и выключать трассировку действий с помощью правил вместо ручной настройки экземпляров ActivityListener. Вызовите AddTracing и укажите, какие источники действий, операции и слушатели следует включить или отключить. Правила также могут управляться из конфигурации, поэтому трассировку можно настраивать без повторного развёртывания:
Также добавлена ActivitySourceFactory, а у класса ActivitySource убран модификатор sealed, что предоставляет фабричное создание и обновляемых слушателей.
4. Запуск приостановленных процессов и их поиск по идентификатору
System.Diagnostics.Process добавляет более тонкий контроль над запуском и поиском процессов:
- ProcessStartInfo.StartSuspended запускает процесс в приостановленном состоянии в Windows. Используйте его в паре с SafeProcessHandle.Resume, чтобы он продолжил работу после завершения какой-либо настройки, например подключения отладчика или настройки объектов заданий.
- Process.TryGetProcessById возвращает false вместо исключения, если процесса с заданным идентификатором не существует.
- SafeProcessHandle.Open и TryOpen открывают дескриптор существующего процесса по идентификатору.
5. Десятичные типы с плавающей запятой по IEEE 754
System.Numerics получает три десятичных типа с плавающей запятой IEEE 754-2019 — Decimal32, Decimal64 и Decimal128 — с точностью до 7, 16 и 34 знаков после запятой соответственно. В отличие от System.Decimal, они используют кодировку обмена двоичными целыми и десятичными числами IEEE (BID), и поддерживают специальные значения IEEE, такие как бесконечность и NaN. Они поддерживают обобщённые математические операции через IDecimalFloatingPointIeee754<TSelf>, поэтому обобщённые числовые алгоритмы могут использовать их наряду с существующими типами с плавающей запятой.
Типы включают в себя API для парсинга, преобразований, арифметических и сравнительных операторов, округления, умножения-сложения, квантования, а также двоичного и десятичного кодирования. Базовые арифметические операции проверяются с точностью до бита по тестовым векторам библиотеки Intel Decimal Floating-Point Math Library. Функции более высокого уровня, такие как трансцендентные числа, не являются точными в .NET 11, но планируется сделать их точными в будущей версии.
6. Частичный парсинг чисел
INumberBase<TSelf>.TryParsePartial добавляет универсальные перегрузки для парсинга чисел, которые сообщают, сколько входных данных было обработано. Это позволяет парсерам для таких форматов, как CSV, анализировать одно числовое поле и продолжать обработку оставшихся входных данных без их копирования или обработки разделителя как ошибки парсинга:
7. Сжатие HTTP-запросов
System.Net.Http получает три обёртки над HttpContent, которые сжимают тела запросов в сети — GZipCompressedContent, BrotliCompressedContent и ZstandardCompressedContent в пространстве имен System.Net.Http. Каждая обёртка устанавливает соответствующий заголовок Content-Encoding, очищает Content-Length и передаёт сжатые байты потоком по мере сериализации запроса. Для каждого алгоритма предоставляются два конструктора: один принимает CompressionLevel для быстрой настройки скорости/размера, а другой принимает полный тип параметров алгоритма (ZLibCompressionOptions, BrotliCompressionOptions или ZstandardCompressionOptions) для более точной настройки. Сжатие запросов является необязательным: используйте его только в том случае, если вы знаете, что сервер поддерживает выбранный кодировщик содержимого, поскольку HttpClient не согласовывает сжатие содержимого запроса автоматически.
8. Поддержка паролей для ZIP-архивов
System.IO.Compression теперь читает и записывает защищённые паролем ZIP-файлы. ZipArchiveEntry получает перегрузки Open и OpenAsync, принимающие пароль типа ReadOnlySpan<char>, ZipArchive.CreateEntry получает перегрузки, принимающие пароль и ZipEncryptionMethod (ZipCrypto, Aes128, Aes192, Aes256), а новое свойство ZipArchiveEntry.EncryptionMethod отображает алгоритм, используемый существующей записью.
9. Фабрики HybridCache с поддержкой параметров
Microsoft.Extensions.Caching.Hybrid.HybridCache добавляет перегрузки GetOrCreateAsync, чей фабричный метод обратного вызова получает изменяемый HybridCacheEntryContext в дополнение к состоянию и токену отмены. Теперь фабрика может настраивать параметры Expiration, LocalCacheExpiration, Flags и новое свойство LocalSize (HybridCacheEntryOptions.LocalSize, используемое для MemoryCache.Size) в зависимости от только что полученного значения — например, предоставляя более длительный срок жизни (TTL) для большого вычисленного результата.
Источники:
- https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview6/libraries.md
- https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview7/libraries.md
Вышли Превью 6 и 7 .NET 11
Посмотрим на самые интересные новинки в библиотеках.
1. Асинхронная валидация в DataAnnotations
System.ComponentModel.DataAnnotations теперь поддерживает асинхронную валидацию. Правило валидации, требующее операций ввода-вывода — поиск в базе данных, вызов удалённого API — теперь может выполняться без блокировки потока.
Способы выражения асинхронного правила:
- Наследовать от AsyncValidationAttribute и переопределить его метод IsValidAsync.
- Реализовать IAsyncValidatableObject в модели.
- Вызвать новые методы Validator.ValidateObjectAsync, TryValidateObjectAsync, ValidatePropertyAsync и ValidateValueAsync.
Microsoft.Extensions.Options получает соответствующую поддержку: конфигурация приложения может проверяться асинхронно, в том числе при запуске, с помощью нового IAsyncStartupValidator.
2. System.Text.Json сериализует типы-объединения
Сериализатор распознаёт объединение через новый тип контракта JsonTypeInfoKind.Union, читает и записывает активный вариант и поддерживает как сериализатор на основе рефлексии, так и генератор исходного кода. Новые API JsonUnionAttribute, JsonUnionCaseInfo и классификаторы типов (JsonTypeClassifier и JsonSerializerOptions.TypeClassifiers) позволяют настраивать способ обнаружения и именования вариантов.
3. Настройка трассировки действий с помощью правил
Новый API настройки трассировки в Microsoft.Extensions.Diagnostics позволяет включать и выключать трассировку действий с помощью правил вместо ручной настройки экземпляров ActivityListener. Вызовите AddTracing и укажите, какие источники действий, операции и слушатели следует включить или отключить. Правила также могут управляться из конфигурации, поэтому трассировку можно настраивать без повторного развёртывания:
builder.Services.AddTracing(trc =>
{
trc.EnableTracing(sourceName: "MyCompany.Orders");
trc.DisableTracing(sourceName: "MyCompany.Orders", operationName: "HealthCheck");
});
Также добавлена ActivitySourceFactory, а у класса ActivitySource убран модификатор sealed, что предоставляет фабричное создание и обновляемых слушателей.
4. Запуск приостановленных процессов и их поиск по идентификатору
System.Diagnostics.Process добавляет более тонкий контроль над запуском и поиском процессов:
- ProcessStartInfo.StartSuspended запускает процесс в приостановленном состоянии в Windows. Используйте его в паре с SafeProcessHandle.Resume, чтобы он продолжил работу после завершения какой-либо настройки, например подключения отладчика или настройки объектов заданий.
- Process.TryGetProcessById возвращает false вместо исключения, если процесса с заданным идентификатором не существует.
- SafeProcessHandle.Open и TryOpen открывают дескриптор существующего процесса по идентификатору.
5. Десятичные типы с плавающей запятой по IEEE 754
System.Numerics получает три десятичных типа с плавающей запятой IEEE 754-2019 — Decimal32, Decimal64 и Decimal128 — с точностью до 7, 16 и 34 знаков после запятой соответственно. В отличие от System.Decimal, они используют кодировку обмена двоичными целыми и десятичными числами IEEE (BID), и поддерживают специальные значения IEEE, такие как бесконечность и NaN. Они поддерживают обобщённые математические операции через IDecimalFloatingPointIeee754<TSelf>, поэтому обобщённые числовые алгоритмы могут использовать их наряду с существующими типами с плавающей запятой.
Типы включают в себя API для парсинга, преобразований, арифметических и сравнительных операторов, округления, умножения-сложения, квантования, а также двоичного и десятичного кодирования. Базовые арифметические операции проверяются с точностью до бита по тестовым векторам библиотеки Intel Decimal Floating-Point Math Library. Функции более высокого уровня, такие как трансцендентные числа, не являются точными в .NET 11, но планируется сделать их точными в будущей версии.
6. Частичный парсинг чисел
INumberBase<TSelf>.TryParsePartial добавляет универсальные перегрузки для парсинга чисел, которые сообщают, сколько входных данных было обработано. Это позволяет парсерам для таких форматов, как CSV, анализировать одно числовое поле и продолжать обработку оставшихся входных данных без их копирования или обработки разделителя как ошибки парсинга:
using System;
using System.Globalization;
ReadOnlySpan<char> input = "123; 456";
if (int.TryParsePartial(
input,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out int value,
out int charsConsumed))
{
Console.WriteLine(value); // 123
input = input[charsConsumed..];
Console.WriteLine(input); // ; 456
}
7. Сжатие HTTP-запросов
System.Net.Http получает три обёртки над HttpContent, которые сжимают тела запросов в сети — GZipCompressedContent, BrotliCompressedContent и ZstandardCompressedContent в пространстве имен System.Net.Http. Каждая обёртка устанавливает соответствующий заголовок Content-Encoding, очищает Content-Length и передаёт сжатые байты потоком по мере сериализации запроса. Для каждого алгоритма предоставляются два конструктора: один принимает CompressionLevel для быстрой настройки скорости/размера, а другой принимает полный тип параметров алгоритма (ZLibCompressionOptions, BrotliCompressionOptions или ZstandardCompressionOptions) для более точной настройки. Сжатие запросов является необязательным: используйте его только в том случае, если вы знаете, что сервер поддерживает выбранный кодировщик содержимого, поскольку HttpClient не согласовывает сжатие содержимого запроса автоматически.
8. Поддержка паролей для ZIP-архивов
System.IO.Compression теперь читает и записывает защищённые паролем ZIP-файлы. ZipArchiveEntry получает перегрузки Open и OpenAsync, принимающие пароль типа ReadOnlySpan<char>, ZipArchive.CreateEntry получает перегрузки, принимающие пароль и ZipEncryptionMethod (ZipCrypto, Aes128, Aes192, Aes256), а новое свойство ZipArchiveEntry.EncryptionMethod отображает алгоритм, используемый существующей записью.
9. Фабрики HybridCache с поддержкой параметров
Microsoft.Extensions.Caching.Hybrid.HybridCache добавляет перегрузки GetOrCreateAsync, чей фабричный метод обратного вызова получает изменяемый HybridCacheEntryContext в дополнение к состоянию и токену отмены. Теперь фабрика может настраивать параметры Expiration, LocalCacheExpiration, Flags и новое свойство LocalSize (HybridCacheEntryOptions.LocalSize, используемое для MemoryCache.Size) в зависимости от только что полученного значения — например, предоставляя более длительный срок жизни (TTL) для большого вычисленного результата.
Источники:
- https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview6/libraries.md
- https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview7/libraries.md
👍5
День 2765. #ЗаметкиНаПолях
Ограничьте Возможности NuGet-пакетов в Вашем Проекте
При добавлении NuGet-пакета в проект принято думать о нём только как о библиотеке времени выполнения. В действительности пакеты также могут импортировать свойства/таргеты MSBuild и анализаторы Roslyn. Эти ресурсы запускаются во время восстановления или сборки. Описание общих рисков безопасности и лучших практик можно найти тут.
Т.е. пакет может выполнять код на вашем компьютере или в CI без вашего ведома. Если вам нужна только часть ресурсов пакета, вы можете ограничить импорт, используя IncludeAssets и ExcludeAssets в PackageReference.
Почему это важно
Пакеты NuGet могут включать:
- файлы build, buildMultitargeting и buildTransitive (свойства и таргеты MSBuild);
- анализаторы (включая генераторы кода);
- библиотеки времени выполнения и компиляции.
Если вам не нужна логика сборки или анализаторы из пакета, их блокировка уменьшает поверхность атаки и делает сборки более предсказуемыми. Если вы читали последние новости об атаках на цепочки поставок, вы знаете, что любой код, работающий на вашей машине или в CI, представляет потенциальный риск. По умолчанию импортируются все ресурсы из пакета, поэтому важно помнить об этом и принимать меры при необходимости.
Включайте только то, что вам нужно
Наиболее безопасный подход часто заключается в разрешении только необходимых вам ресурсов:
При такой конфигурации NuGet не импортирует анализаторы и таргеты сборки из пакета.
Исключение определённых типов ресурсов
Если вы предпочитаете сохранить поведение по умолчанию и удалить только некоторые группы ресурсов, используйте ExcludeAssets:
Это полезно, когда вам по-прежнему нужны другие ресурсы, такие как contentFiles.
Распространённые сценарии
Включать только ресурсы среды выполнения/библиотеки:
Включать библиотеку времени выполнения, отключить анализаторы:
Оставить анализаторы, отключить таргеты сборки:
Использовать пакет только для сборки:
Параметр "PrivateAssets="all" предотвращает транзитивное распространение зависимости на проекты, которые ссылаются на ваш проект.
Примечания и оговорки
- Для корректной работы некоторых пакетов требуются анализаторы или таргеты сборки. Тестируйте после изменения фильтров ресурсов.
- Предпочтительнее явно указывать и документировать выбор критически важных зависимостей в файлах .csproj.
- Сочетайте это с файлами блокировки пакетов и сопоставлением источников пакетов для более надёжной защиты цепочки поставок.
Источник: https://www.meziantou.net/limit-what-nuget-packages-can-do-in-your-project.htm
Ограничьте Возможности NuGet-пакетов в Вашем Проекте
При добавлении NuGet-пакета в проект принято думать о нём только как о библиотеке времени выполнения. В действительности пакеты также могут импортировать свойства/таргеты MSBuild и анализаторы Roslyn. Эти ресурсы запускаются во время восстановления или сборки. Описание общих рисков безопасности и лучших практик можно найти тут.
Т.е. пакет может выполнять код на вашем компьютере или в CI без вашего ведома. Если вам нужна только часть ресурсов пакета, вы можете ограничить импорт, используя IncludeAssets и ExcludeAssets в PackageReference.
Почему это важно
Пакеты NuGet могут включать:
- файлы build, buildMultitargeting и buildTransitive (свойства и таргеты MSBuild);
- анализаторы (включая генераторы кода);
- библиотеки времени выполнения и компиляции.
Если вам не нужна логика сборки или анализаторы из пакета, их блокировка уменьшает поверхность атаки и делает сборки более предсказуемыми. Если вы читали последние новости об атаках на цепочки поставок, вы знаете, что любой код, работающий на вашей машине или в CI, представляет потенциальный риск. По умолчанию импортируются все ресурсы из пакета, поэтому важно помнить об этом и принимать меры при необходимости.
Включайте только то, что вам нужно
Наиболее безопасный подход часто заключается в разрешении только необходимых вам ресурсов:
<ItemGroup>
<PackageReference Include="Some.Package" Version="1.2.3">
<IncludeAssets>compile;runtime</IncludeAssets>
</PackageReference>
</ItemGroup>
При такой конфигурации NuGet не импортирует анализаторы и таргеты сборки из пакета.
Исключение определённых типов ресурсов
Если вы предпочитаете сохранить поведение по умолчанию и удалить только некоторые группы ресурсов, используйте ExcludeAssets:
<ItemGroup>
<PackageReference Include="Some.Package" Version="1.2.3">
<ExcludeAssets>analyzers;build;buildTransitive</ExcludeAssets>
</PackageReference>
</ItemGroup>
Это полезно, когда вам по-прежнему нужны другие ресурсы, такие как contentFiles.
Распространённые сценарии
Включать только ресурсы среды выполнения/библиотеки:
<PackageReference Include="Some.Package" Version="1.2.3">
<IncludeAssets>runtime;compile</IncludeAssets>
</PackageReference>
Включать библиотеку времени выполнения, отключить анализаторы:
<PackageReference Include="Some.Package" Version="1.2.3">
<ExcludeAssets>analyzers</ExcludeAssets>
</PackageReference>
Оставить анализаторы, отключить таргеты сборки:
<PackageReference Include="Some.Package" Version="1.2.3">
<ExcludeAssets>build;buildMultitargeting;buildTransitive</ExcludeAssets>
</PackageReference>
Использовать пакет только для сборки:
<PackageReference Include="Some.Package" Version="1.2.3" PrivateAssets="all">
<IncludeAssets>build;buildMultitargeting;buildTransitive</IncludeAssets>
</PackageReference>
Параметр "PrivateAssets="all" предотвращает транзитивное распространение зависимости на проекты, которые ссылаются на ваш проект.
Примечания и оговорки
- Для корректной работы некоторых пакетов требуются анализаторы или таргеты сборки. Тестируйте после изменения фильтров ресурсов.
- Предпочтительнее явно указывать и документировать выбор критически важных зависимостей в файлах .csproj.
- Сочетайте это с файлами блокировки пакетов и сопоставлением источников пакетов для более надёжной защиты цепочки поставок.
Источник: https://www.meziantou.net/limit-what-nuget-packages-can-do-in-your-project.htm
👍3
День 2766. #ЧтоНовенького #NET11
Вышли Превью 6 и 7 .NET 11
Посмотрим на самые интересные новинки в ASP.NET Core.
1. Асинхронная проверка для минимальных API
Теперь минимальные API поддерживают асинхронные валидаторы. Самый простой способ добавить асинхронное правило — это пользовательский атрибут валидации. Наследуйте от AsyncValidationAttribute и реализуйте IsValidAsync, чтобы запрашивать БД или удаленный API без блокировки потока. Синхронный метод IsValid также является абстрактным; выбрасывайте исключение, если атрибут проверяется только асинхронно.
Для проверки, охватывающей несколько свойств или весь объект, реализуйте IAsyncValidatableObject и возвращайте результаты в виде IAsyncEnumerable<ValidationResult>. Поскольку IAsyncValidatableObject наследует IValidatableObject, также реализуйте синхронный метод Validate. Если тип проверяется только асинхронно, выбрасывайте исключение из Validate, чтобы его проверка не пропускалась молча синхронными API.
Валидаторы выполняются параллельно, где это возможно: асинхронные атрибуты одного и того же члена запускаются одновременно, элементы коллекции проверяются параллельно, и фреймворк сохраняет существующий порядок проверки: член, тип, IValidatableObject.
2. Автоматическая защита от межсайтовых запросов (CSRF)
Приложения, созданные с помощью WebApplication.CreateBuilder, теперь автоматически отклоняют небезопасные межсайтовые запросы на основе заголовков Sec-Fetch-Site и Origin браузера. Эта легковесная защита от CSRF включается без какой-либо настройки и применяется к минимальным API, MVC, Razor Pages и Blazor. Разрешаются запросы из одного источника, инициированные пользователем переходы и запросы от клиентов, не использующих браузер, в то время как межсайтовый запрос браузера, пытающийся обработать форму, отклоняется. Эту защиту можно использовать вместо или вместе с существующей системой защиты от подделки запросов на основе токенов. Поскольку она работает автоматически, проектам Blazor Web App больше не требуется вызывать
Чтобы отключить защиту конечной точки, используйте метод
3. OpenAPI 3.2 по умолчанию
Сгенерированные документы OpenAPI теперь по умолчанию ориентированы на OpenAPI 3.2 – новейшую версию спецификации. Изменяется только версия по умолчанию; ваш код генерации и конфигурация остаются неизменными. Укажите версию OpenAPI явно, чтобы она была ориентирована на более раннюю версию для инструментов, которые ещё не используют 3.2.
4. Объединения в ASP.NET Core
Поскольку ASP.NET Core использует System.Text.Json для JSON, типы-объединения работают как тела JSON-запросов и возвращаемые типы без какой-либо специфической конфигурации:
- Минимальные API — параметры тела запроса и возвращаемые типы, включая Task<Union>, IAsyncEnumerable<Union> и Results<T1, T2>;
- MVC и Razor Pages — в телах JSON-запросов и ответах;
- SignalR — параметры методов хаба, возвращаемые значения и элементы потока при использовании протокола JSON Hub;
- Blazor — параметры компонентов, аргументы JavaScript-interop и результаты, а также сохраняемое состояния компонента.
Для OpenAPI конечная точка, возвращающая объединение, описывается схемой anyOf, в которой перечислены все типы вариантов. Сторонние генераторы, такие как Swashbuckle и NSwag, пока не распознают объединения и будут создавать свою схему по умолчанию.
В этом превью действуют некоторые ограничения. Поддерживаются только тела запросов и ответы в формате JSON. Привязка объединения к строке запроса, значениям маршрута, заголовкам или полям формы пока недоступна. Когда несколько вариантов сериализуются в один и тот же формат JSON, их можно различить с помощью классификатора
5. Атрибут «прерыватель цепи» для конечных точек
Новый атрибут
6. Встроенная локализация валидации
Microsoft.Extensions.Validation теперь локализует сообщения валидации и отображаемые имена. Пакет Microsoft.Extensions.Validation.Localization, доступный только в предварительной версии, и его API AddValidationLocalization<TResource>()/IValidationLocalizer удалены; локализация теперь активируется автоматически, как только регистрируется IStringLocalizerFactory, и поиск подходящей локализации инициируется генератором кода валидации в вашей сборке.
Ключи определяются на основе собственных ресурсов модели, а в случае промаха используется встроенное сообщение атрибута. Атрибуты, которые уже локализуются (через
К валидации минимальных API и Blazor применяются те же правила локализации, поэтому сообщение локализуется одинаково независимо от того, где используется модель.
Источники:
- https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview6/aspnetcore.md
- https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview7/aspnetcore.md
Вышли Превью 6 и 7 .NET 11
Посмотрим на самые интересные новинки в ASP.NET Core.
1. Асинхронная проверка для минимальных API
Теперь минимальные API поддерживают асинхронные валидаторы. Самый простой способ добавить асинхронное правило — это пользовательский атрибут валидации. Наследуйте от AsyncValidationAttribute и реализуйте IsValidAsync, чтобы запрашивать БД или удаленный API без блокировки потока. Синхронный метод IsValid также является абстрактным; выбрасывайте исключение, если атрибут проверяется только асинхронно.
Для проверки, охватывающей несколько свойств или весь объект, реализуйте IAsyncValidatableObject и возвращайте результаты в виде IAsyncEnumerable<ValidationResult>. Поскольку IAsyncValidatableObject наследует IValidatableObject, также реализуйте синхронный метод Validate. Если тип проверяется только асинхронно, выбрасывайте исключение из Validate, чтобы его проверка не пропускалась молча синхронными API.
Валидаторы выполняются параллельно, где это возможно: асинхронные атрибуты одного и того же члена запускаются одновременно, элементы коллекции проверяются параллельно, и фреймворк сохраняет существующий порядок проверки: член, тип, IValidatableObject.
2. Автоматическая защита от межсайтовых запросов (CSRF)
Приложения, созданные с помощью WebApplication.CreateBuilder, теперь автоматически отклоняют небезопасные межсайтовые запросы на основе заголовков Sec-Fetch-Site и Origin браузера. Эта легковесная защита от CSRF включается без какой-либо настройки и применяется к минимальным API, MVC, Razor Pages и Blazor. Разрешаются запросы из одного источника, инициированные пользователем переходы и запросы от клиентов, не использующих браузер, в то время как межсайтовый запрос браузера, пытающийся обработать форму, отклоняется. Эту защиту можно использовать вместо или вместе с существующей системой защиты от подделки запросов на основе токенов. Поскольку она работает автоматически, проектам Blazor Web App больше не требуется вызывать
app.UseAntiforgery().Чтобы отключить защиту конечной точки, используйте метод
.DisableAntiforgery() (для минимальных API) или [IgnoreAntiforgeryToken] (для MVC); чтобы отключить эту функцию для всего приложения, установите ключ конфигурации DisableCsrfProtection. Для полного контроля над решением о доверии зарегистрируйте собственную реализацию ICsrfProtection.3. OpenAPI 3.2 по умолчанию
Сгенерированные документы OpenAPI теперь по умолчанию ориентированы на OpenAPI 3.2 – новейшую версию спецификации. Изменяется только версия по умолчанию; ваш код генерации и конфигурация остаются неизменными. Укажите версию OpenAPI явно, чтобы она была ориентирована на более раннюю версию для инструментов, которые ещё не используют 3.2.
4. Объединения в ASP.NET Core
Поскольку ASP.NET Core использует System.Text.Json для JSON, типы-объединения работают как тела JSON-запросов и возвращаемые типы без какой-либо специфической конфигурации:
- Минимальные API — параметры тела запроса и возвращаемые типы, включая Task<Union>, IAsyncEnumerable<Union> и Results<T1, T2>;
- MVC и Razor Pages — в телах JSON-запросов и ответах;
- SignalR — параметры методов хаба, возвращаемые значения и элементы потока при использовании протокола JSON Hub;
- Blazor — параметры компонентов, аргументы JavaScript-interop и результаты, а также сохраняемое состояния компонента.
Для OpenAPI конечная точка, возвращающая объединение, описывается схемой anyOf, в которой перечислены все типы вариантов. Сторонние генераторы, такие как Swashbuckle и NSwag, пока не распознают объединения и будут создавать свою схему по умолчанию.
В этом превью действуют некоторые ограничения. Поддерживаются только тела запросов и ответы в формате JSON. Привязка объединения к строке запроса, значениям маршрута, заголовкам или полям формы пока недоступна. Когда несколько вариантов сериализуются в один и тот же формат JSON, их можно различить с помощью классификатора
[JsonUnion] (из System.Text.Json). Объединения в SignalR требуют протокола JSON Hub; Протоколы MessagePack и Newtonsoft.Json не поддерживают объединения.5. Атрибут «прерыватель цепи» для конечных точек
Новый атрибут
[ShortCircuit] помечает конечную точку для запуска сразу после маршрутизации, пропуская остальную часть конвейера промежуточного ПО. Это атрибутная форма существующего соглашения о конечных точках ShortCircuit(), поэтому его можно применять непосредственно к контроллерам и действиям MVC. Это полезно для конечных точек, которым не требуется аутентификация, CORS или другое промежуточное ПО, например, health-check или ответ robots.txt. Это позволяет избежать затрат на запуск этого промежуточного ПО. Конечная точка по-прежнему запускается и выдаёт свой ответ; можно передать необязательный код состояния, например [ShortCircuit(404)].6. Встроенная локализация валидации
Microsoft.Extensions.Validation теперь локализует сообщения валидации и отображаемые имена. Пакет Microsoft.Extensions.Validation.Localization, доступный только в предварительной версии, и его API AddValidationLocalization<TResource>()/IValidationLocalizer удалены; локализация теперь активируется автоматически, как только регистрируется IStringLocalizerFactory, и поиск подходящей локализации инициируется генератором кода валидации в вашей сборке.
builder.Services.AddLocalization();
builder.Services.AddValidation();
…
[ValidatableType]
public class CustomerModel
{
// ключ ресурса для имени
[Display(Name = "CustomerName")]
// ключ ресурса для сообщения об ошибке
[Required(ErrorMessage = "NameRequired")]
public string? Name { get; set; }
}
Ключи определяются на основе собственных ресурсов модели, а в случае промаха используется встроенное сообщение атрибута. Атрибуты, которые уже локализуются (через
ErrorMessageResourceType или [Display(ResourceType = …)]), полностью обходят конвейер локализации валидации. Пользовательский атрибут, которому необходимо подставить свои значения в шаблон сообщения, может реализовать интерфейс IValidationMessageFormatter.К валидации минимальных API и Blazor применяются те же правила локализации, поэтому сообщение локализуется одинаково независимо от того, где используется модель.
Источники:
- https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview6/aspnetcore.md
- https://github.com/dotnet/core/blob/main/release-notes/11.0/preview/preview7/aspnetcore.md
👍3
День 2767. #ЗаметкиНаПолях
TimeProvider — Конец Нетестируемого DateTime.Now. Начало
DateTime.Now — один из тех вызовов, который кажется безобидным, пока вы не попытаетесь написать тест вокруг него. Это статическое свойство, которое считывает системные часы, и нет способа контролировать то, что оно возвращает, без обёртывания его во что-то, что можно внедрить. Начиная с .NET 8, мы можем перестать переизобретать одну и ту же абстракцию и использовать вместо неё TimeProvider.
Как было раньше
Стандартным решением для установки определённого времени в коде всегда было определение интерфейса, который оборачивает часы, и внедрение его в качестве зависимости, которую можно заменить фиктивной реализацией в тестах, зависящих от даты/времени:
В коде вы регистрируете SystemDateTimeProvider. В тестах — FakeDateTimeProvider с устанавливаемым свойством и передаёте любое время, необходимое тесту. Это работает и несложно, но каждая команда делает это немного по-своему: ISystemClock, IClock, IDateTimeProvider, ITimeService или сразу несколько вариантов в одной кодовой базе, потому что, а почему бы и нет? ASP.NET Core одно время поставлял свой ISystemClock в Microsoft.AspNetCore.Authentication.
Встроенное решение: TimeProvider
.NET 8 представил TimeProvider, абстрактный класс в пространстве имен System. Он является частью базовой библиотеки классов (поэтому зависимость NuGet не требуется). Идея та же: вместо прямого вызова DateTime.UtcNow вы вызываете tp.GetUtcNow(). В коде используется TimeProvider.System. В тестах используется фиктивный объект. Основные методы:
Заметьте, что TimeProvider работает с DateTimeOffset (содержащим информацию о смещении часового пояса), а не с DateTime.
В коде используется TimeProvider.System как синглтон. Он считывает данные с системных часов, как и DateTimeOffset.UtcNow. Его необходимо зарегистрировать при запуске системы:
А затем можно внедрять везде, где вашему коду нужно текущее время:
Теперь часы являются зависимостью, а зависимости можно заменять. Например, в тестах.
Окончание следует…
Источник: https://blog.maartenballiauw.be/posts/2026-07-22-timeprovider-and-the-end-of-untestable-datetime-now/
TimeProvider — Конец Нетестируемого DateTime.Now. Начало
DateTime.Now — один из тех вызовов, который кажется безобидным, пока вы не попытаетесь написать тест вокруг него. Это статическое свойство, которое считывает системные часы, и нет способа контролировать то, что оно возвращает, без обёртывания его во что-то, что можно внедрить. Начиная с .NET 8, мы можем перестать переизобретать одну и ту же абстракцию и использовать вместо неё TimeProvider.
Как было раньше
Стандартным решением для установки определённого времени в коде всегда было определение интерфейса, который оборачивает часы, и внедрение его в качестве зависимости, которую можно заменить фиктивной реализацией в тестах, зависящих от даты/времени:
public interface IDateTimeProvider
{
DateTimeOffset UtcNow { get; }
}
public class SystemDateTimeProvider : IDateTimeProvider
{
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
}
В коде вы регистрируете SystemDateTimeProvider. В тестах — FakeDateTimeProvider с устанавливаемым свойством и передаёте любое время, необходимое тесту. Это работает и несложно, но каждая команда делает это немного по-своему: ISystemClock, IClock, IDateTimeProvider, ITimeService или сразу несколько вариантов в одной кодовой базе, потому что, а почему бы и нет? ASP.NET Core одно время поставлял свой ISystemClock в Microsoft.AspNetCore.Authentication.
Встроенное решение: TimeProvider
.NET 8 представил TimeProvider, абстрактный класс в пространстве имен System. Он является частью базовой библиотеки классов (поэтому зависимость NuGet не требуется). Идея та же: вместо прямого вызова DateTime.UtcNow вы вызываете tp.GetUtcNow(). В коде используется TimeProvider.System. В тестах используется фиктивный объект. Основные методы:
// Текущее время UTC (DateTimeOffset, а не DateTime)
DateTimeOffset utcNow = tp.GetUtcNow();
// Местное время
DateTimeOffset localNow = tp.GetLocalNow();
// Местный часовой пояс
TimeZoneInfo zone = tp.LocalTimeZone;
// Высокоточные метки времени
long start = tp.GetTimestamp();
// ... do work ...
TimeSpan elapsed = tp.GetElapsedTime(start);
Заметьте, что TimeProvider работает с DateTimeOffset (содержащим информацию о смещении часового пояса), а не с DateTime.
В коде используется TimeProvider.System как синглтон. Он считывает данные с системных часов, как и DateTimeOffset.UtcNow. Его необходимо зарегистрировать при запуске системы:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddSingleton<TimeProvider>(TimeProvider.System);
А затем можно внедрять везде, где вашему коду нужно текущее время:
public class SubscriptionReminderService
{
private TimeProvider _tp;
private ISubscriptionRepository _repo;
public SubscriptionReminderService(
TimeProvider tp,
ISubscriptionRepository repo)
{
_tp = tp;
_repo = repo;
}
public async Task<IEnumerable<Subscription>>
GetExpiringSubsAsync()
{
var now = _tp.GetUtcNow();
var cutoff = now.AddDays(7);
return await
_repo.GetExpiringSubsAsync(cutoff);
}
}
Теперь часы являются зависимостью, а зависимости можно заменять. Например, в тестах.
Окончание следует…
Источник: https://blog.maartenballiauw.be/posts/2026-07-22-timeprovider-and-the-end-of-untestable-datetime-now/
День 2768. #ЗаметкиНаПолях
TimeProvider — Конец Нетестируемого DateTime.Now. Окончание
Начало
Тестирование с FakeTimeProvider
.NET предоставляет класс FakeTimeProvider в отдельном NuGet-пакете:
Вы можете задать начальную точку или оставить значение по умолчанию, если вас не интересует точное значение:
В предыдущем примере SubscriptionReminderService принимает TimeProvider (абстрактный базовый класс), куда также можно напрямую передать FakeTimeProvider:
1я подписка истекает 6 сентября (через 6 дней), вторая – 8 сентября (не входит в 7-дневное окно истекающих подписок). Тест детерминирован вне зависимости от того, когда он выполняется.
Перемотка времени вперед
Настоящая мощь проявляется в сценариях, связанных с течением времени. У класса FakeTimeProvider есть метод
Если нужно перейти к точному моменту времени, это сделает метод
Эти методы особенно полезны для тестирования логики, которая накапливает состояние с течением времени: истечение срока действия кэша, задержка повторной попытки, вычисления с использованием скользящего окна. Вы точно контролируете, когда наступит «сейчас», без фактического ожидания.
Таймеры
TimeProvider также предоставляет метод
Он идентичен System.Threading.Timer (те же параметры, тот же метод
Это важно, если у вас есть фоновые сервисы или рабочие процессы, которые выполняют периодическую работу с помощью таймеров. Как только они принимают TimeProvider, весь цикл планирования становится тестируемым.
Источник: https://blog.maartenballiauw.be/posts/2026-07-22-timeprovider-and-the-end-of-untestable-datetime-now/
TimeProvider — Конец Нетестируемого DateTime.Now. Окончание
Начало
Тестирование с FakeTimeProvider
.NET предоставляет класс FakeTimeProvider в отдельном NuGet-пакете:
dotnet add package Microsoft.Extensions.TimeProvider.Testing
Вы можете задать начальную точку или оставить значение по умолчанию, если вас не интересует точное значение:
// Определённый момент времени
var fakeTime = new FakeTimeProvider(
new DateTimeOffset(2026, 8, 29, 0, 0, 0, TimeSpan.Zero));
// По умолчанию
var fakeTime = new FakeTimeProvider();
В предыдущем примере SubscriptionReminderService принимает TimeProvider (абстрактный базовый класс), куда также можно напрямую передать FakeTimeProvider:
using Microsoft.Extensions.Time.Testing;
[Fact]
public async Task GetExpiringSubs_ReturnsSubsWithin7Days()
{
var fakeTime = new FakeTimeProvider(
new DateTimeOffset(2026, 9, 1, 0, 0, 0, TimeSpan.Zero));
var repo = new InMemorySubscriptionRepository(new[]
{
new Subscription {
Id = 1,
Expires = new DateTimeOffset(2026, 9, 6, 0, 0, 0, TimeSpan.Zero) },
new Subscription {
Id = 2,
Expires = new DateTimeOffset(2026, 9, 8, 0, 0, 0, TimeSpan.Zero) },
});
var service =
new SubscriptionReminderService(fakeTime, repo);
var expiring = await service.GetExpiringSubsAsync();
Assert.Single(expiring);
Assert.Equal(1, expiring.First().Id);
}
1я подписка истекает 6 сентября (через 6 дней), вторая – 8 сентября (не входит в 7-дневное окно истекающих подписок). Тест детерминирован вне зависимости от того, когда он выполняется.
Перемотка времени вперед
Настоящая мощь проявляется в сценариях, связанных с течением времени. У класса FakeTimeProvider есть метод
Advance(TimeSpan delta), который переводит часы вперёд на указанную величину:[Fact]
public async Task GetExpiringSubs_AfterTimeAdvances_CapturesMoreSubscriptions()
{
var fakeTime = new FakeTimeProvider(
new DateTimeOffset(2026, 9, 1, 0, 0, 0, TimeSpan.Zero));
var repos = new InMemorySubscriptionRepository(new[]
{
new Subscription {
Id = 1,
Expires = new DateTimeOffset(2026, 9, 6, 0, 0, 0, TimeSpan.Zero) },
new Subscription {
Id = 2,
Expires = new DateTimeOffset(2026, 9, 8, 0, 0, 0, TimeSpan.Zero) },
});
var service =
new SubscriptionReminderService(fakeTime, repo);
// 1 сентября только 1я подписка в истекающих
var check1 = await service.GetExpiringSubsAsync();
Assert.Single(check1);
// 5 сентября - обе
fakeTime.Advance(TimeSpan.FromDays(5));
var check2 = await service.GetExpiringSubsAsync();
Assert.Equal(2, check2.Count());
}
Если нужно перейти к точному моменту времени, это сделает метод
SetUtcNow(DateTimeOffset utcNow). Оба метода также запускают любые ожидающие таймеры, время срабатывания которых попадает в диапазон перемотки (подробности ниже).Эти методы особенно полезны для тестирования логики, которая накапливает состояние с течением времени: истечение срока действия кэша, задержка повторной попытки, вычисления с использованием скользящего окна. Вы точно контролируете, когда наступит «сейчас», без фактического ожидания.
Таймеры
TimeProvider также предоставляет метод
CreateTimer(), который возвращает ITimer из System.Threading:ITimer timer = tp.CreateTimer(
callback: state => DoSomeWork(),
state: null,
dueTime: TimeSpan.FromMinutes(5),
period: TimeSpan.FromMinutes(5));
Он идентичен System.Threading.Timer (те же параметры, тот же метод
Change(TimeSpan dueTime, TimeSpan period) для перепланирования), но основан на абстракции. В FakeTimeProvider вызов Advance(), приводящий к истечению времени таймера, запустит обратный вызов таймера (в примере выше – DoSomeWork()). Не нужно использовать Thread.Sleep или Task.Delay.Это важно, если у вас есть фоновые сервисы или рабочие процессы, которые выполняют периодическую работу с помощью таймеров. Как только они принимают TimeProvider, весь цикл планирования становится тестируемым.
Источник: https://blog.maartenballiauw.be/posts/2026-07-22-timeprovider-and-the-end-of-untestable-datetime-now/
👍6
День 2769. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
44. Распределённая трассировка
«Как бы вы реализовали распределённую трассировку для мониторинга и отладки взаимодействия микросервисов? Приведите примеры инструментов и методологий, которые вы бы использовали».
Хороший ответ
Реализация распределённой трассировки включает в себя интеграцию инструментов, которые могут отслеживать ход выполнения и производительность запросов по мере их прохождения через микросервисы. Вот как можно её настроить.
1. Выбрать систему распределённой трассировки, совместимую с .NET, например, OpenTelemetry, которая стала стандартом для обеспечения наблюдаемости в облачных приложениях. Она поддерживает трассировку, метрики и журналы.
2. Добавить необходимые пакеты OpenTelemetry в проект.
3. Настроить трассировку в Program.cs, убедившись, что она захватывает как входящие, так и исходящие запросы для всестороннего отслеживания взаимодействия между микросервисами:
4. Убедиться, что все части приложения, включая внешние библиотеки и зависимости, настроены на отправку трассировок. Это может включать настройку дополнительных инструментов для баз данных или очередей сообщений, если они не охватываются автоматически.
5. Использовать соответствующие заголовки для распространения контекстов трассировки по HTTP-запросам для поддержки распределённой трассировки через границы. Обычно это обрабатывается библиотеками OpenTelemetry, но следует проверить полноту их работы.
6. Использовать инструменты, вроде Jaeger, Zipkin или коммерческие решения, такие как Dynatrace или DataDog, которые могут визуализировать данные трассировки и помогать в отладке и мониторинге производительности.
7. Убедиться, что бэкенд трассировки настроен на обработку объёма данных трассировки, генерируемых нашими сервисами, и может масштабироваться по мере необходимости.
Преимущества
- Улучшенная отладка: Быстрое определение, какая часть взаимодействия сервисов завершилась с ошибкой или вызывала задержки.
- Оптимизация производительности: Анализ данных трассировки для оптимизации производительности отдельных микросервисов и системы в целом.
- Улучшенная наблюдаемость: представление о поведении приложений и о том, как сервисы взаимодействуют в производственной среде.
Часто встречающийся плохой ответ
«Я бы просто логировал запросы в каждом сервисе. Затем можно сопоставить эти логи и отследить, как обрабатываются запросы в сервисах.»
Почему это неправильно
- Отсутствие масштабируемости и эффективности: Использование журналов для ручной трассировки запросов не масштабируемо и может быть крайне неэффективным, особенно в сложных или высоконагруженных средах.
- Отсутствие полноты: Журналы не предоставляют структурированной, коррелированной информации, которую предлагают специализированные инструменты трассировки. Они могут упускать важные детали взаимодействия или не содержать контекста, необходимого для эффективной отладки.
- Ресурсоёмкость: Зависимость исключительно от журналов при трассировке может привести к значительным накладным расходам как на хранение, так и на обработку, влияя на производительность системы.
Эта ошибка обычно возникает из-за недостаточного понимания специализированных инструментов и методов распределённой трассировки или из-за недооценки сложности, связанной с ручной трассировкой распределённых транзакций. Это подчёркивает необходимость интегрированных автоматизированных решений для трассировки в современных архитектурах приложений.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
44. Распределённая трассировка
«Как бы вы реализовали распределённую трассировку для мониторинга и отладки взаимодействия микросервисов? Приведите примеры инструментов и методологий, которые вы бы использовали».
Хороший ответ
Реализация распределённой трассировки включает в себя интеграцию инструментов, которые могут отслеживать ход выполнения и производительность запросов по мере их прохождения через микросервисы. Вот как можно её настроить.
1. Выбрать систему распределённой трассировки, совместимую с .NET, например, OpenTelemetry, которая стала стандартом для обеспечения наблюдаемости в облачных приложениях. Она поддерживает трассировку, метрики и журналы.
2. Добавить необходимые пакеты OpenTelemetry в проект.
3. Настроить трассировку в Program.cs, убедившись, что она захватывает как входящие, так и исходящие запросы для всестороннего отслеживания взаимодействия между микросервисами:
var builder = WebApplication.CreateBuilder(args);
// Добавляем OpenTelemetry
builder.Services.AddOpenTelemetryTracing(trc =>
{
trc.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(opts => {
opts.Endpoint =
new Uri("http://your-tracing-backend:4317");
});
});
var app = builder.Build();
app.MapGet("/api/data", async (HttpClient client) =>
{
// Пример исходящего запроса
var response = await client.GetStringAsync(
"http://another-service/api/data");
return Results.Ok(response);
});
app.Run();
4. Убедиться, что все части приложения, включая внешние библиотеки и зависимости, настроены на отправку трассировок. Это может включать настройку дополнительных инструментов для баз данных или очередей сообщений, если они не охватываются автоматически.
5. Использовать соответствующие заголовки для распространения контекстов трассировки по HTTP-запросам для поддержки распределённой трассировки через границы. Обычно это обрабатывается библиотеками OpenTelemetry, но следует проверить полноту их работы.
6. Использовать инструменты, вроде Jaeger, Zipkin или коммерческие решения, такие как Dynatrace или DataDog, которые могут визуализировать данные трассировки и помогать в отладке и мониторинге производительности.
7. Убедиться, что бэкенд трассировки настроен на обработку объёма данных трассировки, генерируемых нашими сервисами, и может масштабироваться по мере необходимости.
Преимущества
- Улучшенная отладка: Быстрое определение, какая часть взаимодействия сервисов завершилась с ошибкой или вызывала задержки.
- Оптимизация производительности: Анализ данных трассировки для оптимизации производительности отдельных микросервисов и системы в целом.
- Улучшенная наблюдаемость: представление о поведении приложений и о том, как сервисы взаимодействуют в производственной среде.
Часто встречающийся плохой ответ
«Я бы просто логировал запросы в каждом сервисе. Затем можно сопоставить эти логи и отследить, как обрабатываются запросы в сервисах.»
Почему это неправильно
- Отсутствие масштабируемости и эффективности: Использование журналов для ручной трассировки запросов не масштабируемо и может быть крайне неэффективным, особенно в сложных или высоконагруженных средах.
- Отсутствие полноты: Журналы не предоставляют структурированной, коррелированной информации, которую предлагают специализированные инструменты трассировки. Они могут упускать важные детали взаимодействия или не содержать контекста, необходимого для эффективной отладки.
- Ресурсоёмкость: Зависимость исключительно от журналов при трассировке может привести к значительным накладным расходам как на хранение, так и на обработку, влияя на производительность системы.
Эта ошибка обычно возникает из-за недостаточного понимания специализированных инструментов и методов распределённой трассировки или из-за недооценки сложности, связанной с ручной трассировкой распределённых транзакций. Это подчёркивает необходимость интегрированных автоматизированных решений для трассировки в современных архитектурах приложений.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍4
День 2770. #BestPractices
Лучшие Практики Безопасности REST API в ASP.NET Core. Начало
Неважно, насколько чиста ваша архитектура или насколько быстро выполняются запросы — если злоумышленник может прочитать заказы другого пользователя или подделать токен, всё это не имеет значения. Большинство проблем с безопасностью API возникают из-за игнорирования основ:
- устаревший NuGet-пакет с уязвимостью,
- отсутствие валидации,
- слабая настройка аутентификации,
- слишком либеральная политика CORS,
- секрет, внесённый в систему контроля версий.
Хорошая новость в том, что ASP.NET Core из коробки предоставляет почти всё необходимое для устранения проблем.
1. HTTPS везде
Каждый запрос к API должен передаваться по зашифрованному соединению. Без HTTPS токены, пароли и личные данные передаются в открытом виде, и любой, кто находится на пути следования по сети, может их прочитать. Обычный HTTP также открывает двери для атак с понижением уровня безопасности, когда злоумышленник заставляет клиента использовать небезопасное соединение. ASP.NET Core предоставляет два инструмента.
HSTS пропускается в среде разработки, поскольку там часто используется
2. Аутентификация с помощью токенов, а не сессий
Предпочтительнее использовать аутентификацию с помощью токенов без сохранения состояния, чем серверные сессии. Сессия хранит состояние аутентификации в памяти сервера или в общем хранилище, что привязывает каждого пользователя к серверу и затрудняет горизонтальное масштабирование. Токен содержит подтверждение личности, поэтому любой экземпляр вашего API может проверить его без поиска пользователя в базе. Для большинства API подойдёт токены JWT bearer, часто выдаваемые поставщиком идентификации через OAuth 2.0 или OpenID Connect:
Клиент отправляет токен в заголовке
См. также про референтные токены и отзыв токенов.
3. Проверка подписи JWT, издателя, аудитории и срока действия
Токен безопасен только после проверки того, кто его выпустил, для кого он предназначен, что он не истёк и что его подпись действительна. Настройте эти проверки явно через TokenValidationParameters, а не доверяйте значениям по умолчанию фреймворка:
Каждый флаг закрывает лазейку:
- ValidateIssuerSigningKey подтверждает подпись — никогда не отключайте этот параметр, иначе кто угодно сможет подделать токен;
- ValidateIssuer и ValidateAudience гарантируют, что токен получен от вашего поставщика идентификации и предназначен для вашего API, а не для какого-то другого сервиса;
- ValidateLifetime отклоняет просроченные токены.
- ClockSkew - существует для того, чтобы допускать небольшие расхождения во времени между серверами, но его значение по умолчанию составляет целых 5 минут, поэтому просроченный токен может приниматься ещё до 5 минут. Сокращение параметра до 30 секунд или даже до 0 ужесточает контроль за истечением срока действия токенов, при условии синхронизации часов ваших серверов (с помощью NTP).
Продолжение следует…
Источник: https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
Лучшие Практики Безопасности REST API в ASP.NET Core. Начало
Неважно, насколько чиста ваша архитектура или насколько быстро выполняются запросы — если злоумышленник может прочитать заказы другого пользователя или подделать токен, всё это не имеет значения. Большинство проблем с безопасностью API возникают из-за игнорирования основ:
- устаревший NuGet-пакет с уязвимостью,
- отсутствие валидации,
- слабая настройка аутентификации,
- слишком либеральная политика CORS,
- секрет, внесённый в систему контроля версий.
Хорошая новость в том, что ASP.NET Core из коробки предоставляет почти всё необходимое для устранения проблем.
1. HTTPS везде
Каждый запрос к API должен передаваться по зашифрованному соединению. Без HTTPS токены, пароли и личные данные передаются в открытом виде, и любой, кто находится на пути следования по сети, может их прочитать. Обычный HTTP также открывает двери для атак с понижением уровня безопасности, когда злоумышленник заставляет клиента использовать небезопасное соединение. ASP.NET Core предоставляет два инструмента.
UseHttpsRedirection перенаправляет HTTP-запросы на HTTPS, а HSTS (HTTP Strict Transport Security) указывает браузерам подключаться только по HTTPS. Совместимые браузеры вообще отказываются взаимодействовать с вашим API по HTTP, что блокирует атаки с понижением уровня безопасности:var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHsts(opts =>
{
opts.MaxAge = TimeSpan.FromDays(365);
opts.IncludeSubDomains = true;
opts.Preload = true;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
app.UseHsts();
app.UseHttpsRedirection();
// …
HSTS пропускается в среде разработки, поскольку там часто используется
http://localhost.2. Аутентификация с помощью токенов, а не сессий
Предпочтительнее использовать аутентификацию с помощью токенов без сохранения состояния, чем серверные сессии. Сессия хранит состояние аутентификации в памяти сервера или в общем хранилище, что привязывает каждого пользователя к серверу и затрудняет горизонтальное масштабирование. Токен содержит подтверждение личности, поэтому любой экземпляр вашего API может проверить его без поиска пользователя в базе. Для большинства API подойдёт токены JWT bearer, часто выдаваемые поставщиком идентификации через OAuth 2.0 или OpenID Connect:
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opts =>
{
opts.Authority = "https://my-idp.com";
opts.Audience = "my-api";
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
Клиент отправляет токен в заголовке
Authorization: Bearer <token> в каждом запросе. Ваш API проверяет его и считывает идентификационные данные пользователя из утверждений токена — никакого хранилища сессий, никаких «липких» сессий, никакого масштабируемого состояния на стороне сервера. Это делает ваш API «не сохраняющим состояние» и упрощает горизонтальное масштабирование.См. также про референтные токены и отзыв токенов.
3. Проверка подписи JWT, издателя, аудитории и срока действия
Токен безопасен только после проверки того, кто его выпустил, для кого он предназначен, что он не истёк и что его подпись действительна. Настройте эти проверки явно через TokenValidationParameters, а не доверяйте значениям по умолчанию фреймворка:
.AddJwtBearer(opts =>
{
opts.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://my-idp.com",
ValidateAudience = true,
ValidAudience = "my-api",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
});
Каждый флаг закрывает лазейку:
- ValidateIssuerSigningKey подтверждает подпись — никогда не отключайте этот параметр, иначе кто угодно сможет подделать токен;
- ValidateIssuer и ValidateAudience гарантируют, что токен получен от вашего поставщика идентификации и предназначен для вашего API, а не для какого-то другого сервиса;
- ValidateLifetime отклоняет просроченные токены.
- ClockSkew - существует для того, чтобы допускать небольшие расхождения во времени между серверами, но его значение по умолчанию составляет целых 5 минут, поэтому просроченный токен может приниматься ещё до 5 минут. Сокращение параметра до 30 секунд или даже до 0 ужесточает контроль за истечением срока действия токенов, при условии синхронизации часов ваших серверов (с помощью NTP).
Продолжение следует…
Источник: https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
👍10
День 2771. #BestPractices
Лучшие Практики Безопасности REST API в ASP.NET Core. Продолжение
Начало
4. Авторизация с политиками
Аутентификация определяет, кто является пользователем. Авторизация определяет, что ему разрешено делать. Простая проверка через
Примените её к методу действия контроллера:
Или к конечной точке минимальных API:
Когда правило зависит от конкретного ресурса — например, «только владелец может редактировать этот заказ» — используйте авторизацию на основе ресурсов с помощью
5. Принцип наименьших привилегий
Предоставьте каждому клиенту минимальный необходимый доступ и ничего больше. Токен, набор утверждений или ключ API должны предоставлять только те разрешения, которые необходимы для выполнения его задачи. Мобильное приложение, которое только читает каталог товаров, не должно иметь токен, позволяющий удалять заказы. Ограничьте это на уровне токена. Когда ваш поставщик идентификации выдаёт токен, он должен включать только те области действия, которые были предоставлены клиенту, а ваши политики - проверять их наличие:
Тот же принцип применим к ключам API и аккаунтам баз данных — ограничьте их область действия. Если учётные данные утекут, принцип наименьших привилегий ограничит радиус атаки только тем, что могут сделать эти аккаунты.
6. Проверка и очистка входных данных
Некорректные или вредоносные входные данные должны отклоняться на границе, до того, как они достигнут бизнес-логики или базы данных. ASP.NET Core помогает в этом: атрибут
В минимальных API проверка выполняется с помощью фильтра или явно:
Ранняя проверка предотвращает целый класс атак — слишком длинные строки, числа, выходящие за пределы допустимого диапазона, отсутствующие поля и т.п.
7. Овер-постинг
Никогда не привязывайте входящие JSON-данные напрямую к сущности вашей БД. Так клиенты смогут устанавливать поля, которые они никогда не должны контролировать —
Используйте отдельные DTO запроса, который отображает только те поля, которые клиенту разрешено устанавливать, а затем самостоятельно сопоставляйте его с сущностью:
Сущность Product также может содержать поля
Продолжение следует…
Источник: https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
Лучшие Практики Безопасности REST API в ASP.NET Core. Продолжение
Начало
4. Авторизация с политиками
Аутентификация определяет, кто является пользователем. Авторизация определяет, что ему разрешено делать. Простая проверка через
[Authorize] проверяет только то, что пользователь авторизован. Для реальных правил — «только менеджеры могут удалять заказы», «пользователи могут видеть только свои данные» — необходима авторизация на основе политик и ресурсов. Определите политику:builder.Services.AddAuthorization(opts =>
{
opts.AddPolicy("orders:write", p =>
p.RequireRole("Manager")
.RequireClaim("permission", "orders:write"));
});
Примените её к методу действия контроллера:
public class OrdersController : ControllerBase
{
// …
[HttpDelete("{id:int}")]
[Authorize(Policy = "orders:write")]
public IActionResult Delete(int id)
{
_orderService.Delete(id);
return NoContent();
}
}
Или к конечной точке минимальных API:
app.MapDelete("/api/orders/{id:int}",
(int id, IOrderService orders) =>
{
orders.Delete(id);
return Results.NoContent();
})
.RequireAuthorization("orders:write");Когда правило зависит от конкретного ресурса — например, «только владелец может редактировать этот заказ» — используйте авторизацию на основе ресурсов с помощью
IAuthorizationService.AuthorizeAsync(user, order, "OrderOwner"), которая оценивает политику на основе фактической сущности. Политики позволяют хранить логику авторизации в одном месте, вне тела конечных точек.5. Принцип наименьших привилегий
Предоставьте каждому клиенту минимальный необходимый доступ и ничего больше. Токен, набор утверждений или ключ API должны предоставлять только те разрешения, которые необходимы для выполнения его задачи. Мобильное приложение, которое только читает каталог товаров, не должно иметь токен, позволяющий удалять заказы. Ограничьте это на уровне токена. Когда ваш поставщик идентификации выдаёт токен, он должен включать только те области действия, которые были предоставлены клиенту, а ваши политики - проверять их наличие:
builder.Services.AddAuthorization(opts =>
{
opts.AddPolicy("catalog:read", p =>
p.RequireClaim("scope", "catalog:read"));
opts.AddPolicy("orders:write", p =>
p.RequireClaim("scope", "orders:write"));
});
Тот же принцип применим к ключам API и аккаунтам баз данных — ограничьте их область действия. Если учётные данные утекут, принцип наименьших привилегий ограничит радиус атаки только тем, что могут сделать эти аккаунты.
6. Проверка и очистка входных данных
Некорректные или вредоносные входные данные должны отклоняться на границе, до того, как они достигнут бизнес-логики или базы данных. ASP.NET Core помогает в этом: атрибут
[ApiController] автоматически возвращает ошибку 400 с подробным описанием проблемы на запрос, не прошедший валидацию модели. Добавьте аннотации данных или, для более сложных правил, FluentValidation:public class CreateProductRequest
{
[Required]
[StringLength(200, MinimumLength = 1)]
public string Name { get; set; } = string.Empty;
[Range(0.01, 1_000_000)]
public decimal Price { get; set; }
}
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
[HttpPost]
public IActionResult
Create(CreateProductRequest req)
{
// Если мы здесь, модель прошла валидацию
// …
}
}
В минимальных API проверка выполняется с помощью фильтра или явно:
app.MapPost("/api/products",
(CreateProductRequest req,
IValidator<CreateProductRequest> validator) =>
{
var result = validator.Validate(request);
if (!result.IsValid)
return Results
.ValidationProblem(result.ToDictionary());
// …
});Ранняя проверка предотвращает целый класс атак — слишком длинные строки, числа, выходящие за пределы допустимого диапазона, отсутствующие поля и т.п.
7. Овер-постинг
Никогда не привязывайте входящие JSON-данные напрямую к сущности вашей БД. Так клиенты смогут устанавливать поля, которые они никогда не должны контролировать —
IsAdmin, Balance, Status или ID другого пользователя. Это называется овер-постингом или массовым присваиванием.Используйте отдельные DTO запроса, который отображает только те поля, которые клиенту разрешено устанавливать, а затем самостоятельно сопоставляйте его с сущностью:
// DTO запроса – то, что клиент может изменять
public record UpdateProductRequest(string Name, decimal Price);
[HttpPut("{id:int}")]
public async Task<IActionResult> Update(
int id, UpdateProductRequest request)
{
var prod = await _dbContext.Products.FindAsync(id);
if (prod is null)
return NotFound();
// Задаём только разрешённые поля
prod.Name = request.Name;
prod.Price = request.Price;
await _dbContext.SaveChangesAsync();
return NoContent();
}
Сущность Product также может содержать поля
CreatedAt, OwnerId или IsFeatured, но, т.к. клиент может отправлять только Name и Price, эти поля недоступны извне. Отдельные DTO запросов требуют небольшого количества дополнительного кода, но закрывают целую категорию ошибок, приводящих к повышению привилегий.Продолжение следует…
Источник: https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
👍5
День 2772. #BestPractices
Лучшие Практики Безопасности REST API в ASP.NET Core. Продолжение
1-3
4-7
8. Типы контента и размер запроса
Конечная точка, принимающая JSON, должна отклонять другие типы контента, и ни одна конечная точка не должна принимать неограниченное тело запроса. Загрузка нескольких гигабайтных файлов — простой способ исчерпать память сервера и вывести из строя ваш API. Ограничьте тип контента с помощью
Также можно задать лимит глобально в Kestrel:
То же относится и к объёму данных, которые клиент может получить. Всегда используйте пагинацию с ограничением максимального размера страницы в конечных точках коллекций, чтобы клиент не мог запросить неограниченный набор результатов:
9. Параметризованные запросы и EF Core
Никогда не создавайте SQL-запросы путем конкатенации строк из пользовательского ввода — это способ внедрения SQL-инъекций. Параметризованные запросы сохраняют пользовательский ввод как данные, а не как исполняемый SQL. EF Core параметризует всё по умолчанию, поэтому LINQ-запрос всегда безопасен:
Когда вам нужен чистый SQL, используйте FromSql или FromSqlInterpolated, которые преобразуют интерполированные значения в параметры, а не в литеральный текст:
10. Лимитирование трафика
Без ограничений один клиент — или злоумышленник — может атаковать точку входа методом перебора паролей или завалить API запросами до тех пор, пока он не выйдет из строя. ASP.NET Core имеет встроенное лимитирование запросов, которое настраивается в Program.cs:
Затем можно применить политику к методу действия контроллера:
или конечной точке минимальных API:
Когда клиент превышает лимит, он получает ошибку
11. CORS
CORS (Cross-Origin Resource Sharing) контролирует, какие веб-источники могут отправлять запросы к вашему API. Опасно использовать
Так только
12. Минимальные сведения об ошибке
Ответ об ошибке должен помогать вызывающей стороне, а не злоумышленнику. Обычная страница исключения раскрывает трассировку стека, версии фреймворков, пути к файлам и SQL-запросы — карту ваших внутренних механизмов. Вместо этого возвращайте чистое, стандартное сообщение об ошибке, используя сведения о проблеме:
В производственной среде необработанное исключение возвращает структурированное тело ProblemDetails с кодом состояния и стандартным сообщением — и ничего о ваших внутренних процессах.
Окончание следует…
Источник: https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
Лучшие Практики Безопасности REST API в ASP.NET Core. Продолжение
1-3
4-7
8. Типы контента и размер запроса
Конечная точка, принимающая JSON, должна отклонять другие типы контента, и ни одна конечная точка не должна принимать неограниченное тело запроса. Загрузка нескольких гигабайтных файлов — простой способ исчерпать память сервера и вывести из строя ваш API. Ограничьте тип контента с помощью
[Consumes] и размер тела запроса с помощью [RequestSizeLimit]:[HttpPost]
[Consumes("application/json")]
[RequestSizeLimit(1_000_000)] // 1 MB
public IActionResult Create(CreateProductRequest request)
{
// …
}
Также можно задать лимит глобально в Kestrel:
builder.WebHost.ConfigureKestrel(opts =>
{
opts.Limits.MaxRequestBodySize = 1_000_000;
});
То же относится и к объёму данных, которые клиент может получить. Всегда используйте пагинацию с ограничением максимального размера страницы в конечных точках коллекций, чтобы клиент не мог запросить неограниченный набор результатов:
[HttpGet]
public IActionResult GetProducts(
int page = 1, int pageSize = 20)
{
// Принудительно ограничиваем размер страницы
pageSize = Math.Min(pageSize, 100);
// …
}
9. Параметризованные запросы и EF Core
Никогда не создавайте SQL-запросы путем конкатенации строк из пользовательского ввода — это способ внедрения SQL-инъекций. Параметризованные запросы сохраняют пользовательский ввод как данные, а не как исполняемый SQL. EF Core параметризует всё по умолчанию, поэтому LINQ-запрос всегда безопасен:
// EF Core параметризует 'search'
var products = await _dbContext.Products
.Where(p => p.Name.Contains(search))
.ToListAsync();
Когда вам нужен чистый SQL, используйте FromSql или FromSqlInterpolated, которые преобразуют интерполированные значения в параметры, а не в литеральный текст:
// 'category' становится параметром SQL
var products = await _dbContext.Products
.FromSqlInterpolated(
$"SELECT * FROM Products WHERE Category = {category}")
.ToListAsync();
// Никогда не делайте так
var sql = "SELECT * FROM Products WHERE Category = '" + category + "'";
// Не используйте FromSqlRaw с интерполяцией
var products = await _dbContext.Products
.FromSqlRaw(
$"SELECT * FROM Products WHERE Category = {category}")
.ToListAsync();
10. Лимитирование трафика
Без ограничений один клиент — или злоумышленник — может атаковать точку входа методом перебора паролей или завалить API запросами до тех пор, пока он не выйдет из строя. ASP.NET Core имеет встроенное лимитирование запросов, которое настраивается в Program.cs:
builder.Services.AddRateLimiter(opts =>
{
opts.AddFixedWindowLimiter("api", lim =>
{
lim.PermitLimit = 10;
lim.Window = TimeSpan.FromMinutes(1);
lim.QueueLimit = 0;
});
opts.RejectionStatusCode =
StatusCodes.Status429TooManyRequests;
});
var app = builder.Build();
app.UseRateLimiter();
Затем можно применить политику к методу действия контроллера:
[HttpPost("login")]
[EnableRateLimiting("api")]
public IActionResult Login(LoginRequest request)
{ /* … */ }или конечной точке минимальных API:
app.MapPost("/login", (LoginRequest request) =>
{ /* … */ })
.RequireRateLimiting("api");Когда клиент превышает лимит, он получает ошибку
429 Too Many Requests. Это снижает вероятность атак методом перебора паролей и атак типа «отказ в обслуживании».11. CORS
CORS (Cross-Origin Resource Sharing) контролирует, какие веб-источники могут отправлять запросы к вашему API. Опасно использовать
AllowAnyOrigin(), что позволяет любому веб-сайту в интернете вызывать ваш API от имени авторизованного пользователя. Определите именованную политику, которая точно перечисляет разрешённые источники, методы и заголовки:builder.Services.AddCors(opts =>
{
opts.AddPolicy("Web", p =>
p.WithOrigins("https://mywebsite.com")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Authorization", "Content-Type"));
});
var app = builder.Build();
app.UseCors("Web");
Так только
https://mywebsite.com может вызвать ваш API, только с этими методами и с такими заголовками. Всё остальное отклоняется. Оставьте AllowAnyOrigin для действительно общедоступных, неаутентифицированных API — и никогда не используйте его в сочетании с учётными данными.12. Минимальные сведения об ошибке
Ответ об ошибке должен помогать вызывающей стороне, а не злоумышленнику. Обычная страница исключения раскрывает трассировку стека, версии фреймворков, пути к файлам и SQL-запросы — карту ваших внутренних механизмов. Вместо этого возвращайте чистое, стандартное сообщение об ошибке, используя сведения о проблеме:
builder.Services.AddProblemDetails();
var app = builder.Build();
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler();
В производственной среде необработанное исключение возвращает структурированное тело ProblemDetails с кодом состояния и стандартным сообщением — и ничего о ваших внутренних процессах.
Окончание следует…
Источник: https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
👍10
День 2773. #BestPractices
Лучшие Практики Безопасности REST API в ASP.NET Core. Окончание
1-3
4-7
8-12
13. Настройка заголовков безопасности
Заголовки, такие как
-
-
-
-
Для чистого JSON API эти параметры больше имеют смысл при отображении ответов в браузере, но они являются недорогой страховкой для любого API.
Примечание: в продакшене рекомендуется использовать поддерживаемую библиотеку или обратный прокси для управления этими заголовками и более строгую Content-Security-Policy, адаптированную под ваше приложение.
14. Безопасное хранение секретов
Ключи, строки подключения и токены никогда не должны храниться в системе контроля версий. Секрет, добавленный в файл appsettings.json, является утечкой — он навсегда остаётся в истории Git и виден всем, кто имеет доступ к репозиторию. Кроме того, ИИ-агенты могут напрямую считывать ваши секреты из конфигурации, если вы не запретите им доступ.
В процессе разработки используйте менеджер секретов .NET. В производственной среде используйте переменные среды или управляемое хранилище секретов, например Azure Key Vault:
Ваш код затем считывает конфигурацию одинаково, независимо от того, откуда взялось значение. Приложению не важен источник — важно лишь то, чтобы значение не находилось в файле репозитория.
15. Защита от CSRF, где это необходимо
Защита от межсайтовой подделки запросов (CSRF) важна, когда вы аутентифицируете с помощью cookie. CSRF обманывает браузер авторизованного пользователя, заставляя его отправлять нежелательный запрос, используя cookie, который браузер автоматически добавляет. Если ваш API аутентифицирует с помощью cookie, вам необходимы токены защиты от подделки запросов:
Затем требуйте валидный токен в конечных точках, изменяющих состояние:
Замечание: API, основанные исключительно на токенах, в значительной степени защищены от CSRF-атак. Если ваш клиент отправляет JWT в заголовке Authorization (а не cookie), браузер не добавляет его автоматически, поэтому поддельный межсайтовый запрос не содержит учётных данных. Защита от подделки запросов в основном необходима для конечных точек, аутентифицируемых с помощью cookie — приложений, отрисовываемых сервером, и API, использующих аутентификацию на основе cookie. Согласуйте защиту с вашей моделью аутентификации: cookie нуждаются в защите от CSRF, а bearer-токены, как правило, нет.
16. Версионирование и удаление устаревших небезопасных конечных точек
Версионирование позволяет отказаться от неудачного дизайна, не нарушая работу клиентов. Когда конечная точка оказывается небезопасной или плохо структурированной, нужен способ ввести исправленную версию и поэтапно вывести из эксплуатации старую.
Добавьте пакет Asp.Versioning.Mvc и отметьте старую версию как устаревшую:
17. Журналирование и аудит событий безопасности
События безопасности — неудачные попытки входа в систему, отказы в авторизации, подозрительные шаблоны запросов — необходимо логировать с достаточным контекстом для восстановления произошедшего. Записывайте их в виде структурированных журналов:
Регистрируйте сбои аутентификации, отказы в авторизации и всё, что выглядит как попытка взлома. Записывайте достаточно контекста для проведения настоящего криминалистического расследования, что может включать конфиденциальные данные, если этого требует ваша модель угроз: учётная запись, исходный IP и предпринятое действие.
Т.к. эти журналы могут содержать конфиденциальные данные, относитесь к ним как к конфиденциальным: храните их там, где их могут читать только авторизованные лица, и применяйте правила хранения и контроля доступа.
18. Поддерживайте актуальность зависимостей
Ваш API зависит от десятков NuGet-пакетов и среды выполнения .NET, и каждый из них является потенциальной точкой взлома, когда обнаруживается уязвимость. Поддержание актуальности — одна из самых важных вещей. .NET может предоставить вам список уязвимых пакетов:
Выполняйте это регулярно и привяжите сканирование в ваш конвейер CI, чтобы обнаружение уязвимости зависимости роняло сборку.
Обновляйте NuGet-пакеты и среду выполнения .NET по расписанию, а не только при возникновении проблем. В сочетании с такими инструментами, как Dependabot или оповещениями безопасности GitHub, это устраняет уязвимость, на которую чаще всего полагаются злоумышленники.
Источник: https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
Лучшие Практики Безопасности REST API в ASP.NET Core. Окончание
1-3
4-7
8-12
13. Настройка заголовков безопасности
Заголовки, такие как
Content-Security-Policy, X-Content-Type-Options и Referrer-Policy, указывают браузеру, как обрабатывать ваши ответы — какие скрипты могут выполняться, следует ли угадывать тип контента, какой объём информации об источнике следует раскрывать. Добавьте их с помощью небольшого промежуточного ПО, которое запускается при каждом ответе:app.Use(async (context, next) =>
{
var hdrs = context.Response.Headers;
hdrs["X-Content-Type-Options"] = "nosniff";
hdrs["Referrer-Policy"] = "no-referrer";
hdrs["Content-Security-Policy"] = "default-src 'self'";
hdrs["X-Frame-Options"] = "DENY";
await next();
});
-
X-Content-Type-Options: nosniff предотвращает попытки браузера угадывать (и неправильно обрабатывать) тип контента;-
Content-Security-Policy ограничивает источники загрузки скриптов, стилей и других ресурсов;-
Referrer-Policy контролирует, какая часть вашего URL-адреса отправляется на другие сайты;-
X-Frame-Options: DENY предотвращает встраивание ваших ответов во фрейм (кликджекинг).Для чистого JSON API эти параметры больше имеют смысл при отображении ответов в браузере, но они являются недорогой страховкой для любого API.
Примечание: в продакшене рекомендуется использовать поддерживаемую библиотеку или обратный прокси для управления этими заголовками и более строгую Content-Security-Policy, адаптированную под ваше приложение.
14. Безопасное хранение секретов
Ключи, строки подключения и токены никогда не должны храниться в системе контроля версий. Секрет, добавленный в файл appsettings.json, является утечкой — он навсегда остаётся в истории Git и виден всем, кто имеет доступ к репозиторию. Кроме того, ИИ-агенты могут напрямую считывать ваши секреты из конфигурации, если вы не запретите им доступ.
В процессе разработки используйте менеджер секретов .NET. В производственной среде используйте переменные среды или управляемое хранилище секретов, например Azure Key Vault:
builder.Configuration.AddAzureKeyVault(
new Uri("https://myapi.vault.azure.net/"),
new DefaultAzureCredential());
var connectionString = builder
.Configuration
.GetConnectionString("Myapi");
Ваш код затем считывает конфигурацию одинаково, независимо от того, откуда взялось значение. Приложению не важен источник — важно лишь то, чтобы значение не находилось в файле репозитория.
15. Защита от CSRF, где это необходимо
Защита от межсайтовой подделки запросов (CSRF) важна, когда вы аутентифицируете с помощью cookie. CSRF обманывает браузер авторизованного пользователя, заставляя его отправлять нежелательный запрос, используя cookie, который браузер автоматически добавляет. Если ваш API аутентифицирует с помощью cookie, вам необходимы токены защиты от подделки запросов:
builder.Services.AddAntiforgery(opts =>
{
opts.HeaderName = "X-CSRF-TOKEN";
});
var app = builder.Build();
app.UseAntiforgery();
Затем требуйте валидный токен в конечных точках, изменяющих состояние:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(CreateOrderRequest request)
{ /* … */ }
Замечание: API, основанные исключительно на токенах, в значительной степени защищены от CSRF-атак. Если ваш клиент отправляет JWT в заголовке Authorization (а не cookie), браузер не добавляет его автоматически, поэтому поддельный межсайтовый запрос не содержит учётных данных. Защита от подделки запросов в основном необходима для конечных точек, аутентифицируемых с помощью cookie — приложений, отрисовываемых сервером, и API, использующих аутентификацию на основе cookie. Согласуйте защиту с вашей моделью аутентификации: cookie нуждаются в защите от CSRF, а bearer-токены, как правило, нет.
16. Версионирование и удаление устаревших небезопасных конечных точек
Версионирование позволяет отказаться от неудачного дизайна, не нарушая работу клиентов. Когда конечная точка оказывается небезопасной или плохо структурированной, нужен способ ввести исправленную версию и поэтапно вывести из эксплуатации старую.
Добавьте пакет Asp.Versioning.Mvc и отметьте старую версию как устаревшую:
builder.Services.AddApiVersioning(opts =>
{
opts.DefaultApiVersion = new ApiVersion(2, 0);
opts.ReportApiVersions = true;
});
…
[ApiController]
[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/orders")]
public class OrdersController : ControllerBase
{
…
}
ReportApiVersions добавляет заголовки api-supported-versions и api-deprecated-versions к вашим ответам, чтобы клиенты видели предстоящее устаревание и могли перейти на новую версию до того, как вы удалите v1. Это превращает ситуацию «мы не можем это исправить, потому что клиенты зависят от этого» в управляемую миграцию.17. Журналирование и аудит событий безопасности
События безопасности — неудачные попытки входа в систему, отказы в авторизации, подозрительные шаблоны запросов — необходимо логировать с достаточным контекстом для восстановления произошедшего. Записывайте их в виде структурированных журналов:
[HttpPost("login")]
public async Task<IActionResult> Login(LoginRequest request)
{
var result = await _authService.AuthenticateAsync(request);
if (!result.Succeeded)
{
_logger.LogWarning(
"Неудачный вход для {Email}, IP: {IpAddress}",
request.Email, HttpContext.Connection.RemoteIpAddress);
return Unauthorized();
}
return Ok(result.Token);
}Регистрируйте сбои аутентификации, отказы в авторизации и всё, что выглядит как попытка взлома. Записывайте достаточно контекста для проведения настоящего криминалистического расследования, что может включать конфиденциальные данные, если этого требует ваша модель угроз: учётная запись, исходный IP и предпринятое действие.
Т.к. эти журналы могут содержать конфиденциальные данные, относитесь к ним как к конфиденциальным: храните их там, где их могут читать только авторизованные лица, и применяйте правила хранения и контроля доступа.
18. Поддерживайте актуальность зависимостей
Ваш API зависит от десятков NuGet-пакетов и среды выполнения .NET, и каждый из них является потенциальной точкой взлома, когда обнаруживается уязвимость. Поддержание актуальности — одна из самых важных вещей. .NET может предоставить вам список уязвимых пакетов:
dotnet list package --vulnerable --include-transitive
dotnet list package --outdated
Выполняйте это регулярно и привяжите сканирование в ваш конвейер CI, чтобы обнаружение уязвимости зависимости роняло сборку.
Обновляйте NuGet-пакеты и среду выполнения .NET по расписанию, а не только при возникновении проблем. В сочетании с такими инструментами, как Dependabot или оповещениями безопасности GitHub, это устраняет уязвимость, на которую чаще всего полагаются злоумышленники.
Источник: https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
👍6
День 2774. #Книги
«Предметно-ориентированное проектирование. Модернизация легаси-систем и снижение рисков с помощью DDD.» (Швентнер Х., Лилиенталь К. — Астана: «Спринт Бук», 2027).
3 года назад мы в нашей компании завершили многолетнее обновление нашей легаси системы, переписав всё на .NET (правда, .NET Framework, но это другая история и тому были причины). И, читая эту книгу, я дико сожалел, что у меня не было её под рукой, когда мы это делали. Уверен, что процесс прошёл бы гораздо быстрее, эффективнее, и привёл бы к более качественным результатам.
Эта книга только отчасти про DDD. Знаю, многие его не любят, отчасти потому, что для полного понимания его сути и всех преимуществ, надо довольно глубоко погрузиться. Уже писал про книгу Эванса и излишнюю академичность её русскоязычного перевода, поэтому не удивляюсь, что у нас она не получила большого распространения. Ведь мы, разработчики, живём по принципу «show me the code и побыстрее», а там 300+ страниц мелким шрифтом и довольно заумным языком.
Сразу говорю, здесь всё не так! Перевод гораздо более простым языком, а примеры гораздо понятнее. Хотя, эта книга вряд ли подойдёт или принесёт много пользы разработчикам до уровня сеньора или тем, кто просто хочет писать код. А вот всем опытным разработчикам, кто хочет повысить свой уровень разработки и моделирования систем, а может и попробовать себя в роли архитектора, эта книга просто обязательна к прочтению. Авторы – между прочим, практикующие консультанты по модернизации и проектированию информационных систем – собрали весь практический опыт сбора требований, моделирования, разработки, организации команд и последних новинок DDD, и относительно кратно, но доступно, изложили его в этой книге.
Как правильно собрать требования и описания бизнес-процессов, абстрагируясь от существующей системы и «теневого ИТ»? Чем отличаются Domain Storytelling, Event Storming и Scenario Casting, и когда какой метод использовать? Как создать понятную всем документацию и диаграммы бизнес-процессов, что поможет любому быстро разобраться в системе на любом уровне детализации? Как из этих описаний смоделировать информационную систему и правильно выделить ограниченные контексты, сохраняя их связность и слабую связанность между ними? Как сравнить эту модель с существующей системой и понять, как её модернизировать? Как наиболее эффективно организовать команды разработки? Как разобрать процесс модернизации на стратегические и тактические этапы и распределить задачи между командами? Как правильно рефакторить сильно связанный код? Как избавиться от анемичных объектов, максимально приблизить их к бизнес-процессам и снизить вероятность ошибок? В общем, как разобрать «большой комок грязи» и превратить его в систему, с которой легко работать.
Об этом и о многом другом на понятных примерах в этой книге.
«Предметно-ориентированное проектирование. Модернизация легаси-систем и снижение рисков с помощью DDD.» (Швентнер Х., Лилиенталь К. — Астана: «Спринт Бук», 2027).
3 года назад мы в нашей компании завершили многолетнее обновление нашей легаси системы, переписав всё на .NET (правда, .NET Framework, но это другая история и тому были причины). И, читая эту книгу, я дико сожалел, что у меня не было её под рукой, когда мы это делали. Уверен, что процесс прошёл бы гораздо быстрее, эффективнее, и привёл бы к более качественным результатам.
Эта книга только отчасти про DDD. Знаю, многие его не любят, отчасти потому, что для полного понимания его сути и всех преимуществ, надо довольно глубоко погрузиться. Уже писал про книгу Эванса и излишнюю академичность её русскоязычного перевода, поэтому не удивляюсь, что у нас она не получила большого распространения. Ведь мы, разработчики, живём по принципу «show me the code и побыстрее», а там 300+ страниц мелким шрифтом и довольно заумным языком.
Сразу говорю, здесь всё не так! Перевод гораздо более простым языком, а примеры гораздо понятнее. Хотя, эта книга вряд ли подойдёт или принесёт много пользы разработчикам до уровня сеньора или тем, кто просто хочет писать код. А вот всем опытным разработчикам, кто хочет повысить свой уровень разработки и моделирования систем, а может и попробовать себя в роли архитектора, эта книга просто обязательна к прочтению. Авторы – между прочим, практикующие консультанты по модернизации и проектированию информационных систем – собрали весь практический опыт сбора требований, моделирования, разработки, организации команд и последних новинок DDD, и относительно кратно, но доступно, изложили его в этой книге.
Как правильно собрать требования и описания бизнес-процессов, абстрагируясь от существующей системы и «теневого ИТ»? Чем отличаются Domain Storytelling, Event Storming и Scenario Casting, и когда какой метод использовать? Как создать понятную всем документацию и диаграммы бизнес-процессов, что поможет любому быстро разобраться в системе на любом уровне детализации? Как из этих описаний смоделировать информационную систему и правильно выделить ограниченные контексты, сохраняя их связность и слабую связанность между ними? Как сравнить эту модель с существующей системой и понять, как её модернизировать? Как наиболее эффективно организовать команды разработки? Как разобрать процесс модернизации на стратегические и тактические этапы и распределить задачи между командами? Как правильно рефакторить сильно связанный код? Как избавиться от анемичных объектов, максимально приблизить их к бизнес-процессам и снизить вероятность ошибок? В общем, как разобрать «большой комок грязи» и превратить его в систему, с которой легко работать.
Об этом и о многом другом на понятных примерах в этой книге.
👍9
День 2775. #Оффтоп
Сортировка I-Can’t-Believe-It-Can-Sort
Думаю, что с каждым чуть ли не ежедневно случается ситуация, когда вы написали код и думаете, что он работает, а он не работает. А бывало ли у вас наоборот?
Однажды один профессор на лекции про алгоритмы сортировки написал наивный и очевидно неверный алгоритм. Два вложенных цикла, внутри которых одно условие и смена элементов местами, если условие выполняется (некий неверный вариант пузырьковой сортировки):
Полная бессмыслица, которая, кажется, должна либо не сделать ничего, либо просто перемешать элементы. Однако позже выяснилось, что алгоритм действительно правильно сортирует элементы. Доказательство вот тут. Этот алгоритм стал популярным примером для изучения с помощью инструментов формальной верификации программ, позволяющих доказать, что подобная контринтуитивная структура действительно работает. В итоге алгоритм назвали сортировкой «Не-Могу-Поверить-Что-Это-Сортирует» (I-Can’t-Believe-It-Can-Sort).
Про него и про другие алгоритмы сортировки в новом видео Мэта Паркера.
А если вы хотите подробно изучить все алгоритмы сортировки, можно на пару часиков залипнуть вот сюда.
Сортировка I-Can’t-Believe-It-Can-Sort
Думаю, что с каждым чуть ли не ежедневно случается ситуация, когда вы написали код и думаете, что он работает, а он не работает. А бывало ли у вас наоборот?
Однажды один профессор на лекции про алгоритмы сортировки написал наивный и очевидно неверный алгоритм. Два вложенных цикла, внутри которых одно условие и смена элементов местами, если условие выполняется (некий неверный вариант пузырьковой сортировки):
for i = 0 to length - 1:
for j = 0 to length - 1:
if A[i] < A[j]:
swap(A[i], A[j])
Полная бессмыслица, которая, кажется, должна либо не сделать ничего, либо просто перемешать элементы. Однако позже выяснилось, что алгоритм действительно правильно сортирует элементы. Доказательство вот тут. Этот алгоритм стал популярным примером для изучения с помощью инструментов формальной верификации программ, позволяющих доказать, что подобная контринтуитивная структура действительно работает. В итоге алгоритм назвали сортировкой «Не-Могу-Поверить-Что-Это-Сортирует» (I-Can’t-Believe-It-Can-Sort).
Про него и про другие алгоритмы сортировки в новом видео Мэта Паркера.
А если вы хотите подробно изучить все алгоритмы сортировки, можно на пару часиков залипнуть вот сюда.
👍5
День 2776. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
45. Проверка работоспособности и мониторинг
«Как бы вы реализовали проверку работоспособности и мониторинг .NET-сервиса? Опишите инструменты и методы, которые вы бы использовали для обеспечения надёжного управления работоспособностью приложения».
Хороший ответ
Реализация проверки работоспособности и мониторинга включает в себя настройку конечных точек, к которым могут обращаться балансировщики нагрузки или инструменты мониторинга для проверки состояния приложения.
Можно добавить и настроить проверки работоспособности, используя встроенные функции ASP.NET Core:
Этот фрагмент кода настраивает простую проверку работоспособности, которая всегда возвращает «Healthy». Можно заменить логику проверки реальными тестами, вроде проверки подключения к БД или доступности внешних зависимостей.
Для более сложных сценариев можно добавить проверки для конкретных сервисов, таких как базы данных, серверы кэширования или API, от которых зависит приложение, например, вот проверка доступности базы:
Необходимо убедиться, что конечные точки проверки работоспособности хорошо документированы и доступны для соответствующих инструментов мониторинга, но защищены от несанкционированного доступа.
Важно добавить логирование проверок работоспособности для регистрации сбоев или изменений в поведении системы. Это может помочь в диагностике проблем, приводящих к сбоям.
Преимущества
- Проактивный мониторинг: позволяет команде обнаруживать проблемы и реагировать на них до того, как они повлияют на пользователей.
- Наблюдаемость: обеспечивает прозрачность состояния приложения и помогает поддерживать его надёжность и производительность.
- Переключение при сбоях и высокая доступность: обеспечивает автоматическое переключение при сбоях проверок работоспособности в облачных средах.
Часто встречающийся плохой ответ
Важно правильно логировать сообщения об ошибках и убедиться, что приложение автоматически перезапускается в случае сбоя. Этого должно быть достаточно для поддержания его работы.
Почему это неправильно:
- Отсутствие проактивного мониторинга: полагаться исключительно на журналы ошибок и автоматические перезапуски не предотвращает сбои, а лишь реагирует после их возникновения, что может привести к простоям.
- Игнорирование преимуществ проверок работоспособности, которые могут отслеживать состояние приложения в режиме реального времени и предоставлять ранние предупреждения о потенциальных проблемах.
- Неадекватные стратегии отказоустойчивости: автоматические перезапуски могут не устранять основные проблемы и приводить к повторным сбоям без надлежащей диагностики или решения.
Эта ошибка часто возникает из-за непонимания возможностей и важности проверок работоспособности и мониторинга в современных архитектурах приложений, возможно, из-за недостатка опыта работы в средах, где высокая доступность и надёжность имеют решающее значение.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
45. Проверка работоспособности и мониторинг
«Как бы вы реализовали проверку работоспособности и мониторинг .NET-сервиса? Опишите инструменты и методы, которые вы бы использовали для обеспечения надёжного управления работоспособностью приложения».
Хороший ответ
Реализация проверки работоспособности и мониторинга включает в себя настройку конечных точек, к которым могут обращаться балансировщики нагрузки или инструменты мониторинга для проверки состояния приложения.
Можно добавить и настроить проверки работоспособности, используя встроенные функции ASP.NET Core:
var builder = WebApplication.CreateBuilder(args);
// Добавляем проверки
builder.Services.AddHealthChecks()
.AddCheck("Sample Health Check", () =>
HealthCheckResult.Healthy("OK"));
var app = builder.Build();
// Конечная точка
app.MapHealthChecks("/health");
app.Run();
Этот фрагмент кода настраивает простую проверку работоспособности, которая всегда возвращает «Healthy». Можно заменить логику проверки реальными тестами, вроде проверки подключения к БД или доступности внешних зависимостей.
Для более сложных сценариев можно добавить проверки для конкретных сервисов, таких как базы данных, серверы кэширования или API, от которых зависит приложение, например, вот проверка доступности базы:
builder.Services.AddHealthChecks()
.AddDbContextCheck<ApplicationDbContext>();
Необходимо убедиться, что конечные точки проверки работоспособности хорошо документированы и доступны для соответствующих инструментов мониторинга, но защищены от несанкционированного доступа.
Важно добавить логирование проверок работоспособности для регистрации сбоев или изменений в поведении системы. Это может помочь в диагностике проблем, приводящих к сбоям.
Преимущества
- Проактивный мониторинг: позволяет команде обнаруживать проблемы и реагировать на них до того, как они повлияют на пользователей.
- Наблюдаемость: обеспечивает прозрачность состояния приложения и помогает поддерживать его надёжность и производительность.
- Переключение при сбоях и высокая доступность: обеспечивает автоматическое переключение при сбоях проверок работоспособности в облачных средах.
Часто встречающийся плохой ответ
Важно правильно логировать сообщения об ошибках и убедиться, что приложение автоматически перезапускается в случае сбоя. Этого должно быть достаточно для поддержания его работы.
Почему это неправильно:
- Отсутствие проактивного мониторинга: полагаться исключительно на журналы ошибок и автоматические перезапуски не предотвращает сбои, а лишь реагирует после их возникновения, что может привести к простоям.
- Игнорирование преимуществ проверок работоспособности, которые могут отслеживать состояние приложения в режиме реального времени и предоставлять ранние предупреждения о потенциальных проблемах.
- Неадекватные стратегии отказоустойчивости: автоматические перезапуски могут не устранять основные проблемы и приводить к повторным сбоям без надлежащей диагностики или решения.
Эта ошибка часто возникает из-за непонимания возможностей и важности проверок работоспособности и мониторинга в современных архитектурах приложений, возможно, из-за недостатка опыта работы в средах, где высокая доступность и надёжность имеют решающее значение.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍6
День 2777. #ЗаметкиНаПолях
Конечные Точки в ASP.NET Core не Имеют Таймаута
ASP.NET Core по умолчанию не применяет таймаут приложения к входящим запросам. Встроенное промежуточное ПО для таймаута запроса добавляет дедлайн, но вызывает только HttpContext.RequestAborted. Конечная точка должна передать этот токен в операцию, которую вы хотите остановить.
Медленный запрос к БД или зависший вызов API могут продолжать потреблять ресурсы даже после того, как ответ перестанет быть полезным. В .NET 8 было введено промежуточное ПО для таймаута запроса, чтобы обеспечить кооперативный дедлайн обработки.
Зарегистрируем промежуточное ПО и установим тайм-аут в Program.cs:
-
-
- Через 3 секунды
* Тестируйте это без отладчика, т.к. при подключенном отладчике таймаут не срабатывает.
Токен должен достичь отменяемой работы
Промежуточное ПО таймаута не прерывает поток и не вызывает HttpContext.Abort(). Оно отменяет токен и продолжает ждать конечную точку.
Удалите
То же правило применяется и к реальным зависимостям. Передавайте токен через сервисы приложения в EF Core:
EF Core пересылает токен провайдеру БД, который решает, можно ли отменить операцию в БД. Токен должен пройти по всей цепочке вызовов, прежде чем провайдер сможет его увидеть (см. также: ошибки при передаче токена отмены). Передавайте токен в HttpClient, клиенты обмена сообщениями и другие асинхронные операции, где безопасно отказаться от операции.
Выберите таймауты для каждой конечной точки
Не все конечные точки должны иметь одинаковый лимит. Небольшое чтение из API и экспорт отчёта имеют разные дедлайны, поэтому задайте для них разные политики:
Для событий Server-Sent, Web-сокетов, long pooling и больших загрузок обычно требуется более длительная политика таймаутов или метод
Итого
Ошибка 504 от промежуточного ПО таймаута означает, что отмена достигла его. Это не значит, что все нижележащие операции были остановлены. Передавайте токен отмены во все нижележащие операции. Используйте логи или трассировку, чтобы убедиться, что зависимости обработали отмену.
Источник: https://milanjovanovic.tech/blog/your-aspnetcore-endpoints-dont-have-a-timeout
Конечные Точки в ASP.NET Core не Имеют Таймаута
ASP.NET Core по умолчанию не применяет таймаут приложения к входящим запросам. Встроенное промежуточное ПО для таймаута запроса добавляет дедлайн, но вызывает только HttpContext.RequestAborted. Конечная точка должна передать этот токен в операцию, которую вы хотите остановить.
Медленный запрос к БД или зависший вызов API могут продолжать потреблять ресурсы даже после того, как ответ перестанет быть полезным. В .NET 8 было введено промежуточное ПО для таймаута запроса, чтобы обеспечить кооперативный дедлайн обработки.
Зарегистрируем промежуточное ПО и установим тайм-аут в Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRequestTimeouts();
var app = builder.Build();
app.UseRequestTimeouts();
app.MapGet("/reports", async (
CancellationToken ct) =>
{
await Task.Delay(TimeSpan.FromSeconds(10), ct);
return Results.Ok("Ready");
})
.WithRequestTimeout(TimeSpan.FromSeconds(3));
app.Run();
-
AddRequestTimeouts только регистрирует необходимые сервисы, но не устанавливает ограничение.-
WithRequestTimeout задаёт для конечной точки 3-секундный таймаут. Минимальные API привязывают параметр CancellationToken к HttpContext.RequestAborted.- Через 3 секунды
Task.Delay обнаруживает отмену и генерирует исключение. Если это исключение достигает промежуточного ПО до начала ответа, ответ по умолчанию — пустой 504 Gateway Timeout.* Тестируйте это без отладчика, т.к. при подключенном отладчике таймаут не срабатывает.
Токен должен достичь отменяемой работы
Промежуточное ПО таймаута не прерывает поток и не вызывает HttpContext.Abort(). Оно отменяет токен и продолжает ждать конечную точку.
Удалите
ct из вызова Task.Delay выше, и обработчик будет ждать полные 10 секунд, прежде чем вернуть 200 OK. Ошибка 504 не возникает, т.к. исключение отмены не достигает промежуточного ПО.То же правило применяется и к реальным зависимостям. Передавайте токен через сервисы приложения в EF Core:
public Task<Order?> GetByIdAsync(
Guid id,
CancellationToken ct)
{
return dbContext.Orders
.AsNoTracking()
.SingleOrDefaultAsync(
order => order.Id == id,
ct);
}
EF Core пересылает токен провайдеру БД, который решает, можно ли отменить операцию в БД. Токен должен пройти по всей цепочке вызовов, прежде чем провайдер сможет его увидеть (см. также: ошибки при передаче токена отмены). Передавайте токен в HttpClient, клиенты обмена сообщениями и другие асинхронные операции, где безопасно отказаться от операции.
Выберите таймауты для каждой конечной точки
Не все конечные точки должны иметь одинаковый лимит. Небольшое чтение из API и экспорт отчёта имеют разные дедлайны, поэтому задайте для них разные политики:
builder.Services.AddRequestTimeouts(opts =>
{
opts.AddPolicy("short", TimeSpan.FromSeconds(3));
opts.AddPolicy("long", TimeSpan.FromSeconds(30));
});
app.MapGet("/orders/{id:guid}", GetOrder)
.WithRequestTimeout("short");
app.MapGet("/reports/{id:guid}", ExportReport)
.WithRequestTimeout("long");
app.MapGet("/events", StreamEvents)
.DisableRequestTimeout();
Для событий Server-Sent, Web-сокетов, long pooling и больших загрузок обычно требуется более длительная политика таймаутов или метод
.DisableRequestTimeout().Итого
Ошибка 504 от промежуточного ПО таймаута означает, что отмена достигла его. Это не значит, что все нижележащие операции были остановлены. Передавайте токен отмены во все нижележащие операции. Используйте логи или трассировку, чтобы убедиться, что зависимости обработали отмену.
Источник: https://milanjovanovic.tech/blog/your-aspnetcore-endpoints-dont-have-a-timeout
День 2778. #ЗаметкиНаПолях
Потоковая Передача JSON в .NET. Начало
Допустим, у вас есть конечная точка, которая возвращает список: все заказы за последний квартал или поток показаний датчиков. Код выглядит нормально:
Для пары сотен строк это не заметно. При паре десятков тысяч происходит скачок потребления памяти, начинает работать сборщик мусора, и время до получения первого байта растёт, как и нагрузка на процесс при увеличении количества одновременных запросов. Решение в том, чтобы прекратить буферизацию. Сериализуем элемент, отдаём, переходим к следующему.
System.Text.Json может передавать IAsyncEnumerable<T> потоком начиная с .NET 6. Платформа выдаёт JSON-массив потоком, а не буферизирует его:
Это решает проблему использования памяти. Сервер хранит один заказ за раз, а не весь список. Вывод – JSON-массив:
Это вполне приемлемо для браузера, вызывающего функцию
Окончание следует…
Источник: https://thecodeman.net/posts/streaming-json-in-dotnet-with-json-lines
Потоковая Передача JSON в .NET. Начало
Допустим, у вас есть конечная точка, которая возвращает список: все заказы за последний квартал или поток показаний датчиков. Код выглядит нормально:
app.MapGet("/orders/export",
async (OrderService service) =>
{
List<Order> orders = await service.GetAllAsync();
return Results.Ok(orders);
});GetAllAsync извлекает все строки в List<Order>. Затем Results.Ok передаёт весь список сериализатору, который формирует JSON-массив в памяти. Т.е. вы храните две копии данных одновременно — объекты и их сериализованную форму, — а клиент ждёт, пока не будет сериализована последняя строка, прежде чем что-либо получить.Для пары сотен строк это не заметно. При паре десятков тысяч происходит скачок потребления памяти, начинает работать сборщик мусора, и время до получения первого байта растёт, как и нагрузка на процесс при увеличении количества одновременных запросов. Решение в том, чтобы прекратить буферизацию. Сериализуем элемент, отдаём, переходим к следующему.
System.Text.Json может передавать IAsyncEnumerable<T> потоком начиная с .NET 6. Платформа выдаёт JSON-массив потоком, а не буферизирует его:
// возвращает IAsyncEnumerable<Order>
app.MapGet("/orders/export", (OrderService service) =>
service.GetAllAsyncStream());
…
public async IAsyncEnumerable<Order>
GetAllAsyncStream(
[EnumeratorCancellation] CancellationToken ct = default)
{
await foreach (var order in _repo.ReadAllAsync(ct))
yield return order;
}
Это решает проблему использования памяти. Сервер хранит один заказ за раз, а не весь список. Вывод – JSON-массив:
[{"Id":1,"Total":42.0},{"Id":2,"Total":19.5}, …]Это вполне приемлемо для браузера, вызывающего функцию
fetch и использующего await res.json(). Но есть недостаток для больших объёмов данных: массив представляет собой один JSON-документ. Потребитель, желающий обрабатывать элементы по мере их поступления, должен разбирать массив постепенно, и, если соединение обрывается на полпути, остается усечённый, некорректный JSON-документ — завершающий символ ] не доходит. Добавить к нему данные тоже невозможно. Для потоковой обработки, записи в лог или экспорта данных JSON-массив имеет неправильный формат.Окончание следует…
Источник: https://thecodeman.net/posts/streaming-json-in-dotnet-with-json-lines
👍6
День 2779. #ЧтоНовенького #NET11
Потоковая Передача JSON в .NET 11. Окончание
Начало
JSON-строки: один объект на строку
JSON-строки (или NDJSON - Newline Delimited JSON) — формат, который уже используется большинством инструментов потоковой обработки данных — одно значение JSON на строку, разделённые символом
Каждая строка представляет собой полный, независимый JSON-документ. Потребитель читает строку, разбирает её, обрабатывает и забывает о ней. Если соединение обрывается после второй строки, первые две строки остаются действительными и пригодными для использования. Вы можете добавить четвёртую строку в файл, не затрагивая первые три. Конвейеры обработки, логи, загрузчики данных, потоки событий используют этот формат.
В .NET 11 System.Text.Json может создавать его напрямую. Новые перегрузки JsonSerializer.SerializeAsyncEnumerable принимают флаг topLevelValues:
При использовании
Потоковая передача NDJSON из конечной точки
В ASP.NET Core результат по умолчанию сериализуется в JSON-массив, поэтому для отправки JSON-строк нужно самостоятельно записывать данные в поток ответа:
SerializeAsyncEnumerable возвращает Task, поэтому конечная точка просто возвращает его. Заказы поступают из БД через сериализатор по одному. Память остаётся неизменной независимо от того, экспортируется 100 строк или 10 миллионов. Используйте тип содержимого
Чтение NDJSON
Чтение работает в любой версии .NET, т.к. строка представляет собой обычный JSON:
Вы обрабатываете каждую запись по мере её поступления и не создаёте большую коллекцию.
Когда использовать?
Когда данные большие или неопределённого размера: экспорт большой таблицы, возврат длинного отчёта, подача данных в конвейер обработки или запись лога или файла событий с возможностью добавления. Формат особенно эффективен, когда потребитель обрабатывает записи по одной и когда разорванное соединение должно оставлять после себя действительные частичные данные.
Небольшие ответы прекрасно поместятся в память, а обычный JSON-массив браузеры и большинство HTTP-клиентов ожидают по умолчанию. Переход на JSON-строки в этом случае только усложнит обработку ответа без каких-либо преимуществ.
FAQ
1. В чем разница между JSON-строками и NDJSON?
Это один и тот же формат. "NDJSON" (Newline Delimited JSON) и "JSON Lines" (JSONL) — два его названия, а
2. Загружает ли SerializeAsyncEnumerable всю коллекцию в память?
Нет. Он перебирает элементы IAsyncEnumerable<T> по одному, сериализует каждый и записывает его в поток вывода, прежде чем перейти к следующему. Это обеспечивает стабильность использованной памяти независимо от количества передаваемых элементов.
3. Нужен ли NDJSON для потоковой передачи, или достаточно IAsyncEnumerable?
Возвращение IAsyncEnumerable<T> уже обеспечивает потоковую передачу JSON-массива без буферизации, поэтому управление памятью происходит в любом случае. JSON-строки добавляют преимущества формата: каждая запись является независимой, частичный вывод при разрыве соединения всё равно валиден, и можно дописывать данные в файл.
4. Могут ли браузеры читать ответ NDJSON?
Автоматически – нет. Если используется
Источник: https://thecodeman.net/posts/streaming-json-in-dotnet-with-json-lines
Потоковая Передача JSON в .NET 11. Окончание
Начало
JSON-строки: один объект на строку
JSON-строки (или NDJSON - Newline Delimited JSON) — формат, который уже используется большинством инструментов потоковой обработки данных — одно значение JSON на строку, разделённые символом
\n:{"Id":1,"Total":42.0}
{"Id":2,"Total":19.5}
{"Id":3,"Total":88.25}Каждая строка представляет собой полный, независимый JSON-документ. Потребитель читает строку, разбирает её, обрабатывает и забывает о ней. Если соединение обрывается после второй строки, первые две строки остаются действительными и пригодными для использования. Вы можете добавить четвёртую строку в файл, не затрагивая первые три. Конвейеры обработки, логи, загрузчики данных, потоки событий используют этот формат.
В .NET 11 System.Text.Json может создавать его напрямую. Новые перегрузки JsonSerializer.SerializeAsyncEnumerable принимают флаг topLevelValues:
using System.Text;
using System.Text.Json;
static async IAsyncEnumerable<Reading> GetReadings()
{
yield return new("sensor-1", 21.5);
yield return new("sensor-2", 22.0);
}
await using var stream = new MemoryStream();
await JsonSerializer.SerializeAsyncEnumerable(
stream,
GetReadings(),
topLevelValues: true);
Console.WriteLine(
Encoding.UTF8.GetString(stream.ToArray()));
// {"Id":"sensor-1","Value":21.5}
// {"Id":"sensor-2","Value":22}
public sealed record Reading(string Id, double Value);
При использовании
topLevelValues: true отсутствуют открывающая и закрывающая квадратные скобки и запятые между элементами. Каждый элемент сериализуется и сопровождается переводом строки. Формат также игнорирует WriteIndented, поэтому каждый объект остается на отдельной строке.Потоковая передача NDJSON из конечной точки
В ASP.NET Core результат по умолчанию сериализуется в JSON-массив, поэтому для отправки JSON-строк нужно самостоятельно записывать данные в поток ответа:
app.MapGet("/orders/export",
(OrderService service,
HttpResponse response,
CancellationToken ct) =>
{
response.ContentType = "application/x-ndjson";
return JsonSerializer.SerializeAsyncEnumerable(
response.Body,
service.GetAllAsyncStream(ct),
topLevelValues: true);
});SerializeAsyncEnumerable возвращает Task, поэтому конечная точка просто возвращает его. Заказы поступают из БД через сериализатор по одному. Память остаётся неизменной независимо от того, экспортируется 100 строк или 10 миллионов. Используйте тип содержимого
application/x-ndjson (или application/jsonl), чтобы клиенты знали, что они получают, вместо того чтобы предполагать наличие единого массива.Чтение NDJSON
Чтение работает в любой версии .NET, т.к. строка представляет собой обычный JSON:
using var reader = new StreamReader(stream);
string? line;
while ((line = await reader.ReadLineAsync()) is not null)
{
if (line.Length == 0) continue;
var reading =
JsonSerializer.Deserialize<Reading>(line)!;
await ProcessAsync(reading);
}
Вы обрабатываете каждую запись по мере её поступления и не создаёте большую коллекцию.
Когда использовать?
Когда данные большие или неопределённого размера: экспорт большой таблицы, возврат длинного отчёта, подача данных в конвейер обработки или запись лога или файла событий с возможностью добавления. Формат особенно эффективен, когда потребитель обрабатывает записи по одной и когда разорванное соединение должно оставлять после себя действительные частичные данные.
Небольшие ответы прекрасно поместятся в память, а обычный JSON-массив браузеры и большинство HTTP-клиентов ожидают по умолчанию. Переход на JSON-строки в этом случае только усложнит обработку ответа без каких-либо преимуществ.
FAQ
1. В чем разница между JSON-строками и NDJSON?
Это один и тот же формат. "NDJSON" (Newline Delimited JSON) и "JSON Lines" (JSONL) — два его названия, а
application/x-ndjson — это тип содержимого, который вы будете встречать чаще всего.2. Загружает ли SerializeAsyncEnumerable всю коллекцию в память?
Нет. Он перебирает элементы IAsyncEnumerable<T> по одному, сериализует каждый и записывает его в поток вывода, прежде чем перейти к следующему. Это обеспечивает стабильность использованной памяти независимо от количества передаваемых элементов.
3. Нужен ли NDJSON для потоковой передачи, или достаточно IAsyncEnumerable?
Возвращение IAsyncEnumerable<T> уже обеспечивает потоковую передачу JSON-массива без буферизации, поэтому управление памятью происходит в любом случае. JSON-строки добавляют преимущества формата: каждая запись является независимой, частичный вывод при разрыве соединения всё равно валиден, и можно дописывать данные в файл.
4. Могут ли браузеры читать ответ NDJSON?
Автоматически – нет. Если используется
await res.json() — он ожидает один JSON-документ. Браузер должен читать поток ответа и разделять его по символам новой строки, самостоятельно разбирая каждую строку. Для стандартного запроса данных из браузера обычный массив проще; JSON-строки следует использовать для конвейеров обработки и экспорта больших объёмов данных.Источник: https://thecodeman.net/posts/streaming-json-in-dotnet-with-json-lines
👍10
День 2780. #ЧтоНовенького #VS
Избегаем Путаницы при Переключении Между Окнами Visual Studio
Если вы запускаете несколько экземпляров Visual Studio одновременно для нескольких решений, все они выглядят одинаково. Бывало ли, что вы переключались между окнами и начинали работать не в том? Было бы здорово, если бы для каждого решения можно было задать свою цветовую тему. Эта функция уже реализована, и не только в отношении цвета.
Откройте меню Tools > Options (Инструменты > Параметры). В верхней части окна найдите выпадающий список Applies to (Применить к) и переключите его с профиля пользователя на текущее решение (Current solution). С этого момента любые изменения настроек будут применяться только к открытому решению. Если вы закроете его и откроете позже снова, тема сохранится. А при открытии другого решения вы увидите назначенную для него цветовую схему. Выбирайте цвета с заметным контрастом для решений, с которыми работаете одновременно. Например: темная тема для сервиса, синяя — для клиентской части. Цель — различать их мгновенно, не вчитываясь в заголовок окна.
Выпадающий список Applies to — не просто функция для выбора цвета. Это модель определения области действия в новой системе настроек, а цветовая тема — лишь самый наглядный пример того, как эту модель можно использовать. Новая система объединяет настройки в единый согласованный интерфейс с возможностью поиска и поддержкой формата JSON.
Таким образом, главное нововведение заключается в том, что настройки теперь имеют область действия и привязаны к файлу, а файл можно добавить в систему контроля версий. Настройки, привязанные к конкретному решению, хранятся в файле
Важный нюанс
Настройки уровня решения хранятся отдельно от общих пользовательских настроек, поэтому изменение параметров для конкретного проекта не затронет конфигурацию, которую вы используете повсеместно. Чтобы изменить настройки по умолчанию для всех решений, просто переключите выпадающий список Applies to обратно на ваш профиль пользователя.
Такое разделение позволяет безопасно экспериментировать. Можно задать для одного решения особую тему оформления, проверить, удобно ли с ней работать, и, если результат не понравится, просто удалить настройку уровня решения. Visual Studio автоматически вернётся к вашим пользовательским настройкам — восстанавливать их вручную не придется.
Если хочется большего
Задолго до появления этой функции ту же задачу решало расширение Solution Colors, но с иным подходом. Оно работает только с цветами и обеспечивает более тонкую настройку: вместо смены всей темы оно окрашивает отдельные элементы IDE в цвета, специфичные для конкретного решения. При этом цвет может подбираться автоматически, так что вам даже не придётся ничего решать самостоятельно. Если вам нужен более тонкий контроль над тем, какие именно элементы меняют цвет, это расширение по-прежнему доступно и заслуживает внимания.
Источник: https://devblogs.microsoft.com/visualstudio/stop-alt-tabbing-into-the-wrong-visual-studio/
Избегаем Путаницы при Переключении Между Окнами Visual Studio
Если вы запускаете несколько экземпляров Visual Studio одновременно для нескольких решений, все они выглядят одинаково. Бывало ли, что вы переключались между окнами и начинали работать не в том? Было бы здорово, если бы для каждого решения можно было задать свою цветовую тему. Эта функция уже реализована, и не только в отношении цвета.
Откройте меню Tools > Options (Инструменты > Параметры). В верхней части окна найдите выпадающий список Applies to (Применить к) и переключите его с профиля пользователя на текущее решение (Current solution). С этого момента любые изменения настроек будут применяться только к открытому решению. Если вы закроете его и откроете позже снова, тема сохранится. А при открытии другого решения вы увидите назначенную для него цветовую схему. Выбирайте цвета с заметным контрастом для решений, с которыми работаете одновременно. Например: темная тема для сервиса, синяя — для клиентской части. Цель — различать их мгновенно, не вчитываясь в заголовок окна.
Выпадающий список Applies to — не просто функция для выбора цвета. Это модель определения области действия в новой системе настроек, а цветовая тема — лишь самый наглядный пример того, как эту модель можно использовать. Новая система объединяет настройки в единый согласованный интерфейс с возможностью поиска и поддержкой формата JSON.
Таким образом, главное нововведение заключается в том, что настройки теперь имеют область действия и привязаны к файлу, а файл можно добавить в систему контроля версий. Настройки, привязанные к конкретному решению, хранятся в файле
settings.VisualStudio.json в корневой папке решения — прямо рядом с файлом .sln или .slnx. Можно добавить этот файл в систему контроля версий, и каждый, кто клонирует репозиторий, получит те же настройки. Новый коллега откроет решение, и IDE сразу будет выглядеть и вести себя так, как договорилась команда, — без необходимости изучать инструкции по настройке: «Перейдите в меню Tools > Options и измените вот эти девять параметров». Не хотите навязывать свои предпочтения остальным? Добавьте файл в .gitignore, и настройки останутся только вашими.Важный нюанс
Настройки уровня решения хранятся отдельно от общих пользовательских настроек, поэтому изменение параметров для конкретного проекта не затронет конфигурацию, которую вы используете повсеместно. Чтобы изменить настройки по умолчанию для всех решений, просто переключите выпадающий список Applies to обратно на ваш профиль пользователя.
Такое разделение позволяет безопасно экспериментировать. Можно задать для одного решения особую тему оформления, проверить, удобно ли с ней работать, и, если результат не понравится, просто удалить настройку уровня решения. Visual Studio автоматически вернётся к вашим пользовательским настройкам — восстанавливать их вручную не придется.
Если хочется большего
Задолго до появления этой функции ту же задачу решало расширение Solution Colors, но с иным подходом. Оно работает только с цветами и обеспечивает более тонкую настройку: вместо смены всей темы оно окрашивает отдельные элементы IDE в цвета, специфичные для конкретного решения. При этом цвет может подбираться автоматически, так что вам даже не придётся ничего решать самостоятельно. Если вам нужен более тонкий контроль над тем, какие именно элементы меняют цвет, это расширение по-прежнему доступно и заслуживает внимания.
Источник: https://devblogs.microsoft.com/visualstudio/stop-alt-tabbing-into-the-wrong-visual-studio/
👍9
День 2781. #ЗаметкиНаПолях
Паттерн «Производитель-потребитель» c System.Threading.Channels. Начало
Представим эндпоинт, принимающий файлы, изменяющий их размер и возвращающий ответ. В демо-версии всё работает отлично. Но в проде нагрузка растёт, и эндпойнт получает сотни запросов в секунду. Каждый запрос запускает задачу по изменению размера прямо в потоке обработки; CPU загружается до 100%, а остальные функции приложения начинают завершаться по таймауту, т.к. пул потоков перегружен.
Не обязательно обрабатывать запросы сразу. Достаточно принять данные и обработать их в удобном для системы темпе. Это классическая задача типа «производитель-потребитель»: одна сторона передает задачу, а другая выполняет её со скоростью, которую реально может поддерживать. Для простейшей её реализации не нужны брокеры сообщений, достаточно System.Threading.Channels. Далее рассмотрим реализацию.
Каналы в .NET позволяют передавать данные между производителями и потребителями, работающими параллельно в одном процессе. Производители записывают данные в
Почему «наивное» решение только всё усугубляет
Первый порыв в решении проблемы – сделать всё асинхронным: запустить задачу через
Такой подход обеспечивает быстрый отклик, поэтому кажется удачным решением. Но это не так. Количество запускаемых задач ничем не ограничено, поэтому всплеск нагрузки, который раньше «вешал» CPU, делает это снова — при этом всем отправляется ответ
Правильное решение — использовать очередь с ограничением размера между двумя компонентами. Когда очередь заполняется, отправитель вынужден ждать; это ожидание служит сигналом о том, что задачи поступают быстрее, чем система успевает их обрабатывать, — и именно эту информацию важно выявлять, а не скрывать.
Основы работы с каналами
У
Режим переполнения (FullMode) — ключевой элемент:
- Wait —
- DropWrite — молча отбрасывает записываемый элемент;
- DropOldest — заменяет самый старый элемент из очереди входящим;
- DropNewest — заменяет самый новый элемент из очереди входящим.
Режим Wait подходит, когда важен каждый элемент и лучше замедлить работу производителя, чем потерять данные. Режимы Drop* предназначены для телеметрии и потоков данных в реальном времени, где свежий элемент важнее полной истории: например, при передаче метрик отбросить самое старое показание вполне допустимо.
Параметры SingleReader и SingleWriter служат для оптимизации. Устанавливайте их в
Запросы поступают из множества потоков и записываются в один канал. Опустошением канала занимается небольшой фиксированный пул потребителей. Когда канал переполняется, операция записи приостанавливается, и сигнал обратного давления передаётся непосредственно вызывающему коду — именно это и требуется, так как замедление становится явным и предсказуемым.
Продолжение следует…
Источник: https://thecodeman.net/posts/producer-consumer-with-channels-in-dotnet
Паттерн «Производитель-потребитель» c System.Threading.Channels. Начало
Представим эндпоинт, принимающий файлы, изменяющий их размер и возвращающий ответ. В демо-версии всё работает отлично. Но в проде нагрузка растёт, и эндпойнт получает сотни запросов в секунду. Каждый запрос запускает задачу по изменению размера прямо в потоке обработки; CPU загружается до 100%, а остальные функции приложения начинают завершаться по таймауту, т.к. пул потоков перегружен.
Не обязательно обрабатывать запросы сразу. Достаточно принять данные и обработать их в удобном для системы темпе. Это классическая задача типа «производитель-потребитель»: одна сторона передает задачу, а другая выполняет её со скоростью, которую реально может поддерживать. Для простейшей её реализации не нужны брокеры сообщений, достаточно System.Threading.Channels. Далее рассмотрим реализацию.
Каналы в .NET позволяют передавать данные между производителями и потребителями, работающими параллельно в одном процессе. Производители записывают данные в
ChannelWriter<T>, потребители считывают их из ChannelReader<T>, а канал обеспечивает потокобезопасную асинхронную передачу между ними. Важный момент — канал с ограниченным размером (bounded) автоматически обеспечивает механизм «обратного давления» (backpressure).Почему «наивное» решение только всё усугубляет
Первый порыв в решении проблемы – сделать всё асинхронным: запустить задачу через
Task.Run и сразу вернуть ответ:[HttpPost("process")]
public IActionResult Process(UploadRequest request)
{
// Запустил и забыл. Выглядит асинхронно
_ = Task.Run(() => _imgService.Resize(request));
return Accepted();
}Такой подход обеспечивает быстрый отклик, поэтому кажется удачным решением. Но это не так. Количество запускаемых задач ничем не ограничено, поэтому всплеск нагрузки, который раньше «вешал» CPU, делает это снова — при этом всем отправляется ответ
202, сигнализирующий об успешном принятии запроса. Если происходит перезапуск процесса, эта работа бесследно исчезает. А т.к. за задачами никто не следит, возникающие в них исключения просто пропадают. В итоге вы меняете явное замедление системы на скрытую потерю огромного объёма работы.Правильное решение — использовать очередь с ограничением размера между двумя компонентами. Когда очередь заполняется, отправитель вынужден ждать; это ожидание служит сигналом о том, что задачи поступают быстрее, чем система успевает их обрабатывать, — и именно эту информацию важно выявлять, а не скрывать.
Основы работы с каналами
У
Channel<T> есть два конца. Вы создаёте его и передаёте каждый конец соответствующей стороне:using System.Threading.Channels;
// Вместимость 100. При заполнении писатели ждут
var ch = Channel.CreateBounded<WorkItem>(
new BoundedChannelOptions(100)
{
FullMode = BoundedChannelFullMode.Wait,
SingleReader = false,
SingleWriter = false
});
ChannelWriter<WorkItem> writer = ch.Writer;
ChannelReader<WorkItem> reader = ch.Reader;
Режим переполнения (FullMode) — ключевой элемент:
- Wait —
WriteAsync ждёт появления свободного места (по умолчанию);- DropWrite — молча отбрасывает записываемый элемент;
- DropOldest — заменяет самый старый элемент из очереди входящим;
- DropNewest — заменяет самый новый элемент из очереди входящим.
Режим Wait подходит, когда важен каждый элемент и лучше замедлить работу производителя, чем потерять данные. Режимы Drop* предназначены для телеметрии и потоков данных в реальном времени, где свежий элемент важнее полной истории: например, при передаче метрик отбросить самое старое показание вполне допустимо.
Параметры SingleReader и SingleWriter служат для оптимизации. Устанавливайте их в
true, только когда у вас действительно ровно один читатель или один писатель — так канал использует более быстрый внутренний путь обработки. Если сомневаетесь, оставляйте false: ошибочный true может привести к трудноуловимому состоянию гонки.Запросы поступают из множества потоков и записываются в один канал. Опустошением канала занимается небольшой фиксированный пул потребителей. Когда канал переполняется, операция записи приостанавливается, и сигнал обратного давления передаётся непосредственно вызывающему коду — именно это и требуется, так как замедление становится явным и предсказуемым.
Продолжение следует…
Источник: https://thecodeman.net/posts/producer-consumer-with-channels-in-dotnet
👍5