.NET Разработчик
6.51K 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
День 1409. #Тестирование
Динамическое Тестирование Безопасности Приложений
Dynamic application security testing (DAST) может помочь обнаружить недостатки безопасности в вашем коде. И может делать это автоматически в процессе сборки.

В прошлом автоматизированное тестирование безопасности было довольно медленным, ресурсоемким, и часто результаты были полны ложных срабатываний. Сегодня существует DAST — это ПО, которое взаимодействует с запущенным приложением, автоматически отправляя запросы и ответы, пытаясь найти уязвимости в веб-приложениях и API. Если приложение или API находится в Интернете, это означает, что любой может сканировать его с помощью динамического сканера. Хотя существуют системы, которые могут блокировать некоторые из атак, большинство сайтов не используют их (из-за стоимости, задержки и требуемых ресурсов), т.е. большинство веб-приложений и API хотя бы один раз были просканированы на уязвимости.

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

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

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

DAST может работать по принципу «plug-n-play», подразумевая, что настройка практически не требуется. Но тогда придётся подождать несколько часов для получения результатов и запускать каждое сканирование вручную.

Один из вариантов ускорить тесты - создать архивные файлы HTML (HAR) и передать их в DAST. Если у вас есть команда обеспечения качества (QA), которая создаёт файлы HAR для автоматизации тестирования UI, эта стратегия может работать очень хорошо. DAST проверяет только те функции и код, которые являются частью HAR. Тесты пройдут значительно быстрее, и будет тестироваться только небольшая часть приложения, которая содержится в HAR. Однако, если вы не создаёте и не обновляете файлы HAR, добавится много работы.

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

Ещё вариант — удалить любые тесты, которые просто не применимы к тому, что вы создали. Если вы создали приложение .Net Core, размещённое в Azure с базой данных MS SQL, вам не нужно запускать тесты для MongoDB, AWS, WordPress и множество других, включённых в большинство DAST.

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

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

Источник: https://stackoverflow.blog/2022/11/30/continuous-delivery-meet-continuous-security/
👍6
День 1410. #ЧтоНовенького #DotNet8
Замороженные Коллекции
Продолжаем рассматривать, чего новенького могут предложить нам в 8й версии .NET. Сегодня рассмотрим, что такое замороженные коллекции и как они работают.

Вот код, который работает в альфа-версии .NET8:
List<int> list = new() { 1, 2, 3 };
ReadOnlyCollection<int> readonlyList =
list.AsReadOnly();
ImmutableList<int> immutableList =
list.ToImmutableList();
FrozenSet<int> frozenSet =
list.ToFrozenSet();

list.Add(4);

list.Count; // 4
readonlyList.Count; // 4
immutableList.Count; // 3
frozenSet.Count; // 3

ReadOnlyList — это просто «представление» объекта, из которого он был создан. Поэтому, если вы обновите список, из которого он был первоначально создан, ReadOnlyList также отразит это изменение. Пользователь ReadOnlyList просто не имеет возможности изменить внутреннее состояние самого списка.

Замораживаемую коллекцию можно изменять до тех пор, пока она не будет заморожена. После этого она больше не отражает изменений. Поэтому Count = 3.
Неизменяемый список также содержит только 3 элемента, но при этом неизменяемые коллекции имеют эффективные способы изменения списка путем создания нового:
// это создаст новый неизменяемый список
var newList = immutableList.Add(2);
Для замороженных коллекций такого поведения не предусмотрено.

На данный момент в .NET8 есть два типа замороженных коллекций: FrozenSet и FrozenDictionary. Возникает вопрос: зачем они?

Замороженные объекты вследствие своих ограничений могут дать преимущества в производительности. Часто коллекции инициализируются в какой-то момент, но никогда не меняют своего состояния. Например, посмотрим для списка из чисел от 1 до 1000 тест скорости метода Contains:
public void Lookup()
{
for (var i = 0; i < 1000; i++)
_ = list.Contains(i);
}

List: 57.561 us
FrozenSet: 1.963 us
HashSet: 2.997 us
ImmutableHashSet: 15.422 us

Особенности замороженных коллекций:
1. Они действительно доступны только для чтения и неизменны. Они создаются таким образом, чтобы данные были плотными и оптимально организованными для быстрого чтения.
2. При создании находится оптимальный размер таблицы сегментов, чтобы уменьшить или устранить коллизии хэшей, что сокращает среднее время поиска.
3. Особое внимание уделяется коллекциям со строковыми ключами. Различные компараторы выбираются динамически для повышения скорости хеширования во время поиска. Код просматривает все ключи и пытается найти кратчайшую подстроку, которая создаёт уникальный набор ключей. Обычно это значительно сокращает количество символов, используемых при вычислении хэш-кодов во время поиска.
4. Эти коллекции могут содержать не более 64КБ состояния, что покрывает 99% случаев.

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

Вот репозиторий с примерами кода и тестами.

Источник: https://steven-giesel.com/blogPost/34e0fd95-0b3f-40f2-ba2a-36d1d4eb5601
👍17
Developing-on-AWS-with-CSharp.pdf
12.7 MB
День 1411. #Книги
Сегодня порекомендую вам (и сохраню для себя, т.к. ещё не читал) книгу «Developing on AWS with C#», O’Reilly Media, 2023.

Авторы Ноа Гифт, основатель Pragmatic AI Labs, и Джеймс Чарльзуорт, технический руководитель Pendo, расскажут вам о широте инструментов .NET на AWS. Вы изучите методы использования контейнеров Linux и Windows и бессерверной архитектуры для создания, обслуживания и масштабирования современных приложений .NET на AWS. Из этой книги вы узнаете, как сделать ваши приложения более современными, отказоустойчивыми и экономичными. Кроме того, в книге описано:
- Как начать создавать решения с помощью C# на AWS.
- Какие существуют лучшие практики DevOps для AWS, инструменты и сервисы разработки, которые предоставляет AWS.
- Как успешно перенести устаревшее приложение .NET на AWS.
- Как разрабатывать бессерверные микросервисы .NET на AWS.
- Как использовать контейнеризацию, переместить приложение в облако, отслеживать и тестировать.
👍15
День 1412. #ЗаметкиНаПолях #AsyncTips
Выполнение Кода с Помощью Планировщика. Начало: Создание Планировщика

Задача
Есть несколько частей кода, которые требуется выполнить определённым способом. Например, все они должны выполняться в UI-потоке или же в любой момент времени должно выполняться только определённое количество частей.

Решение
Здесь рассмотрим тип TaskScheduler, хотя, в .NET есть и другие способы решения этой задачи.

Простейшая разновидность — TaskScheduler.Default — ставит работу в очередь пула потоков. Его редко придётся использовать явно, потому что он используется по умолчанию во многих сценариях планирования: в Task.Run, в параллельном коде и в коде потоков данных.

Вы можете сохранить конкретный контекст и позднее спланировать работу в этом контексте:
var scheduler = 
TaskScheduler
.FromCurrentSynchronizationContext();
Этот код создаёт объект TaskScheduler, чтобы сохранить текущий контекст и спланировать выполнение кода в нём. Тип SynchronizationContext представляет контекст планирования общего назначения. В .NET предусмотрено несколько разных контекстов: многие UI-фреймворки предоставляют контекст SynchronizationContext, представляющий UI-поток, а в ASP.NET до Core предоставлялся контекст SynchronizationContext, представляющий контекст запроса HTTP. Также возможно напрямую использовать SynchronizationContext для выполнения кода в этом контексте; но это не рекомендуется. Там, где это возможно, используйте await для возобновления в неявно сохранённом контексте либо TaskScheduler.

ConcurrentExclusiveSchedulerPair — тип, представляющий в действительности два планировщика, связанных друг с другом:
- ConcurrentScheduler позволяет нескольким задачам выполняться одновременно, при условии, что ни одна задача не выполняется в ExclusiveScheduler;
- ExclusiveScheduler выполняет только по одной задаче за раз и только если в настоящее время никакие задачи не выполняются в ConcurrentScheduler.
var schPair = new ConcurrentExclusiveSchedulerPair();
var concurrent = schPair.ConcurrentScheduler;
var exclusive = schPair.ExclusiveScheduler;

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

Также тип может применяться в качестве регулирующего планировщика, который будет ограничивать собственный уровень параллелизма. При этом ExclusiveScheduler обычно не используется:
var schPair = new ConcurrentExclusiveSchedulerPair(
TaskScheduler.Default,
maxConcurrencyLevel: 8);
var scheduler = schPair.ConcurrentScheduler;

Заметьте, что такая регулировка влияет на код только во время его выполнения. В частности, асинхронный код не считается выполняемым во время ожидания операции. ConcurrentScheduler регулирует выполняющийся код; тогда как другие виды регулировки (такие, как SemaphoreSlim) осуществляют регулировку на более высоком уровне (т.е. всего async-метода.)

Заметьте, что конструктору ConcurrentExclusiveSchedulerPair передаётся объект TaskScheduler.Default. Это объясняется тем, что ConcurrentExclusiveSchedulerPair применяет свою логику конкурентности/эксклюзивности выполнения к существующему TaskScheduler.

Никогда не используйте платформенно-зависимые типы для выполнения кода в UI-потоке. WPF, Silverlight, iOS и Android предоставляют тип Dispatcher, Universal Windows использует тип CoreDispatcher, а в Windows Forms существует интерфейс ISynchronizeInvoke (т. е. Control.Invoke). Не используйте эти типы в новом коде; просто считайте, что их вообще нет. Эти типы только без всякой необходимости привязывают код к конкретной платформе. SynchronizationContext — абстракция общего назначения на базе этих типов.

Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 13.
👍11
День 1413. #ЗаметкиНаПолях #AsyncTips
Выполнение Кода с Помощью Планировщика. Окончание: Использование Планировщика

Задача:
требуется управлять выполнением отдельных фрагментов в параллельном коде.

Решение
После того как вы создадите экземпляр TaskScheduler (см. Создание Планировщика), можете включить его в набор параметров, передаваемых методу Parallel. Следующий код получает набор коллекций матриц, запускает несколько параллельных циклов и ограничивает общий параллелизм всех циклов одновременно независимо от количества матриц в каждом наборе:
void RotateMatrices(
IEnumerable<IEnumerable<Matrix>> collections,
float degrees)
{
var schPair = new ConcurrentExclusiveSchedulerPair(
TaskScheduler.Default,
maxConcurrencyLevel: 8);
var scheduler = schPair.ConcurrentScheduler;
var opts = new ParallelOptions {
TaskScheduler = scheduler };

Parallel.ForEach(
collections,
opts,
matrices => Parallel.ForEach(
matrices,
opts,
m => m.Rotate(degrees)));
}

Parallel.Invoke также получает экземпляр ParallelOptions, поэтому вы можете передать TaskScheduler при вызове Parallel.Invoke так же, как и для Parallel.ForEach. При выполнении динамического параллельного кода можно передать TaskScheduler непосредственно TaskFactory.StartNew или Task.ContinueWith.

Передать TaskScheduler коду Parallel LINQ (PLINQ) невозможно.

Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 13.
👍3
День 1414. #ЧтоНовенького
Новые Возможности MVC в .NET 7. Часть 1
В этой серии рассмотрим некоторые нововведения в ASP.NET Core в .NET 7.

1. IParseable/TryParse для привязки примитивных типов
MVC использует подход «поставщика привязки». Можно привязать параметры методов действий к значениям формы, строки запроса, заголовкам и т.п. По умолчанию MVC связывает некоторые простые типы или любые типы, имеющие TypeConverter из строки. Всё остальное рассматривается как сложный тип, т.е. либо привязывается к телу запроса, либо привязывается не сам тип, а каждое из отдельных свойств типа. Допустим, у нас есть тип:
public class Range
{
public int From { get; set; }
public int To { get; set; }
}
и привязка его в методе действия контроллера:
public class MyController : ControllerBase
{
public IEnumerable<MyClass> Get(Range days)
{ … }
}
В .NET 6 вам нужно либо написать TypeConverter для Range, либо привязать свойства From и To:
/MyController?from=1&to=3

