I have a business-process(BMPN) that needs to make two http calls. The first one is used for authentication, it contains a login and a password in its body, and its response contains cookies. Then I take these cookies and put them in the second request. Basically, the purpose of the pair is first to authenticate, then to upload a file. But there can be many such files to be uploaded, each file will have its own instance of the business-process, so there can be many of these instances at the same time.

My service currently looks something like this:

class FileUploadService {
    private readonly string _appUrl;
    private readonly string _authServiceUrl;
    private readonly string _userName;
    private readonly string _userPassword;
    private readonly IHttpClientFactory _httpClientFactory;
    private IEnumerable<string> _cookies;

    public FileUploadService(string appUrl, string userName, string userPassword) {
        _appUrl = appUrl;
        _authServiceUrl = _appUrl + @"/ServiceModel/AuthService.svc/Login";
        _userName = userName;
        _userPassword = userPassword;

        var serviceCollection = new ServiceCollection();
        serviceCollection.AddHttpClient("MyHttpClient").ConfigurePrimaryHttpMessageHandler(
            configureHandler: () => new HttpClientHandler {
                 UseCookies = false
        });
        var serviceProvider = serviceCollection.BuildServiceProvider();
        _httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
    }

    public void TryAuthnenticate() {
        HttpClient httpClient = _httpClientFactory.CreateClient("MyHttpClient");
        string requestBody = @"{
            ""UserName"":""" + _userName + @""",
            ""UserPassword"":""" + _userPassword + @"""
            }";
        HttpContent content = new StringContent(requestBody, Encoding.UTF8, "application/json");
        HttpResponseMessage response = httpClient.PostAsync(_authServiceUrl, content).Result;
        _cookies = response.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value;
    }

    public void TryUploadFile() {
        HttpClient httpClient = _httpClientFactory.CreateClient("MyHttpClient");
        httpClient.BaseAddress = new Uri(_appUrl);
        string filePath = @"C:\Users\defaultUser\Desktop\12345.pdf";
        string url = getUrl(filePath);
        MultipartFormDataContent content = getContent(filePath);
        HttpResponseMessage response = httpClient.PostAsync(url, content).Result;
    }

    private string getUrl(string fileInfo) {
        // some code that returns a string url
        return "";
    }

    private MultipartFormDataContent getContent(string filePath) {
        MultipartFormDataContent content = new MultipartFormDataContent();

        byte[] fileBytes = File.ReadAllBytes(filePath);
        ByteArrayContent fileContent = new ByteArrayContent(fileBytes);
        FileInfo fileInfo = new FileInfo(filePath);
        content.Add(fileContent, "12345", fileInfo.Name);
        long length = fileInfo.Length;
        long rangeEnd = length - 1;
        content.Headers.Add("Content-Range", $"bytes 0-{rangeEnd}/{length}");

        string cookieValue = "";
        foreach (var cookie in _cookies) {
           var firstCookieParts = cookie.Split(';');
           var secondCookieParts = firstCookieParts[0].Split('=');
           cookieValue = cookieValue+$"{secondCookieParts[0]}={secondCookieParts[1]}; ";
        }
        content.Headers.Add("Cookie", cookieValue);
        return content;
    }
}

My idea here is that each instance of my business-process will use this service like that:

FileUploadService fileUploadService = new FileUploadService("https://myhost.com", "admin", "test");
fileUploadService.TryAuthnenticate();
fileUploadService.TryUploadFile();

The code does work, meaning the file gets uploaded, but I'm not sure if it won't break with time. There are a few things in particular that I'm worried about.

  1. It is my understending that since I need a lot of HttpClients and they don't need to be long-lived, It is better to use IHttpClientFactory. Since, CookieContainer can be shared among the pool, I want to get rid of CookieContainer and send cookies as a header, hence UseCookies = false. But would it be the same/better/worse if I used one static HttpClient with UseCookies = false and cookies would be send as a header?

  2. The way I instantiating IHttpClientFactory, is it correct? 95% of the information I've been able to find about it was about ASP.NET Core, but I use .NET Core and I'm not allowed to turn it into ASP.NET Core. So, I have little understanding if the way I do that is correct.

  3. Should IHttpClientFactory be static? Since there will be more than one instance of the service, there will be more than one instance of IHttpClientFactory which feels wrong, but I have not seen example of using a static IHttpClientFactory, so I'm not sure if there is anything wrong with that.

  4. When adding a named client my idea was that I need to somehow make all my Http Clients not to use CookieContainer, so I made this MyHttpClient client. What I don't understand is will it be one and the same instance of HttpClient every time I call CreateClient("MyHttpClient")? Will it break the whole idea of having multiple clients? If so, how do I make the factory to give me only clients that have UseCookies set to false?

