In HttpclientAdd, set same urlbase for all Interface from a assembly

I'm migrating a Web API client from .NET to .NET Core 5.

I have more 100 entities to migrate. I try it with a console project type.

In IHostBuilder.AddServices I have:

services.AddHttpClient("App", client => { client.BaseAddress = new Uri(@"https://localhost:60447/"); } 

services.AddTransient(IEntity001HttpClient, Entity001HttpClient);
services.AddTransient(IEntity002HttpClient, Entity002HttpClient);
.....
services.AddTransient(IEntity100HttpClient, Entity100HttpClient);

This is an example class to call server

public class Entiy001HttpClient : IEntity001HttpClient
{
    private readonly System.Net.Http.HttpClient _httpClient;

    public Entity001HttpClient(System.Net.Http.HttpClient _hc)
    {
        _httpClient = _hc;            
    }

    public async Task<IResult<List<Entity001>>>GetAllAsync()
    {                       
        var response = await _httpClient.GetAsync(Entity001HttpRoutes.GetAll);
        return await response.ToResult<List<Entity001>>();
    }
}

At this point

var response = await _httpClient.GetAsync(Entity001Routes.GetAll);

_httpClient.BaseUrl is null.

If I change IHostBuilder.AddServices to:

services.AddHttpClient<IEntity001HttpClient>("App", client => { client.BaseAddress = new Uri(@"https://localhost:60447/"); } 
services.AddHttpClient<IEntity002HttpClient>("App", client => {client.BaseAddress = new Uri(@"https://localhost:60447/"); } 
....
services.AddHttpClient<IEntity100HttpClient>("App", client => {client.BaseAddress = new Uri(@"https://localhost:60447/"); } 


services.AddTransient(IEntity001HttpClient, Entity001HttpClient);
services.AddTransient(IEntity002HttpClient, Entity002HttpClient);
.....
services.AddTransient(IEntity100HttpClient, Entity100HttpClient);

At this point

var response = await _httpClient.GetAsync(Entity001Routes.GetAll);

now _httpClient.BaseUrl is https://localhost:60447 and the API call is running Ok.

Is there a way to assign the urlbase without having to add the interfaces one by one 100 times?

Another idea, is assign it by reflection, I can assign AddTransient, but I don't know how to assign AddHttpclient using reflection

public static IServiceCollection AddManagers(this IServiceCollection services)
{
    var managers = typeof(IHttpClient);

    var types = managers
            .Assembly
            .GetExportedTypes()
            .Where(t => t.IsClass && !t.IsAbstract)
            .Select(t => new
            {
                Service = t.GetInterface($"I{t.Name}"),
                Implementation = t
            })
            .Where(t => t.Service != null);

    foreach (var type in types)
    {
        if (managers.IsAssignableFrom(type.Service))
        {
            services.AddTransient(type.Service, type.Implementation);                    
            services.AddHttpClient<type.Service>("App", client =>
                {
                    client.BaseAddress = new Uri(@"https://localhost:60447/");
                });
         }
     }

     return services;
}

At this point in my code:

services.AddHttpClient<type.Service>("App", client =>

I get a compile error type.Service

Thanks


UPDATE

Thanks Richard Deeming.

I had to change some code.

First Add the cliente Extension:

static class EntityHttpClientExtensions
{
    private static readonly MethodInfo AddMethodBase = typeof(EntityHttpClientExtensions).GetMethod(nameof(AddEntityHttpClient));

    public static IServiceCollection AddEntityHttpClient<TClientImplementation>(this IServiceCollection services)
        where TClientImplementation : class
        //TClientImplementation : TClientInterface
    {
        string mibaseurl = @"https://localhost:57608/";
        services.AddHttpClient<TClientImplementation>("App", client =>
            {
                client.BaseAddress = new Uri(mibaseurl);
            });

        return services;
    }

    public static IServiceCollection AddEntitiesHttpClientsAndTransientFrom(this IServiceCollection services,  Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.IsClass && !t.IsAbstract)
            .Select(t => new
            {
                Implementation = t,
                Service = t.GetInterface($"I{t.Name}"),
            })
            .Where(t => t.Service != null);

        var typeParameters = new Type[1];
        var methodParameters = new object[] { services };

        foreach (var type in types)
        {
            services.AddTransient(type.Service, type.Implementation);

            typeParameters[0] = type.Implementation;                
            var method = AddMethodBase.MakeGenericMethod(typeParameters);
            method.Invoke(null, methodParameters);
            
        }

        return services;
    }
}

}

And configure

 public static IHostBuilder AddServices(this IHostBuilder host)
    {
        string ClientName = "IU.Consola";

        host.ConfigureServices((hostingContext, services) =>
        {
            var configurationRoot = hostingContext.Configuration;

            services.AddEntitiesHttpClientsAndTransientFrom(typeof(IHttpClient).Assembly);

            services.AddHostedService<RunConsole>(); 
        });
        return host;
    }

And use

var mihttpclient001 = _serviceProvider.GetRequiredService<HttpClient001>();
var midata001 = await mihttpclient001.GetAllAsync();

I have a structure that my entities inherit from class base

IHttpClient

  • IHttpClient001
  • IHttpClient002
  • IHttpClient003
  • IHttpClient003 ..... IHttpClient100

Now I append the assembly base class and the extension automatically I have httpclientfactory configurated for all my entities.

Thank you very much

1 answer

  • answered 2022-01-24 14:54 Richard Deeming

    You could create your own generic extension method to configure the client and set the base address in one go:

    static class EntityHttpClientExtensions
    {
        public static IServiceCollection AddEntityHttpClient<TInterface, TImplementation>(
            this IServiceCollection services)
            where TImplementation : TInterface
        {
            services.AddHttpClient<TInterface>("App", client => 
            { 
                client.BaseAddress = new Uri(@"https://localhost:60447/"); 
            });
            
            services.AddTransient<TInterface, TImplementation>();
        }
    }
    

    Usage:

    services.AddEntityHttpClient<IEntity002HttpClient, Entity002HttpClient>(); 
    ....
    services.AddEntityHttpClient<IEntity100HttpClient, Entity100HttpClient>();
    

    If you want to register all applicable types from an assembly automatically, you'll need to use reflection:

    static class EntityHttpClientExtensions
    {
        private static readonly MethodInfo AddMethodBase
            = typeof(EntityHttpClientExtensions)
            .GetMethod(nameof(AddEntityHttpClient));
        
        public static IServiceCollection AddEntityHttpClient<TInterface, TImplementation>(
            this IServiceCollection services)
            where TImplementation : TInterface
        {
            services.AddHttpClient<TInterface>("App", client => 
            { 
                client.BaseAddress = new Uri(@"https://localhost:60447/"); 
            });
    
            services.AddTransient<TInterface, TImplementation>();
        }
        
        public static IServiceCollection AddEntityHttpClientsFrom(
            this IServiceCollection services,
            Assembly assembly)
        {
            var types = assembly.GetExportedTypes()
                .Where(t => t.IsClass && !t.IsAbstract)
                .Select(t => new
                {
                    Implementation = t,
                    Service = t.GetInterface($"I{t.Name}"),
                })
                .Where(t => t.Service != null);
            
            var typeParameters = new Type[2];
            var methodParameters = new object[] { services };
            
            foreach (var type in types)
            {
                typeParameters[0] = type.Service;
                typeParameters[1] = type.Implementation;
                var method = AddMethodBase.MakeGenericMethod(typeParameters);
                method.Invoke(null, methodParameters);
            }
            
            return services;
        }
    }
    

    Usage:

    services.AddEntityHttpClientsFrom(typeof(IEntity001HttpClient).Assembly);
    

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum