Add client-side cache and exchange rate providers
MODIFY
MagicalCryptoWallet.Backend/Controllers/BlockchainController.cs
MagicalCryptoWallet.Backend/Controllers/BlockchainController.cs
@@ -10,6 +10,7 @@ using MagicalCryptoWallet.Logging;
using MagicalCryptoWallet.WebClients.SmartBit;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Caching.Memory;
using NBitcoin;
using NBitcoin.RPC;
@@ -22,6 +23,13 @@ namespace MagicalCryptoWallet.Backend.Controllers
[Route("api/v1/btc/[controller]")]
public class BlockchainController : Controller
{
+ private readonly IMemoryCache _cache;
+
+ public BlockchainController(IMemoryCache memoryCache)
+ {
+ _cache = memoryCache;
+ }
+
private static RPCClient RpcClient => Global.RpcClient;
private static Network Network => Global.Config.Network;
@@ -145,22 +153,36 @@ namespace MagicalCryptoWallet.Backend.Controllers
/// <response code="200">Returns an array of exchange rates.</response>
[HttpGet("exchange-rates")]
[ProducesResponseType(typeof(IEnumerable<ExchangeRate>), 200)]
+ [ResponseCache(Duration = 60, Location=ResponseCacheLocation.Client)]
public async Task<IEnumerable<ExchangeRate>> GetExchangeRatesAsync()
{
- // ToDo: Implement caching for instant answers.
- // ToDo: Implement redundancy, call another API if SmartBit fails, if that fails, call another one.
- using (var client = new SmartBitClient(Network, disposeHandler: true))
- {
- var rates = await client.GetExchangeRatesAsync(CancellationToken.None);
- var rate = rates.Single(x => x.Code == "USD");
+ List<ExchangeRate> exchangeRates;
- var exchangeRates = new List<ExchangeRate>
+ if (!_cache.TryGetValue(nameof(GetExchangeRatesAsync), out exchangeRates))
{
- new ExchangeRate() { Rate = rate.Rate, Ticker = "USD" },
+ var exchangeRateProviders = new IExchangeRateProvider[]{
+ new SmartBitExchangeRateProvider(new SmartBitClient(Network, disposeHandler: true)),
+ new BlockchainInfoExchangeRateProvider()
};
- return exchangeRates;
+ foreach(var provider in exchangeRateProviders)
+ {
+ try
+ {
+ exchangeRates = await provider.GetExchangeRateAsync();
+ break;
+ }catch(Exception){
+ // Ignore it and try with the next one
+ }
}
+
+ var cacheEntryOptions = new MemoryCacheEntryOptions()
+ .SetSlidingExpiration(TimeSpan.FromSeconds(10));
+
+ _cache.Set(nameof(GetExchangeRatesAsync), exchangeRates, cacheEntryOptions);
+ }
+
+ return exchangeRates;
}
/// <summary>

ADD
MagicalCryptoWallet.Backend/Controllers/ExchangeRateProviders.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using MagicalCryptoWallet;
+using MagicalCryptoWallet.Backend.Models;
+using MagicalCryptoWallet.WebClients.SmartBit;
+using Newtonsoft.Json;
+
+namespace MagicalCryptoWallet.Backend
+{
+ interface IExchangeRateProvider
+ {
+ Task<List<ExchangeRate>> GetExchangeRateAsync();
+ }
+
+
+ class SmartBitExchangeRateProvider : IExchangeRateProvider
+ {
+ private SmartBitClient _client;
+
+ public SmartBitExchangeRateProvider(SmartBitClient smartBitClient)
+ {
+ _client = smartBitClient;
+ }
+
+ public async Task<List<ExchangeRate>> GetExchangeRateAsync()
+ {
+ var rates = await _client.GetExchangeRatesAsync(CancellationToken.None);
+ var rate = rates.Single(x => x.Code == "USD");
+
+ var exchangeRates = new List<ExchangeRate>
+ {
+ new ExchangeRate() { Rate = rate.Rate, Ticker = "USD" },
+ };
+
+ return exchangeRates;
+ }
+ }
+
+ class BlockchainInfoExchangeRateProvider : IExchangeRateProvider
+ {
+ class BlockchainInfoExchangeRate{
+ public decimal Last { get; set; }
+ public decimal Buy { get; set; }
+ public decimal Sell { get; set; }
+ }
+
+ class BlockchainInfoExchangeRates{
+ public BlockchainInfoExchangeRate USD { get; set; }
+ }
+
+ public async Task<List<ExchangeRate>> GetExchangeRateAsync()
+ {
+ using(var httpClient = new HttpClient())
+ {
+ httpClient.BaseAddress = new Uri("https://blockchain.info");
+ var response = await httpClient.GetAsync("/ticker");
+ var rates = await response.ReadAsAsync<BlockchainInfoExchangeRates>();
+
+ var exchangeRates = new List<ExchangeRate>
+ {
+ new ExchangeRate() { Rate = rates.USD.Sell, Ticker = "USD" },
+ };
+
+ return exchangeRates;
+ }
+ }
+ }
+
+ static class HttpResponseMessageExtensions
+ {
+ public static async Task<T> ReadAsAsync<T>(this HttpResponseMessage me)
+ {
+ var jsonString = await me.Content.ReadAsStringAsync();
+ return JsonConvert.DeserializeObject<T>(jsonString);
+ }
+ }
+}
+

MODIFY
MagicalCryptoWallet.Backend/Startup.cs
MagicalCryptoWallet.Backend/Startup.cs
@@ -18,6 +18,7 @@ namespace MagicalCryptoWallet.Backend
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
+ services.AddMemoryCache();
services.AddMvc();
// Register the Swagger generator, defining one or more Swagger documents

Add client-side cache and exchange rate providers