Backend Developer C# / .NET
Глубокое понимание C# и платформы .NET для backend-разработки. Курс разбирает не синтаксис, а семантику языка и поведение среды выполнения: модель типов и обобщения, асинхронность и многопоточность, работу с памятью и коллекциями, устройство CLR — JIT-компиляцию, сборку мусора, пул потоков, — а также прикладной слой ASP.NET Core и EF Core. Цель — научиться видеть стоимость абстракций и поведение кода под нагрузкой, а не просто заставлять его работать.
Что внутри курса
01
Язык C#
12 уроковСемантика языка C# и её последствия для производительности: модель типов и обобщения, делегаты и события, исключения, время жизни ресурсов, а также продвинутый слой — pattern matching, records, expression trees, reflection и source generators.
- Как устроены обобщения в .NETВ .NET аргумент типа сохраняется в метаданных и доступен во время выполнения: работает рефлексия по типу, обобщённые коллекции значимых типов не упаковывают значения, а JIT порождает отдельный машинный код для каждого значимого типа.
- Делегаты и события — модель и распространённые ловушкиДелегат — неизменяемый объект с упорядоченным списком вызова: наружу уходит только последний результат, а исключение обрывает остаток списка. Событие оставляет снаружи лишь подписку и отписку, и пока жив издатель, неотписанный обработчик удерживает подписчика в памяти.
- Исключения: стоимость, типы, паттерны и анти-паттерныСреда выполнения несёт брошенное исключение вверх по стеку вызовов, ища совместимый catch в две фазы: сначала поиск обработчика с вычислением фильтров when, затем раскрутка стека с прогоном finally. Из этой модели следует, почему дорог именно бросок, чем throw отличается от throw ex и какие типы бросать нельзя.
- IDisposable, using и финализаторыСборщик мусора освобождает только управляемую память, поэтому внешние ресурсы вроде файлов и соединений закрывают вручную: Dispose чистит детерминированно, а using разворачивается в try/finally и гарантирует вызов даже при исключении. Финализатор — недетерминированная страховка, которую почти всегда заменяет SafeHandle.
- Nullable reference types — что они на самом деле делаютЗнак вопроса у ссылочного типа — это аннотация для компилятора, а не новый тип и не защита от null во время выполнения. Урок строит модель null-state и анализа потока: почему string и string? — один тип в среде выполнения, как читать предупреждения и чинить контракт вместо подавления сигнала.
- Сопоставление с образцом: тест и сужение в одном выраженииОбразец проверяет характеристику значения и попутно сужает его, образцы вкладываются рекурсивно, а компилятор разбирает их набор как покрытие входов. Отсюда предсказуемы поведение на null, приоритет not/and/or и исключение непокрытого switch-выражения в рантайме.
- Records: равенство по значению, with и деконструкцияRecord — это модификатор поверх class или struct: ссылочную или значимую природу задаёт базовый тип, а компилятор добавляет равенство по полям, with и деконструкцию. Урок объясняет, что именно синтезируется, почему with копирует поверхностно и почему изменяемая запись теряется в словаре.
- Деревья выражений: лямбда как данные, описывающие кодОдна и та же лямбда становится либо делегатом для вызова, либо деревом объектов, описывающим код: что именно получится, решает целевой тип. Урок объясняет, как это дерево читают и переводят в SQL, почему тело лямбды на сервере не исполняется и какие конструкции C# в дерево не помещаются.
- Reflection: модель метаданных и её стоимостьРефлексия не добывает информацию о типах в рантайме, а читает метаданные, которые компилятор записал в сборку рядом с кодом. Из этой модели становятся предсказуемы цена трёх способов получить тип, медленный поздний вызов через Invoke и поломки при обрезке кода и AOT.
- Source generators — компиляторное метапрограммированиеSource generator — это код, который компилятор запускает во время сборки: он читает модель вашего кода и дописывает рядом новые файлы, ничего не меняя в существующих. Урок строит модель кэшируемого конвейера, объясняет, почему сгенерированный код переживает trimming и AOT там, где рефлексия падает, и где это видно на диске и в счётчиках.
- Строки: интернирование, StringBuilder и стоимость конкатенацииСтрока в .NET неизменяема, поэтому каждое её изменение создаёт новый объект, а конкатенация в цикле обходится квадратично по аллокациям. Урок показывает, когда выгоден StringBuilder, как он растит ёмкость, и что на самом деле делает пул интернирования — почему одинаковые строки не обязательно один объект.
- Равенство и хеш-код: контракт Equals/GetHashCode и упаковкаDictionary и HashSet находят элементы через GetHashCode, а сверяют через Equals, поэтому равные объекты обязаны давать равный хеш — иначе объект теряется в коллекции. Урок разбирает контракт двух методов, опасность мутабельного ключа и упаковку при сравнении значимых типов.
02
Среда выполнения .NET (CLR)
10 уроковКак устроена среда выполнения .NET: загрузка сборок и типов, JIT-компиляция, управление памятью и сборка мусора, пул потоков, граница managed/unmanaged, Native AOT и P/Invoke. Помогает отличать гарантии языка от поведения кода под нагрузкой.
- Архитектура CLR: загрузчик, JIT, GC, метаданныеСобранный файл .NET содержит не машинный код, а переносимый промежуточный код и метаданные, которые среда выполнения доводит до работы цепочкой подсистем: загрузчик, верификатор, JIT и сервисы исполнения. Урок строит карту этих подсистем и показывает, где гарантии языка расходятся с поведением кода под нагрузкой.
- JIT, многоуровневая компиляция и ReadyToRunСреда выполнения .NET компилирует метод в нативный код при первом вызове, а горячие методы позже переписывает в оптимизированную версию в фоне. Урок строит модель многоуровневой компиляции и предкомпиляции ReadyToRun и связывает её со временем старта, формой графика latency и счётчиками JIT.
- Сборки и загрузка типов в .NETСборка задаёт не только файл рядом с приложением, но и идентичность типа: один и тот же по имени тип, загруженный в два контекста, становится двумя разными типами. Урок строит модель того, как среда выполнения находит, грузит и изолирует код через контексты загрузки, почему AppDomain остался легаси и почему выгрузка кода завершается только после сборки мусора.
- GC: поколения, фазы, mark/sweep/compactСборщик мусора .NET освобождает не «ненужные», а недостижимые от корней объекты, и делает это не сразу: по поколениям и фазами. Урок строит модель корней и достижимости, объясняет Gen 0/1/2 как модель стоимости, фазы mark/plan/relocate/compact/sweep и то, по каким сигналам dotnet-counters видно реальное поведение сборщика.
- Сборщик мусора: server и workstation, задержка против пропускной способностиWorkstation и server-сборщик отличаются не скоростью, а топологией: одна куча на потоке-инициаторе против выделенной кучи и потока на каждый процессор. Урок строит модель выбора под нагрузку и память, разводит разновидность, фоновую сборку и режимы задержки и показывает, что меняет DATAS с .NET 9.
- Large Object Heap и фрагментацияОбъекты не меньше 85 000 байт среда выполнения кладёт в отдельную область кучи, где они сразу считаются старым поколением и по умолчанию не уплотняются, а только сметаются в список свободных участков. Урок объясняет, откуда отсюда растёт фрагментация, чем она отличается от утечки памяти и по каким счётчикам её видно.
- Managed и unmanaged ресурсы: SafeHandle, IDisposable, ArrayPoolСборщик мусора освобождает память managed-объектов, но не закрывает файлы, сокеты и соединения за ними. Урок разделяет три механизма освобождения ресурса — детерминированный Dispose, страховку-финализатор и обёртку SafeHandle — и показывает, чем пул массивов снижает нагрузку на сборщик и по каким сигналам видно утечку дескрипторов.
- Как работает пул потоков и планирование в .NETПул потоков .NET — это два разных пула с раздельными очередями: рабочие потоки под нагрузку на процессор и потоки завершения асинхронного ввода-вывода. Урок строит модель кражи работы, подбора числа потоков обратной связью по пропускной способности и дросселирования инъекции и показывает, почему блокирующий вызов вызывает голод потоков при свободном процессоре.
- Native AOT: что включается, что выключаетсяNative AOT компилирует приложение в нативный код заранее, на этапе публикации, и убирает JIT — взамен включая обязательную обрезку недостижимого кода. Урок строит модель режима как смены контракта исполнения с фиксированной ценой: что выигрывается в старте и памяти, что выключается из рефлексии и динамики и по каким предупреждениям сборки это видно.
- P/Invoke и interop — что происходит на границеВызов нативной функции из управляемого кода идёт не напрямую: между объявлением и библиотекой стоит сгенерированный переходник, который преобразует типы, закрепляет или копирует память и делает переход относительно сборщика мусора. Урок строит модель этой границы и связывает её промахи с конкретными сбоями.
03
Асинхронность и многопоточность
4 урокаАсинхронность и многопоточность в .NET: модель async/await и Task как конечный автомат, отличие асинхронной работы от параллелизма, композиция задач, кооперативная отмена и типичные ошибки вроде блокирующих вызовов и взаимоблокировок.
- Что делают async и await на самом делеЧто компилятор и среда выполнения делают за async и await: метод становится конечным автоматом, который в точке ожидания освобождает поток, а не блокирует его. Почему асинхронность не равна параллелизму и откуда берётся риск взаимоблокировки на блокирующих вызовах.
- Композиция задач: WhenAll, WhenAny и параллельный ввод-выводЗадача начинает работать в момент запуска, а не при await, поэтому именно порядок запуска и ожидания решает, идут операции с перекрытием или одна за другой. Дальше — сбор задач, отличие WhenAll и WhenAny, агрегация исключений и таймауты.
- Отмена и таймауты: CancellationToken и кооперативная отменаОтмена в .NET кооперативна: запрос остановки лишь выставляет необратимый сигнал, а останавливается сама операция, когда его заметит. Урок строит модель токена и его источника, разбирает таймауты и связанные токены и разводит отменённую задачу от упавшей.
- Асинхронные потоки: IAsyncEnumerable и await foreachКогда источник отдаёт данные порциями во времени, разница между «дождаться весь набор сразу» и «получать элементы по одному» меняет память и задержку первого элемента. Разбираем async-итератор, ленивый старт перечисления, отмену и границу с каналами.
04
LINQ и коллекции
3 урокаКоллекции и их стоимость по памяти и времени, и LINQ как декларативный слой запросов: отложенное выполнение, разница между IEnumerable и IQueryable и момент материализации запроса.
- Коллекции и их стоимость: выбор по памяти и времениУ каждой коллекции внутри своя структура данных, и именно она задаёт стоимость операций по времени и памяти. Выбор между списком, словарём, множеством и связанным списком — это решение о цене, а не об удобстве API.
- LINQ — внутренняя модель, IEnumerable, deferred executionПеременная LINQ-запроса хранит не данные, а отложенное описание операции: источник читается при перечислении, каждый раз заново. Урок делит операторы на немедленные, потоковые и непотоковые и показывает границу, где запрос транслируется в SQL, а где соскальзывает в память.
- IEnumerable и IQueryable: где выполняется запрос и когда материализуетсяСтатический тип источника решает, где исполнится запрос: над IEnumerable фильтр крутится в памяти процесса, а над IQueryable превращается в дерево выражений и переводится в SQL для базы. Урок показывает границу исполнения на клиенте и сервере и момент, в который запрос реально обращается к источнику.
05
Типы, память и упаковка
4 урокаЗначимые и ссылочные типы, их размещение в стеке и куче, упаковка, выбор между struct и record, а также работа с буферами без лишних аллокаций (Span, Memory, stackalloc) и unsafe-контекст. От этих решений зависит нагрузка на сборщик мусора.
- Значимые и ссылочные типы в C#Значимый тип хранит сами данные, ссылочный — ссылку на объект: отсюда поведение при копировании и передаче в метод, идентичность объектов, упаковка и осознанный выбор между struct и class, а не лозунг «struct на стеке, class в куче».
- Struct vs class на границе производительностиВыбор между значимым и ссылочным типом — это решение о том, где разместится экземпляр, что копируется при передаче и когда он упаковывается на куче. Урок строит модель стоимости struct и показывает границу, после которой он перестаёт выигрывать у class.
- Span<T>, Memory<T> и stackalloc — работа с буферами без копийКак работать с непрерывным буфером через вид из ссылки и длины, не копируя и не нагружая сборщик мусора: почему Span живёт только на стеке, чем за это платит Memory и когда stackalloc даёт буфер без аллокаций в куче.
- Unsafe-контекст и указатели в C#Безопасность C# — это проверяемость, а unsafe-контекст её снимает и открывает прямой доступ к памяти через указатели. Центральный конфликт темы — сырой указатель против перемещающего сборщика мусора: отсюда закрепление объектов, его цена и выбор в пользу безопасных примитивов.
06
Синхронизация и параллелизм
5 уроковДоступ к общему состоянию из нескольких потоков без гонок и взаимоблокировок: примитивы синхронизации (lock, SemaphoreSlim, Interlocked), модель памяти и видимость изменений, потокобезопасные коллекции и Channels, параллельные паттерны.
- lock, Monitor и гонки данных: модель памяти и видимостьПочему незащищённое общее состояние ломается при доступе из нескольких потоков и как lock чинит это сразу двумя гарантиями — взаимным исключением и упорядочиванием памяти. Внутри: устройство lock через Monitor, выбор объекта блокировки и почему volatile не заменяет лок.
- SemaphoreSlim и асинхронная координация: почему lock не дружит с awaitЗамок lock привязан к потоку, поэтому await внутри него запрещён компилятором. Как координировать доступ к общему ресурсу в асинхронном коде через SemaphoreSlim: взаимное исключение, ограничение конкурентности и режимы отказа, которых нет у lock.
- Interlocked и основы lock-free: атомарные операции и CASПочему обычный инкремент общего счётчика теряет обновления и как атомарные операции меняют одну ячейку без захвата лока. Внутри: чем атомарность одной операции отличается от потокобезопасности инварианта, как из сравнения с обменом строится CAS-петля и когда lock-free проигрывает обычной блокировке.
- Потокобезопасные коллекции и ChannelsПотокобезопасная коллекция гарантирует атомарность отдельной операции, но не корректность вашей логики поверх неё. Где это ломается, как выбрать структуру под профиль доступа и чем синхронный producer/consumer отличается от асинхронных каналов с обратным давлением.
- Параллельные паттерны: Parallel.For/ForEach и PLINQParallel.For/ForEach и PLINQ распараллеливают данные ради загрузки ядер, и это не делает любой цикл быстрее: потолок упирается в число процессоров за вычетом накладных расходов. Внутри — почему на мелких телах параллель медленнее, чем общая запись из тела рождает гонку и чем I/O-работа с Parallel.ForEachAsync отличается от процессорной.
07
ASP.NET Core
3 урокаКак устроено приложение ASP.NET Core: модель хостинга и запуск через Program.cs, система конфигурации с приоритетом источников и паттерн Options для типобезопасного доступа к настройкам.
- Хостинг и запуск: WebApplication и Program.csПриложение ASP.NET Core — это хост: он держит контейнер зависимостей, конфигурацию, логирование и веб-сервер как одну из фоновых служб. Запуск проходит две фазы с границей Build и блокирует поток до корректной остановки.
- Система конфигурации: провайдеры и приоритет источниковНастройки приложения собираются не из одного файла, а из упорядоченной цепочки источников в общий плоский словарь, где при совпадении ключа выигрывает добавленный последним. Урок даёт модель, по которой можно предсказать итоговое значение любого ключа.
- Паттерн Options: IOptions, IOptionsSnapshot, IOptionsMonitorТипобезопасный доступ к настройкам приложения через классы вместо строковых ключей. Три интерфейса дают один и тот же объект настроек, но с разным временем жизни — и от этого зависит, увидит ли код правку конфигурации после старта.
08
Внедрение зависимостей
4 урокаКонтейнер внедрения зависимостей в .NET: регистрация сервисов, время жизни transient/scoped/singleton, захват зависимости через несовместимое время жизни и продвинутые способы регистрации.
- Контейнер DI и регистрация сервисовКласс объявляет зависимости через конструктор, а создаёт их встроенный контейнер .NET. Контейнер хранит не готовые объекты, а рецепты их создания: регистрация описывает контракт и способ создания, а резолв собирает граф зависимостей по конструкторам.
- Время жизни сервисов: transient, scoped, singletonВремя жизни сервиса — это правило контейнера: на каждый запрос вернуть новый экземпляр, тот же в пределах области или один на всё приложение. Transient, scoped и singleton связаны со scope и с моментом, когда контейнер создаёт и освобождает объект; transient-disposable при этом не освобождается сразу.
- Захваченные зависимости и утечки через время жизниКогда сервис с коротким временем жизни попадает внутрь сервиса с более длинным, долгоживущий владелец удерживает короткоживущую зависимость и продлевает её срок до своего. Один такой случай контейнер ловит проверкой на старте, другой молча течёт в память; способ починки у каждого свой.
- Продвинутая регистрация: keyed-сервисы, открытые обобщения, коллекцииКогда под один контракт зарегистрировано несколько реализаций, выбор зависит от устройства коллекции сервисов: одиночное обращение берёт последнюю регистрацию, а запрос коллекции отдаёт все по порядку. Сюда же относятся регистрация по ключу, открытые обобщения одной строкой и фабрики создания.
09
Конвейер и маршрутизация
3 урокаКонвейер обработки запроса в ASP.NET Core: middleware и значение их порядка, маршрутизация запроса к обработчику и привязка модели — откуда обработчик получает свои аргументы.
- Конвейер middleware: порядок и короткое замыканиеКаждый HTTP-запрос проходит через упорядоченную вложенную цепочку middleware, где каждый компонент работает до и после следующего и может оборвать цепочку. Порядок регистрации напрямую определяет поведение — от обработки ошибок до аутентификации.
- Маршрутизация и endpoints: как запрос находит обработчикМаршрутизация связывает запрос с обработчиком в две фазы: сначала по шаблону выбирается самый подходящий endpoint, затем он выполняется. Ограничения вроде {id:int} различают маршруты, а не проверяют данные, поэтому несовпадение даёт 404.
- Привязка модели: откуда берутся аргументы обработчикаПривязка модели превращает данные запроса — маршрут, строку запроса, форму, тело, заголовки — в типизированные аргументы обработчика, сопоставляя их по источнику и имени. Она конвертирует типы, но не проверяет данные: это отдельный слой валидации.
10
HTTP и REST-семантика
4 урокаСемантика HTTP независимо от фреймворка: методы и коды состояния, безопасность и идемпотентность, кэширование с условными запросами и согласование содержимого — основа предсказуемого API.
- HTTP-методы и коды состояния: что они означаютHTTP — общий словарь: метод объявляет намерение операции, а код состояния — класс исхода. На эти объявления полагаются клиенты, кэши и прокси, поэтому неверный код вводит их в заблуждение, а PUT и POST выражают разные намерения, а не стиль.
- Безопасность и идемпотентность методовМетод объявляет два свойства: безопасный только читает, а идемпотентный даёт от многих одинаковых запросов тот же эффект на сервере, что и от одного. Идемпотентность про эффект, а не про одинаковый ответ — на этом держится безопасность повторов.
- Кэширование и условные запросы: Cache-Control и ETagКэш переиспользует свежий ответ без обращения к серверу, а устаревший проверяет условным запросом с валидатором ETag, получая в ответ 304 без тела. Cache-Control управляет хранением, и no-cache означает не «не хранить», а «проверять перед выдачей».
- Согласование содержимого: Accept и форматтерыОдин ресурс может отдаваться в разных представлениях. Клиент выражает предпочтение заголовком Accept, сервер выбирает формат и объявляет его в Content-Type. Accept — это предпочтение, а не гарантия, а рассогласование даёт 406 или 415.
11
Контракты и валидация
4 урокаКонтракт API как обещание клиентам: валидация входных данных, единый формат ошибок, описание через OpenAPI и версионирование, чтобы изменения сервиса не ломали потребителей.
- Валидация модели: DataAnnotations и ModelStateВалидация проверяет привязанные данные на соответствие правилам и складывает ошибки в ModelState; с атрибутом ApiController нарушение даёт автоматический ответ 400. Серверная валидация авторитетна, а клиентская лишь удобство и легко обходится.
- Единый контракт ошибок: ProblemDetails (RFC 9457)ProblemDetails — стандартная машиночитаемая форма тела ошибки с полями type, title, status, detail и instance, чтобы клиенты разбирали ответы любого API единообразно. HTTP-код при этом остаётся авторитетным, а тело лишь дополняет его деталями.
- Описание API через OpenAPIOpenAPI — машиночитаемое описание API: какие есть операции, что они принимают и возвращают. В ASP.NET Core документ генерируется из endpoint'ов и отражает код, а Swagger UI лишь отображает его; из документа инструменты строят клиентов и тесты.
- Версионирование APIВерсионирование позволяет старой и новой версии API сосуществовать, чтобы ломающие изменения не рушили существующих клиентов. Версию передают через URL, query, заголовок или media type, а новую заводят на ломающее изменение, а не на каждое добавление.
12
EF Core
7 уроковКак ORM работает поверх реляционной БД: DbContext и отслеживание изменений, трансляция LINQ в SQL, загрузка связанных данных, миграции и стоимость материализации. Где EF Core экономит время, а где порождает лишние запросы — и когда дешевле Dapper.
- DbContext и отслеживание измененийDbContext — это короткоживущая единица работы с трекером изменений: каждая сущность имеет состояние, а EF хранит снимок исходных значений и на SaveChanges пишет только изменившиеся столбцы. Урок объясняет, как сущности отслеживаются, как обнаруживаются изменения и почему контекст должен быть короткоживущим.
- IQueryable против IEnumerable и трансляция запроса в SQLDbSet — это IQueryable с деревом выражений, которое EF переводит в SQL, тогда как IEnumerable фильтрует уже загруженные данные в памяти. Урок разбирает отложенное выполнение, жизнь запроса до материализации и ловушки трансляции: где непереводимый метод бросает исключение, а где AsEnumerable тянет всю таблицу.
- Загрузка связанных данных: lazy, eager и explicitСвязанные сущности в EF Core не грузятся сами — есть три стратегии: eager через Include, explicit по требованию и lazy через прокси. Урок разбирает, кто и когда инициирует загрузку, и две ловушки: N+1 у ленивой загрузки и декартово размножение строк при нескольких Include.
- AsNoTracking, SplitQuery и стоимость материализацииЧтение через EF стоит по двум осям: трекинг со снимками на стороне приложения и форма SQL по сети. Урок объясняет, что даёт AsNoTracking и identity resolution, почему несколько Include порождают декартово размножение строк и чем за это платят split query и проекция в DTO.
- Миграции: модель, обратная совместимость и откатМиграция в EF Core — это версионируемые файлы с методами Up и Down плюс снимок модели, а таблица истории хранит уже применённые миграции. Урок объясняет, как EF вычисляет изменения и узнаёт, что применять, чем безопасно катить схему на прод и почему откат через Down не всегда обходится без потери данных.
- Сырой SQL, хранимые процедуры и блокировки строкEF позволяет спуститься к сырому SQL, когда LINQ не хватает: FromSql и ExecuteSql параметризуют значения и защищают от инъекций, опасна только ручная склейка в raw-вариантах. Урок также объясняет, почему EF по умолчанию оптимистичен, как работает токен конкуренции и почему блокировку строки приходится писать SQL-ом.
- Dapper против EF Core: когда ORM лишнийDapper быстр не магией, а потому что делает меньше: только маппит результат вашего SQL в объекты, без отслеживания изменений, миграций и трансляции LINQ. Урок разбирает компромисс между полным ORM и микро-ORM и показывает, где EF оправдан, где лишний и почему эти инструменты часто используют вместе.
13
Безопасность в ASP.NET Core
4 урокаКак ASP.NET Core реализует аутентификацию и авторизацию: схемы и обработчики, проверка JWT через JwtBearer, авторизация на ролях, claim'ах и политиках, а также безопасное хранение секретов через конфигурацию. Сами протоколы (OAuth2/OIDC/JWT) — отдельный курс; здесь — их прикладной слой в .NET.
- Аутентификация в ASP.NET Core: схемы и обработчикиАутентификация в ASP.NET Core — не одна настройка, а сервис со схемами: пара «обработчик + настройки» (AddJwtBearer, AddCookie) строит ClaimsPrincipal и кладёт в HttpContext.User, на который опирается авторизация. Урок разбирает схемы, действия authenticate/challenge/forbid и почему UseAuthentication стоит до UseAuthorization.
- Проверка JWT через JwtBearerJwtBearerHandler строит личность из claim'ов токена, но набор проверок задаётся явно: полная валидация — это подпись, issuer, audience и срок, настраиваемые через TokenValidationParameters. Урок объясняет, зачем каждая проверка, почему ключ подписи асимметричный и почему отключить проверку — это дыра, а не починка.
- Авторизация: роли, claim'ы и политикиРоли, claim'ы и политики в ASP.NET Core — не три разных приёма, а один механизм: политика состоит из requirements, а handlers проверяют их по claim'ам пользователя. Урок объясняет лестницу [Authorize]→роли→политики, разницу AND по requirements и OR по handlers и когда нужна resource-based авторизация по владельцу ресурса.
- Конфигурация и секреты в .NETКонфигурация в .NET — стопка провайдеров с приоритетом, где переменная окружения перекрывает appsettings, поэтому секреты держат вне кода: user-secrets в Development, env/хранилище в Production. Урок разбирает приоритет источников и options pattern — различие IOptions, IOptionsSnapshot и IOptionsMonitor.
14
Готовность к продакшну
2 урокаЧто отличает рабочий сервис от учебного: тесты, наблюдаемость, устойчивость к сбоям, фоновые задачи и предсказуемый деплой — свойства, которые закладываются в код, а не добавляются потом. Модуль задаёт рамку секции и разбирает один сквозной механизм хостинга — корректное завершение процесса под управлением оркестратора.
- Что отличает продакшн-сервис от учебногоРабочий сервис отличается от учебного не функциональностью, а свойствами, которые проявляются под нагрузкой и при отказах: его можно проверить тестами, наблюдать в проде, он переживает сбои зависимостей, выносит долгую работу из запроса и предсказуемо разворачивается. Урок задаёт рамку пяти осей секции и показывает, почему эти свойства закладывают в код, а не добавляют постфактум.
- Жизненный цикл хоста и graceful shutdown.NET-сервис живёт внутри хоста (IHost), который запускает и останавливает приложение по сигналам среды. Когда оркестратор шлёт SIGTERM, хост не обрывает процесс сразу: он запускает корректное завершение — перестаёт принимать новые запросы, даёт текущим доработать в пределах таймаута и освобождает ресурсы. Урок разбирает фазы остановки и почему без них деплой теряет запросы.
15
Тестирование
4 урокаТесты фиксируют поведение сервиса и защищают от регрессий: пирамида юнит-, интеграционных и end-to-end тестов, изоляция зависимостей через тестовые двойники и проектирование кода под проверяемость. Модуль разбирает, что тестировать на каждом уровне, как заменять зависимости и как проверять код, завязанный на время и базу данных.
- Пирамида тестов: что проверять на каждом уровнеТесты делят по охвату и стоимости: много быстрых юнит-тестов на логику в изоляции, меньше интеграционных на стыки с БД и внешними сервисами, единицы end-to-end на сценарий целиком. Урок объясняет, почему форма именно пирамида, а не перевёрнутая, что тестировать на каждом уровне и почему попытка проверить всё через медленные сквозные тесты ломает обратную связь.
- Юнит-тесты и тестовые двойникиЮнит-тест проверяет одну единицу поведения в изоляции, а зависимости заменяет двойниками: stub отдаёт заранее заданные данные, mock проверяет факт вызова, fake даёт рабочую упрощённую реализацию. Урок разбирает структуру AAA, различие видов двойников на примере Moq и главную ошибку — мокать всё подряд и проверять реализацию вместо наблюдаемого поведения.
- Проектирование под тестируемостьТестируемость — свойство дизайна, а не тестов: код, который сам создаёт зависимости и читает DateTime.Now, тяжело проверить, потому что у теста нет точки подмены. Урок показывает, как внедрение зависимостей и абстракции над временем (TimeProvider) и недетерминированными источниками создают швы для подстановки, и почему за трудный тест отвечает дизайн, а не тест.
- Интеграционные тесты ASP.NET CoreИнтеграционный тест проверяет сервис через настоящий HTTP-конвейер и реальные зависимости: WebApplicationFactory поднимает приложение в памяти и шлёт запросы через TestServer, а Testcontainers даёт одноразовую базу в контейнере вместо моков репозитория. Урок разбирает, что ловит этот уровень поверх юнитов и почему изоляция БД важнее, чем общая тестовая база.
16
Наблюдаемость
4 урокаПод нагрузкой поведение сервиса непрозрачно, и наблюдаемость возвращает прозрачность: логи, метрики и трассировки как три разных канала, структурированное логирование со сквозным идентификатором запроса, health-проверки для оркестратора и распределённая трассировка через OpenTelemetry. Модуль учит видеть, что сервис делает в проде, а не догадываться по симптомам.
- Три канала наблюдаемости: логи, метрики, трассировкиНаблюдаемость стоит на трёх разных каналах, и каждый отвечает на свой вопрос: логи фиксируют дискретные события («что случилось здесь»), метрики агрегируют числа во времени («сколько и как часто»), трассировки восстанавливают путь одного запроса через сервисы («где время и где сбой»). Урок разводит их по назначению и показывает, почему подмена одного канала другим оставляет слепые зоны.
- Структурированные логи и сквозной идентификаторЛог-строка как текст плохо ищется и не агрегируется; структурированный лог — это событие с именованными полями, по которым можно фильтровать и считать. Урок разбирает структурированное логирование через Serilog и сквозной идентификатор запроса (correlation id), который связывает записи одного запроса в разных сервисах в единую цепочку, и почему без него распределённый лог бесполезен.
- Health-проверки и пробы оркестратораОркестратор должен знать, жив ли сервис и готов ли он принимать трафик, и узнаёт это через health-проверки. Урок разделяет два разных вопроса — liveness (процесс жив или его перезапустить) и readiness (зависимости подняты, можно слать трафик) — показывает их реализацию в ASP.NET Core и объясняет, почему путать пробы опасно: одна перезапускает контейнер, другая выводит его из балансировки.
- Метрики и распределённая трассировкаМетрики отвечают на «сколько и как быстро» через счётчики и гистограммы, распределённая трассировка — на «где в цепочке сервисов потерялось время». Урок разбирает сбор метрик и трассировок через OpenTelemetry .NET как единый стандарт, экспорт в Prometheus и Grafana, propagation контекста трассировки между сервисами и чем метрика отличается от лога по стоимости.
17
Устойчивость к сбоям
3 урокаСетевые вызовы падают, и сервис должен переживать частичные отказы без каскада: правильное время жизни HttpClient, повторы с таймаутом и выдержкой, размыкатель цепи и деградация вместо полного отказа. Модуль начинает с самой частой .NET-ошибки — исчерпания сокетов из-за неверного обращения с HttpClient — и доводит до набора политик устойчивости.
- HttpClient и исчерпание сокетовHttpClient устроен контринтуитивно: создавать его на каждый запрос нельзя, потому что освобождённый клиент держит TCP-соединение в состоянии TIME_WAIT, и под нагрузкой сервис исчерпывает порты. Урок разбирает жизненный цикл клиента и его обработчика, почему «using на каждый вызов» ломает прод и как IHttpClientFactory чинит это пулом и ротацией обработчиков.
- Повторы, таймауты и выдержкаПовтор упавшего вызова кажется очевидным лекарством, но без таймаута он висит, без выдержки добивает уже перегруженную зависимость, а без идемпотентности дублирует эффект. Урок разбирает связку «таймаут + повтор + экспоненциальная выдержка с джиттером», почему повторять можно не каждый запрос и не каждую ошибку, и чем transient-сбой отличается от постоянного.
- Размыкатель цепи и деградацияКогда зависимость лежит, повторы только усиливают нагрузку; размыкатель цепи (circuit breaker) на время перестаёт слать запросы и быстро отдаёт отказ, давая зависимости восстановиться. Урок разбирает три состояния размыкателя, его сборку через Microsoft.Extensions.Resilience поверх Polly и graceful degradation — отдать урезанный ответ вместо падения, чтобы сбой не шёл каскадом.
18
Фоновая обработка
2 урокаДлительная работа не должна жить в обработчике HTTP-запроса: её выносят в фоновые сервисы, которые живут весь срок процесса. Модуль разбирает hosted-сервисы и их жизненный цикл, типичную ошибку со scoped-зависимостью внутри singleton, очереди работы внутри процесса через Channels и границу, за которой нужна уже внешняя очередь сообщений.
- Hosted-сервисы и их жизненный циклФоновая работа в .NET живёт в hosted-сервисе, который хост запускает при старте и останавливает при завершении. Урок разбирает BackgroundService и его цикл с CancellationToken, корректную остановку вместе с хостом и частую ошибку: фоновый сервис регистрируется как singleton, поэтому внедрить в него scoped-зависимость напрямую нельзя — её берут через IServiceScopeFactory на единицу работы.
- Очереди внутри процесса и ChannelsЧтобы развязать приём запроса и его обработку, работу кладут в очередь внутри процесса, а фоновый сервис её разбирает. Урок разбирает System.Threading.Channels как потокобезопасный канал producer/consumer с ограничением размера и обратным давлением, и показывает границу: такая очередь живёт в памяти и умирает с процессом — когда нужна устойчивость и доставка, берут внешний брокер.
19
Контейнеры и CI/CD
2 урокаВоспроизводимая сборка делает деплой предсказуемым: сервис упаковывают в контейнер из небольшого образа, конфигурацию выносят в переменные окружения, а сборку, тесты и публикацию автоматизируют конвейером. Модуль разбирает multi-stage Dockerfile для .NET и базовый pipeline сборки и доставки — тонко, по реальному спросу junior/middle.
- Контейнеризация .NET-сервисаКонтейнер делает сборку воспроизводимой, но наивный образ тащит весь SDK и весит сотни мегабайт. Урок разбирает multi-stage Dockerfile: на стадии сборки используется тяжёлый образ SDK с dotnet publish, а в финальный образ копируется только результат поверх тонкого рантайм-образа. Показывает разницу SDK- и runtime-образов, вынос конфигурации в переменные окружения и почему слои важны для кеша.
- Конвейер сборки и доставкиCI/CD-конвейер превращает коммит в развёрнутый сервис через фиксированную цепочку шагов: сборка, прогон тестов, публикация образа и его продвижение по средам. Урок разбирает назначение каждого шага, тегирование образа неизменяемой версией вместо latest и принцип «собрать один раз, продвигать артефакт», а конфигурацию среды держать снаружи образа — чтобы тестировали ровно то, что поедет в прод.
20
Углублённые направления
1 урокНаправления для роста после уверенного владения основой: профилирование, локальная оркестрация, специализированные протоколы и режимы выполнения. Модуль задаёт рамку секции и главный принцип — эти темы изучают по реальной потребности, когда задача действительно их требует, а не впрок.
- Когда тянуться к специализацииУглублённые направления — профилирование, оркестрация, специализированные протоколы и режимы компиляции — решают узкие задачи ценой дополнительной сложности. Урок задаёт рамку секции и объясняет, почему их изучают по реальной потребности, когда задача доказанно их требует, а не добавляют впрок: преждевременная специализация — та же ловушка, что преждевременная оптимизация.
21
Профилирование
2 урокаОптимизация начинается с измерения, а не с догадок: как найти настоящее узкое место профилировщиком и счётчиками, прежде чем что-то менять, и как .NET-инструменты диагностики показывают аллокации, паузы сборщика мусора и горячий код. Модуль учит измерять, а не угадывать, где сервис тормозит.
- Измерять, а не угадыватьОптимизация по интуиции почти всегда бьёт мимо: настоящее узкое место редко там, где его подозревают, а ускорение не-узкого места не ускоряет систему. Урок объясняет, почему сначала измеряют профилировщиком, как узкое место определяет общую производительность и почему преждевременная оптимизация усложняет код без выигрыша.
- Инструменты диагностики .NET.NET даёт набор инструментов, чтобы увидеть, что сервис делает под нагрузкой: счётчики (dotnet-counters) показывают метрики в реальном времени, трассировки (dotnet-trace) и профилировщики — горячий код и аллокации, снимки памяти — утечки. Урок разбирает, какой инструмент отвечает на какой вопрос и как читать аллокации и паузы сборщика мусора.
22
.NET Aspire
1 урокРаспределённое приложение из нескольких сервисов трудно запускать и настраивать локально. .NET Aspire оркеструет такой набор для разработки: поднимает сервисы и их зависимости вместе, связывает их обнаружением сервисов и включает наблюдаемость по умолчанию. Модуль показывает, какую задачу разработки Aspire решает и где его граница.
- .NET Aspire и локальная оркестрацияРаспределённое приложение из нескольких сервисов и их зависимостей мучительно поднимать и настраивать на машине разработчика вручную. .NET Aspire берёт это на себя: описывает набор сервисов в одном месте, запускает их вместе с зависимостями, связывает обнаружением сервисов и даёт телеметрию из коробки. Урок разбирает, какую задачу разработки решает Aspire и где его граница.
23
gRPC на .NET
1 урокКак gRPC устроен в .NET на практике: контракт в файле .proto, из которого генерируются и сервис, и типизированный клиент, поверх HTTP/2 с бинарным форматом Protobuf и поддержкой стриминга. Модуль — про реализацию gRPC в ASP.NET Core; решение «gRPC или REST» разбирает курс по архитектуре.
- gRPC на .NET: контракт и реализацияВ .NET gRPC устроен contract-first: сервис и его сообщения описывают в файле .proto, а из него генерируются и серверная заготовка, и строго типизированный клиент — дублировать описание не нужно. Урок разбирает, как gRPC-сервис хостится в ASP.NET Core поверх HTTP/2, как работает типизированный клиент и виды стриминга, и где проходит граница с браузером (gRPC-Web).
24
Native AOT
1 урокОбычно .NET компилирует код в нативный на лету (JIT) при запуске. Native AOT компилирует приложение в нативный код заранее, при сборке: быстрее старт и меньше потребление памяти ценой ограничений на рефлексию и совместимость. Модуль показывает, что даёт AOT и почему это выбор для специфичных нагрузок, а не дефолт.
- Native AOT и его компромиссыПо умолчанию .NET компилирует код в нативный на лету при запуске (JIT). Native AOT делает это заранее, при сборке, отдавая самодостаточный нативный бинарь: он быстрее стартует и потребляет меньше памяти. Урок разбирает, чем платят за это — ограничениями рефлексии и совместимости — и почему AOT берут для специфичных нагрузок вроде serverless и CLI, а не как режим по умолчанию.
25
Real-time и GraphQL
2 урокаДва специализированных способа общения клиента и сервера: двусторонняя связь в реальном времени через WebSockets и SignalR, когда сервер должен сам слать обновления клиенту, и гибкие запросы через GraphQL, когда клиент хочет сам выбирать, какие данные получить. Модуль показывает, какую задачу решает каждый и какой операционной ценой.
- Real-time: WebSockets и SignalRОбычный HTTP устроен как запрос-ответ: сервер не может сам, по своей инициативе, отправить данные клиенту. Для обновлений в реальном времени нужен постоянный двусторонний канал — WebSockets, — а SignalR даёт абстракцию поверх него с хабами, выбором транспорта и рассылкой группам. Урок разбирает, почему запрос-ответ не годится для server-push и какую задачу решают WebSockets и SignalR.
- GraphQL: клиент выбирает данныеВ REST форму ответа задаёт сервер, и клиент часто получает лишнее (over-fetching) или вынужден делать несколько запросов (under-fetching). GraphQL переворачивает это: один эндпойнт и схема, а клиент сам описывает в запросе, какие поля ему нужны. Урок разбирает, какую задачу это решает и какой ценой — сложностью запросов, кэширования и контроля нагрузки на сервер.
Готовьтесь по структуре, а не вслепую
Откройте курс в приложении и закрепляйте темы в тренажёре вопросов с AI-разбором.