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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|