В .NET 7 есть другой вариант. Можно реализовать интерфейс IParseable<T> и создать объект Range из любой строки. Например, мы хотим иметь такой URL:
/MyController/1-3

Формально реализовывать IParseable<T> не нужно, достаточно реализовать метод TryParse(). Пример реализации ниже. Детали не особенно важны, и здесь не учитывается множество пограничных случаев:
public readonly struct Range : IParsable<Range>
{
public int From { get; }
public int To { get; }

public Range(int from, int to)
{
From = from;
To = to;
}

public static bool TryParse(
string? value,
IFormatProvider? provider,
out Range result)
{
if (value != null)
{
var sep = value.IndexOf('-');
if (sep > 0 && sep < value.Length - 1)
{
var fromSpan = value.AsSpan().Slice(0, sep);
var toSpan = value.AsSpan(sep + 1);

if (int.TryParse(fromSpan,
out var from)
&& int.TryParse(toSpan,
out var to))
{
result = new Range(from, to);
return true;
}
}
}

result = default;
return false;
}

public static Range Parse(
string value,
IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
throw new ArgumentException();

return result;
}
}

Затем можно просто добавить атрибут к методу контроллера:
[HttpGet("{days}")]
public IEnumerable<MyClass> Get(Range days)
{ … }

Проще ли реализовать IParseable<T>, чем написать свой связыватель модели? Вероятно. Проще, чем свой TypeConverter? Возможно. Однако при этом подходе вы можете использовать одни и те же типы в минимальных API и в контроллерах. Это должно упростить переход от минимальных API к контроллерам (и наоборот).

Если же вы хотите отключить эту функцию, удалите TryParseModelBinderProvider из MvcOptions:
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<MvcOptions>(opts => {
opts.ModelBinderProviders
.RemoveType<TryParseModelBinderProvider>();
});

Источник: https://andrewlock.net/5-new-mvc-features-in-dotnet-7/
👍8
День 1415. #ЧтоНовенького
Новые Возможности MVC в .NET 7. Часть 2
В этой серии рассмотрим некоторые нововведения в ASP.NET Core в .NET 7.
Часть 1

2. Контроллеры MVC могут автоматически определять сервисы
Контроллеры в .NET 7 автоматически определяют сервисы, зарегистрированные в контейнере внедрения зависимостей, не требуя явного атрибута [FromServices]. В минимальных API вы можете просто внедрять сервисы непосредственно в конечные точки API:
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/
👍7
День 1416. #ЧтоНовенького
Новые Возможности MVC в .NET 7. Часть 3
В этой серии рассмотрим некоторые нововведения в ASP.NET Core в .NET 7.
Часть 1
Часть 2

3. Использование nullable-аннотаций
С каждым выпуском .NET поддержка ссылочных обнуляемых типов становится немного лучше. Для ASP.NET Core в .NET 7 она была добавлена в нескольких местах:

1) Обнуляемое тело запроса
В .NET 6 при привязке к телу запроса (независимо от того, используется ли явно [FromBody]), попытка привязки к запросу с пустым телом (где Content-Length == 0) завершится ошибкой "A non-empty request body is required." (Требуется непустое тело запроса).

Вы можете обойти это глобально в .NET 6 и разрешить null для параметра, настроив свойство MvcOptions.AllowEmptyInputInBodyModelBinding:
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/
👍4
День 1417. #юмор
В тему хайпа про ChatGPT.
👍11
День 1418. #Оффтоп
Сегодня порекомендую вам видео с канала Computerphile, которое называется «Horrible, Helpful, http3 Hack» https://youtu.be/wV9FSyFB8tk

