csharpproglib | Unsorted

Telegram-канал csharpproglib - Библиотека шарписта | C#, F#, .NET, ASP.NET

23284

Все самое полезное для C#-разработчика в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/b60af5a4 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead

Subscribe to a channel

Библиотека шарписта | C#, F#, .NET, ASP.NET

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

Курс «C# Developer. Professional» — про Best Practices .NET-разработки и реальную инженерную работу. Вы углубитесь в работу с атрибутами, асинхронность и многопоточность, разберёте паттерны проектирования, работу с SQL и NoSQL, кэширование, CI/CD и тестирование.

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

➡️Старт курса - 27 февраля. Курс доступен в рассрочку. Пройдите короткое вступительное тестирование и получите специальные условия на обучение: https://clc.to/bFJJ_g

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🤩 Regex вместо while-loop

Классический паттерн очистки строк, который встречается в приложениях:

while (text.Contains(" "))
{
text = text.Replace(" ", "");
}


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

Строки в .NET иммутабельны. Каждый вызов Replace() — это полная аллокация новой строки, копирование содержимого и выброс старого объекта на съедение GC. При длинных прогонах повторяющихся символов цикл делает это многократно. Сложность — O(n²), давление на GC — ощутимое.

Правильная замена — Regex.Replace с квантификатором +:
text = Regex.Replace(text, @"-+", "-");


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

Оптимальный вариант для .NET 8+ — Source-Generated Regex:
public partial class StringNormalizer
{
[GeneratedRegex(@"-+")]
private static partial Regex ConsecutiveDashRegex();

public static string NormalizeSlug(string input)
=> ConsecutiveDashRegex().Replace(input, "-");
}


Атрибут [GeneratedRegex] переносит компиляцию паттерна на этап сборки. В рантайме — никакого парсинга, никаких лишних аллокаций при инициализации, валидация паттерна прямо в IDE.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📎 Новая фича C#

В C# 15 появится возможность передавать аргументы в конструктор или фабричный метод коллекции прямо внутри выражения коллекции — через элемент with(...).

Раньше, если нужно было создать List<string> с заданной ёмкостью или HashSet<string> с нестандартным компаратором, приходилось делать это отдельно. Теперь всё пишется в одну строку:

// Задаём начальную ёмкость списка
List<string> names = [with(capacity: values.Length * 2), .. values];

// Передаём компаратор в HashSet
HashSet<string> set = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO", "hello"];
// В set окажется один элемент — все строки равны при OrdinalIgnoreCase


with(...) должен быть первым элементом в выражении коллекции.

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

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📈 Подборка вакансий для шарпистов

.NET-разработчик — от 250 000 ₽, удалёнка

C#/.NET-разработчик — 265 000 ₽, Офис/Гибрид в Екатеринбурге

Middle/Senior .NET Developer — до 3 500 $, удалёнка

➡️ Еще больше топовых вакансий — в нашем канале C# Jobs

🐸 Библиотека шарписта

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📎 Парсим HTML в C#

FreeSpire.Doc для .NET — профессиональная библиотека для обработки документов. Она позволяет разработчикам на C# читать, анализировать и извлекать HTML-контент с минимальным количеством кода и без внешних зависимостей.

Установка через NuGet:

Install-Package FreeSpire.Doc


Необходимые неймспейсы:
using Spire.Doc;
using Spire.Doc.Documents;
using Spire.Doc.Fields;
using System;
using System.IO;
using System.Text.RegularExpressions;


Кейс 1. Извлечение текста из HTML-строки

Библиотека справляется с обработкой динамически генерируемых HTML-фрагментов, например, контента из фронтенд-редакторов или HTML-строк, возвращаемых API.

string htmlContent = @"
<html>
<body>
<h2>Описание возможностей продукта</h2>
<p>Этот компонент поддерживает<span style='font-size:14px;'> парсинг HTML</span> и имеет следующие преимущества:</p>
<ul>
<li>Кроссплатформенная совместимость .NET</li>
<li>Не зависит от Office</li>
<li>Быстрое извлечение текста</li>
</ul>
</body>
</html>
";

Document parseDoc = new Document();
Section section = parseDoc.AddSection();
Paragraph paragraph = section.AddParagraph();

