using DailyDigestWorker.Configuration; // Для DataSourceUrls using DailyDigestWorker.Models; using Microsoft.Extensions.Options; // Для IOptions using System.Globalization; // Для CultureInfo и NumberStyles using System.Xml.Linq; // Для работы с XML namespace DailyDigestWorker.Services { public class CbrCurrencyService : ICurrencyService // Реализуем интерфейс { private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; private readonly DataSourceUrls _dataSourceUrls; // Внедряем зависимости public CbrCurrencyService( IHttpClientFactory httpClientFactory, IOptions dataSourceUrlsOptions, ILogger logger) { _httpClientFactory = httpClientFactory; _logger = logger; _dataSourceUrls = dataSourceUrlsOptions.Value; // Получаем URL из настроек } public async Task GetCurrencyRatesAsync(CancellationToken cancellationToken) { _logger.LogInformation("Запрос курсов валют с API ЦБ РФ..."); var client = _httpClientFactory.CreateClient(); // Создаем HttpClient try { // Выполняем GET-запрос к URL из конфигурации HttpResponseMessage response = await client.GetAsync(_dataSourceUrls.Cbr, cancellationToken); // Проверяем, успешен ли запрос if (!response.IsSuccessStatusCode) { _logger.LogError("Ошибка при запросе к API ЦБ РФ. Статус код: {StatusCode}", response.StatusCode); return null; // Возвращаем null при ошибке HTTP } // Читаем ответ как поток (для работы с XML) using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); // Загружаем XML из потока XDocument xmlDoc = await XDocument.LoadAsync(responseStream, LoadOptions.None, cancellationToken); // Ищем нужные валюты (USD и EUR) var usdElement = xmlDoc.Root?.Elements("Valute") .FirstOrDefault(v => v.Attribute("ID")?.Value == "R01235"); // ID для USD var eurElement = xmlDoc.Root?.Elements("Valute") .FirstOrDefault(v => v.Attribute("ID")?.Value == "R01239"); // ID для EUR if (usdElement == null || eurElement == null) { _logger.LogError("Не удалось найти элементы USD или EUR в XML ответе ЦБ РФ."); return null; } // Получаем дату курсов из атрибута Date корневого элемента DateTime coursesDate = DateTime.Today; // Значение по умолчанию var dateAttribute = xmlDoc.Root?.Attribute("Date")?.Value; if (!string.IsNullOrEmpty(dateAttribute) && DateTime.TryParseExact(dateAttribute, "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedDate)) { coursesDate = parsedDate; } else { _logger.LogWarning("Не удалось распарсить дату курсов из XML ЦБ РФ. Используется текущая дата."); } // Извлекаем значения курсов (в виде строк) var usdValueStr = usdElement.Element("Value")?.Value; var eurValueStr = eurElement.Element("Value")?.Value; if (usdValueStr == null || eurValueStr == null) { _logger.LogError("Не удалось найти значения Value для USD или EUR в XML."); return null; } // Парсим строки в decimal, учитывая русскую культуру (запятая как разделитель) var culture = CultureInfo.GetCultureInfo("ru-RU"); if (decimal.TryParse(usdValueStr, NumberStyles.Any, culture, out decimal usdRate) && decimal.TryParse(eurValueStr, NumberStyles.Any, culture, out decimal eurRate)) { _logger.LogInformation("Курсы валют успешно получены: USD={UsdRate}, EUR={EurRate} на {Date}", usdRate, eurRate, coursesDate.ToString("dd.MM.yyyy")); // Сначала создаем объект CurrencyData var resultData = new CurrencyData { Date = coursesDate }; // Затем добавляем элементы в его словарь Rates resultData.Rates.Add("USD", usdRate); resultData.Rates.Add("EUR", eurRate); // Возвращаем готовый объект return resultData; } else { _logger.LogError("Не удалось преобразовать строковые значения курсов в decimal."); return null; } } catch (HttpRequestException ex) { _logger.LogError(ex, "Ошибка HTTP при запросе к API ЦБ РФ."); return null; } catch (System.Xml.XmlException ex) { _logger.LogError(ex, "Ошибка парсинга XML ответа ЦБ РФ."); return null; } catch (OperationCanceledException) // Ловим отмену операции { _logger.LogInformation("Запрос курсов валют был отменен."); return null; // Возвращаем null, если операция отменена } catch (Exception ex) // Ловим любые другие неожиданные ошибки { _logger.LogError(ex, "Неожиданная ошибка при получении курсов валют ЦБ РФ."); return null; } } } }