How to unit test HttpClient GetAsync - c#

I have the following in my controller:
public async Task<IActionResult> IndexAsync()
{
string baseUrl = "https://apilink.com";
using (HttpClient client = new HttpClient())
using (HttpResponseMessage response = await client.GetAsync(baseUrl))
using (HttpContent content = response.Content)
{
string data = await content.ReadAsStringAsync();
if (data != null)
{
var recipeList = JsonConvert.DeserializeObject<Recipe[]>(data);
return View();
}
}
return View();
}
I want to unit test this but cannot work out how to test the HttpClient.
I have tried:
[Test]
public void Index_OnPageLoad_AllRecipesLoaded()
{
var testController = new HomeController();
mockHttpClient.Setup(m => m.GetAsync(It.IsAny<string>())).Returns(
() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
mockHttpContent.Setup(m => m.ReadAsStringAsync()).Returns(() => Task.FromResult(LoadJson()));
var result = testController.IndexAsync();
Assert.IsNotNull(result);
}
// Loads the Json data as I don't actually want to make the API call in the test.
public string LoadJson()
{
using (StreamReader r = new StreamReader("testJsonData.json"))
{
string json = r.ReadToEnd();
return json;
}
}
Is there a way to mock this effectively/simply? Or should I maybe inject my own IHttpClient interface? (I am not sure if that is good practice?)
Thanks

There are several ways to unit test HttpClient, but none are straightforward because HttpClient does not implement a straightforward abstraction.
1) Write an abstraction
Here is a straightforward abstraction and you can use this instead of HttpClient. This is my recommended approach. You can inject this into your services and mock the abstraction with Moq as you have done above.
public interface IClient
{
/// <summary>
/// Sends a strongly typed request to the server and waits for a strongly typed response
/// </summary>
/// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
/// <param name="request">The request that will be translated to a http request</param>
/// <returns>The response as the strong type specified by TResponseBody /></returns>
/// <typeparam name="TRequestBody"></typeparam>
Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(IRequest<TRequestBody> request);
/// <summary>
/// Default headers to be sent with http requests
/// </summary>
IHeadersCollection DefaultRequestHeaders { get; }
/// <summary>
/// Base Uri for the client. Any resources specified on requests will be relative to this.
/// </summary>
AbsoluteUrl BaseUri { get; }
}
Full code reference here. The Client class implements the abstraction.
2) Create a Fake Http Server and Verify the Calls on the Server-Side
This code sets up a fake server, and your tests can verify the Http calls.
using var server = ServerExtensions
.GetLocalhostAddress()
.GetSingleRequestServer(async (context) =>
{
Assert.AreEqual("seg1/", context?.Request?.Url?.Segments?[1]);
Assert.AreEqual("seg2", context?.Request?.Url?.Segments?[2]);
Assert.AreEqual("?Id=1", context?.Request?.Url?.Query);
Assert.AreEqual(headerValue, context?.Request?.Headers[headerKey]);
if (hasRequestBody)
{
var length = context?.Request?.ContentLength64;
if (!length.HasValue) throw new InvalidOperationException();
var buffer = new byte[length.Value];
_ = (context?.Request?.InputStream.ReadAsync(buffer, 0, (int)length.Value));
Assert.AreEqual(requestJson, Encoding.UTF8.GetString(buffer));
}
if (context == null) throw new InvalidOperationException();
await context.WriteContentAndCloseAsync(responseJson).ConfigureAwait(false);
});
Full code reference here.
3) Mock the HttpHandler
You can inject a Mock HttpHandler into the HttpClient. Here is an example:
private static void GetHttpClientMoq(out Mock<HttpMessageHandler> handlerMock, out HttpClient httpClient, HttpResponseMessage value)
{
handlerMock = new Mock<HttpMessageHandler>();
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(value)
.Verifiable();
httpClient = new HttpClient(handlerMock.Object);
}
Full code reference. You can then verify the calls from the mock itself.

Related

Request/Response pattern with TPL Dataflow