try
{
// Ключевой метод: загрузка и парсинг HTML
paragraph.AppendHTML(htmlContent);

// Извлечение чистого текста
string pureText = parseDoc.GetText();

// Очистка текста от лишних пробелов
pureText = Regex.Replace(pureText, @"\s+", " ").Trim();

Console.WriteLine("Извлечённый текст:");
Console.WriteLine(pureText);
}
finally
{
parseDoc.Dispose();
}


Кейс 2. Извлечение структурированного контента из файла

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

string htmlFilePath = "sample.html";

Document parseDoc = new Document();
parseDoc.LoadFromFile(htmlFilePath);

// Извлечение заголовков (h1-h6)
foreach (Section sec in parseDoc.Sections)
{
foreach (Paragraph para in sec.Paragraphs)
{
if (para.GetStyle().Name.StartsWith("Heading"))
{
Console.WriteLine(para.Text.Trim());
}
}
}

// Извлечение элементов списков
foreach (Section sec in parseDoc.Sections)
{
foreach (Paragraph para in sec.Paragraphs)
{
if (para.ListFormat.ListType != ListType.NoList)
{
Console.WriteLine(para.Text.Trim());
}
}
}

// Извлечение гиперссылок
foreach (Section sec in parseDoc.Sections)
{
foreach (Paragraph para in sec.Paragraphs)
{
foreach (DocumentObject docObj in para.ChildObjects)
{
if (docObj is Field field && field.Type == FieldType.FieldHyperlink)
{
string linkText = field.FieldText;
string linkUrl = field.GetFieldCode().Split('"')[1];
Console.WriteLine($"Текст: {linkText}, URL: {linkUrl}");
}
}
}
}


Лучшие практики:

• Всегда оборачивайте Document в using или вызывайте Dispose() для освобождения ресурсов

• Проверяйте существование файлов и обрабатывайте исключения парсинга

• Обрабатывайте извлечённый текст с помощью Regex.Replace для нормализации пробелов

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#garbage_collector

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

💿 S.M.A.R.T. уже знает, что с вашим диском

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

Как давно вы проверяли свои файловые хранилища?

Как раз вышла новая версия CrystalDiskInfo. Ничего революционного — но повод скачать актуальную сборку и наконец запустить её.

➡️ Проверить диск

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

😊 Менторство на работе

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

Идеальная ситуация

Компания доплачивает 15-20% к зарплате, джун быстро схватывает и задает умные вопросы. Вы получаете деньги, прокачиваете скилл объяснения. Все в плюсе.

Ловушка «помоги новенькому»

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

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

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

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

💬 Менторили джунов? Как у вас это происходило? Делитесь в комментах 👇

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#entry_point

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🔄 Дайджест недели

Коротко про события недели.

Microsoft разрабатывает функцию перемещения панели задач в Windows 11

Функция ожидается летом 2026 года как часть усилий по улучшению репутации ОС.

Linux 6.19

Превью .NET 11

Бустим рабочее место за 10 шагов

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#garbage_collector

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

Собираем фулл-хаус: 3 курса по цене 1

Хватит выбирать между «полезно», «модно» и «для души». Мы запустили механику, которая позволяет собрать кастомный стек навыков без удара по бюджету: покупаете один курс — два других забираете бесплатно.

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

Для тех, кто хочет понимать математику ML-моделей (вне зависимости от языка) — полный набор по AI: от линала и ML-старта до разработки автономных агентов.

Ну и классика: обновлённый Python как второй язык для скриптов.

Собрать свой пак

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🧑‍💻 Когда чистый код всё равно требует комментариев

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

Роберт Мартин в книге «Чистый код» не запрещает комментарии полностью. Он против тех, что дублируют очевидное или устаревают со временем.

Когда комментарии спасают ситуацию

• Сложная логика или хак:

// Обход бага в IE11 с обработкой форм
if (isIE11()) {
polyfillFormSubmit();
}


• Предупреждения о рисках:
// Не трогать: изменение сломает кэш в проде до деплоя новой версии
cache.invalidateOnlyOnRestart();


• Указание на патенты, стандарты или тикеты в баг-трекере.

💬 Когда, по вашему мнению, ещё могут пригодиться комментарии в коде

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🧑‍💻 Композиция функций в C#

Функциональное программирование — не просто академическая теория. Это парадигма, превращающая императивный спагетти-код в декларативные, тестируемые и поддерживаемые решения.

Проблема императивного кода:

