Небольшая вводная, что будет реализовано:
- версия платформы .NET Core: 2.1 или выше;
- тип приложения: ASP.NET Core MVC;
- данные о курсах валют берутся с сайта ЦБ РФ;
- формат входящих данных: файл XML (не веб-сервис, не JSON);
- процесс получения данных реализован в виде фоновой задачи, которая выполняется с некоторой периодичностью, пока приложение запущено;
- класс-модель, с которым удобно работать и который содержит в себе данные о курсах и позволяет проводить конвертацию валют;
- данные хранятся в кэше в оперативной памяти на сервере.
Это наиболее общий функционал, который будет реализован в приложении. Дальнейшая специализация на ваше усмотрение.
Создадим в Visual Studio новое приложение типа ASP.NET Core. Сразу выберем шаблон MVC, чтобы Visual Studio за нас подготовила минимальный необходимый функционал для запуска приложения. Режим аутентификации или защищенное https-соединение в этом примере не используется.
Сразу замечу, что функционал этого примера во многом заточен именно под работу с ЦБ РФ, как источником данных о курсах валют. В работе с ним есть некоторые нюансы, о чем сказано ниже. Если вы хотите брать курсы валют из другого источника, а они в целом более-менее все похожи, то изучите особенности API того сервиса, и в соответствии с этим скорректируйте код приложения.
Ознакомиться с информацией о том, как получать данные от ЦБ РФ, используя XML, можно по этой ссылке: Получение данных, используя XML
Придумаем себе техническое задание, в котором сказано, что основная валюта на сайте – это российские рубли. Также должна быть возможность представить какую-то цену на товар в иностранной валюте, а именно в долларах США, евро или гривнах.
Под это ТЗ создадим простейший класс-модель для того, чтобы работать с данными в объектно-ориентированной манере. Пусть также в обязанности этого класса входит конвертация одной валюты в другую. Для простоты демонстрации нарушим принцип SRP (Single Responsibility Principle).
public class CurrencyConverter
{
public decimal USD { get; set; }
public decimal ConvertToUSD(decimal priceRUB) => priceRUB / USD;
public decimal EUR { get; set; }
public decimal ConvertToEUR(decimal priceRUB) => priceRUB / EUR;
//10 гривен номинал (такие данные от ЦБ)
public decimal UAH10 { get; set; }
public decimal ConvertToUAH(decimal priceRUB) => priceRUB / (UAH10 / 10);
}
После того как мы определили модель, реализуем функционал получения курсов валют и обработки их внутри нашего приложения. Для того чтобы этот процесс был автоматизирован и работал фоном с некоторой периодичностью, в ASP.NET Core очень удобно использовать класс BackgroundService, который в свою очередь реализует интерфейс IHostedService.
Создадим свой класс и наследуемся от класса BackgroundService:
public class CurrencyService : BackgroundService
{
private readonly IMemoryCache memoryCache;
public CurrencyService(IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
//так как эта задача выполняется в другом потоке, то велика вероятность, что
//культура по умолчанию может отличаться от той, которая установлена в нашем приложении,
//поэтому явно укажем нужную нам, чтобы не было проблем с разделителями, названиями и т.д.
Thread.CurrentThread.CurrentCulture = new CultureInfo("ru-RU"); // <== нужная вам культура
//кодировка файла xml с сайта ЦБ == windows-1251
//по умолчанию она недоступна в .NET Core, поэтому регистрируем нужный провайдер
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
//т.к. мы знаем что данные к нам приходят именно в файле, именно в формате XML,
//поэтому нет необходимости использовать WebRequest,
//используем в работе класс XDocument и сразу забираем файл с удаленного сервера
XDocument xml = XDocument.Load("http://www.cbr.ru/scripts/XML_daily.asp");
//далее парсим файл и находим нужные нам валюты по их коду ID, и заполняем класс-модель
CurrencyConverter currencyConverter = new CurrencyConverter();
currencyConverter.USD = Convert.ToDecimal(xml.Elements("ValCurs").Elements("Valute").FirstOrDefault(x => x.Element("NumCode").Value == "840").Elements("Value").FirstOrDefault().Value);
currencyConverter.EUR = Convert.ToDecimal(xml.Elements("ValCurs").Elements("Valute").FirstOrDefault(x => x.Element("NumCode").Value == "978").Elements("Value").FirstOrDefault().Value);
currencyConverter.UAH10 = Convert.ToDecimal(xml.Elements("ValCurs").Elements("Valute").FirstOrDefault(x => x.Element("NumCode").Value == "980").Elements("Value").FirstOrDefault().Value);
memoryCache.Set("key_currency", currencyConverter, TimeSpan.FromMinutes(1440));
}
catch (Exception e)
{
//logger.LogError(e.InnerException.Message);
}
//если указаний о завершении данной задачи не поступало,
//то запрашиваем обновление данных каждый час
await Task.Delay(3600000, stoppingToken);
}
}
}
* в коде выше добавлены поясняющие комментарии по некоторым неочевидным вопросам, это опять же возвращаясь к тому, что данный пример во многом заточен именно под ЦБ РФ как источник данных курсов валют.
Также не забудем добавить поддержку кэширования в проект и подключим наш CurrencyService как сервис в файле Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<CurrencyService>();
services.AddMemoryCache();
services.AddControllersWithViews();
}
На данном этапе если мы запустим приложение на выполнение, то сразу же запустится фоновая задача и произойдет попытка получения данных о курсах валют. После этого класс-модель будет заполнен этими данными и помещен в кэш.
Давайте для примера извлечем нашу модель из кэша в HomeController, и передадим ее в представление Index():
public IActionResult Index()
{
if (!memoryCache.TryGetValue("key_currency", out CurrencyConverter model))
{
throw new Exception("Ошибка получения данных");
}
return View(model);
}
@model CurrencyConverter
@{
decimal priceRub = 1000;
}
<div>
Цена товара в рублях: @priceRub руб.
<div>
@($"Курс доллара: {Model.USD}, цена в долларах: {Decimal.Round(Model.ConvertToUSD(priceRub), 2)}")
</div>
<div>
@($"Курс евро: {Model.EUR}, цена в евро: {Decimal.Round(Model.ConvertToEUR(priceRub), 2)}")
</div>
<div>
@($"Курс гривны: {Model.UAH10 / 10}, цена в гривнах: {Decimal.Round(Model.ConvertToUAH(priceRub), 2)}")
</div>
</div>