We have a problem where we need a request/response pattern while using the TPL Dataflow library. Our problem is we have a .NET core API that calls a dependent service. The dependent service limits concurrent requests. Our API does not limit concurrent requests; therefore, we could receive thousands of requests at a time. In this case, the dependent service would reject requests after reaching its limit. Therefore, we implemented a BufferBlock<T> and a TransformBlock<TIn, TOut>. The performance is solid and works great. We tested our API front end with 1000 users issuing 100 requests/sec with 0 problems. The buffer block buffers requests and the transform block executes in parallel our desired amount of requests. The dependency service receives our requests and responds. We return that response in the transform block action and all is well. Our problem is the buffer block and transform block are disconnected which means requests/responses are not in sync. We are experiencing an issue where a request will receive the response of another requester (please see the code below).
Specific to the code below, our problem lies in the GetContent method. That method is called from a service layer in our API which ultimately is called from our controller. The code below and the service layer are singletons. The SendAsync to the buffer is disconnected from the transform block ReceiveAsync so that arbitrary responses are returned and not necessarily the request that was issued.
So, our question is: Is there a way using the dataflow blocks to correlate request/responses? The ultimate goal is a request comes in to our API, gets issued to the dependent service, and is returned to the client. The code for our data flow implementation is below.
public class HttpClientWrapper : IHttpClientManager
{
private readonly IConfiguration _configuration;
private readonly ITokenService _tokenService;
private HttpClient _client;
private BufferBlock<string> _bufferBlock;
private TransformBlock<string, JObject> _actionBlock;
public HttpClientWrapper(IConfiguration configuration, ITokenService tokenService)
{
_configuration = configuration;
_tokenService = tokenService;
_bufferBlock = new BufferBlock<string>();
var executionDataFlowBlockOptions = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 10
};
var dataFlowLinkOptions = new DataflowLinkOptions
{
PropagateCompletion = true
};
_actionBlock = new TransformBlock<string, JObject>(t => ProcessRequest(t),
executionDataFlowBlockOptions);
_bufferBlock.LinkTo(_actionBlock, dataFlowLinkOptions);
}
public void Connect()
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add("x-ms-client-application-name",
"ourappname");
}
public async Task<JObject> GetContent(string request)
{
await _bufferBlock.SendAsync(request);
var result = await _actionBlock.ReceiveAsync();
return result;
}
private async Task<JObject> ProcessRequest(string request)
{
if (_client == null)
{
Connect();
}
try
{
var accessToken = await _tokenService.GetTokenAsync(_configuration);
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post,
new Uri($"https://{_configuration.Uri}"));
// add the headers
httpRequestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
// add the request body
httpRequestMessage.Content = new StringContent(request, Encoding.UTF8,
"application/json");
var postRequest = await _client.SendAsync(httpRequestMessage);
var response = await postRequest.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<JObject>(response);
}
catch (Exception ex)
{
// log error
return new JObject();
}
}
}
What you have to do is tag each incoming item with an id so that you can correlate the data input to the result output. Here's an example of how to do that:
namespace ConcurrentFlows.DataflowJobs {
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
/// <summary>
/// A generic interface defining that:
/// for a specified input type => an awaitable result is produced.
/// </summary>
/// <typeparam name="TInput">The type of data to process.</typeparam>
/// <typeparam name="TOutput">The type of data the consumer expects back.</typeparam>
public interface IJobManager<TInput, TOutput> {
Task<TOutput> SubmitRequest(TInput data);
}
/// <summary>
/// A TPL-Dataflow based job manager.
/// </summary>
/// <typeparam name="TInput">The type of data to process.</typeparam>
/// <typeparam name="TOutput">The type of data the consumer expects back.</typeparam>
public class DataflowJobManager<TInput, TOutput> : IJobManager<TInput, TOutput> {
/// <summary>
/// It is anticipated that jobHandler is an injected
/// singleton instance of a Dataflow based 'calculator', though this implementation
/// does not depend on it being a singleton.
/// </summary>
/// <param name="jobHandler">A singleton Dataflow block through which all jobs are processed.</param>
public DataflowJobManager(IPropagatorBlock<KeyValuePair<Guid, TInput>, KeyValuePair<Guid, TOutput>> jobHandler) {
if (jobHandler == null) { throw new ArgumentException("Argument cannot be null.", "jobHandler"); }
this.JobHandler = JobHandler;
if (!alreadyLinked) {
JobHandler.LinkTo(ResultHandler, new DataflowLinkOptions() { PropagateCompletion = true });
alreadyLinked = true;
}
}
private static bool alreadyLinked = false;
/// <summary>
/// Submits the request to the JobHandler and asynchronously awaits the result.
/// </summary>
/// <param name="data">The input data to be processd.</param>
/// <returns></returns>
public async Task<TOutput> SubmitRequest(TInput data) {
var taggedData = TagInputData(data);
var job = CreateJob(taggedData);
Jobs.TryAdd(job.Key, job.Value);
await JobHandler.SendAsync(taggedData);
return await job.Value.Task;
}
private static ConcurrentDictionary<Guid, TaskCompletionSource<TOutput>> Jobs {
get;
} = new ConcurrentDictionary<Guid, TaskCompletionSource<TOutput>>();
private static ExecutionDataflowBlockOptions Options {
get;
} = GetResultHandlerOptions();
private static ITargetBlock<KeyValuePair<Guid, TOutput>> ResultHandler {
get;
} = CreateReplyHandler(Options);
private IPropagatorBlock<KeyValuePair<Guid, TInput>, KeyValuePair<Guid, TOutput>> JobHandler {
get;
}
private KeyValuePair<Guid, TInput> TagInputData(TInput data) {
var id = Guid.NewGuid();
return new KeyValuePair<Guid, TInput>(id, data);
}
private KeyValuePair<Guid, TaskCompletionSource<TOutput>> CreateJob(KeyValuePair<Guid, TInput> taggedData) {
var id = taggedData.Key;
var jobCompletionSource = new TaskCompletionSource<TOutput>();
return new KeyValuePair<Guid, TaskCompletionSource<TOutput>>(id, jobCompletionSource);
}
private static ExecutionDataflowBlockOptions GetResultHandlerOptions() {
return new ExecutionDataflowBlockOptions() {
MaxDegreeOfParallelism = Environment.ProcessorCount,
BoundedCapacity = 1000
};
}
private static ITargetBlock<KeyValuePair<Guid, TOutput>> CreateReplyHandler(ExecutionDataflowBlockOptions options) {
return new ActionBlock<KeyValuePair<Guid, TOutput>>((result) => {
RecieveOutput(result);
}, options);
}
private static void RecieveOutput(KeyValuePair<Guid, TOutput> result) {
var jobId = result.Key;
TaskCompletionSource<TOutput> jobCompletionSource;
if (!Jobs.TryRemove(jobId, out jobCompletionSource)) {
throw new InvalidOperationException($"The jobId: {jobId} was not found.");
}
var resultValue = result.Value;
jobCompletionSource.SetResult(resultValue);
}
}
}
Also see this answer for reference.
Doing a simple throttling is not a particularly enticing use case for the TPL Dataflow library, and using instead a SemaphoreSlim seems simpler and more attractive. But if you wanted more features, like for example enforcing a minimum duration for each request, or having a way for waiting all the pending requests to complete, then the TPL Dataflow could have something to offer that the SemaphoreSlim couldn't. The basic idea is to avoid passing naked input values to the block, and trying later to associate them with the produced results. It is much safer to create tasks immediately upon request, send the tasks to an ActionBlock<Task>, and let the block activate and await asynchronously these tasks using its specified MaxDegreeOfParallelism. This way the input value and its result will be unambiguously tied together forever.
public class ThrottledExecution<T>
{
private readonly ActionBlock<Task<Task<T>>> _actionBlock;
private readonly CancellationToken _cancellationToken;
public ThrottledExecution(int concurrencyLevel, int minDurationMilliseconds = 0,
CancellationToken cancellationToken = default)
{
if (minDurationMilliseconds < 0) throw new ArgumentOutOfRangeException();
_actionBlock = new ActionBlock<Task<Task<T>>>(async task =>
{
try
{
var delay = Task.Delay(minDurationMilliseconds, cancellationToken);
task.RunSynchronously();
await task.Unwrap().ConfigureAwait(false);
await delay.ConfigureAwait(false);
}
catch { } // Ignore exceptions (errors are propagated through the task)
}, new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = concurrencyLevel,
CancellationToken = cancellationToken,
});
_cancellationToken = cancellationToken;
}
public Task<T> Run(Func<Task<T>> function)
{
// Create a cold task (the function will be invoked later)
var task = new Task<Task<T>>(function, _cancellationToken);
var accepted = _actionBlock.Post(task);
if (!accepted)
{
_cancellationToken.ThrowIfCancellationRequested();
throw new InvalidOperationException(
"The component has been marked as complete.");
}
return task.Unwrap();
}
public void Complete() => _actionBlock.Complete();
public Task Completion => _actionBlock.Completion;
}
Usage example:
private ThrottledExecution<JObject> throttledExecution
= new ThrottledExecution<JObject>(concurrencyLevel: 10);
public Task<JObject> GetContent(string request)
{
return throttledExecution.Run(() => ProcessRequest(request));
}
I appreciate the answer provided by JSteward. His is a perfectly acceptable approach; however, I ended up doing this by using a SemaphoreSlim. SemaphoreSlim provides two things that allow this to be a powerful solution. First, it provides a constructor overload where you can send in a count. This count refers to the number of concurrent items that will be able to get past the semaphore waiting mechanism. The waiting mechanism is provided by a method called WaitAsync. With the below approach where the Worker class is as a Singleton, concurrent requests come in, are limited to 10 at a time executing the http request, and the responses are all returned to the correct requests. So an implementation might look like the following:
public class Worker: IWorker
{
private readonly IHttpClientManager _httpClient;
private readonly ITokenService _tokenService;
private readonly SemaphoreSlim _semaphore;
public Worker(IHttpClientManager httpClient, ITokenService tokenService)
{
_httpClient = httpClient;
_tokenService = tokenService;
// we want to limit the number of items here
_semaphore = new SemaphoreSlim(10);
}
public async Task<JObject> ProcessRequestAsync(string request, string route)
{
try
{
var accessToken = await _tokenService.GetTokenAsync(
_timeSeriesConfiguration.TenantId,
_timeSeriesConfiguration.ClientId,
_timeSeriesConfiguration.ClientSecret);
var cancellationToken = new CancellationTokenSource();
cancellationToken.CancelAfter(30000);
await _semaphore.WaitAsync(cancellationToken.Token);
var httpResponseMessage = await _httpClient.SendAsync(new HttpClientRequest
{
Method = HttpMethod.Post,
Uri = $"https://someuri/someroute",
Token = accessToken,
Content = request
});
var response = await httpResponseMessage.Content.ReadAsStringAsync();
return response;
}
catch (Exception ex)
{
// do some logging
throw;
}
finally
{
_semaphore.Release();
}
}
}

