Best practices to add FluentFTP to IServiceCollection in .NET 6 - c#

I want to use FluentFTP in .NET6.
Is there best practice to add FluentFtp.FtpClient to IServiceCollection?
Is there any implementation like IHttpClientFactory & AddHttpClient?
I tried the following code. It works but not best.
Some my wrapper
public class MyFtpClient
{
private readonly FluentFTP.FtpClient _client;
public MyFtpClient(MyFtpClientSettings settings)
{
_client = new FtpClient(); // create instance with settings
}
}
In Porgram.cs
services.AddScoped(x => new MyFtpClient(settings);

Related

Injecting IHttpClientFactory in structuremap

We are using an older version of Structuremap (3.1.9.463). It's been a while since I used structuremap and HttpClients alongside and I wonter how to properly inject the IHttpClientFactory in structuremap.
Simply using bootStrapper.For<IHttpClientFactory>().Use<HttpClient>(); won't work
A usage example is
public class DialogClient : IDialogClient
{
private readonly HttpClient _client;
public DialogClient(IHttpClientFactory httpClientFactory)
{
_client = httpClientFactory.CreateClient();
_client.BaseAddress = new Uri(ConfigurationManager.AppSettings["Dialog:url"]);
}
}
The project also use .NET Framework, not Core.
implement the interface
class MyHttpClientFactory: IHttpClientFactory
{
public HttpClient CreateClient(string name)
{
// logic for creating client here
}
}
and then register it
For<IHttpClientFactory>().Singleton().Use<MyHttpClientFactory>();

How to unit test/dependency inject a class reliant on HttpClient with a custom HttpClientHandler configuration

I'm looking for suggestions on how to improve on my current design for testing a class (example below) that depends on HttpClient with a custom HttpClientHandler configuration. I normally use constructor injection to inject a HttpClient that is consistent across the application, however because this is in a class library I can't rely on the consumers of the library to set up the HttpClientHandler correctly.
For testing I follow the standard approach of replacing HttpClientHandler in the HttpClient constructor. Because I can't rely on the consumer of the library to inject a valid HttpClient I'm not putting this in a public constructor, instead I'm using a private constructor with an internal static method (CreateWithCustomHttpClient()) to create it. The intent behind this is:
Private constructor should not be called by a dependency injection library automatically. I'm aware that if I made it public/internal then some DI libraries that had a HttpClient already registered would call that constructor.
Internal static method can be called by a unit testing library using InternalsVisibleToAttribute
This setup seems quite complex to me and I'm hoping someone might be able to suggest an improvement, I am however aware that this could be quite subjective so if there are any established patterns or design rules to follow in this case I would really appreciate hearing about them.
I've included the DownloadSomethingAsync() method just to demonstrate why the non-standard configuration is required for HttpClientHandler. The default is for redirect responses to automatically redirect internally without returning the response, I need the redirect response so that I can wrap it in a class that report progress on the download (the functionality of that is not relevant to this question).
public class DemoClass
{
private static readonly HttpClient defaultHttpClient = new HttpClient(
new HttpClientHandler
{
AllowAutoRedirect = false
});
private readonly ILogger<DemoClass> logger;
private readonly HttpClient httpClient;
public DemoClass(ILogger<DemoClass> logger) : this(logger, defaultHttpClient) { }
private DemoClass(ILogger<DemoClass> logger, HttpClient httpClient)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
[Obsolete("This is only provided for testing and should not be used in calling code")]
internal static DemoClass CreateWithCustomHttpClient(ILogger<DemoClass> logger, HttpClient httpClient)
=> new DemoClass(logger, httpClient);
public async Task<FileSystemInfo> DownloadSomethingAsync(CancellationToken ct = default)
{
// Build the request
logger.LogInformation("Sending request for download");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/downloadredirect");
// Send the request
HttpResponseMessage response = await httpClient.SendAsync(request, ct);
// Analyse the result
switch (response.StatusCode)
{
case HttpStatusCode.Redirect:
break;
case HttpStatusCode.NoContent:
return null;
default: throw new InvalidOperationException();
}
// Get the redirect location
Uri redirect = response.Headers.Location;
if (redirect == null)
throw new InvalidOperationException("Redirect response did not contain a redirect URI");
// Create a class to handle the download with progress tracking
logger.LogDebug("Wrapping release download request");
IDownloadController controller = new HttpDownloadController(redirect);
// Begin the download
logger.LogDebug("Beginning release download");
return await controller.DownloadAsync();
}
}
In my opinion, I'd use IHttpClientFactory in Microsoft.Extensions.Http, and create a custom dependency injection extension for consumers of the class library to use:
public static class DemoClassServiceCollectionExtensions
{
public static IServiceCollection AddDemoClass(
this IServiceCollection services,
Func<HttpMessageHandler> configureHandler = null)
{
// Configure named HTTP client with primary message handler
var builder= services.AddHttpClient(nameof(DemoClass));
if (configureHandler == null)
{
builder = builder.ConfigurePrimaryHttpMessageHandler(
() => new HttpClientHandler
{
AllowAutoRedirect = false
});
}
else
{
builder = builder.ConfigurePrimaryHttpMessageHandler(configureHandler);
}
services.AddTransient<DemoClass>();
return services;
}
}
In DemoClass, use IHttpClientFactory to create named HTTP client:
class DemoClass
{
private readonly HttpClient _client;
public DemoClass(IHttpClientFactory httpClientFactory)
{
// This named client will have pre-configured message handler
_client = httpClientFactory.CreateClient(nameof(DemoClass));
}
public async Task DownloadSomethingAsync()
{
// omitted
}
}
You could require consumers to must call AddDemoClass in order to use DemoClass:
var services = new ServiceCollection();
services.AddDemoClass();
In this way, you could hide details of HTTP client construction.
Meanwhile, in tests, you could mock IHttpClientFactory to return HttpClient for testing purpose.

Blazor: Unable to resolve service for type xx while attempting to activate yyy

I have read many other SO questions on the same topic, but none of the answers that I found applies to my case.
I have successfully added 4 services in my Startup.cs, and it was working fine before. I then added the 5th, and now I realize that something is broken - none of the services work. Even if I remove the 5th completely, the other ones are now also broken with the same error.
Unable to resolve service for type xx while attempting to activate
This is my Startup.cs ConfigureServices.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddStorage();
services.AddSingleton<IMyLocalStorage, MyLocalStorage>();
services.AddSingleton<IFrontEndService, FrontEndService>();
services.AddSingleton<ISystemProvider, SystemProviderService>();
services.AddSingleton<IAuthenticationService, AuthenticationService>();
}
It's the last AuthenticationService that I noticed the error, but even the older previously working services fails now.
public interface IAuthenticationService
{
// ...
}
public class AuthenticationService : IAuthenticationService
{
private readonly FrontEndService frontEndService;
private readonly MyLocalStorage myLocalStorage;
public AuthenticationService(FrontEndService frontEndService, MyLocalStorage myLocalStorage)
{
this.frontEndService = frontEndService;
this.myLocalStorage = myLocalStorage;
}
// ...
}
The services are simple; one interface, one implementation of that interface, and then adding in Startup.cs. I can't figure out why it stopped working.
So if I remove IAuthenticationService, then the error instead shows up in FrontEndService, then complaining on the MyLocalStorage:
public interface IFrontEndService
{
Task<T> GetAsync<T>(string requestUri);
}
public class FrontEndService : IFrontEndService
{
private readonly HttpClient client;
private readonly MyLocalStorage myLocalStorage;
public FrontEndService(HttpClient client, MyLocalStorage myLocalStorage)
{
// ...
}
}
and
public class MyLocalStorage : IMyLocalStorage
{
public MyLocalStorage(LocalStorage storage)
{
this.storage = storage;
}
}
What am I missing here?
When you call methods on IServiceCollection such as .AddSingleton<IFrontEndService, FrontEndService>(), you're saying to the container, "Whenever you see an IFrontEndService dependency, inject an instance of FrontEndService." Now if you take a look at your AuthenticationService:
public class AuthenticationService : IAuthenticationService
{
private readonly FrontEndService frontEndService;
private readonly MyLocalStorage myLocalStorage;
public AuthenticationService(FrontEndService frontEndService, MyLocalStorage myLocalStorage)
{
this.frontEndService = frontEndService;
this.myLocalStorage = myLocalStorage;
}
// ...
}
Notice how you're passing in dependencies of FrontEndService and MyLocalStorage, rather than the interfaces you registered. That means the container doesn't recognise them, so it doesn't know how to fulfil the dependency graph.
You need to change the service to depend on the interfaces, as those are what you've registered with the container:
public class AuthenticationService : IAuthenticationService
{
private readonly IFrontEndService frontEndService;
private readonly IMyLocalStorage myLocalStorage;
public AuthenticationService(IFrontEndService frontEndService, IMyLocalStorage myLocalStorage)
{
this.frontEndService = frontEndService;
this.myLocalStorage = myLocalStorage;
}
// ...
}
#Ted,
Do you remember a question of yours from a couple of weeks ago, in which you used LocalStorage in a service ? At that service you had a constructor with IStorage parameter, but this caused an error, the reason of which was that though the LocalStorage class implements the IStorage interface, the creators of this library added the LocalStorage to the DI container as a concrete class like this:
public static IServiceCollection AddStorage(this IServiceCollection services)
{
return services.AddSingleton<SessionStorage>()
.AddSingleton<LocalStorage>();
}
And therefore, you had to use
(LocalStorage storage)
instead of
(IStorage storage)
The extension method above, could be rewritten thus:
public static IServiceCollection AddStorage(this IServiceCollection services)
{
return services.AddSingleton<IStorage, SessionStorage>()
.AddSingleton<IStorage, LocalStorage>();
}
In which case, you could use the IStorage interface in your constructor.
Now you may form a general rule, and act accordingly.
Ted says:
Thats odd, cause I have used exactly this approach before, and it
worked fine. If you read the docs, Microsoft also uses the concrete
class, not the interface
HttpClient derives from HttpMessageInvoker. It does not implement any interface.
This code-snippet shows how the HttpClient is added to the service container, and made available for injection in your client-side Blazor:
services.AddSingleton<HttpClient>(s =>
{
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var uriHelper = s.GetRequiredService<IUriHelper>();
return new HttpClient
{
BaseAddress = new Uri(WebAssemblyUriHelper.Instance.GetBaseUri())
};
});
Hope this helps...