// Традиционный подход - может быть трудно читать и сложно тестировать
public OrderDto ProcessOrder(int orderId)
{
var order = _dbContext.Orders.Find(orderId);
if (order == null) throw new NotFoundException();

if (order.Status != "Pending")
throw new InvalidOperationException();

var customer = _dbContext.Customers.Find(order.CustomerId);
if (!customer.IsActive)
throw new InvalidOperationException();

var discount = customer.IsVip ? 0.1m :
order.Total > 1000 ? 0.05m : 0m;

order.FinalTotal = order.Total * (1 - discount);
_dbContext.SaveChanges();

return new OrderDto { ... };
}


Проблемы: смешанные обязанности, жёсткая связанность с БД, невозможность переиспользования, хрупкость при изменениях.

Композиционное решение:
public class OrderProcessingPipeline
{
private readonly Func<int, Task<Order?>> _loadOrder;
private readonly Func<Order, Task<Customer?>> _loadCustomer;

public async Task<Result<OrderDto, Error>> ProcessAsync(int orderId)
{
return await Result<Order, Error>
.FromNullable(await _loadOrder(orderId), Error.OrderNotFound)
.BindAsync(_loadCustomer)
.BindAsync(_validateStatus)
.BindAsync(_calculateDiscount)
.BindAsync(_persistOrder)
.MapAsync(_mapToDto);
}
}


Преимущества: единственная ответственность каждой функции, простота тестирования, композируемость, декларативный флоу.

Основа композиции — чистые функции без побочных эффектов:
public static class PricingFunctions
{
public static decimal CalculateDiscount(CustomerType type, decimal total) =>
type switch
{
CustomerType.Vip => total * 0.15m,
CustomerType.Premium => total * 0.10m,
_ => 0m
};

public static decimal ApplyTax(decimal amount, decimal rate) =>
amount * (1 + rate);

// Композиция
public static Func<CustomerType, decimal, decimal> CalculateFinalPrice =>
(type, total) => ApplyTax(total - CalculateDiscount(type, total), 0.08m);
}


Тест такой функции:
[Theory]
[InlineData(CustomerType.Vip, 1000, 918.00)]
public void CalculateFinalPrice_ReturnsExpected(CustomerType type, decimal total, decimal expected)
{
Assert.Equal(expected, PricingFunctions.CalculateFinalPrice(type, total));
}


Начните с малого: замените один цикл на Map, один if/throw на Result. Паттерны быстро станут естественными, и ваша кодовая база скажет спасибо.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📷 Подборка вакансий для шарпистов

C#/.NET разработчик — 265 000 ₽. Удалёнка

Middle/Senior .NET Developer — от 3 000 до 3 500 $. Удалёнка

C#/.NET-разработчик — Удалёнка

➡️ Еще больше топовых вакансий — в нашем канале C# Jobs

🐸 Библиотека шарписта

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🤩 Свежее превью .NET

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

➡️ Библиотеки

• Поддержка Zstandard compression для эффективного сжатия
• BFloat16 — новый тип с плавающей точкой для ML-сценариев
• Улучшения ZipArchiveEntry для работы с архивами
• FrozenDictionary теперь поддерживает collection expressions

➡️ API и утилиты:

• Расширенная поддержка Rune в String, StringBuilder и TextWriter
• MediaTypeMap для определения MIME-типов
• API верификации HMAC и KMAC
• Создание жёстких ссылок
• DivisionRounding для режимов целочисленного деления
• Happy Eyeballs в Socket.ConnectAsync для быстрого подключения по IPv4/IPv6

➡️ Рантайм

• Runtime Async — улучшенная асинхронность на уровне рантайма
• CoreCLR на WebAssembly — полноценный рантайм в браузере
• Расширение интерпретатора
• JIT-оптимизации производительности
• GC Heap Hard Limit для 32-битных процессов
• Поддержка архитектур RISC-V и s390x

➡️ SDK

dotnet run теперь интерактивно выбирает целевой фреймворк и устройство
dotnet test поддерживает позиционные аргументы
dotnet watch — горячая перезагрузка для изменений в ссылках и настраиваемые порты

➡️ C# и F#

• C#: передача коллекций как аргументов
• C# расширенная поддержка раскладки памяти

• F#: параллельная компиляция включена по умолчанию, ускорение компиляции вычислительных выражений, новые флаги --disableLanguageFeature и --typecheck-only для FSI.

➡️ Блог разработчиков

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

👩‍💻 Гендерный разрыв в IT