Best way to test Get() method of controller

I have the following constructor with a get () method and I need to test if this method works correctly through NUnit.
public ConsumersController(IProvider<Consumer> provider)
{
this.provider = provider;
}
/// <summary>
/// Get the list of consumers that you are allowed to see and serve
/// </summary>
/// <response code="200">Returns the consumers you are allowed to see and serve</response>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<Consumer>), 200)]
[Produces(typeof(IEnumerable<Consumer>))]
[SwaggerResponse((int)HttpStatusCode.OK, Type = typeof(IEnumerable<Consumer>))]
public IEnumerable<Consumer> Get()
{
return this.provider.GetAll();
}
I created this code based on this link Testing controller logic in ASP.NET Core but I do not know if it is correct, the test does not pass.
Thanks in advance.
[TestFixture]
public class ConsumerTest
{
private readonly HttpClient consumerHttp;
private readonly ConsumersController consumerControl;
[Test]
public async Task Get_ConsumersController()
{
var result = this.consumerControl.Get();
var response = await consumerHttp.GetAsync("http://localhost:9000/consumers");
response.EnsureSuccessStatusCode();
var consumerList = JsonConvert.DeserializeObject<IEnumerable<Consumer>>(await response.Content.ReadAsStringAsync());
var consumerTest = consumerList.First();
Assert.AreEqual(result.First().Name, consumerTest.Name);
}
}
Thanks for the answers. In fact it lacked to use the MOQ library and there was an error with the use of Async. I got it with this code.
[Test]
public void Get_ReturnsAConsumer_WithAListOfIProvider()
{
var mockRepo = new Mock<IProvider<Consumer>>();
mockRepo.Setup(repo => repo.GetAll()).Returns(GetConsumers());
var controller = new ConsumersController(mockRepo.Object);
var result = controller.Get();
Assert.IsAssignableFrom<List<Consumer>>(result);
Assert.AreEqual(1, result.Count());
}