How to pass/inject more than one HttpClient parameter to a typed HttpClientClass?

I want to register a typed HttpClient as here Microsoft docs.
Basically, the approach should be
services.AddHttpClient();
normally the pattern of these classes receive only the HttpClient class as a parameter and you implement the logic to call the endpoint. In my case, I need to use 2 HttpClient inside my MyHttpClient, one that pings the endpoint and the other one that talks with an IdentityProvider to discover the refreshEndpoints to refresh my cookies.
public class MyHttpClient : IMyHttpClient
{
public MyHttpClient (HttpClient httpClient,
HttpClient refreshHttpClient)
{
}
}
If I am trying to resolve from a controller an IMyHttpClient, I get an error saying it can't resolve an HttpClient.
In the GitHub code on line 43 AddHttpClient you can see that is calling
DefaultTypedHttpClientFactory.
If you go to the implementation of the DefaultTypedHttpClientFactory implementation you will notice that is a generic type. And when it calls CreateClient it only passes one parameter to the constructor on line 39.
The only workaround I am seeing here is to not create a typed client and register a normal class that receives an IHttpClientFactory and create and configure my clients on the fly, not as typed.
Any other idea?
You can't. You'll either need to inject another service layer or IHttpClientFactory directly
Another service
public class MyRefreshClient
{
private readonly HttpClient _httpClient;
public MyRefreshClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
...
}
public class MyHttpClient : IMyHttpClient
{
private readonly HttpClient _httpClient;
private readonly MyRefreshClient _refreshClient;
public MyHttpClient(HttpClient httpClient, MyRefreshClient refreshClient)
{
_httpClient = httpClient;
_refreshClient = refreshClient;
}
}
Then:
services.AddHttpClient<MyRefreshClient>(c => { ... });
services.AddHttpClient<MyHttpClient>(c => { ... });
Inject IHttpClientFactory (and use named clients):
public class MyHttpClient : IMyHttpClient
{
private readonly HttpClient _httpClient;
private readonly HttpClient _refreshClient;
public MyHttpClient(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("MyHttpClient");
_refreshClient = httpClientFactory.CreateClient("MyRefreshClient");
}
}
Then:
services.AddHttpClient("MyHttpClient", c => { ... });
services.AddHttpClient("MyRefreshClient", c=> { ... });