Женщины в IT получают на 40% меньше мужчин — и первый порыв объяснить это дискриминацией. Но если копнуть в данные, картина усложняется. Разрыв формируется ещё в вузе, продолжается в выборе специализации и усиливается декретом.

➡️ Разбираем, где именно ломается воронка

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

👨‍💻 Конец эпохи State Machine

Все мы знаем и любим async/await. Но знаете ли вы, что под капотом происходило всё это время?

Каждый раз когда вы писали async метод — компилятор переписывал его в большой конечный автомат. Вместо вашего чистого кода генерировался struct, реализующий IAsyncStateMachine, с MoveNext(), switch-блоками и кучей вспомогательных полей.

Microsoft перенесла управление async из компилятора прямо в рантайм. Теперь компилятор больше не генерирует конечный автомат — он просто помечает метод атрибутом [MethodImpl(MethodImplOptions.Async)] и делает шаг назад.

Рантайм сам перехватывает await-точки через AsyncHelpers.Await(...), сохраняет только нужные переменные и возобновляет выполнение когда Task готов.

Что это даёт:

• Zero allocation в happy path — никакого лишнего боксинга
• Читаемые стектрейсы с вашими именами методов, а не MoveNext
• Рантайм понимает async-семантику и может оптимизировать цепочки await
• Native AOT поддержка из коробки

Как включить сейчас:

<Features>$(Features); runtime-async=on</Features>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
<TargetFramework>net11.0</TargetFramework>


Это самое крупное изменение в async-инфраструктуре .NET за последние годы.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🧩 API Gateway в .NET

API Gateway — это единая точка входа, которая берёт на себя маршрутизацию, аутентификацию, rate limiting и агрегацию запросов. Клиент общается с одним адресом — Gateway сам разбирается, куда слать запрос.

Ocelot vs YARP

Ocelot — классика жанра. Конфигурируется через JSON, прост в старте:

{
"Routes": [{
"UpstreamPathTemplate": "/api/orders/{id}",
"DownstreamPathTemplate": "/orders/{id}",
"DownstreamHostAndPorts": [{"Host": "orders-svc", "Port": 80}]
}]
}


YARP — от Microsoft, более гибкий. Конфигурируется и через JSON, и прямо в коде:
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));


Микросервисы не занимаются проверкой токенов — это задача Gateway. Один раз настроите JWT-валидацию:
app.MapReverseProxy(pipeline => {
pipeline.UseAuthentication();
pipeline.UseAuthorization();
});


И все сервисы за Gateway уже защищены.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

Интегрируем AI в .NET экосистему

Бизнес требует внедрения AI, но не в виде игрушки, а как надёжного сервиса. Мы пересобрали курс «Разработка AI-агентов», сделав упор на промышленные стандарты и архитектуру.

В программе:

🔹 От демо к инжинирингу. Управление ресурсами, кэширование, роутинг и контроль затрат (FinOps для AI).

🔹 RAG нового уровня. Пайплайны для работы с «грязными» данными, таблицами и корпоративными базами знаний.

🔹 LangGraph PRO. Управляемые графы состояний, time-travel для отладки и возможность отката действий.

🔹 Юридический контур. Развёртывание решений с учётом 152-ФЗ.

Стартуй сейчас!
Материалы пре-подготовки доступны сразу.

🎟 Промокод Agent — скидка 10 000 ₽ (до 28 февраля).

👉 Инженерный подход к AI

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🤩 Microsoft раскрыла планы по развитию PowerShell и OpenSSH

Пока Microsoft работает над устранением болевых точек Windows 11, компания параллельно вкладывается в инструменты для опытных пользователей.

PowerShell 7.7 станет центральным обновлением года. Среди ключевых изменений: профили и модули переедут из папки Documents в более подходящее место, появится возможность загружать модули без правки профиля, отложенные уведомления об обновлениях для пакетных менеджеров, поддержка псевдонимов и макросов в стиле Bash, а также собственный MCP-сервер.

Windows OpenSSH может получить поддержку аутентификации через EntraID.

➡️ Источник

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

⚡️ Синхронная коммуникация: HTTP/REST и gRPC

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

HTTP/REST — разумный дефолт

Большинство проектов начинается именно с REST. Низкий порог входа, понятная отладка через браузер или Postman, огромная экосистема. В .NET для этого используют HttpClient через IHttpClientFactory.