Pass JSON data in a http GET request to a REST service

Using the following command:
curl -v -X GET -H "Content-Type: application/json" -d {'"mode":"0"'} http://host.domain.abc.com:23423/api/start-trial-api/
I am able to send the JSON data to web request and get the response back.
How can I do the same in C#?
I am able to POST data to the other service and get the response but don't understand how to send the data to GET request.
I tried searching on google and stackoverflow for the same in C#, but did not find anything.
Sample code - Make sure the request method is set to "GET"
string url = "";
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.ContentType = "application/json";
var webResponse = request.GetResponse();
using (var s = webResponse.GetResponseStream())
{
using (TextReader textReader = new StreamReader(s, true))
{
string jsonString = textReader.ReadToEnd();
}
}
Plenty of abstractions here, but hopefully will give a rough guide on how to connect to a service in C#
The Interface
public interface IShopifyAPIGateway
{
HttpResponseMessage Get(string path);
}
Shopify API Gateway, which instatiates HTTPClient()
public sealed class ShopifyAPIGateway : IShopifyAPIGateway
{
/// <summary>
///
/// </summary>
private Identity _identity;
/// <summary>
///
/// </summary>
private HttpClient _httpClient;
/// <summary>
///
/// </summary>
public ShopifyAPIGateway(Identity
identity)
{
_identity = identity;
_httpClient = new HttpClient(ClientHandler());
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public HttpResponseMessage Get(string path)
{
try
{
var response = Connect().GetAsync(path).Result;
return response;
}
catch (CustomHttpResponseException ex)
{
new Email().SendEmail(_identity.ClientName, "Http Response Error - Shopify API Module",
"Http Response Error - Shopify API Module: " + ex.Message,
"error#retain.me");
throw new CustomHttpResponseException(ex.Message);
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
private HttpClient Connect()
{
try
{
_httpClient.BaseAddress = new Uri(_identity.APIURL);
return _httpClient;
}
catch (CustomHttpRequestException ex)
{
throw new CustomHttpRequestException(ex.Message);
}
}
/// <summary>
///
/// </summary>
/// <param name="userKey"></param>
/// <returns></returns>
private HttpClientHandler ClientHandler()
{
try
{
return new HttpClientHandler()
{
Credentials = new NetworkCredential(_identity.APIKey,
_identity.Password),
PreAuthenticate = true
};
}
catch (CustomClientHandlerException ex)
{
throw new CustomClientHandlerException(ex.Message);
}
}
}
Generic repo to return any object(s) where the response object matches T
public sealed class EntityRepository<T> : IEntityRepository<T>
{
private IShopifyAPIGateway _gateWay;
public T Find(string path)
{
try
{
_gateWay = new ShopifyAPIGateway(_identity);
var json = _gateWay.Get(path).Content.ReadAsStringAsync();
T results = JsonConvert.DeserializeObject<T>(json.Result);
return results;
}
catch (System.Exception ex)
{
throw new ApplicationException(ex.Message);
}
}
}
Usage return type must match the Type your passing and also the Type that's being returned in response.
private IEnumerable<Order> Orders()
{
var entityRepo = new EntityRepository<Order>();
return entityRepo.Find("somedomain/api/orders?mode=0", _identity);
}

C# Flurl and HttpClient, no response from REST API

I am having a problem with getting a response from an API with my code, the request does not time out and it does not give me a response at all. I have made an api endpoint in my own API to return the json string to then manually post the json data with "Firefox Poster" and it works just fine. With this I believe that the problem is somewhere in my code.
I got a C# WebAPI which I am developing to be used with an Angular frontend(This works, it's just for history). When calling my API I create the object "EnrollmentRequestDto"
public class EnrollmentRequestDto
{
/// <summary>
/// Context Information for the request. Contains the Ship-To, language code and timezone.
/// </summary>
[JsonProperty("requestContext")]
public RequestContextDto RequestContext { get; set; }
/// <summary>
/// Unique ID provided by DEP to reseller on provisioning DEP access. This would be the reseller's DEP ID if its posted by distributor OBO a reseller, and would be his own depResellerId if a reseller is posting for self.
/// </summary>
[JsonProperty("depResellerId")]
public string DepResellerId { get; set; }
/// <summary>
/// Unique transaction ID provided by the reseller
/// </summary>
[JsonProperty("transactionId")]
public string TransactionId { get; set; }
/// <summary>
/// List of orders in the transaction (limit 1000 per transaction)
/// </summary>
[JsonProperty("orders")]
public List<OrderDto> Orders { get; set; }
}
After this object is created I send to to my class RequestHandler and the method BulkEnrollRequest, which atm is written with HttpClient extension Flurl, which can be found here: github
public IResponse BulkEnrollRequest(EnrollmentRequestDto enrollmentRequest)
{
try
{
var result = _bulkEnrollUrl.PostJsonAsync(enrollmentRequest).ReceiveJson<SuccessResponse>();
result.Wait();
return result.Result;
}
catch (FlurlHttpTimeoutException)
{
throw new AppleTimeOutException();
}
catch (FlurlHttpException ex)
{
return _errorHandler.DeserializeFlurlException(ex);
}
}
I have also tried this to make sure that nothing happens in Flurl(This is just to debug to the point where I want to get the response):
public async Task<IResponse> BulkEnrollRequest(EnrollmentRequestDto enrollmentRequest)
{
var json = JsonConvert.SerializeObject(enrollmentRequest);
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await httpClient.PostAsync(_bulkEnrollUrl, new StringContent(json, Encoding.UTF8, "application/json"));
return new SuccessResponse();
}
And the code freezes at the await point and nothing is happening. I have tried to place a breakpoint after the BulkEnrollRequest so it never leaves this method.
Now here's for the funny part: It did work while I was working on the ErrorHandler and at some point the API just stopped responding. Wireshark shows that a request is being made... so I am stuck.
Any help is appreciated.
EDIT
This now works! I implemented async all the way from the API Controller down to RequestHandler, then awaited for every call. For reference:
public async Task<IResponse> BulkEnrollRequest(EnrollmentRequestDto enrollmentRequest)
{
try
{
var result = await _bulkEnrollUrl.PostJsonAsync(enrollmentRequest).ReceiveJson<SuccessResponse>();
return result;
}
catch (FlurlHttpTimeoutException)
{
throw new AppleTimeOutException();
}
catch (FlurlHttpException ex)
{
return _errorHandler.DeserializeFlurlException(ex);
}
}
This is now fixed. I believe that the reason why the Wireshark traffic looked so weird was because of a deadlock in the code, and the timeout was on my side which meant that I could never FIN ACK the information. To fix this i implemented async on all methods, from the Controller down to my RequestHandler class. For reference:
public async Task<IResponse> BulkEnrollRequest(EnrollmentRequestDto enrollmentRequest)
{
try
{
var result = await _bulkEnrollUrl.PostJsonAsync(enrollmentRequest).ReceiveJson<SuccessResponse>();
return result;
}
catch (FlurlHttpTimeoutException)
{
throw new AppleTimeOutException();
}
catch (FlurlHttpException ex)
{
return _errorHandler.DeserializeFlurlException(ex);
}
}
Try this code
Install-Package Microsoft.AspNet.WebApi.Client
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:9000/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("api/products", enrollmentRequest);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<T>();
}
}

Deciding between HttpClient and WebClient [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 months ago.
The community reviewed whether to reopen this question 9 months ago and left it closed:
Original close reason(s) were not resolved
Improve this question
Our web application is running in .NET Framework 4.0. The UI calls the controller methods through Ajax calls.
We need to consume the REST service from our vendor. I am evaluating the best way to call the REST service in .NET 4.0. The REST service requires a basic authentication scheme and it can return data in both XML and JSON.
There isn't any requirement for uploading/downloading huge data and I don't see anything in future. I took a look at few open source code projects for REST consumption and didn't find any value in those to justify additional dependency in the project. I started to evaluate WebClient and HttpClient. I downloaded HttpClient for .NET 4.0 from NuGet.
I searched for differences between WebClient and HttpClient and this site mentioned that single HttpClient can handle concurrent calls and it can reuse resolved DNS, cookie configuration and authentication. I am yet to see practical values that we may gain due to the differences.
I did a quick performance test to find how WebClient (synchronous calls), HttpClient (synchronous and asynchronous) perform. And here are the results:
I am using the same HttpClient instance for all the requests (minimum - maximum).
WebClient sync: 8 ms - 167 ms
HttpClient sync: 3 ms - 7228 ms
HttpClient async: 985 - 10405 ms
Using a new HttpClient for each request (minimum - maximum):
WebClient sync: 4 ms - 297 ms
HttpClient sync: 3 ms - 7953 ms
HttpClient async: 1027 - 10834 ms
Code
public class AHNData
{
public int i;
public string str;
}
public class Program
{
public static HttpClient httpClient = new HttpClient();
private static readonly string _url = "http://localhost:9000/api/values/";
public static void Main(string[] args)
{
#region "Trace"
Trace.Listeners.Clear();
TextWriterTraceListener twtl = new TextWriterTraceListener(
"C:\\Temp\\REST_Test.txt");
twtl.Name = "TextLogger";
twtl.TraceOutputOptions = TraceOptions.ThreadId | TraceOptions.DateTime;
ConsoleTraceListener ctl = new ConsoleTraceListener(false);
ctl.TraceOutputOptions = TraceOptions.DateTime;
Trace.Listeners.Add(twtl);
Trace.Listeners.Add(ctl);
Trace.AutoFlush = true;
#endregion
int batchSize = 1000;
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = batchSize;
ServicePointManager.DefaultConnectionLimit = 1000000;
Parallel.For(0, batchSize, parallelOptions,
j =>
{
Stopwatch sw1 = Stopwatch.StartNew();
GetDataFromHttpClientAsync<List<AHNData>>(sw1);
});
Parallel.For(0, batchSize, parallelOptions,
j =>
{
Stopwatch sw1 = Stopwatch.StartNew();
GetDataFromHttpClientSync<List<AHNData>>(sw1);
});
Parallel.For(0, batchSize, parallelOptions,
j =>
{
using (WebClient client = new WebClient())
{
Stopwatch sw = Stopwatch.StartNew();
byte[] arr = client.DownloadData(_url);
sw.Stop();
Trace.WriteLine("WebClient Sync " + sw.ElapsedMilliseconds);
}
});
Console.Read();
}
public static T GetDataFromWebClient<T>()
{
using (var webClient = new WebClient())
{
webClient.BaseAddress = _url;
return JsonConvert.DeserializeObject<T>(
webClient.DownloadString(_url));
}
}
public static void GetDataFromHttpClientSync<T>(Stopwatch sw)
{
HttpClient httpClient = new HttpClient();
var response = httpClient.GetAsync(_url).Result;
var obj = JsonConvert.DeserializeObject<T>(
response.Content.ReadAsStringAsync().Result);
sw.Stop();
Trace.WriteLine("HttpClient Sync " + sw.ElapsedMilliseconds);
}
public static void GetDataFromHttpClientAsync<T>(Stopwatch sw)
{
HttpClient httpClient = new HttpClient();
var response = httpClient.GetAsync(_url).ContinueWith(
(a) => {
JsonConvert.DeserializeObject<T>(
a.Result.Content.ReadAsStringAsync().Result);
sw.Stop();
Trace.WriteLine("HttpClient Async " + sw.ElapsedMilliseconds);
}, TaskContinuationOptions.None);
}
}
}
My Questions
The REST calls return in 3-4 seconds which is acceptable. Calls to REST
service are initiated in the controller methods which gets invoked from
Ajax calls. To begin with, the calls runs in a different thread and doesn't block the UI. So, can I just stick with synchronous calls?
The above code was run in my localbox. In a production setup, DNS and proxy
lookup will be involved. Is there an advantage of using HttpClient over WebClient?
Is HttpClient concurrency better than WebClient? From the test results, I see WebClient synchronous calls perform better.
Will HttpClient be a better design choice if we upgrade to .NET 4.5? Performance is the key design factor.
HttpClient is the newer of the APIs and it has the benefits of
has a good asynchronous programming model
being worked on by Henrik F Nielson who is basically one of the inventors of HTTP, and he designed the API so it is easy for you to follow the HTTP standard, e.g. generating standards-compliant headers
is in the .NET framework 4.5, so it has some guaranteed level of support for the forseeable future
also has the xcopyable/portable-framework version of the library if you want to use it on other platforms - .NET 4.0, Windows Phone, etc.
If you are writing a web service which is making REST calls to other web services, you should want to be using an asynchronous programming model for all your REST calls, so that you don't hit thread starvation. You probably also want to use the newest C# compiler which has async/await support.
Note: It isn't more performant, AFAIK. It's probably somewhat similarly performant if you create a fair test.
HttpClientFactory
It's important to evaluate the different ways you can create an HttpClient, and part of that is understanding HttpClientFactory.
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
This is not a direct answer I know - but you're better off starting here than ending up with new HttpClient(...) everywhere.
When it comes to ASP.NET apps I still prefer WebClient over HttpClient because:
The modern implementation comes with async/awaitable task-based methods
Has smaller memory footprint and 2-5 times faster (other answers already mention that)
It's suggested to "reuse a single
instance of HttpClient for the lifetime of your application". But
ASP.NET has no "lifetime of application", only lifetime of a request. The current guidance for ASP.NET 5 is to use HttpClientFactory, but it can only be used via dependency injection. Some people want a simpler solution.
Most importantly, if you're using one singleton instance of HttpClient through the lifetime of the app like MS suggests - it has known issues. For example the DNS caching issue - HttpClient simply ignores the TTL and caches DNS "forever". There are workarounds, however. If you'd like to learn more about the issues and confusion with HttpClient just read this comment at Microsoft GitHub.
Firstly, I am not an authority on WebClient vs. HttpClient, specifically. Secondly, from your comments above, it seems to suggest that WebClient is synchronous only whereas HttpClient is both.
I did a quick performance test to find how WebClient (synchronous calls), HttpClient (synchronous and asynchronous) perform. And here are the results.
I see that as a huge difference when thinking for future, i.e., long running processes, responsive GUI, etc. (add to the benefit you suggest by .NET framework 4.5 - which in my actual experience is hugely faster on IIS).
Perhaps you could think about the problem in a different way. WebClient and HttpClient are essentially different implementations of the same thing. What I recommend is implementing the Dependency Injection pattern with an IoC Container throughout your application. You should construct a client interface with a higher level of abstraction than the low level HTTP transfer. You can write concrete classes that use both WebClient and HttpClient, and then use the IoC container to inject the implementation via config.
What this would allow you to do would be to switch between HttpClient and WebClient easily so that you are able to objectively test in the production environment.
So questions like:
Will HttpClient be a better design choice if we upgrade to .Net 4.5?
Can actually be objectively answered by switching between the two client implementations using the IoC container. Here is an example interface that you might depend on that doesn't include any details about HttpClient or WebClient.
/// <summary>
/// Dependency Injection abstraction for rest clients.
/// </summary>
public interface IClient
{
/// <summary>
/// Adapter for serialization/deserialization of http body data
/// </summary>
ISerializationAdapter SerializationAdapter { get; }
/// <summary>
/// Sends a strongly typed request to the server and waits for a strongly typed response
/// </summary>
/// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
/// <typeparam name="TRequestBody">The type of the request body if specified</typeparam>
/// <param name="request">The request that will be translated to a http request</param>
/// <returns></returns>
Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(Request<TRequestBody> request);
/// <summary>
/// Default headers to be sent with http requests
/// </summary>
IHeadersCollection DefaultRequestHeaders { get; }
/// <summary>
/// Default timeout for http requests
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// Base Uri for the client. Any resources specified on requests will be relative to this.
/// </summary>
Uri BaseUri { get; set; }
/// <summary>
/// Name of the client
/// </summary>
string Name { get; }
}
public class Request<TRequestBody>
{
#region Public Properties
public IHeadersCollection Headers { get; }
public Uri Resource { get; set; }
public HttpRequestMethod HttpRequestMethod { get; set; }
public TRequestBody Body { get; set; }
public CancellationToken CancellationToken { get; set; }
public string CustomHttpRequestMethod { get; set; }
#endregion
public Request(Uri resource,
TRequestBody body,
IHeadersCollection headers,
HttpRequestMethod httpRequestMethod,
IClient client,
CancellationToken cancellationToken)
{
Body = body;
Headers = headers;
Resource = resource;
HttpRequestMethod = httpRequestMethod;
CancellationToken = cancellationToken;
if (Headers == null) Headers = new RequestHeadersCollection();
var defaultRequestHeaders = client?.DefaultRequestHeaders;
if (defaultRequestHeaders == null) return;
foreach (var kvp in defaultRequestHeaders)
{
Headers.Add(kvp);
}
}
}
public abstract class Response<TResponseBody> : Response
{
#region Public Properties
public virtual TResponseBody Body { get; }
#endregion
#region Constructors
/// <summary>
/// Only used for mocking or other inheritance
/// </summary>
protected Response() : base()
{
}
protected Response(
IHeadersCollection headersCollection,
int statusCode,
HttpRequestMethod httpRequestMethod,
byte[] responseData,
TResponseBody body,
Uri requestUri
) : base(
headersCollection,
statusCode,
httpRequestMethod,
responseData,
requestUri)
{
Body = body;
}
public static implicit operator TResponseBody(Response<TResponseBody> readResult)
{
return readResult.Body;
}
#endregion
}
public abstract class Response
{
#region Fields
private readonly byte[] _responseData;
#endregion
#region Public Properties
public virtual int StatusCode { get; }
public virtual IHeadersCollection Headers { get; }
public virtual HttpRequestMethod HttpRequestMethod { get; }
public abstract bool IsSuccess { get; }
public virtual Uri RequestUri { get; }
#endregion
#region Constructor
/// <summary>
/// Only used for mocking or other inheritance
/// </summary>
protected Response()
{
}
protected Response
(
IHeadersCollection headersCollection,
int statusCode,
HttpRequestMethod httpRequestMethod,
byte[] responseData,
Uri requestUri
)
{
StatusCode = statusCode;
Headers = headersCollection;
HttpRequestMethod = httpRequestMethod;
RequestUri = requestUri;
_responseData = responseData;
}
#endregion
#region Public Methods
public virtual byte[] GetResponseData()
{
return _responseData;
}
#endregion
}
Full code
HttpClient Implementation
You can use Task.Run to make WebClient run asynchronously in its implementation.
Dependency Injection, when done well helps alleviate the problem of having to make low level decisions upfront. Ultimately, the only way to know the true answer is try both in a live environment and see which one works the best. It's quite possible that WebClient may work better for some customers, and HttpClient may work better for others. This is why abstraction is important. It means that code can quickly be swapped in, or changed with configuration without changing the fundamental design of the app.
BTW: there are numerous other reasons that you should use an abstraction instead of directly calling one of these low-level APIs. One huge one being unit-testability.
I have benchmarked between HttpClient, WebClient, and HttpWebResponse, and then called the REST Web API.
And the results:
Call REST Web API Benchmark
---------------------Stage 1 ---- 10 Request
{00:00:17.2232544} ====>HttpClinet
{00:00:04.3108986} ====>WebRequest
{00:00:04.5436889} ====>WebClient
---------------------Stage 1 ---- 10 Request--Small Size
{00:00:17.2232544}====>HttpClinet
{00:00:04.3108986}====>WebRequest
{00:00:04.5436889}====>WebClient
---------------------Stage 3 ---- 10 sync Request--Small Size
{00:00:15.3047502}====>HttpClinet
{00:00:03.5505249}====>WebRequest
{00:00:04.0761359}====>WebClient
---------------------Stage 4 ---- 100 sync Request--Small Size
{00:03:23.6268086}====>HttpClinet
{00:00:47.1406632}====>WebRequest
{00:01:01.2319499}====>WebClient
---------------------Stage 5 ---- 10 sync Request--Max Size
{00:00:58.1804677}====>HttpClinet
{00:00:58.0710444}====>WebRequest
{00:00:38.4170938}====>WebClient
---------------------Stage 6 ---- 10 sync Request--Max Size
{00:01:04.9964278}====>HttpClinet
{00:00:59.1429764}====>WebRequest
{00:00:32.0584836}====>WebClient
WebClient Is faster
var stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 10; ++i)
{
CallGetHttpClient();
CallPostHttpClient();
}
stopWatch.Stop();
var httpClientValue = stopWatch.Elapsed;
stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 10; ++i)
{
CallGetWebRequest();
CallPostWebRequest();
}
stopWatch.Stop();
var webRequesttValue = stopWatch.Elapsed;
stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 10; ++i)
{
CallGetWebClient();
CallPostWebClient();
}
stopWatch.Stop();
var webClientValue = stopWatch.Elapsed;
//-------------------------Functions
private void CallPostHttpClient()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
var responseTask = httpClient.PostAsync("PostJson", null);
responseTask.Wait();
var result = responseTask.Result;
var readTask = result.Content.ReadAsStringAsync().Result;
}
private void CallGetHttpClient()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
var responseTask = httpClient.GetAsync("getjson");
responseTask.Wait();
var result = responseTask.Result;
var readTask = result.Content.ReadAsStringAsync().Result;
}
private string CallGetWebRequest()
{
var request = (HttpWebRequest)WebRequest.Create("https://localhost:44354/api/test/getjson");
request.Method = "GET";
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
var content = string.Empty;
using (var response = (HttpWebResponse)request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
using (var sr = new StreamReader(stream))
{
content = sr.ReadToEnd();
}
}
}
return content;
}
private string CallPostWebRequest()
{
var apiUrl = "https://localhost:44354/api/test/PostJson";
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(new Uri(apiUrl));
httpRequest.ContentType = "application/json";
httpRequest.Method = "POST";
httpRequest.ContentLength = 0;
using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
{
using (Stream stream = httpResponse.GetResponseStream())
{
var json = new StreamReader(stream).ReadToEnd();
return json;
}
}
return "";
}
private string CallGetWebClient()
{
string apiUrl = "https://localhost:44354/api/test/getjson";
var client = new WebClient();
client.Headers["Content-type"] = "application/json";
client.Encoding = Encoding.UTF8;
var json = client.DownloadString(apiUrl);
return json;
}
private string CallPostWebClient()
{
string apiUrl = "https://localhost:44354/api/test/PostJson";
var client = new WebClient();
client.Headers["Content-type"] = "application/json";
client.Encoding = Encoding.UTF8;
var json = client.UploadString(apiUrl, "");
return json;
}

Categories

Resources