HTTP3 вышел летом этого года, но создание его было сопряжено с некоторыми трудностями. В том числе пришлось использовать довольно кривой костыль. Ричард Дж. Клегг из Университета Королевы Мэри в Лондоне сначала напоминает про некоторые уровни сетевого взаимодействия, объясняет, что такое QUIC, и почему он не может определиться, любит он HTTP3 или ненавидит его.

Видео на английском, но доступны автоматические русские субтитры.
👍7
День 1419. #ЧтоНовенького
Новые Возможности MVC в .NET 7. Часть 4
В этой серии рассмотрим некоторые нововведения в ASP.NET Core в .NET 7.
Часть 1
Часть 2
Часть 3

4. Поддержка IResult
MVC имеет IActionResult и ActionResult<T>; минимальные API имеют IResult и IValueHttpResult<TValue>. И не стоит их путать!

К сожалению, это легче сказать, чем сделать. Если вы пишете много минимальных API, а затем погружаетесь в MVC, вы можете случайно написать что-то вроде этого:
[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/
👍8
День 1420. #Debugging
Я Исправил Ошибку. Что Дальше?
Чем больше кода вы пишете, тем больше ошибок создаёте. Т.е. как разработчик ПО вы должны тратить часть своего времени на отладку.

Есть несколько общих шагов при отладке приложения:
1. Получение всей необходимой информации для понимания проблемы (этапы воспроизведения, стек вызовов, журналы и т. д.)
2. Воспроизведение проблемы и отладка кода
3. Исправление кода

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

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

2. Легко ли воспроизвести проблему в среде разработки?
- Легко ли начать работу над проектом? (Получить код/Открыть проект в IDE/Начать отладку)
- Правильно ли задокументирован процесс?
- Нужно ли вручную настраивать секреты для подключения к внешним службам?
- Требуется ли дополнительное ПО на машине и как его получить?
- Легко ли настроить ту же среду, что и в рабочей среде (Azure Web App, Docker, Kubernetes)? Легко ли отлаживать эту среду?
- Можно ли получить анонимизированные живые данные, когда это необходимо?
- Если нельзя воспроизвести проблему в своей среде, можно ли вы отладить промежуточную/производственную версию или получить дамп? Будьте очень осторожны, когда делаете это, чтобы не заблокировать работающий сервис из-за достижения точки останова и не раскрыть важные данные.

3. Легко ли работать над кодовой базой?
- Хорошо ли организован код? Находите ли вы то, что ищете?
- Код легко читается? Соблюдается ли соглашение об именовании и стиль написания кода?
- Есть ли в коде неявные зависимости?
- Насколько быстро вы можете вносить изменения и наблюдать за их результатами в приложении?
- Можете ли вы воспользоваться своей IDE для отладки?
- Можете ли использовать точки останова и просматривать локальные значения или вычислять выражения?
- Ваши типы переопределяют ToString или отмечены атрибутом [DebuggerDisplay], чтобы вы могли быстро увидеть значения во время отладки?

4. Почему разработчик внёс эту ошибку в код?
На этом этапе вы должны найти причины, по которым разработчик допустил ошибку. Сделайте шаг назад и проанализируйте проблему.
- Код слишком сложный?
- Методы слишком длинные и слишком сложные?
- Используете ли вы правильный инструмент для выполнения работы?
- Возможна ли путаница? Например, два типа с одинаковым именем в разных пространствах имен.
- Ясно ли объясняются пред- и пост-условия метода (возможно ли передать/вернуть null, дату локальную/UTC, относительный/абсолютный путь?
- Отсутствует или неясна документация/комментарии?
- Влияет ли изменение части приложения на другую часть?
- Надёжны ли тесты?
- Есть ли хотя бы один сквозной тест для основного сценария приложения?

Источник: https://www.meziantou.net/how-to-correctly-fix-a-bug.htm
👍6
This media is not supported in your browser
VIEW IN TELEGRAM
День 1421. #ЧтоНовенького
Превью «Липкого» Скроллинга в VS 17.5
Писать длинные классы с многоуровневой вложенностью кода стало ещё приятнее в превью 2 Visual Studio 17.5. А всё благодаря новой функции «липкого скроллинга» (Sticky Scroll).

Она поддерживает контекст для кода, с которым вы работаете, сохраняя соответствующие заголовки. Когда вы прокручиваете код, пространства имён, классы и методы будут прилипать к верхней части редактора. См. видео. Помимо этого, нажатие мыши на заголовок приведёт к этой строке кода. Sticky Scroll поддерживает несколько форматов кода, включая C#, C++, XAML и JSON.

Sticky Scroll можно включить в меню Tools > Options > Text Editor > General > Sticky Scroll (Инструменты > Параметры > Текстовый редактор > Основные > Sticky Scroll). Там же вы можете установить максимальное количество строк, которые будут «прилипать».

Источник: https://devblogs.microsoft.com/visualstudio/sticky-scroll-now-in-preview/
👍20
День 1422. #BestPractices
Хеширование Паролей в C#. Начало
В системе, которая хранит учётные данные пользователей, нужно уделить особое внимание тому, как хранить пароли. Рассмотрим, как использовать методы хэширования в C# для безопасного хранения паролей пользователей.

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

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

По мере того, как всё больше вычислительной мощности становится доступной для широкой публики, алгоритмы хеширования, которые в прошлом считались достаточно безопасными (например, MD5 и SHA), стали небезопасными. Их нельзя использовать их для хеширования паролей. Следующие алгоритмы безопасны.
1. PBKDF2 — стандарт формирования ключа на основе пароля, изначально доступный в среде .NET. В отличие от MD5 или SHA, PBKDF2 повышает защиту пароля от метода грубой силы, добавляя дополнительную сложность.

2. Семейство алгоритмов BCrypt/SCrypt спроектировано так, чтобы интенсивно использовать ЦП и память, особенно новый SCrypt, который очень устойчив к атакам методом грубой силы.

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

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

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

Лучшие практики
1. Использовать разные соли для каждого пароля. Общая на всех соль делает этот метод намного менее эффективным.
2. Если возможно, хранить соли паролей отдельно.
3. Соль пароля должна быть трудной для угадывания
злоумышленником. Рекомендуется использовать криптографический генератор случайных чисел, такой как RandomNumberGenerator в пакете System.Security.Cryptography.
4. Соль должна быть достаточно длинной. Хорошее эмпирическое правило — сделать её того же размера, что и выходной хэш.

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

Источник:
https://code-maze.com/csharp-hashing-salting-passwords-best-practices/
👍20
День 1423. #BestPractices
Хеширование Паролей в C#. Окончание
Начало

Пример хеширования с помощью PBKDF2
Мы будем использовать статический метод Rfc2898DeriveBytes.Pbkdf2(). Он принимает несколько параметров, включая соль. Чтобы сгенерировать правильную случайную соль, воспользуемся статическим методом RandomNumberGenerator.GetBytes():
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/
👍19
День 1424. #Оффтоп #ЗадачиНаСобеседовании
Больше недели думал, писать ли что-нибудь про ChatGPT. Если кто не знает, что это, вот описание из русской Википедии:
«ChatGPT — это прототип чат-бота с искусственным интеллектом, разработанный OpenAI и специализирующийся на диалогах. ChatGPT — большая языковая модель, отлаженная как с помощью методов обучения с учителем, так и с подкреплением. Базовой моделью, которая была доработана, была языковая модель от OpenAI GPT-3.5, улучшенная версия GPT-3.
ChatGPT был запущен 30 ноября 2022 года и привлёк внимание своими подробными ответами и четкими формулировками, хотя его фактическая точность подверглась критике.»

Сам его пока не ковырял, не было времени, а отзывы в программистской среде пока самые разнообразные от восторженно-панических «программисты больше не нужны» до скептически-злорадных «да там косяк на косяке». Но сегодня всё-таки поделюсь пока самым впечатляющим из того что я видел. Видео от Клемента Михайлеску, создателя платформы AlgoExpert.io, которая предлагает задачи для собеседований на кодирование.

Клемент задал боту задачи «как есть», так, как они описаны на его сайте, предварительно дав ему контекст: «Ты проходишь собеседование в Google с написанием кода». Всего было 3 алгоритмических задачи разного уровня: простой, посложнее и самый сложный. Как сказал Клемент, если первые две задачи довольно стандартные и решения можно найти в сети, то третья (опять же, по его словам) в сети не встречается.

В общем, что из этого вышло, смотрите в новом видео от Клемента Михайлеску https://youtu.be/gOf2SQVMUL0

PS: Кстати, кому интересно, можете запаузить видео и попробовать решить сами.
👍10
День 1425. #юмор
👍18
День 1426. #ЗаметкиНаПолях
Добавляем Все Проекты в Решение с Помощью CLI
Допустим, по какой-то причине вам нужно быстро создать файл решения Visual Studio, включающий все проекты во всех подпапках. Как вариант, может быть нужно быстро рассчитать метрики кода для всего репозитория GitHub или вы просто хотите увидеть все проекты в одном месте. Может быть утомительно находить и добавлять каждый файл csproj в каждой подпапке вручную, особенно в кодовой базе, с которой вы не знакомы.

Команда dotnet sln
Файлы решений .NET можно создавать из командной строки. Синтаксис довольно прост:
dotnet 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/
👍15
День 1427. #УправлениеПроектом
Как Ускорить Конвейер Разработки ПО
Приоритетом в конвейере разработки ПО является скорость. Независимо от типа ПО, над которым вы работаете, скорость работы команды будет превыше всего. При этом необходимо знать некоторые стратегии, чтобы ускорить работу команды без ущерба для качества или безопасности кода. Вот советы, призванные помочь командам работать максимально быстро и эффективно.

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

2. Установите лимиты незавершенной работы
Сейчас большинство конвейеров разработки ПО соответствуют методологиям управления проектами Kanban или Scrum для отслеживания задач проекта на различных стадиях завершения. Эти элементы незавершенной работы (Work-In-Progress) помогают следить за прогрессом команды и её способностью выполнять больше работы.
Проблема в том, что часто этот список разрастается и выходит из-под контроля. Тогда члены команды пытаются работать в режиме многозадачности, переключаясь между различными элементами, чтобы попытаться избавиться от невыполненной работы. При этом темп работы команды обычно замедляется, а ошибки начинают проникать в код.
Люди плохо справляются с многозадачностью. Решение в том, чтобы не дать им этого сделать. Установка жёстких ограничений на количество элементов WIP, на каждом этапе рабочего процесса гарантирует, что члены команды не откусят больше, чем могут прожевать, и выполнят больше задач за меньшее время.

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

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

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

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

Источник: https://www.freecodecamp.org/news/how-to-speed-up-your-software-development-pipeline/
👍4
День 1428. #ЧтоНовенького
Пишем Markdown в Visual Studio
Markdown — отличное решение для форматирования кода. GitHub использует его для файлов readme, a Microsoft - в качестве стандарта для документации Visual Studio.

Редактор Markdown должен быть включен и доступен, если вы обновились до последней превью версии VS 17.5. Его можно включить и отключить в меню Tools > Options > Preview Features (Инструменты > Параметры > Функции предварительного просмотра), отметив или сняв флажок “Markdown language service” (Сервис языка Markdown).

Вот некоторые функции редактора Markdown:
- Семантическая подсветка синтаксиса в редакторе.
- Форматирование текста применяется прямо в редакторе.
- Окно предварительного просмотра, которое можно вызвать по Shift+F7.
- Большинство стандартных элементов управления редактора Visual Studio работают и в редакторе Markdown.

Источник: https://devblogs.microsoft.com/visualstudio/write-markdown-without-leaving-visual-studio/
👍4