.NET Разработчик
6.5K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1930. #ЗаметкиНаПолях
Организуем Минимальные API в
ASP.NET Core
Часто говорят, что нельзя использовать минимальные API для «реальных» приложений. Скорее всего, причина в том, что большинство примеров очень просты и не показывают, как организовать код так, чтобы это имело смысл для более крупного приложения.

Проблема в том, что в ASP.NET все примеры заканчиваются некоторыми конечными точками в Program.cs со встроенным кодом (как показано ниже), и на самом деле это не то, каким вы хотите видеть код в более крупном приложении:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/todos", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todos/{id}",
async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

app.Run();

Здесь мы используем пример из руководства Microsoft. Итак, рассмотрим, как организовать код в что-то более удобочитаемое.

1. Используем методы расширения для организации конечных точек
В папке Endpoints или Todos (если вы используете чистую архитектуру) создадим файл TodoEndpoints.cs:
public static class TodoEndpoints
{
public static void
RegisterTodoEndpoints(this WebApplication app)
{
app.MapGet("/todos", async (TodoDb db) =>
await db.Todos.ToListAsync());

app.MapGet("/todos/{id}",
async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

}
}

Это сразу сделает файл Program.cs намного чище:

var app = builder.Build();

app.RegisterTodosEndpoints();
app.Run();

Также мы можем разделить группы конечных точек по файлам.

2. Используем TypedResults вместо Results
TypedResults позволит нам не использовать атрибут или метод Produces для описания типа возвращаемого значения для OpenAPI/Swagger. Тип будет выведен из типа возвращаемого значения:
app.MapGet("/todos/{id}", 
async Task<Results<Ok<Todo>, NotFound>> (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());


3. Отделим функциональность от регистрации
Лямбда–выражение из метода конечной точки можно вынести в отдельный метод, что улучшит читаемость и тестируемость:
app.MapGet("/todos/{id}", GetById);

static async Task<Results<Ok<Todo>, NotFound>>
GetById(int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();

Тогда регистрация TodoEndpoints будет выглядеть так:
app.MapGet("/todos", GetAll);
app.MapGet("/todos/{id}", GetById);

// методы

Теперь в тестах методы можно вызывать напрямую, а не через API.

4. Группируем
Конечные точки можно сгруппировать, чтобы избежать повторения кода:
var todos = app.MapGroup("/todos");

todos.MapGet("/", GetAll);
todos.MapGet("/{id}", GetById);

Также к группе можно применять общие политики, тэги и т.п.
var todos = app.MapGroup("/todos")
.RequireAuthorization()
.WithTags("Todos");

Источник: https://www.tessferrandez.com/blog/2023/10/31/organizing-minimal-apis.html
👍43👎1
День 1931. #TipsAndTricks
Обновляем Сертификат для localhost в .NET Core

Многие команды не используют https на машинах разработчиков, поскольку не хотят возиться с сертификатами. Хорошей новостью является то, что добавить или обновить сертификат локального хоста в случае истечения срока при использовании Kestrel очень просто. Для управления самоподписанным сертификатом можно использовать встроенную команду dotnet dev-certs.

При необходимости сначала можно удалить существующий сертификат:
dotnet dev-certs https --clean

Вывод:
Cleaning HTTPS development certificates from the machine. A prompt might get displayed to confirm the removal of some of the certificates.
HTTPS development certificates successfully removed from the machine.

(Очистка сертификатов разработки HTTPS с компьютера. Может появиться запрос на подтверждение удаления некоторых сертификатов.
Сертификаты разработки HTTPS успешно удалены с компьютера.)


Теперь сгенерируем новый самоподписанный сертификат, и сразу добавим его в доверенные:
dotnet dev-certs https --trust

Вывод:
Trusting the HTTPS development certificate was requested. A confirmation prompt will be displayed if the certificate was not previously trusted. Click yes on the prompt to trust the certificate.
Successfully created and trusted a new HTTPS certificate.

(Было запрошено доверие к сертификату разработки HTTPS. Если сертификат ранее не был доверенным, отобразится запрос на подтверждение. Нажмите «Да» в запросе, чтобы доверять сертификату.
Успешно создан новый доверенный сертификат HTTPS.)

Наконец, следующая команда проверяет существующие сертификаты:
dotnet dev-certs https --check

Output:
A valid certificate was found: 189E61FFAD59C21110E9AD13A009B984EE5E8D5D - CN=localhost - Valid from 2024-04-22 13:11:50Z to 2025-04-22 13:11:50Z - IsHttpsDevelopmentCertificate: true - IsExportable: true

Run the command with both --check and --trust options to ensure that the certificate is not only valid but also trusted.

(Был найден действительный сертификат: 189E61FFAD59C21110E9AD13A009B984EE5E8D5D - CN=localhost - действителен с 22 апреля 2024 г., 13:11:50Z по 22 апреля 2025 г., 13:11:50Z - IsHttpsDevelopmentCertificate: true - IsExportable: true

Запустите команду с параметрами --check и --trust, чтобы убедиться, что сертификат не только действителен, но и добавлен в доверенные.)


Источник: https://bartwullems.blogspot.com/2024/05/net-core-renew-localhost-certificate.html
👍51
День 1933.
Microsoft Build 2024

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

Мероприятие пройдёт как офлайн в Сиэтле, так и онлайн, и онлайн доступ бесплатный.

Вот некоторые из докладов, которые я отметил для себя (на самом деле их гораздо больше - вот полное расписание):
21 мая
21:30 мск - Developer’s Guide to Customizing Microsoft Copilot

22 мая
00:30 мск - Developer experience improvements in Windows
23:00 мск - Demystify Cloud-Native Development with .NET Aspire

23 мая
00:30 мск - .NET Aspire development on any OS with the Visual Studio family
04:00 мск - What’s new in GitHub Copilot and Visual Studio
18:30 мск - What’s New in C# 13
23:45 мск - Leverage Azure Testing Services to build high quality applications

Также есть набор предварительно записанных выступлений, которые можно посмотреть в любое время:
- What's New with WinForms in .NET 9?
- Enhancing .NET MAUI: Quality, Performance, and Interoperability in .NET 9
- Diagnostic techniques for .NET running on Linux and within containers
- .NET API development end-to-end
- EF Core 9: Evolving Data Access in .NET
- How to Quickly Build a .NET Desktop Dashboard
- May the forms be with you: a new hope with Blazor Hybrid on WinForms

Зарегистрироваться на Microsoft Build 2024 можно здесь.
👍2👎2
День 1933. #ЗаметкиНаПолях
Избегаем Конфликтов при Локальном Тестировании Шаблонов Проектов

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

Для использования шаблона проекта dotnet используются команды dotnet install и dotnet new, чтобы создать новый проект на основе локального шаблона. Однако dotnet install может конфликтовать с предыдущей установкой шаблона, опубликованного как пакет NuGet. Рассмотрим, как избежать этого конфликта и как протестировать шаблон локально.

В dotnet есть несколько скрытых аргументов, которые можно использовать для отладки шаблона. Аргумент --debug:custom-hive позволяет указать произвольное расположение кэша шаблонов. Это полезно, если вы хотите протестировать свой шаблон, не затрагивая глобальный кэш шаблонов:
dotnet install /путь/к/шаблону --debug:custom-hive /temp/hive

Теперь можно создать новый проект на основе установленного шаблона, передав в качестве параметра путь к вашему локальному кэшу:
dotnet new my_template --debug:custom-hive /temp/hive

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

Источник: https://www.meziantou.net/how-to-avoid-conflicts-when-testing-your-dotnet-templates-locally.htm
👍3
День 1934. #ЗаметкиНаПолях #Frontend
ASP.NET Unobtrusive Validation. Группируем Сообщения об Ошибках
Сегодня пост из личного опыта. Немного легаси и немного «попрограммируем» на HTML+CSS 😊
Не знаю, как дела обстоят в UI фреймворках, таких, как React или Vue, т.к. не знаком с ними, но мы в компании до сих пор используем старый добрый HTML и jQuery. В том числе для валидации форм используется встроенная в ASP.NET jQuery Unobtrusive Validation. Она позволяет проверять формы на клиенте без написания логики валидации на JS. То есть в модели используются стандартные атрибуты Data Annotations.
Например, для модели:
public class MyModel
{
[Required]
public string Name { get; set; }
}

И формы
<form method="post">
Email: <input asp-for="Name" /> <br />
<span asp-validation-for="Name"></span>

</form>

Будет сгенерирован следующий HTML код:
<form method="post">
Name: <input name="Name" id="Name" type="text" value=""
data-val-required="The Name field is required."
data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Name"></span>

</form>

И эта форма автоматически будет проверяться средствами JS согласно этим атрибутам на клиенте без отправки формы на сервер. В случае непрошедшей валидации в элемент <span> будет добавляться соответствующее сообщение об ошибке, а его класс сменится с field-validation-valid на field-validation-error. Можно сделать так, что все ошибки формы будут выдаваться в одном месте (validation summary), либо для каждого поля в отдельности (как в этом случае).

Конечно, если требуется нестандартная валидация, можно как написать свой атрибут валидации, так и добавить в JS валидатор для него.

Мне же требовалось, чтобы только 2 поля формы поля проверялись в паре и выдавали одно сообщение об ошибке на двоих в одном и том же месте, даже если оба поля не пройдут валидацию. Проблема подробно описана на StackOverflow. Там у автора была дата рождения, у меня аналогично двумя выпадающими списками выбирались месяц и год истечения срока кредитной карты.

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

В общем то, решение довольно простое. Размещаем оба поля и оба валидатора для них:
<form method="post">
Expires:
<select asp-for="Month" asp-items="Model.Months"></select> /
<select asp-for="Year" asp-items="Model.Years"></select>
<div class="val-msg-group">
<span asp-validation-for="Month"></span>
<span asp-validation-for="Year"></span>
</div>

</form>

В этом случае, если оба поля будут пустыми, будут выведены оба сообщения об ошибке, по одному для каждого. Это решается просто сокрытием второго сообщения средствами CSS:
.val-msg-group .field-validation-error ~ .field-validation-error {
display: none;
}

Здесь мы внутри блока val-msg-group скрываем всех потомков, за которыми следует элемент с тем же классом field-validation-error. То есть, первое сообщение об ошибке будет показано (для какого бы поля оно ни было), а остальные будут спрятаны. См. видео ниже.

В общем, думаю, что приём будет полезен и в других UI фреймворках, работающих по подобному принципу.
👍5👎1
День 1935. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 9. Качество требований каждый определяет по-своему. Начало
У требований к ПО есть своя аудитория: как разработчики, которые будут использовать их для выполнения своей работы, так и представители клиентов, которые получат или будут использовать продукт. Именно получатели, а не разработчики требований, должны оценивать их качество.
Если кто-то обнаружит проблемы с моими требованиями, то это повод пересмотреть их. И неважно, насколько хорошими считал их я. Создатели (бизнес-аналитики) и получатели (архитекторы, дизайнеры, разработчики, тестировщики и др.) этих сводов знаний должны согласовать их содержание, формат, организацию, стиль и уровень детализации.

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

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

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

Аудитория: Спонсор, отдел маркетинга, клиенты
Вопросы:
- Позволит ли решение, основанное на требованиях, достичь целей, поставленных клиентами?
- Понятны ли риски и последствия для бизнеса, связанные с каждым требованием?

Пользователи:
- Понятно ли каждое требование?
- Достаточно ли точно каждое требование выражает потребность клиента?
- Будет ли решение, основанное на этом наборе требований, удовлетворять мои потребности?
- Все ли требования необходимы?

Руководитель проекта:
- Сможет ли команда разработать решение с учетом имеющихся ресурсов и в рамках существующих ограничений?
- Позволяет ли описание каждого требования оценить его сложность и влияние на проект?

Бизнес-аналитик, владелец продукта, продакт-менеджер:
- Каждое ли требование представляет ценность для клиента?
- Являются ли требования чёткими и однозначными?
- Отсутствуют ли конфликты между требованиями?

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

Тестировщик:
- Можно ли протестировать выполнение каждого требования?

Другие заинтересованные стороны:
- Соответствуют ли требования всем ожиданиям и ограничениям, накладываемым моей точкой зрения?

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍6
День 1936. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 9. Качество требований каждый определяет по-своему. Окончание

Начало

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

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

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

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

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

5. Необходимость
Каждое требование описывает возможность, в которой действительно нуждаются те или иные заинтересованные стороны.

6. Приоритетность
Требования классифицируются по их относительной важности и своевременности включения в продукт.

7. Отслеживаемость
Каждому требованию присваивается уникальный идентификатор, чтобы его можно было связать с источником и далее с проектами, кодом, тестами и любыми другими элементами, созданными для удовлетворения этого требования. Знание источника дает дополнительный контекст и показывает, к кому можно обратиться за разъяснениями.

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

9. Достоверность
Существует некий объективный, однозначный и эффективный способ определить, удовлетворяет ли решение требованиям. Наиболее распространённый способ проверить достоверность — тестирование, поэтому некоторые называют эту характеристику тестируемостью.

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍3
День 1937. #ЗаметкиНаПолях
Введение в Первичные Конструкторы в C#12. Начало

Первичные конструкторы были впервые представлены в C#9 как часть функциональности записей. В C#12 они стали доступны обычным типам классов и структур.

Чтобы создать тип Person до C#12 нужно было бы написать примерно такой класс:
public class Person
{
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
}

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

Способы использования параметров первичного конструктора:
1. Инициализировать члены (или передать их базовому конструктору).
2. Использовать прямую ссылку на параметры.

1. Инициализация членов параметрами
Инициализация членов напрямую аналогична определению класса, показанному выше, но уменьшает часть дублирования и, как правило, более компактна:
public class Person(string firstName, string lastName)
{
public string FirstName { get; } = firstName;
public string LastName { get; } = lastName;
}

Мы почти вдвое сократили количество кода, при этом сохранив его намерение понятным и очевидным. Заметьте, здесь мы используем классы, но для структур всё будет так же. Аналогично можно задать и приватные поля:
public class Person(string firstName, string lastName)
{
private readonly string _firstName = firstName;
private readonly string _lastName = lastName;

public string FullName => $"{_firstName} {_lastName}";
}

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

2. Ссылаемся на параметры конструктора в членах класса
Если значения приватных полей не используются нигде, кроме свойства FullName, мы можем использовать первичный конструктор, чтобы ещё больше «упростить» класс:
public class Person(string firstName, string lastName)
{
// Напрямую ссылаемся на
// параметры первичного конструктора
public string FullName => $"{firstName} {lastName}";
}

Но мы можем пойти и дальше, и изменять параметры первичного конструктора:
public class Person(string firstName, string lastName)
{

public void SetName(string first, string last)
{
firstName = first;
lastName = last;
}
}

Если вы выполните следующий код, вы увидите, что параметры конструктора изменяемые:
var p = new Person("Andrew", "Lock");
p.SetName("Jon", "Smith");
Console.WriteLine(p.FullName); // "Jon Smith"

Далее рассмотрим, как это работает «под капотом».

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

Источник:
https://andrewlock.net/an-introduction-to-primary-constructors-in-csharp-12/
👍13
День 1938. #ЗаметкиНаПолях
Введение в Первичные Конструкторы в C#12. Окончание

Начало

Отличный способ понять многие новые функции C#, особенно те, которые по сути являются синтаксическим сахаром, — использовать Sharplab.io, чтобы увидеть, как компилятор «понижает» функции до более простого C#.

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

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

Если вы ссылаетесь на параметры основного конструктора где-либо, кроме инициализатора поля или свойства, компилятор автоматически сгенерирует поля с невалидными в обычном C# именами, типа
private string <firstName>P;

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

Добавление проверки к инициализации
Одной из общих проблем, связанных с такими функциями, как первичные конструкторы, является проблема «роста». Есть риск, что функция отлично работает в простых случаях, но как только вам нужно что-то более сложное, приходится полностью от неё отказаться.

Например, в конструкторы обычно добавляют код проверки на null или недопустимость аргументов. В первичных конструкторах их по-прежнему можно использовать:
public class Person(string firstName, string lastName)
{
private readonly string _firstName =
firstName
?? throw new ArgumentNullException(nameof(firstName));
private readonly string _lastName =
IsValidName(lastName)
? lastName
: throw new ArgumentNullException(nameof(lastName));

public string FullName => $"{_firstName} {_lastName}";

private static bool IsValidName(string name)
=> name is not "";
}

Вот результат компиляции.

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

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

Источник: https://andrewlock.net/an-introduction-to-primary-constructors-in-csharp-12/
👍12
День 1939. #ЗаметкиНаПолях
Функции C# и Целевая Платформа

Если вы заглянете на официальную страницу версий языка C#, то можно подумать, что существует сильная связь между целевой платформой и версией языка. И действительно, если вы не укажете версию языка явно в файле проекта, она будет выбрана на основе целевой платформы: C# 12 для .net8, C# 11 для .net7 и C# 7.3 для .NET Framework.

Фактическая же связь между версией языка и целевой платформой более тонкая. Есть три способа, как функция языка может быть связана с целевой платформой:
1. Просто работает
Некоторые функции работают сразу после установки версии языка. Установите нужную версию языка в блоке компиляции файла проекта
<LangVersion>12.0</LangVersion>

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

2. Требуется специальное определение типа
Требует, чтобы компилятор обнаруживал специальные типы. Специальные типы добавляются в основной выпуск .net, соответствующий конкретной версии C#, но вы можете добавить их в свою компиляцию вручную, чтобы эти функции заработали. Компилятору не важно, откуда берётся определение типа: из целевой платформы, из NuGet-пакета или просто является частью проекта.
Например, следующая запись с init-сеттером и обязательным свойством:
public record class MyRecord
{
public int X { get; init; }
public required int Y { get; set; }
}

доступна в проекте netstandard2.0 или net472 при добавлении в проект следующего кода:
namespace System.Runtime.CompilerServices
{
// для init-сеттера
internal class IsExternalInit { }
// для обязательного свойства
internal class RequiredMemberAttribute : System.Attribute { }
internal sealed class CompilerFeatureRequiredAttribute(string featureName) : System.Attribute
{
public string FeatureName { get; set; } = featureName;
}
}
// для обязательного свойства
namespace System.Diagnostics.CodeAnalysis
{
internal class SetsRequiredMembersAttribute : System.Attribute { }
}

Чаще всего компилятор сообщит вам, какого типа или члена типа ему не хватает для успешной компиляции.

3. Зависит от времени выполнения
Лишь небольшая часть новых возможностей языка требует поддержки среды исполнения. Это, например, реализации интерфейсов по умолчанию, встроенные массивы или ref-поля. Такие функции не будут компилироваться, если целевая платформа их не поддерживает. В этом случае компилятор сообщит, что целевая среда выполнения не поддерживает эту функцию.

Трюк с InternalsVisisbleTo
Существует проблема с показанным ранее решением для 2го варианта. Допустим, у вас есть MyProject.csproj, ориентированный на netstandard2.0, и MyProject.Tests.csproj, ориентированный на net8.0 с InternalVisibleTo("MyProject.Tests") внутри MyProject.csproj для доступа к приватным классам проекта из тестов.

В этом случае вы не сможете скомпилировать MyProject.Tests.csproj с ошибкой о повторяющемся определении члена, поскольку тип IsExternalInit будет доступен из двух мест — из MyProject.csproj и из библиотеки времени выполнения .net8.0.

Решение довольно простое: используйте несколько целевых сред выполнения в проекте MyProject.csproj, например:
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>


Источник: https://sergeyteplyakov.github.io/Blog/c%23/2024/03/06/CSharp_Language_Features_vs_Target_Frameworks.html
👍14
День 1940. #МоиИнструменты
Генерируем http-файлы из Спецификации OpenAPI

Http-файлы хороши и удобны, но их обновление также требует определенных усилий. Почему бы не сгенерировать их из cпецификации OpenAPI?

Когда вы создаете новый http-файл для веб-API в .NET 8, вы получаете файл MyProjectName.http. Он выглядит примерно так:
@MyWebApp_HostAddress = https://localhost:5059

GET {{MyWebApp_HostAddress}}/weatherforecast/
Accept: application/json

###

Этот файл используется расширением REST Client в Visual Studio Code, Visual Studio и Rider, что позволяет отправлять HTTP-запросы к вашему API прямо из редактора.

Но есть проблема: их обновление может быть хлопотным. Представьте, что мы добавляем в наш API две новые конечные точки:
app.MapPost("/weatherforecast", 
(CreateWeatherDto dto) => TypedResults.Ok())
.WithName("CreateWeatherForecast")
.WithOpenApi();
app.MapDelete("/weatherforecast/{id}",
(int id) => TypedResults.Ok())
.WithName("DeleteWeatherForecast")
.WithOpenApi();

Чтобы сгенерировать для них запросы в http-файл, используем утилиту httpgenerator.

Установка её очень проста:
dotnet tool install --global httpgenerator 

Теперь её можно использовать, передав ей путь к спецификации OpenAPI (локальный или, при запущенном API, URL):
httpgenerator https://localhost:5059/api/v1/openapi.json --output-type OneFile

--output-type OneFile объединит все конечные точки в один файл, иначе вы получите n файлов для каждой конечной точки, что не очень удобно. Файл всегда будет называться Requests.http и будет помещён в текущий каталог (если не указано иное с помощью параметра --output). Поэтому вы можете переименовать его в MyProjectName.http, чтобы соответствовать соглашению об именах. Получим примерно такой результат:
@contentType = application/json

###################################
### Request: GET /weatherforecast
###################################

GET https://localhost:5059/weatherforecast
Content-Type: {{contentType}}

####################################
### Request: POST /weatherforecast
####################################

POST https://localhost:5059/weatherforecast
Content-Type: {{contentType}}

{
"temperatureC": 0,
"summary": "summary"
}

###########################################
### Request: DELETE /weatherforecast/{id}
###########################################

### Path Parameter: DeleteWeatherForecast_id
@DeleteWeatherForecast_id = 0


DELETE https://localhost:5059/weatherforecast/{{DeleteWeatherForecast_id}}
Content-Type: {{contentType}}

Утилита также поддерживает другие параметры, в том числе передачу Bearer-токенов, о чём можно почитать на странице проекта в GitHub.

Источник: https://steven-giesel.com/blogPost/9fa236ef-67da-4113-95e7-99770dc70444/generate-http-files-from-a-swagger-definition
👍20
Какая из перегрузок метода AddNumbers будет выбрана? (см. картинку в первом комментарии)
#Quiz #CSharp
Anonymous Quiz
45%
int[]
31%
IEnumerable<int>
24%
ReadOnlySpan<int>
👍12👎1
День 1941. #Книги
«Как пасти котов. Наставление для программистов, руководящих другими программистами» (Рейнвотер Дж. — СПб.: Питер, 2022).

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

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

Если вы нацелились на роль тимлида, обязательно прочитайте, что вас ждёт и как с этим справиться.
👍25👎2
День 1942. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 10. Требования должны быть достаточно хорошими, чтобы разработка могла продолжаться с приемлемым уровнем риска

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

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

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

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

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

2. Широта знаний
Объём информации, содержащейся в спецификации. В неё входят все требования пользователей или только высокоприоритетные? Учитываются все атрибуты качества или только первостепенные? Очевидно ли читателю требований, что перед ним полные или не полные требования? Если набор требований неполный, то все ли читатели увидят одни и те же пробелы? Если подразумеваемые и предполагаемые требования нигде не фиксируются, то высока вероятность, что они останутся незамеченными.

3. Глубина детализации
Учитывают ли требования возможные исключения (ошибки) и определяют ли, как система должна их обрабатывать? Если спецификация определяет нефункциональное требование, например установку, охватывает ли она также удаление, повторную установку, восстановление и установку обновлений и исправлений? Любые требования, и функциональные, и нефункциональные, должны быть достаточно полными и точными, чтобы их можно было проверить в реализованном решении.

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

Многие команды Agile-разработки ПО не оформляют все детали требований в письменном виде, но это не значит, что эти детали не нужны разработчикам и тестировщикам. Если письменная информация недоступна во время реализации, то должна быть возможность получить её из нужного источника. В противном случае члены команды должны будут сами восполнять пробелы, что может быть отмечено заказчиком как недостаток. А это будет означать, что требования не полностью готовы для реализации.

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍8
День 1943. #Оффтоп
Быть Разработчиком ПО «На Вызове» - это Нормально?
Сегодня (в выходной день для обычных людей) рассмотрим вопрос с одного из форумов Stackexchange по поводу работы дежурными «на вызове».

Вопрос:
На команду разработчиков возложили обязанности участвовать в ротации дежурных (on-call, т.е. быть доступным в нерабочее время на случай сбоев), чтобы все команды в компании были в одинаковых условиях. Понятно, что люди против того, что с них требуют дополнительную работу без дополнительной компенсации. Но кажется, что ротация дежурств довольно стандартная практика для наёмных разработчиков. Если ПО сломается в нерабочее время, кто ещё его исправит, если не разработчики?

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

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

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

Наконец, есть золотая середина: компании, которым необходимы круглосуточная работа ПО, но которые недостаточно велики, чтобы иметь крупные специализированные службы поддержки и процессы SDLC, гарантирующие, что все проекты пишут логи в одно место, и организация может эффективно контролировать среду для обнаружения систем и процессов, которые выходят из строя, и т. д. Если у вас нет команды, которая круглосуточно присматривает за системами и обнаруживает за годы работы, что «система A требует перезагрузки каждые пару часов из-за утечки памяти», или «если система B падает, то нужно сократить количество работающих серверов системы C, пока всё не восстановится», тогда да, вероятно, разработчикам различных систем придётся делать это самим. Для таких компаний среднего уровня очень характерно иметь ротацию дежурств среди разработчиков, потому что никто другой не может это делать. Разработчики, естественно, бывают недовольны, когда компании переходят от «нас не волнует, работают ли системы, пока все спят» к «нам нужно, чтобы системы работали 24 часа в сутки, 7 дней в неделю, но у нас нет бюджета на три смены администраторов, так что мы просто добавим ответственности разработчикам».

Расскажите в комментариях, как обстоят дела с этим в вашей компании? Есть ли у вас служба поддержки, ротация дедурств или вы решаете все проблемы только в рабочие часы?

Источник: https://workplace.stackexchange.com/questions/185868/are-on-call-responsibilities-for-software-developers-normal-or-unusual
👍7
День 1944. #ЗаметкиНаПолях
Мысли о Первичных Конструкторах. Начало

См. также Введение в Первичные Конструкторы в C#12.

Лучшие варианты использования
Рассмотрим случаи, для которых первичные конструкторы хорошо подходят.

1. Базовая инициализация поля
Первичные конструкторы сокращают объём кода, который вам нужно написать, и вместо этого компилятор генерирует этот код за вас:
public class Person(string firstName, string lastName)
{
private readonly string _firstName = firstName;
private readonly string _lastName = lastName;
}

Однако, даже добавление валидации уже делает код не таким очевидным:
public class Person(string firstName, string lastName)
{
private readonly string _firstName =
!string.IsNullOrEmpty(firstName)
? firstName
: throw new ArgumentException(
"Must not be null or empty", nameof(firstName));

}

Вы не можете использовать защитные методы, доступные в .NET 7+:
ArgumentException.ThrowIfNullOrEmpty(firstName);

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

2. Инициализация в тестовом коде
Возьмем простой пример теста xunit для типа Person:
public class PersonTests(ITestOutputHelper output)
{
[Theory]
[InlineData("Jon", "")]
public void Person_throws_if_null_or_empty(
string firstName, string lastName)
{
output.WriteLine(
$"Testing '{firstName}' and '{lastName}'");
Assert.Throws<ArgumentException>(() =>
new Person(firstName, lastName));
}
}

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

3. Внедрение зависимостей в контроллерах MVC
В ASP.NET Core, вы, вероятно, используете внедрение зависимостей и не часто проверяете зависимости, которые внедряются в контроллеры. Это делает их хорошими кандидатами для первичных конструкторов (можно даже использовать значения напрямую):
public class MyController(
ILogger<MyController> logger,
IService service) : ControllerBase
{
[HttpGet]
public ActionResult<Thing> Get()
{
logger.LogInformation("Get the thing");
return service.GetThing();
}
}

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

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

Тем не менее, при использовании первичных конструкторов есть вещи, на которые стоит обратить внимание.

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

Источник:
https://andrewlock.net/thoughts-about-primary-constructors-3-pros-and-5-cons/
👍12
День 1945. #ЗаметкиНаПолях
Мысли о Первичных Конструкторах. Окончание

Начало

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

1. Повторный захват
Если вы инициализируете поле с помощью параметра первичного конструктора, а также используете его значение в другом члене класса, у вас будет два поля, хранящих одно и то же значение:
public class Person(string name)
{
private string _name = name;

public string Greeting
=> $"Hello, {name}";
}

Эта проблема настолько очевидна, что компилятор выдаст предупреждение, если вы сделаете это, так что тут особо беспокоиться не о чем.

2. Неявные поля не могут быть readonly
Если вы неявно захватываете параметр первичного конструктора, компилятор генерирует поле для его хранения (см. подробнее). Важно, что они изменяемы, т.е. параметру (и этому полю) можно задавать значения в теле класса. Если это то, что вам нужно, нет проблем. Но если поле не должно быть изменяемо, обычно стоит отметить его как readonly, что упрощает понимание назначения поля и устраняет целый класс ошибок, связанных со случайным изменением поля. Не удивлюсь, если это исправят в будущих версиях C#, но сейчас это немного раздражает. Решение в том, чтобы не использовать неявный захват, если вам нужны поля только для чтения.

3. Путаница в соглашениях об именах
Не большая проблема, но это придётся обсудить в команде на раннем этапе: какие соглашения следует использовать для параметров первичного конструктора? Использовать ли для параметров префикс _? Если вы используете неявный захват в коде класса, то там они будут по сути как приватные поля:
public class Person(string _firstName, string _lastName)
{
public string FullName => $"{_firstName} {_lastName}";
}

С другой стороны, создание экземпляра такого класса будет выглядеть… странно:
var p = new Person(_firstName: "Jon", _lastName: "Smith");

Microsoft рекомендует именование как параметров (без префикса). Кстати, вы не можете использовать this. в коде для обращения к параметрам первичного конструктора, такой код просто не скомпилируется.

4. Неявные поля меняют размер структур
В высокопроизводительном коде или при работе с interop иногда бывает важен размер экземпляров и как выстроено их содержимое. Если у нас есть следующая структура:
public struct Person(
string first,
string last,
int age)
{
public int Age = age;
public string FullName => $"{first} {last}";
}

То мы имеем 3 поля (1 – int и 2 – ссылки на string) для неявных полей, всего 8*3=24 байта на экземпляр. Теперь, если мы изменим первичный конструктор:
public struct Person(
string name,
int age)
{
public int Age = age;
public string FullName => name;
}

Внезапно вместо 3 полей останется 2 и размер станет 16 байт, но это без явного изменения публичных полей структуры (если вы не знаете, как реализованы первичные конструкторы). Это может иметь очень плохие последствия, если мы полагаемся на размер Person где-то в коде!

5. Путаница с записями
Всё-таки стоит помнить, что, несмотря на почти идентичный синтаксис, следующие объявления не взаимозаменяемы:
public record Person(string FirstName, string LastName);

public class Person(string FirstName, string LastName);

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

Источник: https://andrewlock.net/thoughts-about-primary-constructors-3-pros-and-5-cons/
👍16👎1
День 1946. #ЧтоНовенького
Гибридный Кэш в .NET 9

В .NET 9 Превью 4 появился новый гибридный вид кэширования.

Кэш в памяти
IMemoryCache - позволяет кэшировать объекты в памяти. Это простое хранилище пар ключ-значение:
builder.Services.AddMemoryCache();

public async BlogPost GetPost(
int id, CancellationToken ct = default)
{
// Cache key
var key = $"BlogPost_{id}";
var post = await _memoryCache
.GetOrCreateAsync(key, async entry =>
{
return await _blogRepo.GetPostAsync(id, ct);
});

return post;
}

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

Распределённый кэш
IDistributedCache - обычно используется для связи между несколькими сервисами и/или если вам необходимо сохранить данные в течение всего срока службы вашего приложения (то есть после завершения работы и перезапуска сервера). Известный пример: Redis.
builder.Services
.AddStackExchangeRedisCache();

Распределённый кэш не имеет такого удобного метода, как GetOrCreateAsync, поэтому вам придётся вручную пытаться извлечь значение из кэша с помощью GetAsync, а если он вернёт null, то получить значение и сохранить его в кэш.

«Паника в кэше» (cache stampede) — это тип каскадного сбоя, который может возникнуть при высокой нагрузке. Сбой возникает, если на получение значения требуется больше времени, чем время между запросами этого значения. Тогда все эти запросы не будут использовать кэш, а вызовут код получения значения. В этом случае ожидание заполнения кэша может быть лучшей стратегией, но этот код нужно писать вручную.

Гибридный кэш
HybridCache (абстрактный класс, а не интерфейс!) - представлен в превью .NET 9, но доступен (благодаря netstandard2.0) даже для .NET Framework 4.7.2 и призван заменить оба предыдущих вида:
builder.Services.AddHybridCache();

public async BlogPost GetPost(
int id, CancellationToken ct = default)
{
return await cache.GetOrCreateAsync(
$"BlogPost_{id}",
async cancel =>
await _blogRepo.GetPostAsync(id, cancel),
token: ct
);
}

Это очень похоже на API IMemoryCache, но «под капотом» ведёт себя как IDistrubtedCache, при этом решая проблемы паники в кэше. По умолчанию используется кэш в памяти, но при добавлении распределённого кэша в сервисы, он будет подключен к гибридному кэшу автоматически. Кроме того, он добавляет несколько новых функций, таких как создание тэгов и возможность удалить все значения в кэше по тегу или флаги, например, чтобы отключить сохранение данных в распределённом кэше.

Источник: https://steven-giesel.com/blogPost/e3ee537a-f70b-43d3-8d13-5cf81bc4406b/memorycache-distributedcache-and-hybridcache
👍35
День 1947. #ЗаметкиНаПолях #BestPractices
Правильное Логирование Минимальных API в Serilog. Начало
В этой серии рассмотрим, как с максимальной пользой использовать Serilog в приложениях минимальных API ASP.NET Core 8. Серия будет состоять из 3х частей.

1. Настройка
Приложения ASP.NET Core — «всего лишь» консольные приложения. Вы можете настроить Serilog и использовать его вообще без каких-либо особенностей ASP.NET Core. Для «пустого» проекта «минимального API» файл Program.cs будет выглядеть так:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Установим Serilog и консольный приёмник (sink):
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console

В начале Program.cs создадим пайплайн логирования и назначим его статическому свойству Log.Logger:
using Serilog;

Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();

Log.Information("Starting up");

// … остальной код минимального API

Теперь при запуске должно появиться сообщение «Starting up» от Serilog, а затем вывод по умолчанию от ASP.NET.

Если приложение не запускается, мы хотим перехватить все возникающие исключения, а также убедиться, что все события буферизованного журнала записаны перед завершением процесса. Добавим try/catch/finally:
using Serilog;

// … пайплайн логирования

try
{
// … код минимального API
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Unhandled exception");
return 1;
}
finally
{
await Log.CloseAndFlushAsync();
}

Здесь мы можем использовать класс Log для записи собственных структурированных событий журнала.

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

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

Источник:
https://nblumhardt.com/2024/04/serilog-net8-0-minimal/
👍5