Создание нового HttpClient на каждый запрос — классическая ошибка, которая приводит к исчерпанию сокетов под нагрузкой.

builder.Services.AddHttpClient<IOrderService, OrderService>(client =>
{
client.BaseAddress = new Uri("https://orders-api/");
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.Timeout = TimeSpan.FromSeconds(30);
});

public class OrderService(HttpClient client) : IOrderService
{
public async Task<Order?> GetAsync(Guid id, CancellationToken ct)
{
var response = await client.GetAsync($"orders/{id}", ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Order>(ct);
}
}


Сразу добавляйте Polly для повторных попыток и circuit breaker. Без этого первый же временный сбой соседнего сервиса положит весь флоу.

Когда не стоит использовать: если между сервисами тысячи вызовов в секунду с жёсткими требованиями по latency — смотрите в сторону gRPC.

gRPC — когда миллисекунды на счету

gRPC работает поверх HTTP/2, использует бинарную сериализацию через Protocol Buffers и генерирует типизированный клиент из .proto-файла. Это означает меньше трафика, меньше CPU на сериализацию и строгий контракт, нарушение которого не скомпилируется.

// orders.proto
service Orders {
rpc GetOrder (OrderRequest) returns (OrderReply);
}
message OrderRequest { string id = 1; }
message OrderReply { string id = 1; string status = 2; }

// в сервисе
builder.Services.AddGrpcClient<OrdersClient>(o =>
o.Address = new Uri("https://orders-grpc-service"));

public class OrderHandler(OrdersClient grpc)
{
public async Task<OrderReply> Handle(GetOrderQuery q, CancellationToken ct)
=> await grpc.GetOrderAsync(
new OrderRequest { Id = q.Id.ToString() },
cancellationToken: ct);
}


Подводный камень: не тащите gRPC туда, где достаточно REST. Если у вас 10 запросов в минуту — вы просто добавите сложность без выигрыша в производительности.

Публичный API или фронтенд — REST без вариантов. Internal-сервисы с высокой нагрузкой и строгим контрактом — gRPC. Если сомневаетесь — начните с REST, профилируйте, и переходите на gRPC там, где это реально болит.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🆚 Микросервисная архитектура vs Монолит

Монолит — это традиционный подход к разработке, где всё приложение представляет собой единое целое. Весь код, бизнес-логика, UI и доступ к данным находятся в одном проекте и деплоятся как единая единица.

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

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

Каждый сервис — это отдельное .NET приложение (мини-монолит) со своей БД, API и бизнес-логикой.

Ключевые отличия

➡️ Структура и организация кода

Монолит:

• Один репозиторий, один проект
• Общие зависимости для всех модулей
• Прямые вызовы методов между компонентами

Микросервисы:

• Множество репозиториев или монорепозиторий
• Независимые зависимости для каждого сервиса
• Коммуникация через API: HTTP, gRPC, сообщения

➡️ Развертывание

Монолит:

• Деплой всего приложения целиком
• Один сервер или кластер серверов
• Простая инфраструктура

Микросервисы:

• Независимый деплой каждого сервиса
• Контейнеризация и оркестрация
• Сложная инфраструктура с API Gateway, Service Discovery

➡️ Масштабирование

Монолит:

• Вертикальное масштабирование — увеличение ресурсов сервера
• Горизонтальное масштабирование всего приложения
• Неэффективно, если нагрузка только на одну функцию

Микросервисы:

• Горизонтальное масштабирование конкретных сервисов
• Масштабируются только те части, которым нужно
• Оптимизация затрат на инфраструктуру

➡️ База данных

Монолит:

• Одна общая база данных
• ACID транзакции работают нативно
• Простота в управлении данными

Микросервисы:

• База данных на каждый сервис
• Распределенные транзакции
• Eventual Consistency

➡️ Технологический стек

Монолит:

• Единый стек технологий
• Сложно внедрить новые технологии
• Один язык программирования

Микросервисы:

• Каждый сервис может использовать свой стек
• Гибкость в выборе технологий
• Можно смешивать .NET, Node.js, Python и др.

Микросервисы — не серебряная пуля, а инструмент для решения конкретных проблем масштаба и сложности.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🌐 Forward или Reverse Proxy

Forward и Reverse прокси звучат похоже — оба это прокси. Но работают в противоположных направлениях и решают разные задачи. Давайте разберёмся.

➡️ Forward Proxy

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

Forward прокси — это посредник между вами и интернетом.

Главное: целевой сервер не знает, что запрос пришёл от вас. Он видит только прокси.

Зачем его используют:

• Логирование всех запросов
• Блокировка нежелательных сайтов
• Экономия трафика через кеширование
• Контроль безопасности

➡️ Reverse Proxy — противоположная история

Теперь представьте большую компанию с множеством отделов.
Когда кто-то звонит в компанию, он попадает на секретаря. Секретарь решает, в какой отдел перенаправить звонок.
Звонящий не знает внутреннюю структуру компании. Он общается только с секретарём.

Reverse прокси — это секретарь для ваших серверов.

Клиент не знает, что за прокси находятся несколько серверов. Он видит только один публичный адрес.

➡️ Реальные примеры использования

1. Балансировка нагрузки

У вас 5 серверов с одним приложением:

Клиент → Reverse Proxy → [Сервер 1]
→ [Сервер 2]
→ [Сервер 3]
→ [Сервер 4]
→ [Сервер 5]

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

2. Единая точка входа

У вас микросервисная архитектура:

• API на порту 5000
• Frontend на порту 3000
• Админ-панель на порту 8080
• WebSocket сервер на порту 4000

Без reverse прокси пользователю нужно знать все эти порты. А с reverse прокси можно сделать так:
https://mysite.com/api → Сервер на :5000
https://mysite.com/ → Сервер на :3000
https://mysite.com/admin → Сервер на :8080
https://mysite.com/ws → Сервер на :4000


3. SSL/TLS

Вместо настройки HTTPS на каждом сервере, настраиваете один раз на прокси. Проще управлять сертификатами, меньше нагрузка на бэк.

4. Защита и безопасность

Внутренние серверы вообще не видны из интернета. Видна только прокси.

5. Кеширование
Статический контент: картинки, CSS и JS; можно кешировать на прокси. Не нужно каждый раз обращаться на бэк.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📎 Пропущенный аудит

Метод ExecuteUpdate в EF Core ускоряет массовые обновления, но пропускает перехватчики и события SaveChanges. Это может сломать аудит изменений.

Перехватчик SaveChanges ловит изменения из трекера. Но ExecuteUpdate работает напрямую с БД и игнорирует его:

public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
{
// Захватывает изменения от SaveChanges
var entries = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Modified);

// Но не от ExecuteUpdate!
}