The service seems to be working but I'm not sure if I understand how to work with HttpClient and IHttpClientFactory. I want this thing not to run out of available sockets.

2

There are 2 best solutions below

0
guxxo On
  1. Well if you are not making many custom configurations of named HttpClient and need to use those across different classes then there is no need to use IHttpClientFactory.. If you have only 1 configuration, and want all instances of FileUploadService to use clients with that same configuration, then you can create HttpClient inside the class and configure it there. Take a look at this about usage of named clients https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-8.0#named-clientsIHttpClientFactory

  2. It depends what one considers correct. I would say it would be better to do the binding of the service in separate class where you do DI stuff, and your FileUploadService constructor should have parameter of IHttpClientFactory clientFactory. Then when asking for FileUploadService from your DI container, your configured DependencyResolver will try to get instance of the class that implements IHttpClientFactory and pass it to the constructor of FileUploadService.

  3. In your case I don't believe it will matter if the factory is static or not, even when you have more than 1 instance of FileUploadService. Because you are only using the CreateClient method from the factory, which actually creates and returns a new instance of HttpClient. So you will always get a new instance of client. The more appropriate question here would be should you use 1 instance of HttpClient ? regarding that you can find some useful info here: https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

    What is the overhead of creating a new HttpClient per call in a WebAPI client?

  4. Well you can actually check that easily, while debugging just call it multiple times, and use Object.ReferenceEquals() on the clients you've created. Finally if you decide to use 1 HttpClient instance, then I don't see a purpose to use the factory.

0
Andrew B On

There are a few changes you need to make to the structure of your code.

The summary is:

  1. Take IHttpClientFactory as a parameter into your Constructor, and only create the HttpClient for the short period you're using it. (see 1)
  2. Restructure your Service to have the authenticate code and the upload code in the same method.
  3. Learn further about using the .NET Dependency Injection framework correctly. (see 2)
  4. Correct your usage of async.

I'll start with your first question about deciding between IHttpClientFactory and HttpClient. In your case, you should take IHttpClientFactory as a parameter into your Constructor.

In there, you could create a new HttpClient specifically for that instance of the service. This is still not ideal, but it's the smallest change to your code:

class FileUploadService {
private readonly string _appUrl;
private readonly string _authServiceUrl;
private readonly string _userName;
private readonly string _userPassword;
private readonly HttpClient _httpClient; // CHANGE
private IEnumerable<string> _cookies;

public FileUploadService(IHttpClientFactory httpClientFactory, string appUrl, string userName, string userPassword) {
    _httpClient = httpClientFactory.CreateClient();
    _appUrl = appUrl;
    // etc.

This still isn't great. Depending on how long your Service instance lives for, it would be keeping an HttpClient object in memory for the same length of time.

It would be better to only create the HttpClient when it is actually being used, and allow it to go out of scope as soon as you've finished. This means you should also consider merging together your TryAuthenticate() and TryUploadFile() methods, because:

  1. They can then use the same HttpClient instance.
  2. It seems unnecessary to have two methods anyway, because your example shows them being called one after the other with no checks.

Your Service code would then look something like:

private readonly IHttpClientFactory _httpClientFactory;

public FileUploadService(IHttpClientFactory httpClientFactory, /* etc */) {
    _httpClientFactory = httpClientFactory;
    // etc
}

public void TryAuthenticateAndUploadFile()
{
    HttpClient httpClient = _httpClientFactory.CreateClient();

    // Your Authentication code goes here.
    // ...followed by your Try Upload File code.
}

There are other ways to inject HttpClient safely, with a new instance per service, without using IHttpClientFactory directly. (see Make HTTP requests using IHttpClientFactory) However, you'd need to become more skilled with the Dependency Injection framework, which brings us to the next point.

You should look to understand how to use the Dependency Injection library correctly. At the moment you're creating a new service provider each time another Service is instantiated. This isn't the right use of the DI framework here. Instead, you should be using the DI framework to create the FileUploadService for you, and all its dependencies. The DI service provider would typically only be created once, at the very beginning of the application's life. Start by reading the Microsoft documentation for .NET Dependency Injection

Finally, you suggest that socket exhaustion is a possibility, which implies you'll have a high throughput. If that's the case, you should make all this code async, because otherwise you'll exhaust your threads possibly before your sockets. Your example code has httpClient.PostAsync but you aren't using await and your method doesn't return Task so it won't be working as you expect. It should look more like:

public async Task TryAuthenticateAndUploadFile()
{
    var httpClient = _httpClientFactory.CreateClient();
    // etc.
    HttpContent content = new StringContent(requestBody, Encoding.UTF8, "application/json");
    HttpResponseMessage response = await httpClient.PostAsync(_authServiceUrl, content); // NOTE: Using await. Don't use .Result
    
    response.EnsureSuccessStatusCode(); // Throw if Authentication failed.

    // etc.
}