HttpClientFactory how to inject into class I have no control over core 2.1

I would like to use the new HttpClientFactory and I am having trouble setting it up.
I have the following (Just noddy examples I put together to explain my point)
public class MyGitHubClient
{
public MyGitHubClient(HttpClient client)
{
Client = client;
}
public HttpClient Client { get; }
}
Then in my webapi.Startup I have
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<MyGitHubClient>(client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
//etc..
});
//NOW I get error "Class name is not valid at this point" for "MyGitHubClient" below
services.AddSingleton<IThirdPartyService>(x => new ThirdPartyService(MyGitHubClient,someOtherParamHere));
///etc...
}
Third party constructor
public ThirdPartyService(HttpClient httpClient, string anotherParm)
{
}
How can I use the HttpClientFactory when I have to call a class that I have no control over?
The AddSingleton delegate used in the original question takes a IServiceProvider as a parameter argument. Use the provider to resolve the desired dependency
services.AddSingleton<IThirdPartyService>(sp =>
new ThirdPartyService(sp.GetService<MyGitHubClient>().Client, someOtherParamHere)
);
In Startup.cs, services.AddHttpClient();
Extension method from https://github.com/dotnet/extensions/blob/master/src/HttpClientFactory/Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs
In your class, add a IHttpClientFactory argument to your constructor.
If you want to use it in a class that doesn't take it, you need to create the HttpClient in a lambda in Add* and pass it in, or register HttpClient itself with that lambda and let DI pass it in
services.AddScoped(s => s.GetRequiredService<IHttpClientFactory>().CreateClient())
There's an example on the project GitHub:
https://github.com/dotnet/extensions/blob/master/src/HttpClientFactory/samples/HttpClientFactorySample/Program.cs

Categories

Resources