Решение через ручной аудит

Добавляйте аудит вручную перед или после ExecuteUpdate:
await context.Products
.Where(p => p.CategoryId == categoryId)
.ExecuteUpdateAsync(s => s
.SetProperty(p => p.Price, newPrice)
.SetProperty(p => p.ModifiedAt, DateTime.UtcNow)
.SetProperty(p => p.ModifiedBy, currentUser));

// Отдельная запись аудита
context.AuditLogs.Add(new AuditLog
{
Action = "BulkPriceUpdate",
EntityType = "Product",
Details = $"Обновил CategoryId={categoryId}, цена={newPrice}"
});
await context.SaveChangesAsync();


Альтернативы для полного аудита

• Триггеры на уровне БД ловят все изменения, включая ExecuteUpdate. Минус — сложнее тестировать и отлаживать.

• Библиотеки расширения EF предлагают BulkUpdate с встроенным аудитом через опции UseAudit.

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

⚡️ C# убил Builder для простых случаев

Target-typed new убирает дублирование типов при инициализации, а with-выражения делают иммутабельные обновления одной строкой кода.

Раньше при создании объекта приходилось дважды писать тип: var user = new UserDto { ... }. Теперь достаточно User user = new() { ... }. Компилятор выводит тип из контекста — при возврате из методов, в параметрах, элементах коллекций

Работа с иммутабельными объектами традиционно требовала copy-конструкторов, Builder'а или AutoMapper. with-выражение создаёт поверхностную копию с изменением указанных свойств: var updated = user with { Age = 31 }. Компилятор генерирует код копирования автоматически.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

👨‍💻 Span<T> как ref struct

Разбираем вопрос с собеса, в этот раз про Span.

Span<T> даёт доступ к памяти без копий и аллокаций. Но почему его сделали ref struct с кучей запретов, и когда лучше взять Memory<T>?

Ответ лежит в нашем канале с вопросами с собесов

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#dotnet_challenge

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📂 File-Scoped Namespaces

