Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин
System.Web с адаптерами, позволяющими коду ссылаться на общий API, ориентированный на .NET Standard 2.0 и пригодный для использования как в контексте ASP.NET, так и в контексте ASP.NET Core.
Недавно вышла вторая превью версия этого средства миграции. Обновленный инструментарий включает в себя улучшения кода, сгенерированного расширением Visual Studio, дополнения в адаптерах System.Web и возможность совместного использования аутентификации между приложениями ASP.NET и ASP.NET Core.
Установите (или обновите, если использовали раньше) расширение Microsoft Project Migrations для Visual Studio. Оно автоматически будет ссылаться на обновлённую версию адаптеров System.Web.
Пошаговая миграция
Многие крупные проекты используются постоянно в критически важных бизнес-приложениях. Они должны продолжать функционировать и не могут быть приостановлены из-за потенциально длительного перехода на ASP.NET Core. Это означает, что во время миграции приложение должно продолжать работать, а новые функции могут добавляться и развёртываться как обычно. Паттерн Душитель используется для замены существующей устаревшей системы по частям, пока вся система не будет обновлена, а старая система не будет выведена из эксплуатации.
Мы имеем приложение ASP.NET, размещённое в IIS и включающее набор процессов для развёртывания и обслуживания. Процесс миграции направлен на переход к ASP.NET Core без ущерба для текущего развёртывания.
Первый шаг — представить новое приложение на основе ASP.NET Core, которое станет точкой входа. Трафик будет поступать в приложение ASP.NET Core, и, если приложение не сможет сопоставить маршрут, оно передаст запрос в приложение ASP.NET через YARP. Большая часть кода по-прежнему будет находиться в приложении ASP.NET, но приложение ASP.NET Core теперь получает первоначальный трафик и управляет маршрутизацией.
Чтобы начать миграцию бизнес-логики, использующей HttpContext, должны быть созданы библиотеки на основе Microsoft.AspNetCore.SystemWebAdapters. Это позволяет библиотекам, использующим API System.Web, ориентироваться на .NET Framework, .NET Core или .NET Standard 2.0.
Теперь можно начать перемещать маршруты по одному в приложение ASP.NET Core. Это могут быть контроллеры MVC или веб-API (или даже один метод из контроллера), страницы веб-форм, обработчики HTTP или какие-либо другие реализации маршрута. Как только маршрут станет доступен в приложении ASP.NET Core, он будет сопоставляться и обслуживаться оттуда. Со временем основное приложение начнёт обрабатывать всё больше маршрутов.
По ходу миграции у вас может быть маршрут как в приложении ASP.NET Core, так и в приложении ASP.NET Framework. Здесь возможно выполнить A/B-тестирование, чтобы убедиться, что функциональность соответствует ожиданиям.
Когда приложение .NET Framework больше не нужно, его можно удалить. При этом приложение ASP.NET Core по-прежнему будет использовать адаптеры. Последним шагом будет отказаться от использования адаптеров, полностью переведя приложение на платформу ASP.NET Core, чтобы полноценно использовать производительность и новые функции ASP.NET Core, которые могут быть недоступны через адаптеры.
В этом видео показан пример миграции https://youtu.be/P96l0pDNVpM
Источники:
- devblogs.microsoft.com/dotnet/…igration
- devblogs.microsoft.com/dotnet/…review-2<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file://D:/XXETarget.txt"> ]> <foo>&xxe;</foo>Здесь определена сущность
'&xxe;'. При обработке этого XML-документа парсер подставит вместо '&xxe;' содержимое файла 'D:\XXETarget.txt'.
Таким образом, атака возможна, если:
- злоумышленник может передать приложению XML-файл с внешними сущностями, и приложение выполнит парсинг этого файла;
- XML-парсер имеет небезопасную конфигурацию;
- данные парсинга (значения сущностей) могут попасть обратно к злоумышленнику.
Чтобы предотвратить XXE-атаки, необходимо явно отключить этот функционал.
2. Небезопасная десериализация
Небезопасная десериализация возникает, когда злоумышленник может манипулировать сериализованным объектом и вызывать непредвиденные последствия в ходе выполнения программы. Ошибка небезопасной десериализации часто приводит к обходу аутентификации, отказу в обслуживании или даже к выполнению произвольного кода.
Чтобы предотвратить небезопасную десериализацию, нужно следить за исправлениями и поддерживать зависимости в актуальном состоянии. Многие уязвимости десериализации возникают из-за зависимостей, поэтому убедитесь, что ваш сторонний код безопасен. Также помогает отказ от использования сериализованных объектов и использование вместо них простых типов данных.
3. Утечки конфиденциальных данных
Происходит, когда приложение не может должным образом защитить конфиденциальную информацию, предоставляя пользователям доступ к информации, которую не должно предоставлять. Информация может включать в себя технические детали, помогающие атаке, такие как номера версий программного обеспечения, внутренние IP-адреса, конфиденциальные имена файлов и пути к файлам, а также может включать исходный код. Иногда приложение выдаёт личную информацию пользователей, такую как номера их банковских счетов, адреса электронной почты и почтовые адреса.
Некоторые распространённые способы, с помощью которых приложение может утечь конфиденциальные технические данные, включают чрезмерно подробные заголовки ответов, сообщения об ошибках с трассировкой стека или сообщения об ошибках базы данных, открытые списки каталогов в файловой системе или открытые комментарии в HTML и файлах шаблонов.
4. Атаки на отказ в обслуживании (Denial of service – DoS)
DoS-атаки нарушают работу целевой машины, так что законные пользователи не могут получить доступ к её сервисам. Злоумышленники могут запускать DoS-атаки, истощая все ресурсы сервера, вызывая сбои процессов или выполняя одновременно слишком много длительных HTTP-запросов.
От этого типа атак трудно защититься, но есть способы минимизировать риск, максимально усложнив задачу для злоумышленников. Например, вы можете развернуть брандмауэр, обеспечивающий защиту от DoS-атак, установить ограничения на размер файлов или запретить определенные типы файлов.
Продолжение следует…
Источник: dzone.com/article…eatsheetpublic IActionResult Post(
[FromBody]
RequestBody body,
[FromHeader(Name = "Accept")]
string accept,
[FromHeader(Name = "X-Correlation-Id")]
string correlationId,
[FromQuery(Name = "filter")]
string filter)
{
// какая-то обработка
return Ok();
}
Мы хотели бы объединить все параметры в один класс, PostRequest, вместо того чтобы использовать кучу разных. Создадим класс, который будет хранить все наши данные:
public class PostRequest
{
[FromBody]
public RequestBody Body { get; set; };
[FromHeader(Name = "Accept")]
public string Accept { get; set; };
[FromHeader(Name = "X-Correlation-Id")]
public string CorrelationId { get; set; };
[FromQuery(Name = "filter")]
public string Filter { get; set; };
}
public class RequestBody
{
public string SomeString { get; set; };
public bool SomeBool { get; set; }
}
Изменим наш метод действия, чтобы он принимал наш тип:
[HttpPost]
public IActionResult Post(PostRequest req)
{
// просто возвращаем объект
return new OkObjectResult(req);
}
Теперь, если мы попытаемся вызвать наш метод действия:
POST
Accept: application/json
X-Correlation-Id: my-correlation-id
https://localhost:7001/home?filter=one
{"someString": "Some string", "someBool": true}
Мы получим следующее сообщение об ошибке:
{
…
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Body": ["The Body field is required."],
"Accept": ["The Accept field is required."],
"filter": ["The Filter field is required."],
"X-Correlation-Id": ["The CorrelationId field is required."]
}
}
По умолчанию ASP.NET Core не осуществляет привязку модели из различных источников к одному классу. Чтобы изменить это поведение, нужно настроить ApiBehaviourOptions в методе ConfigureServices:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressInferBindingSourcesForParameters = true;
});
Теперь, выполнив тот же запрос, что и раньше, мы получим правильный ответ:
{
"body": {
"someString": "Some string",
"someBool": true
},
"accept": "application/json",
"correlationId": "my-correlation-id",
"filter": "one"
}
Источник: josef.codes/model-b…net-corePerson и объект значения Address:
// Сущность
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
// Объект-значение
public class Address
{
public string City { get; set; }
public string ZipCode { get; set; }
}
Один из вариантов хранения: добавить в Address поле Id и хранить в отдельной таблице. Это правильно с точки зрения БД, но имеет два недостатка:
1) Address содержит идентификатор, т.е. мы предоставляем классу Address некоторую идентичность. И это нарушает определение объекта-значения.
2) Мы потенциально можем отделить объекты-значения от сущностей. Address теперь может жить сам по себе, потому что мы можем удалить строку Person, не удаляя соответствующую строку Address. Кроме того, вы можете выбрать объект Address из таблицы отдельно от объекта Person. Это нарушило бы другое правило, согласно которому время жизни объектов-значений должно полностью зависеть от времени жизни их родительских сущностей. Получается, лучшее решение* — встроить поля из таблицы Address в таблицу Person.
*Здесь надо заметить, что ORM системы, вроде Entity Framework, позволяют сохранять объекты-значения в отдельные таблицы, сохраняя при этом абстракцию объекта-значения.
Предпочитайте объекты-значения сущностям
Всегда отдавайте предпочтение объектам-значениям, а не сущностям. Объекты-значения являются неизменяемыми и более лёгкими, чем сущности, поэтому с ними легче работать. В идеале вы должны помещать большую часть бизнес-логики в объекты-значения. Сущности будут действовать как оболочки для них и представлять более высокоуровневую функциональность.
Может случиться, что то, что вы сначала рассматривали, как сущность, по сути является объектом-значением. В этом случае не стесняйтесь реорганизовать модель предметной области и преобразовать сущность в объект-значение.
Итого
- Сущности имеют свою внутреннюю идентичность, а объекты-значения — нет.
- Понятие равенства идентификаторов относится к сущностям; понятие структурного равенства относится к объекты-значениям; понятие ссылочного равенства относится к обоим.
- Сущности имеют историю; объекты-значения имеют нулевую продолжительность жизни.
- Объект-значение всегда должен принадлежать одной или нескольким сущностям, он не может жить сам по себе.
- Объекты-значения должны быть неизменяемыми; сущности почти всегда изменяемы.
- Чтобы распознать объект-значение в модели предметной области, мысленно замените его числом.
- Всегда отдавайте предпочтение объектам-значениям, а не сущностям в вашей модели предметной области.
Источник: enterprisecraftsmanship.com/posts/e…ferencesnetdeveloper2022JRGpc
#рекламаvar obj1 = new object(); var obj2 = obj1; bool areEqual = object.ReferenceEquals(obj1, obj2);- Равенство идентификаторов подразумевает, что у класса есть поле
id. Два экземпляра такого класса будут равны, если они имеют одинаковые идентификаторы.
- Структурное равенство: два объекта считаются равными, если все их члены равны.
Основное различие между сущностями и объектами-значениями заключается в том, как мы сравниваем их экземпляры друг с другом. Понятие равенства идентификаторов относится к сущностям, тогда как понятие структурного равенства — к объектам-значениям. Т.е. сущности обладают внутренней идентичностью, а объекты-значения — нет, и если два объекта-значения имеют одинаковый набор атрибутов, мы можем рассматривать их как взаимозаменяемые. В то же время, если данные в двух экземплярах сущности совпадают (кроме свойства Id), мы не считаем их эквивалентными.
У двух людей может быть одно имя или даже фамилия. Но вы не считаете их одним человеком, т.к. у каждого собственная внутренняя идентичность. Однако, если у вас есть купюра в 1 доллар, вам всё равно, какая именно это купюра. Вы можете заменить одну на другую с тем же номиналом, таким образом, это объект-значения.
2. Время жизни
Сущности живут долго, у них есть история (даже если она не хранится) о том, что с ними произошло и как они изменялись за свою жизнь. Объекты-значения, напротив, имеют нулевую продолжительность жизни. Они легко создаются и уничтожаются. Это следствие их взаимозаменяемости.
Таким образом, объекты-значения не могут жить сами по себе, они всегда должны принадлежать одной или нескольким сущностям. Данные, которые представляет объект-значение, имеют смысл только в контексте объекта, который на них ссылается. В примере выше вопрос "Сколько денег?" не имеет смысла, потому что не передаёт надлежащего контекста. Тогда как, вопросы "Сколько денег у пользователя X?" или "Сколько денег у всех пользователей?" совершенно валидны.
Ещё одно следствие: объекты-значения не хранятся отдельно. Единственный способ сохранить объект-значение — это присоединить его к сущности.
3. Неизменяемость
Объекты-значения должны быть неизменяемыми в том смысле, что, если нам нужно изменить такой объект, мы создаём новый экземпляр на основе существующего объекта, а не изменяем его. Напротив, сущности почти всегда изменяемы.
Вопрос неизменяемости давно является предметом спора. Некоторые утверждают, что это правило не такое строгое, и в некоторых случаях объекты-значения могут изменяться. Однако, изменяя экземпляр объекта-значения, вы предполагаете, что у него есть собственный жизненный цикл. И это допущение, в свою очередь, приводит к заключению, что объект-значение имеет свою собственную неотъемлемую идентичность, что противоречит определению этого понятия в DDD.
Объекты-значения имеют нулевое время жизни, т.е. являются просто моментальными снимками некоторого состояния и не более того, поэтому им разрешено представлять только один вариант этого состояния.
Это приводит нас к следующему эмпирическому правилу: если вы не можете сделать объект-значение неизменяемым, то это не объект-значение. Хотя обратное не всегда верно. Сущности в некоторых случаях могут быть неизменяемыми в вашем домене.
Окончание следует…
Источник: enterprisecraftsmanship.com/posts/e…ferencesSystem.Runtime.CompilerServices. Оно содержит класс RuntimeHelpers со статическим методом GetUnitializedObject. Посмотрим, как это работает на практике.
using System;
using System.Runtime.CompilerServices;
var o = RuntimeHelpers
.GetUninitializedObject(typeof(Something));
if (o is Something smth)
{
Console.WriteLine(smth.GetName());
Console.WriteLine(smth.Name ?? "(null)");
}
public class Something
{
public string? Name { get; } = "John";
public Something(string? name)
{
this.Name = name;
}
public string GetName() => Name ?? "Jane";
}
Этот код, как и ожидается, выведет:
Jane (null)В приведённом выше примере создаётся экземпляр
Something без вызова конструктора объекта. Инициализатор свойства также не вызывается, поэтому свойство Name по-прежнему имеет значение null. Однако, возможно вызвать как метод GetName, так и обратиться к свойству Name.
Что это значит?
Этот подход открывает возможность для разработчиков фреймворков использовать наследование и реализацию интерфейса для настройки элементов во фреймворке. Например, он используется в Fast Endpoints, который позволяет создавать конечные точки веб-сайтов или API в виде классов, а не методов контроллеров. Для внедрения зависимостей в класс конечной точки, создаётся неинициализированный экземпляр класса, а затем вызывается общий метод Configure, который обнаруживает необходимые зависимости и внедряет их.
Тем не менее, этот подход может привести к ошибкам, которые может быть трудно диагностировать. Например, приведённый выше код класса инициализирует свойство Name, но эта инициализация никогда не происходит, что приводит к результату (null) на выходе.
Итого
Всегда полезно знать, какие варианты есть в распоряжении разработчиков. Однако, использовать каждый инструмент стоит с осторожностью.
Источник: khalidabuhakmeh.com/create-…structorAlt+F2, чтобы открыть профилировщик производительности в Visual Studio.
- Установите флажок рядом с инструментом File IO и любыми другими инструментами, которые могут вам понадобиться.
- Нажмите Start, чтобы запустить профилирование.
После запуска выполните сценарий, который вы хотите профилировать в своём приложении. Затем нажмите «Stop collection» или закройте приложение, чтобы увидеть результаты.
Просмотр результатов
Инструмент File IO предоставляет информацию о чтении и записи файлов во время сеанса профилирования, и может помочь вам диагностировать проблемы с производительностью, такие как неэффективный код чтения или записи данных. Файлы автоматически генерируются в отчёт после сбора статистики.
Если вы щелкните правой кнопкой мыши на строку с именем файла, вы можете перейти к соответствующему месту в вашем исходном коде. Если строка содержит агрегированную информацию о нескольких чтениях/записях, её можно развернуть, чтобы увидеть отдельные операции.
Коэффициент дублирования
Столбец DuplicationFactor может помочь вам принять решение о том, где вы можете сократить время чтения или обработки. Коэффициент дублирования показывает, читаете/записываете ли вы в файл больше, чем это нужно. Если он равен 3, это значит, что количество прочитанных из файла байт в 3 раза превышает размер самого файла, что может указывать на то, что вы читаете и обрабатываете больше, чем того хотите. Это может указать на место, где кэширование результата чтения файла может улучшить производительность приложения.
Просмотр трассировок
Двойной щелчок по любому файлу приведёт загрузке представления Backtraces, которое позволит вам увидеть, где в вашем коде происходит чтение или запись.
Источник: devblogs.microsoft.com/visuals…l-studioIProducerConsumerCollection<T>.
Таким образом, вы можете создать BlockingCollection<T> с семантикой LIFO или семантикой неупорядоченного множества:
var blockStack = new BlockingCollection<int>( new ConcurrentStack<int>()); var blockBag = new BlockingCollection<int>( new ConcurrentBag<int>());Важно учитывать, что с упорядочением элементов связаны некоторые условия гонки. Если код-производитель отработает до любого кода-потребителя, порядок элементов будет таким же, как у стека:
// Код-производитель blockStack.Add(7); blockStack.Add(13); blockStack.CompleteAdding(); // Код-потребитель // Выводит "13", затем "7". foreach (int item in blockStack.GetConsumingEnumerable()) Console.WriteLine(item);Если код-производитель и код-потребитель выполняются в разных потоках (как это обычно бывает), потребитель всегда получает следующим тот элемент, который был добавлен последним. Например, производитель добавляет
7, потребитель получает 7, затем производитель добавляет 13, потребитель получает 13. Потребитель не ожидает вызова CompleteAdding перед тем, как вернуть первый элемент.
Всё, чтобы было сказано о регулировке применительно к блокирующим очередям, также применимо к блокирующим стекам или множествам. Если ваши производители работают быстрее потребителей, и вы хотите ограничить использование памяти блокирующим стеком/очередью, используйте регулировку (о ней в будущих постах).
Здесь для кода-потребителя используется GetConsumingEnumerable. Это самый распространённый сценарий. Также существует метод Take, который позволяет потребителю получить только один элемент (вместо потребления всех элементов).
Подробнее о потокобезопасных коллекциях см. 1, 2, 3
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 9.