This project was bootstrapped with Rx.Net.
This is an simple example where there are two observables and fire a forget in another thread.
[HttpGet]
public async Task<IActionResult> GetLatestAsync()
{
return await TaskExecutor.ExecuteAsync(this.currencyService.GetLatest());
}
public IObservable<CurrencyDto> GetFallback()
{
string cacheKey = GetCacheKey();
return this.keyValueStore.Get<CurrencyDto>(cacheKey).FlatMap(currencyDto =>
{
return currencyDto != null
? Observable.Return(currencyDto)
: this.GetFromApi().Map(response =>
{
this.executorService.Run(() =>
this.keyValueStore.Put(cacheKey, response, 60 * 10).ToBlocking()); // mm * ss
return response;
});
});
}
services.AddHttpClient<ICurrencyClient, CurrencyClient>()
.SetTimeout(TimeSpan.FromMilliseconds(1500))
.SetMaxConnectionsPerServer(20)
.SetMaxParallelization(20);
public static void UseWarmUp(this IApplicationBuilder applicationBuilder)
{
Task.Run(() =>
{
IApplicationWarmUpper? applicationWarmUpper =
applicationBuilder.ApplicationServices.GetService<IApplicationWarmUpper>();
if (applicationWarmUpper == null) return;
WarmupExecutor.Warmup(applicationWarmUpper);
}
).Forget();
}
public bool Warmup()
{
Thread.Sleep(1000);
for (int i = 0; i < 3; i++)
{
try
{
if (string.IsNullOrEmpty(this.baseUrl)) return true;
string requestUri = $"{this.baseUrl}/Currency";
HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, requestUri);
HttpResponseMessage httpResponseMessage = this.httpClient.Send(httpRequestMessage);
if (httpResponseMessage.StatusCode == HttpStatusCode.OK) return true;
}
catch (Exception)
{
// ignored
}
Thread.Sleep(1000);
}
return false;
}
public ActionResult<string> Pong()
{
return WarmupExecutor.Initialized
? this.StatusCode(StatusCodes.Status200OK, "pong")
: this.StatusCode(StatusCodes.Status503ServiceUnavailable, "offline");
}
[Fact]
public void Get_Latest_Ok_Fallback_FromApi()
{
this.keyValueStore.Setup(store => store.Get<CurrencyDto>("bluelytics:v1"))
.Returns(Observable.Return(default(CurrencyDto)));
this.currencyClient.Setup(client => client.Get()).Returns(GetLatest());
CurrencyService currencyService = new(this.currencyClient.Object, this.keyValueStore.Object);
CurrencyDto currencyDto = currencyService.GetFallback().ToBlocking();
Assert.NotNull(currencyDto);
Assert.Equal(10.0M, currencyDto.Official!.Buy);
Assert.Equal(11.0M, currencyDto.Official.Sell);
Assert.Equal(12.0M, currencyDto.Blue!.Buy);
Assert.Equal(13.0M, currencyDto.Blue.Sell);
}
[Fact]
public async Task Basic_Integration_Test_InternalServerErrorAsync()
{
this.currencyService.Setup(service => service.GetLatest())
.Returns(Observable.Throw<CurrencyDto>(new ApiException()));
HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync("/Currency");
string responseString = await httpResponseMessage.Content.ReadAsStringAsync();
Assert.NotNull(responseString);
ErrorHandlerMiddleware.ErrorModel? errorModel = JsonConvert
.DeserializeObject<ErrorHandlerMiddleware.ErrorModel>(responseString);
Assert.NotNull(errorModel);
Assert.Equal(500, errorModel.Code);
Assert.Equal(nameof(ApiException), errorModel.Type);
Assert.NotNull(errorModel.Detail);
}
curl 'https://valordolarhoy.herokuapp.com/fallback'
{
"official": {
"sell": 107.57,
"buy": 101.57
},
"blue": {
"sell": 200,
"buy": 196
}
}
{
"code": 404,
"type": "ApiNotFoundException",
"message": "{\"message\":\"Not Found\"}",
"detail": " at ValorDolarHoy.Core.Common..."
}
{
"code": 500,
"type": "ApiException",
"message": "An internal server error has occurred. ",
"detail": "Please try again later"
}