How to create one instance of Httpclient in Xamarin Forms - c#

Is it possible to create one instance of httpclient in Xamarin Forms application in OnStart() and use it everywhere in my application?

Yes you can use the same httpclient for all request in your app. But you will need to take note that if there is API that is having different base URL or headers information, then you will need to create another httpclient for that.
What I do is I have a class to manage the HttpClient instances. If there is no instance that is matching the HttpConfig, it will create and store it. If there is already an existing instance, it will just return it.
Example of code (HttpService is dependency injected):
public class HttpService : IHttpService
{
private static readonly int MAX_CLIENT = 5;
private Dictionary<HttpConfig, HttpClient> mClients;
private Queue<HttpConfig> mClientSequence;
public HttpService()
{
mClients = new Dictionary<HttpConfig, HttpClient>();
mClientSequence = new Queue<HttpConfig>();
}
private HttpClient CreateHttpClientAsync(HttpConfig config)
{
HttpClient httpClient;
if (mClients.ContainsKey(config))
{
httpClient = mClients[config];
}
else
{
// TODO: Create HttpClient...
if (mClientSequence.Count >= MAX_CLIENT)
{
// Remove the first item
var removingConfig = mClientSequence.Dequeue();
mClients.Remove(removingConfig);
}
mClients[config] = httpClient;
mClientSequence.Enqueue(config);
}
return httpClient;
}
...
}
HttpConfig is class where I store BaseUrl, Timeout, Headers, Auth info, etc. You will need to override the Equals method in the class for comparison whether there is existing same config.
public override bool Equals(object obj)
{
// Logic to determine whether it is same config
}

Related

Blazor wasm dependency injection - null http client

I'm creating a dotnet 6 blazor wasm website (core hosted) that uses B2C for auth, but having trouble with http client.
In program.cs I have the following for DI:
builder.Services.AddHttpClient<IBbtDataService, BbtDataService>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
This Bbt service should be injected into the code behind for the FetchBbtData page shown below:
[Authorize]
public partial class FetchBbtData
{
[Inject]
public IBbtDataService BbtDataService { get; set; }
public IEnumerable<ClientOrg> ClientOrgs { get; set; }
protected async override Task OnInitializedAsync()
{
ClientOrgs = (await BbtDataService.GetClientOrgList()).ToList();
}
}
The code for the BbtDataService is as follows:
public class BbtDataService : IBbtDataService
{
private readonly HttpClient httpClient;
public BbtDataService(HttpClient httpClient)
{
httpClient = httpClient;
}
public async Task<IEnumerable<ClientOrg>> GetClientOrgList()
{
return await httpClient.GetFromJsonAsync<IEnumerable<ClientOrg>>($"api/clients");
}
}
If I put a breakpoint on the constructor of the BbtDataService I caan see that the httpClient parameter is valid and contains the correct base url. However, when execution then hits another breakpoint in the GetClientOrgList method, the value of the private readonly field httpClient is null - even though this was set in the constructor.
Anyone see where I went wrong?
You are assigning the parameter to itself. Add 2 _ :
private readonly HttpClient _httpClient;
public BbtDataService(HttpClient httpClient)
{
_httpClient = httpClient;
}

Mocking a HttpClient handler when injected by IHttpClientFactory