Cинтаксис file-scoped namespaces заменяет привычное объявление пространства имён со скобками на однострочное с точкой с запятой.

Раньше каждый файл в C# проекте начинался с namespace MyApp.Services { ... }, что добавляло один уровень отступа ко всему коду внутри. Теперь достаточно написать namespace MyApp.Services; в начале файла, и всё содержимое автоматически находится в этом пространстве имён без дополнительной вложенности.

Код становится более плоским. Вместо того чтобы начинать каждый класс с двух уровней отступа: namespace + class, вы сразу работаете с одним.

При рефакторинге, когда нужно переместить класс в другой namespace, старый подход требовал изменения строки с объявлением. В git diff это выглядело как замена всего файла, даже если логика класса не менялась. C новым подходом меняется только одна строка вверху.

Компилятор не видит разницы — это чисто синтаксический сахар. IL-код идентичен, производительность не меняется.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🌼 Бустим рабочее место за 10 шагов

Удаленка — это не только про свободу выбора места работы, но и про умение создать условия для продуктивности.

➡️ В карточках 5 советов по обустройству рабочего места, а остальные в статье.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

⚡️ Почему IQueryable не должен покидать слой данных

Разработчики используют EF Core и передают IQueryable по всему приложению, считая это эффективным подходом. Но такая практика создаёт серьёзные проблемы с архитектурой и сопровождением кода.

IQueryable позволяет строить динамические SQL-запросы, выполняя фильтрацию на стороне БД, а не в памяти приложения:

var startDate = new DateTime(2023, 1, 1);

// Плохо: загружаем всё в память, затем фильтруем
var allRecords = context.Purchases.ToList();
var filtered = allRecords.Where(p => p.Date > startDate).ToList();

// Хорошо: фильтрация выполняется базой данных
var filtered = context.Purchases
.Where(p => p.Date > startDate)
.ToList();


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

Антипаттерн: распространение IQueryable.

Типичный сценарий — IQueryable передаётся через слои:
// Репозиторий
public class PurchaseRepository
{
public IQueryable<Purchase> GetAll() =>
_context.Purchases.AsQueryable();
}

// Сервис бизнес-логики
public class PurchaseService
{
public IQueryable<Purchase> GetActive() =>
_repository.GetAll().Where(p => p.Status == "Active");
}

// Контроллер
var userPurchases = _service.GetActive()
.Where(p => p.UserId == currentUserId);

// Представление
@foreach(var item in Model.Where(p => p.IsCompleted))
{
<!-- рендеринг -->
}


Проблемы:

• Логика доступа к данным размазана по всему приложению
• Нарушен принцип единственной ответственности
IQueryable наследует IEnumerable — код может не знать, что работает с запросом к БД
• Исключения в runtime, если EF не может преобразовать код в SQL
• Сложное тестирование и отладка

Решить эти проблемы можно, к примеру, паттерном спецификация, или, если спецификации избыточны, передавайте выражения:
public interface IPurchaseRepository
{
Task<List<Purchase>> FindAsync(Expression<Func<Purchase, bool>> predicate);
}

// Использование
var purchases = await _repository.FindAsync(
p => p.IsCompleted && p.UserId == currentUserId);


Запрос по-прежнему выполняется в БД, но логика доступа к данным инкапсулирована.

💬 Как бы вы решили эту проблему

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🧑‍💻 Закон Хайрама: почему вы не можете изменить свой API

Формулировка закона:

При достаточном количестве пользователей API не имеет значения, что вы обещаете в контракте — все наблюдаемое поведение вашей системы станет чьей-то зависимостью.


Вы создали API. Написали документацию. Явно указали, что гарантируете, а что нет.
Но пользователи будут полагаться не только на задокументированное поведение, но и на:

• Порядок элементов в ответе
• Формат временных меток
• Текст сообщений об ошибках
• Время отклика
• Недокументированные эндпоинты
• Побочные эффекты
• Баги

И любое изменение этого может сломать чей-то прод.

Excel и ошибка 1900 года

В ранних версиях Excel был баг: 1900 год считался високосным, но таковым не являлся. Пользователи создали сложные таблицы, которые зависели от этого неправильного вычисления.

Microsoft не мог просто исправить баг — это сломало бы тысячи таблиц. Ошибка сохранялась до Excel 2003. Даже сейчас есть опция «Enable iterative calculation» для совместимости.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view

Читать полностью…
Subscribe to a channel