daily_digest/Services/CbrCurrencyService.cs

132 lines
6.7 KiB
C#
Raw Permalink Normal View History

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<CbrCurrencyService> _logger;
private readonly DataSourceUrls _dataSourceUrls;
// Внедряем зависимости
public CbrCurrencyService(
IHttpClientFactory httpClientFactory,
IOptions<DataSourceUrls> dataSourceUrlsOptions,
ILogger<CbrCurrencyService> logger)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
_dataSourceUrls = dataSourceUrlsOptions.Value; // Получаем URL из настроек
}
public async Task<CurrencyData?> 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;
}
}
}
}