I've created a custom library which automatically sets up Polly policies for specific services which depend on HttpClient.
This is done using the IServiceCollection extension methods and the typed client approach. A simplified example:
public static IHttpClientBuilder SetUpFooServiceHttpClient(this IServiceCollection services)
{
return services
.AddHttpClient<FooService>()
.AddPolicyHandler(GetRetryPolicy());
}
public class FooService
{
private readonly HttpClient _client;
public FooService(HttpClient httpClient)
{
_client = httpClient;
}
public void DoJob()
{
var test = _client.GetAsync("http://example.com");
}
}
Note that my real code uses a generic type and an options builder, but I've omitted that part to keep it simple. The purpose of my tests is to confirm that my options builder correctly applies the policies that I want it to apply. For the sake of example here, let's just assume that it's a hardcoded retry policy which I want to test.
I now want to test if this library correctly registers the Polly policies to my injected HttpClient dependencies.
Note
There are many answers to be found online and on StackOverflow where the suggestion is to construct the HttpClient yourself, i.e.: new HttpClient(new MyMockedHandler());, but this defeats my purpose of needing to test if the actual IHttpClientFactory is constructing httpclients with the requested policies.
To that end, I want to test with a real HttpClient which was generated by a real IHttpClientFactory, but I want its handler to be mocked so I can avoid making actual web requests and artificially cause bad responses.
I'm using AddHttpMessageHandler() to inject a mocked handler, but the factory seems to be ignoring that.
Here's my test fixture:
public class BrokenDelegatingHandler : DelegatingHandler
{
public int SendAsyncCount = 0;
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
SendAsyncCount++;
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError));
}
}
private BrokenDelegatingHandler _brokenHandler = new BrokenDelegatingHandler();
private FooService GetService()
{
var services = new ServiceCollection();
services.AddTransient<BrokenDelegatingHandler>();
var httpClientBuilder = services.SetUpFooServiceHttpClient();
httpClientBuilder.AddHttpMessageHandler(() => _brokenHandler);
services.AddSingleton<FooService>();
return services
.BuildServiceProvider()
.GetRequiredService<FooService>();
}
And here's my test:
[Fact]
public void Retries_client_connection()
{
int retryCount = 3;
var service = GetService();
_brokenHandler.SendAsyncCount.Should().Be(0); // PASS
var result = service.DoJob();
_brokenHandler.SendAsyncCount.Should().Be(retryCount); // FAIL: expected 3 but got 0
}
When I debug the test, the handler's breakpoint is never hit, and the response comes back as a 200 (because it actually connected to the URL, instead of hitting the mocked handler).
Why is my mocked handler being ignored by the http client factory?
Note that I will also accept any answer that allows me to test the policies in another valid way.
I'm aware I can just use a broken URL string but I'm going to need to test specific http responses in my tests.
We had a similar problem few months ago. How to test that the injected HttpClient is decorated with the correct Policies. (We have used a Retry > CircuitBreaker > Timeout policy chain).
We ended up to create several integration tests. We have used WireMock.NET to create a server stub. So, the whole point of this was to let the ASP.NET DI do its magic and then scrutinize the stub's logs.
We have created two base classes which wrapped the WireMock setup (we had a POST endpoint).
FlawlessServer
internal abstract class FlawlessServiceMockBase
{
protected readonly WireMockServer server;
private readonly string route;
protected FlawlessServiceMockBase(WireMockServer server, string route)
{
this.server = server;
this.route = route;
}
public virtual void SetupMockForSuccessResponse(IResponseBuilder expectedResponse = null,
HttpStatusCode expectedStatusCode = HttpStatusCode.OK)
{
server.Reset();
var endpointSetup = Request.Create().WithPath(route).UsingPost();
var responseSetup = expectedResponse ?? Response.Create().WithStatusCode(expectedStatusCode);
server.Given(endpointSetup).RespondWith(responseSetup);
}
}
FaultyServer
(We have used scenarios to simulate timeouts)
internal abstract class FaultyServiceMockBase
{
protected readonly WireMockServer server;
protected readonly IRequestBuilder endpointSetup;
protected readonly string scenario;
protected FaultyServiceMockBase(WireMockServer server, string route)
{
this.server = server;
this.endpointSetup = Request.Create().WithPath(route).UsingPost();
this.scenario = $"polly-setup-test_{this.GetType().Name}";
}
public virtual void SetupMockForFailedResponse(IResponseBuilder expectedResponse = null,
HttpStatusCode expectedStatusCode = HttpStatusCode.InternalServerError)
{
server.Reset();
var responseSetup = expectedResponse ?? Response.Create().WithStatusCode(expectedStatusCode);
server.Given(endpointSetup).RespondWith(responseSetup);
}
public virtual void SetupMockForSlowResponse(ResilienceSettings settings, string expectedResponse = null)
{
server.Reset();
int higherDelayThanTimeout = settings.HttpRequestTimeoutInMilliseconds + 500;
server
.Given(endpointSetup)
.InScenario(scenario)
//NOTE: There is no WhenStateIs
.WillSetStateTo(1)
.WithTitle(Common.Constants.Stages.Begin)
.RespondWith(DelayResponse(higherDelayThanTimeout, expectedResponse));
for (var i = 1; i < settings.HttpRequestRetryCount; i++)
{
server
.Given(endpointSetup)
.InScenario(scenario)
.WhenStateIs(i)
.WillSetStateTo(i + 1)
.WithTitle($"{Common.Constants.Stages.RetryAttempt} #{i}")
.RespondWith(DelayResponse(higherDelayThanTimeout, expectedResponse));
}
server
.Given(endpointSetup)
.InScenario(scenario)
.WhenStateIs(settings.HttpRequestRetryCount)
//NOTE: There is no WillSetStateTo
.WithTitle(Common.Constants.Stages.End)
.RespondWith(DelayResponse(1, expectedResponse));
}
private static IResponseBuilder DelayResponse(int delay) => Response.Create()
.WithDelay(delay)
.WithStatusCode(200);
private static IResponseBuilder DelayResponse(int delay, string response) =>
response == null
? DelayResponse(delay)
: DelayResponse(delay).WithBody(response);
}
Simple test for Slow processing
(proxyApiInitializer is a instance of a WebApplicationFactory<Startup> derived class)
[Fact]
public async Task GivenAValidInout_AndAServiceWithSlowProcessing_WhenICallXYZ_ThenItCallsTheServiceSeveralTimes_AndFinallySucceed()
{
//Arrange - Proxy request
HttpClient proxyApiClient = proxyApiInitializer.CreateClient();
var input = new ValidInput();
//Arrange - Service
var xyzSvc = new FaultyXYZServiceMock(xyzServer.Value);
xyzSvc.SetupMockForSlowResponse(resilienceSettings);
//Act
var actualResult = await CallXYZAsync(proxyApiClient, input);
//Assert - Response
const HttpStatusCode expectedStatusCode = HttpStatusCode.OK;
actualResult.StatusCode.ShouldBe(expectedStatusCode);
//Assert - Resilience Policy
var logsEntries = xyzServer.Value.FindLogEntries(
Request.Create().WithPath(Common.Constants.Routes.XYZService).UsingPost());
logsEntries.Last().MappingTitle.ShouldBe(Common.Constants.Stages.End);
}
XYZ Server init:
private static Lazy<WireMockServer> xyzServer;
public ctor()
{
xyzServer = xyzServer ?? InitMockServer(API.Constants.EndpointConstants.XYZServiceApi);
}
private Lazy<WireMockServer> InitMockServer(string lookupKey)
{
string baseUrl = proxyApiInitializer.Configuration.GetValue<string>(lookupKey);
return new Lazy<WireMockServer>(
WireMockServer.Start(new FluentMockServerSettings { Urls = new[] { baseUrl } }));
}
I hope this can help you.

