Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин
Win+Shift+S для снимка экрана, потом в уведомлениях Snip & Sketch для рисования на нём.dotnet test имеет много полезных опций. Одной из них является --blame-hang, которая позволяет сделать дамп процесса и всех дочерних процессов, когда тест зависает. Вы можете настроить желаемый тайм-аут и тип дампа, который хотите сделать. Затем можно загрузить дамп в артефакты сборки и проанализировать его локально:
dotnet test --blame-hang-timeout 5m --blame-hang-dump-type fullЕсли тест зависает, он завершается с ошибкой и выводит как минимум два файла: файл дампа для каждого процесса и файл последовательности. Файл последовательности содержит список тестов, которые выполнялись до зависания. Если вы используете CI, например GitHub Actions, вы можете загрузить файл дампа как артефакт:
name: sample
on:
workflow_dispatch:
push:
branches:
- '*'
jobs:
run_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: dotnet test a.sln --configuration Debug --blame-hang-timeout 30s
- uses: actions/upload-artifact@v3
if: always()
with:
name: results
retention-days: 1
path: TestResults/**/*
Источник: https://www.meziantou.net/generating-a-dump-file-when-tests-hang-on-a-ci-machine.htmdotnet new sln -n EverythingВ приведённом выше примере флаг -n позволяет указать имя; в противном случае по умолчанию будет использоваться текущее имя папки. Результатом этой команды является пустой файл решения с именем Everything.sln. Далее вам просто нужно добавить все файлы проекта. К сожалению, нет встроенного способа сделать это с помощью команды dotnet sln, но вы можете легко написать это в сценарии. Следующая команда должна работать из Powershell или в Linux:
dotnet sln add (ls -r **/*.csproj)Команда рекурсивно найдёт файлы .csproj в любой подпапке и добавит их в решение. С помощью всего лишь этих двух команд вы можете легко добавить множество проектов в файл решения, не добавляя их вручную. Если у вас уже есть файл решения в папке, из которой вы запускаете команду, вам просто нужно указать имя файла решения в команде. Например, если файл решения называется Everything.sln, запустите следующую команду:
dotnet sln ./Everything.sln add (ls -r **/*.csproj)Источник: https://ardalis.com/add-all-projects-to-solution/
const int keySize = 64;
const int iterations = 350000;
var hashAlgorithm = HashAlgorithmName.SHA512;
string HashPasword(string password, out byte[] salt)
{
salt = RandomNumberGenerator.GetBytes(keySize);
var hash = Rfc2898DeriveBytes.Pbkdf2(
Encoding.UTF8.GetBytes(password),
salt,
iterations,
hashAlgorithm,
keySize);
return Convert.ToHexString(hash);
}
Функция HashPassword() принимает пароль в виде открытого текста и возвращает его хешированную версию. Мы получаем случайную соль, сгенерированную в процессе, через выходной параметр salt. Важно получить и сохранить эту часть данных, так как она необходима для последующей проверки пароля.
keySize - желаемый размер в байтах результирующего хэша и размер случайной соли. Мы должны согласовать это значение с размером хэша, который производит базовый алгоритм хеширования.
PBKDF2 можно применять несколько раз к заданному входному значению для усиления результирующего хэша, поэтому добавим константу iterations. Считается, что безопасные значения для производственных сред исчисляются сотнями тысяч.
Наконец, в переменной hashAlgorithm определяем базовый метод хеширования, который PBKDF2 будет использовать для вывода, в данном случае SHA512.
Пример использования:
var hash =
HashPasword("clear_password", out var salt);
Console.WriteLine($"Hash: {hash}");
Console.WriteLine($"Salt: {Convert.ToHexString(salt)}");
Проверка пароля
Т.к. мы не можем расшифровать хеш-алгоритмы, нужно снова хешировать входящий пароль и сравнивать результат с сохранённой хешированной версией:
bool VerifyPassword(
string password,
string hash,
byte[] salt)
{
var hashToCompare =
Rfc2898DeriveBytes.Pbkdf2(
password, salt, iterations,
hashAlgorithm, keySize);
return hashToCompare.SequenceEqual(
Convert.FromHexString(hash));
}
Функция VerifyPassword() принимает чистый пароль, сохранённый хэш пароля и связанную с ним соль. Она вернет true, если предоставленный чистый пароль сгенерирует тот же хэш. Очень важно предоставить те же значения для iterations, hashAlgorithm и keySize, что и при начальном хешировании.
Другие классы и библиотеки
BCrypt.Net-Next — сторонняя библиотека для хеширования паролей, основанная на алгоритме BCrypt.
Класс PasswordHasher<TUser> является частью пакета Microsoft.AspNetCore.Identity, который реализует хеширование и проверку паролей на основе PBKDF2 со случайными значениями соли и итераций.
Источник: https://code-maze.com/csharp-hashing-salting-passwords-best-practices/[HttpGet]
public IResult Get()
{
return Results.Ok(
new { Name = "My name" });
}
К сожалению, технически это не ошибка: нет предупреждения во время компиляции об использовании IResult с MVC и нет ошибки во время выполнения. Вместо этого MVC сериализует объект IResult в JSON, что похоже на то, что вы хотите, пока вы не увидите вывод:
{
"value": {
"name" : "My name"
},
"statusCode" : 200,
"contentType" : null
}
Как видите, MVC не просто сериализует объект, который мы передали в Ok(), он сериализует весь объект Results. Это напоминает некоторые ужасные API, которые возвращают код состояния 200, но содержат в теле statusCode: 400!
В .NET 7 MVC добавили ограниченную поддержку сериализации объектов IResult, и, как и следовало ожидать, вместо результата выше вы получаете следующий вывод:
{
"name" : "My name"
}
Когда вы возвращаете IResult из метода действия MVC, вы не получаете никаких функций MVC, таких как согласование содержимого и средства форматирования вывода; вы всегда получаете JSON. Тем не менее, если вы создаёте API только с JSON, этот вариант имеет преимущество в том, что вы потенциально можете совместно использовать больше компонентов в ваших минимальных API и контроллерах MVC, просто возвращая IResult, вместо того чтобы реализовывать обработку как для IActionResult, так и для IResult.
Источник: https://andrewlock.net/5-new-mvc-features-in-dotnet-7/var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<MvcOptions>(opts =>
{
opts.AllowEmptyInputInBodyModelBinding = true;
});
Другой вариант - включить его отдельно для каждого метода действия, добавив явный атрибут [FromBody] и установив EmptyBodyBehavior, например:
public IActionResult Post(
[FromBody(EmptyBodyBehavior=EmptyBodyBehavior.Allow)]
MyBody? body)
{
// body будет null при пустом теле
}
Это работает, но это слишком длинно и уродливо…
В .NET 7 всё становится намного проще. Вы можете полагаться на nullable-аннотации. Обнуляемый параметр подразумевает EmptyBodyBehavior.Allow, а необнуляемый - EmptyBodyBehavior.Disallow:
public IActionResult Post(MyBody? body)
{
// body будет null при пустом теле
}
public IActionResult Post(MyBody body)
{
// При пустом теле будет ошибка 400
}
2) Определение необязательности сервиса
В .NET 6, если вы не зарегистрировали тип сервиса в контейнере внедрения зависимостей, то, даже если декорированный атрибутом [FromService] параметр помечен как обнуляемый, попытка вызова конечной точки API приводит к ошибке:
InvalidOperationException: No service for type 'SomeService' has been registered (Сервис типа 'SomeService' не был зарегистрирован)
В .NET 7 обнуляемый параметр с атрибутом [FromServices] будет null, если он недоступен в DI:
public IEnumerable<MyClass> Get(
[FromServices] SomeService? service)
{ … }
Вам может быть интересно, что произойдёт, если вы попытаетесь привязать обнуляемый сервис, который не зарегистрирован в DI, используя автоматическое определение сервиса, описанное в части 2?
Ответ: скорее всего, не то, чего вы хотите. Если тип параметра (SomeService выше) не зарегистрирован в DI, он не будет рассматриваться как сервис, он будет рассматриваться как любой другой сложный тип. Это означает, что MVC попытается привязать его свойства к любым доступным параметрам: телу запроса, строке запроса, заголовкам и т.п.
Источник: https://andrewlock.net/5-new-mvc-features-in-dotnet-7/app.MapGet("/", (IGreeterService service)
=> service.SayHello();
Эквивалент в .NET 6 выглядел бы так:
public class SomeController : Controller
{
public void IActionResult Get(
[FromServices] IGreeterService service)
{
return service.SayHello();
}
}
В .NET 6, атрибут [FromServices] обязателен, иначе MVC попытается связять параметр service с телом запроса. В .NET 7 можно обойтись без него:
public class SomeController : Controller
{
public void IActionResult
Get(IGreeterService service)
{
return service.SayHello();
}
}
Эта функциональность основывается на сервисе IServiceProviderIsService, представленном в .NET 6, и должна «просто работать». Понятно, что [FromService] вряд ли будет использоваться с контроллерами, гораздо более предпочтительный вариант будет внедрение через конструктор, но тем не менее это потенциально удаляет немного шаблонного кода и снова идёт в ногу с минимальными API.
Кстати, о IServiceProviderIsService. Смысл в нём в том, что нужен способ узнать, что за параметр передаётся методу действия. Одним (плохим) решением было бы, чтобы API всегда предполагал, что это сервис, и пытался получить его из контейнера DI. Однако при этом будет предпринята попытка создать экземпляр сервиса, что может иметь непредвиденные последствия. Представьте, что контейнер записывает в журнал каждую попытку получить несуществующий сервис, чтобы вам было легче обнаруживать неверные настройки. Это может повлиять на производительность, если запись в журнал будет делаться для каждого отдельного запроса.
Итак, сервис IServiceProviderIsService:
public partial interface IServiceProviderIsService
{
bool IsService(Type serviceType);
}
Он предоставляет единственный метод, который можно вызвать, чтобы проверить, зарегистрирован ли данный тип сервиса в контейнере внедрения зависимостей. Сам сервис IServiceProviderIsService также можно получить из контейнера. Если вы используете пользовательский контейнер, в котором не добавлена поддержка этой функции, то вызов GetService<IServiceProviderIsService>() вернёт null.
Источники:
- https://andrewlock.net/5-new-mvc-features-in-dotnet-7/
- https://andrewlock.net/exploring-dotnet-6-part-10-new-dependency-injection-features-in-dotnet-6/