132 lines
6.7 KiB
C#
132 lines
6.7 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
} |