Set a default header to each http client call

I want to set a default header for every method in the UserHttpClient but I don`t want that every method is doing that, I want to do it in a general way.
The problem I see with the current implementation is, that when I call one method the _client gets disposed thus at the next call within a Http Request the _client is not initialized, as this happens within the constructor.
The UserHttpClient is registered via DI as per Http Request.
I also do not want to create a private/base method where I pass the _client and do the header addition there.
How would you solve that problem?
public class UserHttpClient : IUserRemoteRepository
{
private readonly string baseUrl = ConfigurationManager.AppSettings["baseUrl"];
private readonly string header = ConfigurationManager.AppSettings["userHeader"];
private readonly HttpClient _client;
public ServiceProductDataProvider(string toolSystemKeyHeader)
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add(header, token);
}
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
using (_client)
{
// do stuff
var users = await _client.GetAsync("url here");
}
}
public async Task<UserDto> GetUser(Guid userId)
{
using (_client)
{
// do stuff
var users = await _client.GetAsync("url here");
}
}
}
The class UserHttpClient has a member that is IDisposable (private readonly HttpClient _client;). That means that the UserHttpClient should also implement IDisposable:
public void Dispose()
{
_client.Dispose();
}
Then, the class/code that is using UserHttpClient is responsible for Disposing it after it's done with it. If the instance is injected, then the DI framework you use probably handles disposing it automatically at the end of the request. What's left for you then is to simply remove the using blocks from the implementation:
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
// do stuff
var users = await _client.GetAsync("url here");
}
---- EDIT ----
You could also work around the issue by not reusing the HttpClient:
private string _toolSystemKeyHeader;
public ServiceProductDataProvider(string toolSystemKeyHeader)
{
_toolSystemKeyHeader = toolSystemKeyHeader
}
private HttpClient GetClientInstance()
{
HttpClient _client = new HttpClient();
_client.DefaultRequestHeaders.Add(header, _toolSystemKeyHeader); //?? in your original code, the toolSystemKeyHeader is not used, but I guess it is the token..?
return _client;
}
And:
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
using (var _client = GetClientInstance())
{
// do stuff
var users = await _client.GetAsync("url here");
}
}

Wcf Web API Sandbox Endpoint Handling

I have an api which will be publicly exposed and have a sandbox. I've written some code in my ResourceFactory so api.sandbox.whatever/whatever will work and also sandbox=true in the arguments will work but this feels like a giant hack. Any better ways to do it?
Here is my code:
public class NinjectResourceFactory : IResourceFactory
{
private readonly IKernel _productionKernel;
private readonly IKernel _sandboxKernel;
public NinjectResourceFactory()
{
_productionKernel = new StandardKernel(new QueryMasterModule());
_sandboxKernel = new StandardKernel(new QueryMasterModule(true));
}
public object GetInstance(Type serviceType, InstanceContext instanceContext, HttpRequestMessage request)
{
string uri = request.RequestUri.ToString();
if (uri.Contains(".sandbox."))
{
return _sandboxKernel.Get(serviceType);
}
else if (uri.Contains("sandbox=true"))
{
request.RequestUri = new Uri(uri.Replace("sandbox=true", ""));
return _sandboxKernel.Get(serviceType);
}
else
{
return _productionKernel.Get(serviceType);
}
}
public void ReleaseInstance(InstanceContext instanceContext, object service)
{
// todo do I need to implement this?
}
}
If it's supposed to be a true sandbox then you don't want the two sites to run in the same process. I would deploy two web sites and let IIS decide which one based on host name. That way the sandbox will be isolated from production, which is the purpose of a sandbox.

Refactoring class to make it injectable/mockable

I am really struggling to properly refactor my class so I can inject it.
This is the class I am talking about:
internal class OCRService : IDisposable, IOCRService
{
private const TextRecognitionMode RecognitionMode = TextRecognitionMode.Handwritten;
private readonly ComputerVisionClient _client;
public OCRService(string apiKey)
{
_client = new ComputerVisionClient(new ApiKeyServiceClientCredentials(apiKey))
{
Endpoint = "https://westeurope.api.cognitive.microsoft.com"
};
}
public async Task<List<Line>> ExtractTextAsync(byte[] image)
{
//Logic with _client here
}
}
I really don't know where to Initialize the ComputerVisionClient. I am thinking of the following options:
Make ComputerVisionClient a public property which can be set after injecting.
Putting the apikey in a config file and then read it in the constructor.
The problem is that I want to mock this service but when I mock it it still calls the constructor which connects to the ComputerVisionClient.
Depending on the rest of your architecture, you have a few options. The simplest is to inject the ComputerVisionClient (or IComputerVisionClient if you can create one) into the constructor, and mock it in your tests.
public class OCRService : IOCRService, IDisposable
{
public OCRService(IComputerVisionClient client)
{
_client = client;
}
}
If, for some reason, you must create the client in the constructor, you can create a factory and inject that:
internal class ComputerVisionClientFactory : IComputerVisionClientFactory
{
public GetClient(string apiKey)
{
return new ComputerVisionClient(new ApiKeyServiceClientCredentials(apiKey))
{
Endpoint = "https://westeurope.api.cognitive.microsoft.com"
};
}
}
// ...
internal class OCRService : IOCRService, IDisposable
{
public OCRService(string apiKey, IComputerVisionClientFactory clientFactory)
{
_client = clientFactory.GetClient(apiKey);
}
}
As #maccettura suggested, you can also further abstract away the apiKey by creating an IOCRServiceConfiguration that contains the logic for getting the key, and pass that into the constructor for either OCRService or ComputerVisionFactory, depending on your architecture. Naively:
internal class OCRServiceConfiguration : IOCRServiceConfiguration
{
public OCRServiceConfiguration(string apiKey)
{
ApiKey = apiKey;
}
public string ApiKey { get; }
}

Categories

Resources