Блоґ
Home / Думки, нариси, різні випадки
Апгрейд netcore 3.1 до dotnet 7
Команда, в якій я працюю, нещодавно постала перед необхідністю апгрейду версії dotnet, на якій працює наш бекенд продукт, який являє собою REST API веб-застосунок, що осблуговує кілька десятків ендпоінтів. Через те, що термін підтримки netcore 3.1 добіг свого кінця, ми постали перед вибором — чи то апгрейдитися на версію dotnet 6, яка має подовжений термін підтримки (LTS), чи то на dotnet 7 зі стандартним терміном підтримки (STS). З одного боку, подовжений термін підтримки — це довший період умовного спокою :), з іншого — сьомий дотнет спокушав новими мовними засобами та широко розрекламованою розголошувачами Майкрософт підвищеною продуктивністю. Зрештою, було ухвалене рішення апгрейдитися на саме dotnet 7, маючи на увазі, що проміжок часу між закінчення дії стандартної підтримки для сьомої версії та подовженої для шостої становить лише півроку, тож, перехід на майбутню версію dotnet 8 LTS в будь-якому разі відбувся б з невеликою різницею в часі.
Солюшн нашого застосунку (вибач, солов'їна, але слово "рішення" чи "вирішення" для позначення того, що у Visual Studio IDE визначається словом solution, як нам мене, лише зіб'є з пантелику, як і для слів "сігнатура", "інтерфейс", "апгрейд", "коміт", "пул-реквест", та той же "файл", зрештою..), так ось, солюшн, що ми маємо, складається з десятка c#-проєктів. Взаємодія з фронтендом забезпечується вищезгаданим REST API, деякі бізнесові задачі опрацьовуються декількома Azure-функціями. По завершенню апгрейда з'ясувалося, що, власне, модифікація початкового коду знадобилася лише у двох чи трьох місцях (трохи детальніше про один такий випадок — далі). Найбільше зусиль при апгрейді потребували зміни в .csproj-файлах, де міститься конфігурація nuget-пакетів, тож моїм найпершим наміром було, користуючись моментом, провести загальний апгрейд всіх тих пакетів до іхніх останніх версій. Я спробував це зробити, але, як каже Танос, "реальність часто розчаровує". Загальний апгрейд версій nuget-пакетів спричинив такий безлад в коді, що я змушений був відкатити солюшн до початкового стану.
Після цього я спробував піти протилежним шляхом, а саме — залишати поточні версії для якнаймога більшої кількості nuget-пакетів, переводячи деякі з них на останні лише в разі необхідності, коли виникали помилки в коді чи не проходив білд. Цей підхід виявився робочим і, зрештою, я змігся успішно проапгрейдити весь солюшн.
Щодо змін у початковому коді — одне з місць, яке вимагало деякого рефакторингу, виникло в одній з Azure-функцій, яка спрацьовує при надходженні повідомлення з Service Bus. В часи netcore 3.1 сігнатура вхідного методу цієї функції була такою:
[FunctionName("LandManagementIdentityServiceBusListenerFunction")]
public async Task Run(
[ServiceBusTrigger("%ServiceBusTopicName%", "%ServiceBusSubscriptionName%", Connection = "ServiceBusConnectionString")] Message message,
ILogger log,
CancellationToken cancellationToken)
{
if (!message.UserProperties.ContainsKey("eventName") || string.IsNullOrWhiteSpace(message.UserProperties["eventName"].ToString()))
{
log.LogError("Event name is empty. Message cannot be processed.");
throw new InvalidOperationException("Event name is empty. Message cannot be processed.");
}
// .. downstream code
}
Після апгрейду до сьомого дотнету з'явилася проблема з параметром Message. З'ясувалося, що тип Microsoft.Azure.ServiceBus.Message більше не використовується для того, аби містити і повідомлення, і його властивості. В новій версії дотнету параметр message має бути типом string, object або якимось суворим типом для подальшої десеріалізації. Однак, у випадку нашої Функції, аби опрацювати вхідне повідомлення, код потрібен отримати кілька UserProperties, які передаються не як частина повідомлення, а як його властивості
У неткорі 3.1 тип Microsoft.Azure.ServiceBus.Message був складним об'єктом, що містив у собі як саме повідомлення, як і його властивості. В дотнет 6/7 це було змінено для іщольованого режиму виконання, і зараз, аби отримати ті користувацькі властивості, методу потрібно додати ще один параметр типу FunctionContext.
[Function("LandManagementIdentityServiceBusListenerFunction")]
public async Task Run(
[ServiceBusTrigger("%ServiceBusTopicName%", "%ServiceBusSubscriptionName%", Connection = "ServiceBusConnectionString")] string message,
FunctionContext functionContext,
CancellationToken cancellationToken)
{
var messageProps = JsonConvert.DeserializeObject>(
functionContext.BindingContext.BindingData.FirstOrDefault(x => x.Key == "UserProperties").Value?.ToString() ?? string.Empty);
if (messageProps is null || !messageProps.ContainsKey("eventName") || string.IsNullOrWhiteSpace(messageProps["eventName"].ToString()))
{
_logger.LogError("Event name is empty. Message cannot be processed.");
throw new InvalidOperationException("Event name is empty. Message cannot be processed.");
}
// .. downstream code
}
Маю сподівання, що ця інформація стане комусь в нагоді. Дякую за увагу!
© theyur.dev. All Rights Reserved. Designed by HTML Codex