I'm trying to implement a Polly Timeout policy using the new .NET Core 2.1 HttpClientFactory; however, I cannot seem to get the timeout to occur.
My ConfigureServices:
// Configure polly policies
TimeoutPolicy<HttpResponseMessage> timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(5, TimeoutStrategy.Pessimistic);
// Configure platform service clients
services.AddHttpClient<IDiscoveryClient, DiscoveryClient>()
.AddPolicyHandler(timeoutPolicy);
My POST method in DiscoveryClient:
public async Task<TResponse> PostXMLAsync<TResponse, TPostData>(string url, TPostData postData)
where TResponse : ClientResponse
where TPostData : ClientPostData
{
HttpResponseMessage response = await httpClient.PostAsXmlAsync(url, postData);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<TResponse>();
}
Unfortunately, the call times out after the default 100s rather than after the 5s defined in the Polly policy.
Any thoughts on what I'm doing wrong?
First let's define a mock server which does respond with 500 after 100 seconds:
const string address = "http://localhost:9000";
var delay = TimeSpan.FromSeconds(100);
var server = WireMockServer.Start(new WireMockServerSettings { Urls = new[] { address } });
server
.Given(Request.Create().WithPath("/").UsingPost())
.RespondWith(Response.Create().WithDelay(delay).WithStatusCode(500));
I've used WireMock.Net for this.
Now, let's see the IDiscoveryClient and DiscoveryClient:
interface IDiscoveryClient
{
Task<TResponse> SendRequest<TResponse, TPostData>(string url, TPostData data);
}
class DiscoveryClient : IDiscoveryClient
{
private readonly HttpClient httpClient;
public DiscoveryClient(HttpClient httpClient) => this.httpClient = httpClient;
public async Task<TResponse> SendRequest<TResponse, TPostData>(string url, TPostData data)
{
var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8);
var response = await httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
var rawData = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResponse>(rawData);
}
}
class TestRequest { public string Content { get; set; } }
class TestResponse { public string Data { get; set; } }
I've used json instead of xml, but that's not imporant from the question point of view.
And finally let's wire up the DI and issue a request:
AsyncTimeoutPolicy<HttpResponseMessage> timeoutPolicy =
Policy.TimeoutAsync<HttpResponseMessage>(5, TimeoutStrategy.Pessimistic);
IServiceCollection services = new ServiceCollection();
services.AddHttpClient<IDiscoveryClient, DiscoveryClient>()
.AddPolicyHandler(timeoutPolicy);
ServiceProvider serviceProvider = services.BuildServiceProvider();
var client = serviceProvider.GetService<IDiscoveryClient>();
Stopwatch sw = Stopwatch.StartNew();
try
{
TestResponse res = await client.SendRequest<TestResponse, TestRequest>(address, new TestRequest { Content = "Test"});
}
catch (TimeoutRejectedException ex)
{
sw.Stop();
Console.WriteLine(sw.Elapsed);
}
The printed output will be something like this:
00:00:05.0296804
The good thing is that it does work with Optimistic and Pessimistic strategies as well.
Related
I am trying to write a Blazor app that uses client secret credentials to get an access token for the API. I wanted to encapsulate it in such a way that it handles the token fetching and refreshing behind the scenes. To achieve this, I created the following inherited class which uses IdentityModel Nuget package:
public class MPSHttpClient : HttpClient
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger Logger;
public MPSHttpClient(IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
public async Task<bool> RefreshTokens()
{
if (Tokens.RefreshToken == null)
return false;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
RefreshToken = Tokens.RefreshToken
});
Logger.LogInformation("Refresh Token Result {0}", result.IsError);
if (result.IsError)
{
Logger.LogError("Error: {0)", result.ErrorDescription);
return false;
}
Tokens.RefreshToken = result.RefreshToken;
Tokens.AccessToken = result.AccessToken;
Logger.LogInformation("Access Token: {0}", result.AccessToken);
Logger.LogInformation("Refresh Token: {0}" , result.RefreshToken);
return true;
}
public async Task<bool> CheckTokens()
{
if (await RefreshTokens())
return true;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
public new async Task<HttpResponseMessage> GetAsync(string requestUri)
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
var response = await base.GetAsync(requestUri);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
response = await base.GetAsync(requestUri);
}
}
return response;
}
}
The idea is to keep from having to write a bunch of redundant code to try the API, then request/refresh the token if you are unauthorized. I tried it at first using extension methods to HttpClient, but there was no good way to inject the Configuration into a static class.
So my Service code is written as this:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly MPSHttpClient _httpClient;
public EngineListService(MPSHttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
Everything compiles great. In my Startup, I have the following code:
services.AddScoped<TokenProvider>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Just to be complete, Token Provider looks like this:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
When I run the App, it complains that it can't find a suitable constructor for EngineListService in the call to services.AddHttpClient. Is there a way to pass AddHttpClient an actual instance of the IEngineListService. Any other way I might be able to achieve this?
Thanks,
Jim
I think that EngineListService should not be registered as a HttpClient in services and instead you should register MPSHttpClient.
This follows the "Typed Client" example in the documentation and uses IHttpClientFactory behind the scenes.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#typed-clients
When you use services.AddHttpClient the constructor needs a HttpClient parameter. That is how the HttpClientFactory initializes the HttpClient and then passes it into your service ready to go.
You can change your MPSHttpClient to not inherit HttpClient and instead add a HttpClient parameter to the constructor. You could also have it implement an interface like IMPSHttpClient
public class MPSHttpClient
{
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
HttpClient = httpClient;
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
}
You must remove these lines from MPSHttpClient and use the injected client.
// remove this
var client = new HttpClient();
In Startup add
services.AddHttpClient<MPSHttpClient>(client =>
{
// add any configuration
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Change EngineListService to a normal service registration as it is not a HttpClient
services.AddScoped<IEngineListService, EngineListService>()
Special thanks to #pinkfloydx33 for helping me solve this. This link that he shared https://blog.joaograssi.com/typed-httpclient-with-messagehandler-getting-accesstokens-from-identityserver/ was everything I needed. The trick was that there exists a class called DelegatingHandler that you can inherit and override the OnSendAsync method and do all of your token-checking there before sending it to the final HttpHandler. So my new MPSHttpClient class is as so:
public class MPSHttpClient : DelegatingHandler
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger<MPSHttpClient> Logger;
private readonly HttpClient client;
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger<MPSHttpClient> logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
client = httpClient;
}
public async Task<bool> CheckTokens()
{
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBearerToken(Tokens.AccessToken);
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
request.SetBearerToken(Tokens.AccessToken);
response = await base.SendAsync(request, cancellationToken);
}
}
return response;
}
}
The big changes here are the inheritance and I used DI to obtain the HttpClient much like #Rosco mentioned. I had tried to override OnGetAsync in my original version. When inheriting from DelegatingHandler, all you have to override is OnSendAsync. This will handle all of your get, put, post, and deletes from your HttpContext all in one method.
My EngineList Service is written as if there were no tokens to be considered, which was my original goal:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly HttpClient _httpClient;
public EngineListService(HttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
The Token Provider stayed the same. I plan to add expirations and such to it, but it works as is:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
The ConfigureServices code changed just a bit:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<TokenProvider>();
services.AddTransient<MPSHttpClient>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
...
}
You instantiate MPSHttpClient as Transient, then reference it with the AddHttpMessageHandler call attached to the AddHttpClient call. I know this is different than how others implement HttpClients, but I learned this method of creating client services from a Pluralsight video and have been using it for everything. I create a separate Service for each entity in the database. If say I wanted to do tires, I would add the following to ConfigureServices:
services.AddHttpClient<ITireListService, TireListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
It will use the same DelegatingHandler so I can just keep adding services for each entity type while no longer worrying about tokens. Thanks to everyone that responded.
Thanks,
Jim
I am trying to implement a request response pattern in C# with the ArtemisNetClient, but having a bit of trouble finding out how to do so in a more generic way in a real solution.
I was able to do something like this in two console applications based on some Java examples:
Sender
static async System.Threading.Tasks.Task Main(string[] args)
{
var connectionFactory = new ConnectionFactory();
var endpoint = Endpoint.Create("localhost", 5672, "guest", "guest");
var connection = await connectionFactory.CreateAsync(endpoint);
string guid = new Guid().ToString();
var requestAddress = "TRADE REQ1";
var responseAddress = "TRADE RESP";
Message message = new Message("BUY AMD 1000 SHARES");
message.SetCorrelationId(guid);
message.ReplyTo = responseAddress;
var producer = await connection.CreateProducerAsync(requestAddress, RoutingType.Anycast);
await producer.SendAsync(message);
var consumer = await connection.CreateConsumerAsync(responseAddress, RoutingType.Anycast);
var responseMessage = await consumer.ReceiveAsync();
Console.WriteLine(responseMessage.GetBody<string>());
}
Receiver
static async System.Threading.Tasks.Task Main(string[] args)
{
// Create connection
var connectionFactory = new ConnectionFactory();
var endpoint = Endpoint.Create("localhost", 5672, "guest", "guest");
var connection = await connectionFactory.CreateAsync(endpoint);
var requestAddress = "TRADE REQ1";
// Create consumer to receive trade request messages
var consumer = await connection.CreateConsumerAsync(requestAddress, RoutingType.Anycast);
var message = await consumer.ReceiveAsync();
Console.WriteLine($"Received message: {message.GetBody<string>()}");
// Confirm trade request and ssend response message
if (!string.IsNullOrEmpty(message.ReplyTo))
{
Message responseMessage = new Message("Confirmed trade request");
responseMessage.SetCorrelationId(message.CorrelationId);
var producer = await connection.CreateProducerAsync(message.ReplyTo);
await producer.SendAsync(responseMessage);
}
}
This worked as expected, but I'd like to have something more down the line of what is described in this article, except it doesn't have any examples of a request response pattern.
To elaborate, I currently have two services that I want to communicate across.
In service 1 I want to create and publish a message and then wait for a response to enrich the instance object and save it to a database. I currently have this, but it lacks the await response message.
public async Task<Instance> CreateInstance(Instance instance)
{
await _instanceCollection.InsertOneAsync(instance);
var #event = new InstanceCreated
{
Id = instance.Id,
SiteUrl = instance.SiteUrl
};
await _messageProducer.PublishAsync(#event);
return instance;
}
I figured I might need to setup a temporary queue/connection or something in the PublishAsync() and change it to e.g. Task<Message> to support returning a response message. But how would I go about doing that? Would I have to do a new connectionfactory + CreateConsumerAsync etc. like in the console app example?
public class MessageProducer
{
private readonly IAnonymousProducer _producer;
public MessageProducer(IAnonymousProducer producer)
{
_producer = producer;
}
public async Task PublishAsync<T>(T message, string replyTo = null, string correlationId = null)
{
var serialized = JsonSerializer.Serialize(message);
var address = typeof(T).Name;
var msg = new Message(serialized);
if (replyTo != null && correlationId != null)
{
msg.CorrelationId = correlationId;
msg.ReplyTo = replyTo;
}
await _producer.SendAsync(address, msg);
}
public async Task PublishAsync<T>(T message, string routeName, string replyTo = null, string correlationId = null)
{
var serialized = JsonSerializer.Serialize(message);
var address = routeName;
var msg = new Message(serialized);
if(replyTo != null && correlationId != null)
{
msg.CorrelationId = correlationId;
msg.ReplyTo = replyTo;
}
await _producer.SendAsync(address, msg);
}
}
In Service 2 I have a InstanceCreatedConsumer which receives messages, but again it lacks a way to return response messages.
public class InstanceCreatedConsumer : ITypedConsumer<InstanceCreated>
{
private readonly MessageProducer _messageProducer;
public InstanceCreatedConsumer(MessageProducer messageProducer)
{
_messageProducer = messageProducer;
}
public async Task ConsumeAsync(InstanceCreated message, CancellationToken cancellationToken)
{
// consume message and return response
}
}
I figured I might be able to extend the ActiveMqExtensions class with a ConsumeAsync and HandleMessage that handles the response message with a return value, but I haven't gotten as far yet.
public static IActiveMqBuilder AddTypedConsumer<TMessage, TConsumer>(this IActiveMqBuilder builder,
RoutingType routingType)
where TConsumer : class, ITypedConsumer<TMessage>
{
builder.Services.TryAddScoped<TConsumer>();
builder.AddConsumer(typeof(TMessage).Name, routingType, HandleMessage<TMessage, TConsumer>);
return builder;
}
private static async Task HandleMessage<TMessage, TConsumer>(Message message, IConsumer consumer, IServiceProvider serviceProvider, CancellationToken token)
where TConsumer : class, ITypedConsumer<TMessage>
{
try
{
var msg = JsonConvert.DeserializeObject<TMessage>(message.GetBody<string>());
using var scope = serviceProvider.CreateScope();
var typedConsumer = scope.ServiceProvider.GetService<TConsumer>();
await typedConsumer.ConsumeAsync(msg, token);
await consumer.AcceptAsync(message);
}
catch(Exception ex)
{
// todo
}
}
Am I totally wrong in what I am trying to achieve here, or is it just not possible with the ArtemisNetClient?
Maybe someone has an example or can confirm whether I am down the right path, or maybe I should be using a different framework.
I am new to this kind of communication through messages like ActiveMQ Artemis, so any guidance is appreciated.
I don't see anything in the ArtemisNetClient that would simplify the request/response pattern from your application's point of view. One might expect something akin to JMS' QueueRequestor, but I don't see anything like that in the code, and I don't see anything like that listed in the documentation.
I recommend you simply do in your application what you did in your example (i.e. manually create the consumer & producer to deal with the responses on each end respectively). The only change I would recommend is to re-use connections so you create as few as possible. A connection pool would be ideal here.
For what it's worth, it looks to me like the first release of ArtemisNetClient was just 3 months ago and according to GitHub all but 2 of the commits to the code-base came from one developer. ArtemisNetClient may grow into a very successful C# client implementation, but at this point it seems relatively immature. Even if the existing code is high quality if there isn't a solid community around the client then chances are it won't have the support necessary to get timely bug fixes, new features, etc. Only time will tell.
With version 2.7.0 ArtemisNetClient introduces IRequestReplyClient interface that can be used to implement a request-response messaging pattern. With ArtemisNetClient.Extensions.DependencyInjection this may look as follows:
Client Side:
First you need to register your typed request-reply client in DI:
public void ConfigureServices(IServiceCollection services)
{
/*...*/
var endpoints = new[] { Endpoint.Create(host: "localhost", port: 5672, "guest", "guest") };
services.AddActiveMq("bookstore-cluster", endpoints)
.AddRequestReplyClient<MyRequestReplyClient>();
/*...*/
}
MyRequestReplyClient is your custom class that expects the IRequestReplyClient to be injected via the constructor. Once you have your custom class, you can either expose the IRequestReplyClient directly or encapsulate sending logic inside of it:
public class MyRequestReplyClient
{
private readonly IRequestReplyClient _requestReplyClient;
public MyRequestReplyClient(IRequestReplyClient requestReplyClient)
{
_requestReplyClient = requestReplyClient;
}
public async Task<TResponse> SendAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken)
{
var serialized = JsonSerializer.Serialize(request);
var address = typeof(TRequest).Name;
var msg = new Message(serialized);
var response = await _requestReplyClient.SendAsync(address, msg, cancellationToken);
return JsonSerializer.Deserialize<TResponse>(response.GetBody<string>());
}
}
That's it regarding the client-side.
Worker side
To implement the worker side you can (as you suggested), change the ITypedConsumer interface to return the message that would be sent back, or you can provide the additional data (ReplyTo and CorrelationId headers) so you can send the response back as part of your consumer logic. I prefer the latter as it's a more flexible option in my opinion.
Modified ITypedConsumer might look like that:
public interface ITypedConsumer<in T>
{
public Task ConsumeAsync(T message, MessageContext context, CancellationToken cancellationToken);
}
Where MessageContext is just a simple dto:
public class MessageContext
{
public string ReplyTo { get; init; }
public string CorrelationId { get; init; }
}
HandleMessage extension method:
private static async Task HandleMessage<TMessage, TConsumer>(Message message, IConsumer consumer, IServiceProvider serviceProvider, CancellationToken token)
where TConsumer : class, ITypedConsumer<TMessage>
{
var msg = JsonSerializer.Deserialize<TMessage>(message.GetBody<string>());
using var scope = serviceProvider.CreateScope();
var typedConsumer = scope.ServiceProvider.GetService<TConsumer>();
var messageContext = new MessageContext
{
ReplyTo = message.ReplyTo,
CorrelationId = message.CorrelationId
};
await typedConsumer.ConsumeAsync(msg, messageContext, token);
await consumer.AcceptAsync(message);
}
MessageProducer has to be slightly changed as well, so you can explicitly pass address and CorrelationId:
public class MessageProducer
{
private readonly IAnonymousProducer _producer;
public MessageProducer(IAnonymousProducer producer)
{
_producer = producer;
}
public async Task PublishAsync<T>(string address, T message, MessageContext context, CancellationToken cancellationToken)
{
var serialized = JsonSerializer.Serialize(message);
var msg = new Message(serialized);
if (!string.IsNullOrEmpty(context.CorrelationId))
{
msg.CorrelationId = context.CorrelationId;
}
await _producer.SendAsync(address, msg, cancellationToken);
}
}
And finally, the exemplary consumer could work like that:
public class CreateBookConsumer : ITypedConsumer<CreateBook>
{
private readonly MessageProducer _messageProducer;
public CreateBookConsumer(MessageProducer messageProducer)
{
_messageProducer = messageProducer;
}
public async Task ConsumeAsync(CreateBook message, MessageContext context, CancellationToken cancellationToken)
{
var #event = new BookCreated
{
Id = Guid.NewGuid(),
Title = message.Title,
Author = message.Author,
Cost = message.Cost,
InventoryAmount = message.InventoryAmount,
UserId = message.UserId,
Timestamp = DateTime.UtcNow
};
await _messageProducer.PublishAsync(context.ReplyTo, #event, new MessageContext
{
CorrelationId = context.CorrelationId
}, cancellationToken);
}
}
I have an ASP.NET MVC application which invokes an ASP.NET Web API REST Service each time a button is pressed in the UI.
Each time this button is pressed below DumpWarehouseDataIntoFile method is executed.
public class MyClass
{
private static HttpClient client = new HttpClient();
public async Task DumpWarehouseDataIntoFile(Warehouse myData, string path, string filename)
{
try
{
//Hosted web API REST Service base url
string Baseurl = "http://XXX.XXX.XX.X:YYYY/";
//using (var client = new HttpClient()) --> I have declared client as an static variable
//{
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
//Define request data format
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Serialize parameter to pass to the asp web api rest service
string jsonParam = Newtonsoft.JsonConvert.SerializeObject(myData);
//Sending request to find web api REST service resource using HttpClient
var httpContent = new StringContent(jsonParam, Encoding.UTF8, "application/json");
HttpResponseMessage Res = await client.PostAsync("api/Warehouse/DumpIntoFile", httpContent);
//Checking the response is successful or not which is sent using HttpClient
if (Res.IsSuccessStatusCode)
{
// Some other sftuff here
}
//}
}
catch (Exception ex)
{
// Do some stuff here
} // End Try
} // End DumpWarehouseDataIntoFile method
} // End class
Warehouse class object:
public class Warehouse
{
public DataTable dt { get; set; }
public string Filepath { get; set; }
}
I have found in this post that pattern:
using (var myClient = new HttpClient())
{
}
is not recommended to be used since it leads to socket exhaustion (System.Net.Sockets.SocketException). There it is recommended to use HttpClient as static variable and reuse it as it helps to reduce waste of sockets. So I have used a static variable.
The problem with this approach (in my scenario) is that it only works first button is pressed, next times button is pressed and DumpWarehouseDataIntoFile method is executed, below exception is thrown:
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: This instance has already started
one or more requests. Properties can only be modified before sending
the first request.
As error says, properties like base address, etc. can only be modified once before sending the first request.
I have googled and found some solutions proposed:
First solution
So it seems like singleton pattern would be a good option, as proposed here. Below the singleton proposed by Alper:
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
//You need to install package Newtonsoft.Json > https://www.nuget.org/packages/Newtonsoft.Json/
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public class MyApiClient : IDisposable
{
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private const string ClientUserAgent = "my-api-client-v1";
private const string MediaTypeJson = "application/json";
public MyApiClient(string baseUrl, TimeSpan? timeout = null)
{
_baseUrl = NormalizeBaseUrl(baseUrl);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
}
public async Task<string> PostAsync(string url, object input)
{
EnsureHttpClientCreated();
using (var requestContent = new StringContent(ConvertToJsonString(input), Encoding.UTF8, MediaTypeJson))
{
using (var response = await _httpClient.PostAsync(url, requestContent))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
public async Task<TResult> PostAsync<TResult>(string url, object input) where TResult : class, new()
{
var strResponse = await PostAsync(url, input);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<TResult> GetAsync<TResult>(string url) where TResult : class, new()
{
var strResponse = await GetAsync(url);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<string> GetAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.GetAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> PutAsync(string url, object input)
{
return await PutAsync(url, new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, MediaTypeJson));
}
public async Task<string> PutAsync(string url, HttpContent content)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.PutAsync(url, content))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> DeleteAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.DeleteAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose()
{
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient()
{
_httpClientHandler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false)
{
Timeout = _timeout
};
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);
if (!string.IsNullOrWhiteSpace(_baseUrl))
{
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
}
private void EnsureHttpClientCreated()
{
if (_httpClient == null)
{
CreateHttpClient();
}
}
private static string ConvertToJsonString(object obj)
{
if (obj == null)
{
return string.Empty;
}
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
private static string NormalizeBaseUrl(string url)
{
return url.EndsWith("/") ? url : url + "/";
}
}
Usage
using (var client = new MyApiClient("http://localhost:8080"))
{
var response = client.GetAsync("api/users/findByUsername?username=alper").Result;
var userResponse = client.GetAsync<MyUser>("api/users/findByUsername?username=alper").Result;
}
The problem I see here is that if you call above code many times (in my case would be each time I press the button on the UI and I call DumpWarehouseDataIntoFile method), you create and instance of MyApiClient each time and therefore a new instance of HttpClient is created and I want to reuse HttpClient, not to make many instances of it.
Second solution
Creating a kind of factory as proposed here by Nico. Below the code he proposes:
public interface IHttpClientFactory
{
HttpClient CreateClient();
}
public class HttpClientFactory : IHttpClientFactory
{
static string baseAddress = "http://example.com";
public HttpClient CreateClient()
{
var client = new HttpClient();
SetupClientDefaults(client);
return client;
}
protected virtual void SetupClientDefaults(HttpClient client)
{
client.Timeout = TimeSpan.FromSeconds(30); //set your own timeout.
client.BaseAddress = new Uri(baseAddress);
}
}
Usage
public HomeController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
readonly IHttpClientFactory _httpClientFactory;
public IActionResult Index()
{
var client = _httpClientFactory.CreateClient();
//....do your code
return View();
}
Here again you create a new instance of HttpClient each time you call CreateClient. You do not reuse HttpClient object.
Third Solution
Making HTTP requests using IHttpClientFactory as explained here.
The problem is that it is only available for .NET Core, not standard ASP.NET Framework, though it seems it is available by installing this nuget package. It seems like it automatically manages efficiently HttpClient instances and I would like to apply it to my scenario. I want to avoid to
reinvent the wheel.
I have never used IHttpClientFactory and I have no idea on how to use it: configure some features like base address, set request headers, create an instance of HttpClient and then invoke PostAsync on it passing as parameter the HttpContent.
I think this is the best approach so could someone tell me the necessary steps I need to do in order to make the same things I do in DumpWarehouseDataIntoFile method but using IHttpClientFactory? I am a bit lost, I do not know how to apply IHttpClientFactory to do the same as I do within DumpWarehouseDataIntoFile method.
Any others solutions not proposed here and also some code snippets will be highly appreciated.
HttpClient
The HttpClient can throw InvalidOperationException in the following cases:
When the BaseAddress setter is called after a request has been sent out
When the Timeout setter is called after a request has been sent out
When the MaxResponseContentBufferSize setter is called after a request has been sent out
When an operation has already started and resend was requested
In order to avoid these you can set the first two on per request level, for example:
CancellationTokenSource timeoutSource = new CancellationTokenSource(2000);
await httpClient.GetAsync("http://www.foo.bar", timeoutSource.Token);
HttpClientFactory
You can use the IHttpClientFactory in .NET Framework with the following trick:
AddHttpClient registers the DefaultHttpClientFactory for IHttpClientFactory
Then you can retrieve it from the DI container
var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
container.RegisterInstance(serviceProvider.GetService<IHttpClientFactory>());
container.ContainerScope.RegisterForDisposal(serviceProvider);
This sample uses SimpleInjector but the same concept can be applied for any other DI framework.
I'm not sure but will what happen if you move this lines to constructor:
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
//Define request data format
client.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
I think that re-initialization is problem.
Better to add the request url and the headers at the message. Don't use httpClient.BaseAddress or httpClient.DefaultRequestHeaders unless you have a default requirement.
HttpRequestMessage msg = new HttpRequestMessage {
Method = HttpMethod.Put,
RequestUri = new Uri(url),
Headers = httpRequestHeaders;
};
httpClient.SendAsync(msg);
It works well for reusing the HttpClient for many requests
I was trying to build a generic HTTP service in my project (c# with .net core 2.1), and I have done it as per the below snippet HttpService.
I also started using it by calling it from my business logic class which uses this generic PostAsync method to post an HTTP call to a 3rd party with a content in body. It works perfectly.
But, when I tried to test it, I failed!
Actually when I tried debugging (testing mode), I get null response when the debugger comes to this line var result = await _httpService.PostAsync("https://test.com/api", content); in business class Processor even with fake objects and mocks, although it works normally in debugging mode without testing/mocking.
HTTP service:
public interface IHttpService
{
Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content);
}
public class HttpService : IHttpService
{
private readonly IHttpClientFactory _httpClientFactory;
public HttpService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
{
var httpClient = _httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(3);
var response = await httpClient.PostAsync(requestUri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return response;
}
}
Business class:
public class Processor : IProcessor
{
private readonly IHttpService _httpService;
public Processor() { }
public Processor(IHttpService httpService, IAppSettings appSettings)
{
_httpService = httpService;
}
public async Task<HttpResponseMessage> PostToVendor(Order order)
{
// Building content
var json = JsonConvert.SerializeObject(order, Formatting.Indented);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// HTTP POST
var result = await _httpService.PostAsync("https://test.com/api", content); // returns null during the test without stepping into the method PostAsyn itself
return result;
}
}
Test class:
public class MyTests
{
private readonly Mock<IHttpService> _fakeHttpMessageHandler;
private readonly IProcessor _processor; // contains business logic
private readonly Fixture _fixture = new Fixture();
public FunctionTest()
{
_fakeHttpMessageHandler = new Mock<IHttpService>();
_processor = new Processor(_fakeHttpMessageHandler.Object);
}
[Fact]
public async Task Post_To_Vendor_Should_Return_Valid_Response()
{
var fakeHttpResponseMessage = new Mock<HttpResponseMessage>(MockBehavior.Loose, new object[] { HttpStatusCode.OK });
var responseModel = new ResponseModel
{
success = true,
uuid = Guid.NewGuid().ToString()
};
fakeHttpResponseMessage.Object.Content = new StringContent(JsonConvert.SerializeObject(responseModel), Encoding.UTF8, "application/json");
var fakeContent = _fixture.Build<DTO>().Create(); // DTO is the body which gonna be sent to the API
var content = new StringContent(JsonConvert.SerializeObject(fakeContent), Encoding.UTF8, "application/json");
_fakeHttpMessageHandler.Setup(x => x.PostAsync(It.IsAny<string>(), content))
.Returns(Task.FromResult(fakeHttpResponseMessage.Object));
var res = _processor.PostToVendor(fakeContent).Result;
Assert.NotNull(res.Content);
var actual = JsonConvert.SerializeObject(responseModel);
var expected = await res.Content.ReadAsStringAsync();
Assert.Equal(expected, actual);
}
}
Your problem is in mock set up:
_fakeHttpMessageHandler.Setup(x => x.PostAsync(It.IsAny<string>(), content))
.Returns(Task.FromResult(fakeHttpResponseMessage.Object));
Second parameter for PostAsync method expected to be content, but since StringContent is a reference type, content you setup in mock is different from content you creating in processor. If you change it to next one, it should work as you expect:
_fakeHttpMessageHandler.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<StringContent>()))
.Returns(Task.FromResult(fakeHttpResponseMessage.Object));
P.S. null response to PostAsync means that method has default setup, which means that it will return default value
This is an example of an Action method inside HomeController:
[HttpPost]
public async Task<dynamic> UnitTest(string data)
{
var httpClient = new HttpClient();
var request = JsonConvert.SerializeObject(data);
var url = "https://jsonplaceholder.typicode.com/posts";
var response = await httpClient.PostAsync(url, new StringContent(request, Encoding.UTF8, "application/json"));
string responseContent = await response.Content.ReadAsStringAsync();
return responseContent;
}
I want to test it, but I do not know how. I tried the following:
[TestMethod]
public async Task JsonRightTest()
{
MyModelR model1 = new MyModelR
{
Title = "foo",
Body = "bar",
UserId = 1
};
string output1 = JsonConvert.SerializeObject(model1);
var url = "Home/UnitTest";
var response = await _client.PostAsync(url, new StringContent(output1, Encoding.UTF8, "application/json"));
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var responseModel = JsonConvert.DeserializeObject<MyModel>(responseContent);
// Assert
Assert.AreEqual(1,
responseModel.UserId);
}
internal class MyModel
{
public string Title { get; set; }
public string Body { get; set; }
public int UserId { get; set; }
public int Id { get; set; }
}
internal class MyModelR
{
public string Title { get; set; }
public string Body { get; set; }
public int UserId { get; set; }
}
Unfortunately, the above does not work. Since I am very confused could you give me some answers to the following:
What's the best way to test UnitTest action? Is my approach wrong? Do I just have to call the API from JsonRightTest method and not involve the action?
Actually, in that case do we have a unit or integrated test?
I want to call the actual external end point.
The API (https://jsonplaceholder.typicode.com/posts) is found on the Internet and is available for testing purposes.
This appears to be an XY problem and a mixing of concerns.
The code under test is tightly coupled to implementation concerns and should encapsulate that external call behind a service abstraction that can be mocked during isolated unit tests.
Some refactoring steps that should be followed....
Those models being constructed in the test should be in the action.
[HttpPost]
public async Task<IActionResult> UnitTest([FromBody]MyDataR data) {
var httpClient = new HttpClient();
var requestJson = JsonConvert.SerializeObject(data);
var url = "https://jsonplaceholder.typicode.com/posts";
var response = await httpClient.PostAsync(url, new StringContent(requestJson, Encoding.UTF8, "application/json"));
if(response.IsSuccessStatusCode) {
var responseContent = await response.Content.ReadAsStringAsync();
var responseModel = JsonConvert.DeserializeObject<MyModel>(responseContent);
return Ok(responseModel);
}else
return StatusCode(response.StatusCode);
}
Refactoring further, the actual calling of the external endpoint should be abstracted away
public interface IExternalService {
Task<MyModel> PostDataAsync(MyData data);
}
and implemented accordingly
public class ExternalService : IExternalService {
// should consider abstracting this as well but that is another matter
static Lazy<HttpClient> _httpClient = new Lazy<HttpClient>(() => new HttpClient());
private HttpClient httpClient {
get { return _httpClient.Value; }
}
public async Task<MyModel> PostDataAsync(MyData data) {
var requestJson = JsonConvert.SerializeObject(data);
var url = "https://jsonplaceholder.typicode.com/posts";
var content = new StringContent(requestJson, Encoding.UTF8, "application/json")
var response = await httpClient.PostAsync(url, content);
var responseContent = await response.Content.ReadAsStringAsync();
if(response.IsSuccessStatusCode) {
var responseContent = await response.Content.ReadAsStringAsync();
var responseModel = JsonConvert.DeserializeObject<MyModel>(responseContent);
return responseModel;
}else
return null;
}
}
with the action in the the controller now looking like
private readonly IExternalService externalService; // Assumed injected into the controller
[HttpPost]
public async Task<IActionResult> UnitTest([FromBody]MyDataR data) {
var responseModel = await externalService.PostDataAsync(data);
if(responseModel != null) {
return Ok(responseModel);
}else
return BadRequest();
}
By removing the tight coupling to the external service call , this would allow the controller to be tested in isolation as needed to verify that it behaves as expected.
The external service call implementation can now be tested on its own if the desire is to check that the external end point behaves as expected. This would be considered an integration test due to its reliance on the actual external endpoint.
[TestMethod]
public async Task JsonRightTest() {
// Arrange
var expected = 1;
var model = new MyModelR {
Title = "foo",
Body = "bar",
UserId = 1
};
var target = new ExternalService(); // System under test
// Act
var responseModel = await target.PostDataAsync(model);
// Assert
Assert.IsNotNull(responseModel);
var actual = responseModel.UserId;
Assert.AreEqual(expected, actual);
}
This should now allow for easier inspection of the external service to verify that it behaves as expected.
In production you would make sure that the external service abstraction and its implementation are registered in the composition root.
services.AddTransient<IExternalService, ExternalService>();
so that it is injected into the dependent controller correctly.