基于HttpClient子类的类型化客户端

4

在我的应用程序中,我将拥有几个TypedClient服务。然而,这些类将共享一堆方法。我解决这个问题的方法是创建CustomHttpClient:

public class CustomHttpClient:HttpClient
{
  //here shared methods
}

那么我的客户端类将使用这个派生类而不是标准的HttpClient:

public class MyService : IMyService
{
  public SomeService(CustomHttpClient client, IConfiguration configuration){}
}

然而,如果我尝试在启动时注册此服务,就会出现错误,表示'MyService'没有合适的构造函数:
services.AddHttpClient<IMyService, MyService>();

在文档中我找到了以下内容:

类型化客户端是一个类,它接受一个HttpClient对象(通过其构造函数注入),并使用它调用一些远程HTTP服务

这是否意味着它不能接受HttpClient的子类? 如果是这种情况,那么我的唯一解决方案将是实现共享方法作为HttpClient扩展方法(我不太喜欢这个解决方案)。 是否有解决方法或扩展方法是我唯一的出路? 我尝试注册CustomHttpClient以便DI容器可以找到它,但错误仍然相同。 你能给我什么建议吗?


似乎微软更喜欢组合而非继承,但由于代码是开源的,您可能想要自定义它。 - Deepak Mishra
3个回答

7
这是否意味着它不能接受HttpClient的子类?
是的。
如果是这种情况,那么我的唯一解决方案就是将共享方法实现为HttpClient扩展方法。
不。根据docs,类型化客户端封装了HttpClient,而不是扩展它。你可以在构造函数中配置HttpClient,然后添加自定义方法到Typed Client中,这些方法使用封装的HttpClient实例。
如果您不想使用框架的HttpClient处理模式,则可以自由创建自己的模式,但这可能不值得努力。
您可以拥有共享基类的类型化客户端。例如:
public class MyBaseTypedClient
{
    public HttpClient Client { get; }

    public MyBaseTypedClient(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");

        Client = client;
    }

    //other methods

}
public class MyTypedClient : MyBaseTypedClient
{
    public MyTypedClient(HttpClient client) : base(client) { }
}

这正是我的问题。我有几个类型化的客户端,所以我必须在每个客户端中重复这些方法。那么我的选择是将共享方法放在每个类型化客户端类中还是使用扩展方法?这就是你的意思吗? - karollo
你可以让所有已经输入的客户端继承一个基本类型,但是他们都必须有接受 HttpClient 的构造函数。请参见更新的答案。 - David Browne - Microsoft
2
类型化的客户端不应该是一个客户端(is-a),而是拥有一个(has-a)客户端。它们可以都有一个共同的基类,但与HttpClient的关系应该是所有权而不是继承。例如,HttpClientFactory不会了解你的HttpClient子类,因此如果需要它,必须完全重写该逻辑(并且你隐含地需要它,因为你正在尝试使用http客户端注入)。而拥有模式只需将普通客户端注入到其他容器中即可。 - pinkfloydx33
我想在运行时更改标头,但无法在服务设置时间更改它们。 - jjxtra
什么阻止了你?你仍然可以使用HttpClient.SendAsync https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync?view=netcore-3.1 - David Browne - Microsoft
显示剩余2条评论

1
如果您只需要添加常用方法,可以创建一个带有这些方法的默认实现的接口,然后只需将IMyService继承该接口即可。
您还可以查看下面的链接,其中有一些有趣的解决方案。

https://github.com/dotnet/extensions/issues/1988


0
我认为这就是你想要做的事情:
(1)按照以下方式创建你的基本 MyBaseTypedClient:

public interface IMyBaseTypedClient
{ 
    //other methods
    //like FetchAsync(), PostAsync()
}

public class MyBaseTypedClient
{
    private HttpClient _client

    public MyBaseTypedClient(HttpClient client)
    {
        _client = client;
    }

    //other methods
    //like FetchAsync(), PostAsync()
}

(2) 然后按照以下方式创建您的类型化客户端:

public interface IServiceOne: IMyBaseTypedClient{ }

public class ServiceOne: MyBaseTypedClient, IServiceOne
{
    public ServiceOne(HttpClient httpClient) : base(httpClient)
    {
    }
}

public interface IServiceTwo: IMyBaseTypedClient{ }

public class ServiceTwo: MyBaseTypedClient, IServiceTwo
{
    public ServiceTwo(HttpClient httpClient) : base(httpClient)
    {
    }
}

(3) 然后按以下方式注册

services
    .AddHttpClient<IServiceOne, ServiceOne>(c => c.BaseAddress = new Uri("https://serviceone.com"));
    .AddHttpClient<IServiceTwo, ServiceTwo>(c => c.BaseAddress = new Uri("https://servicetwo.com");

(4) 然后按照以下方式注入

public void SomeMethodThatNeedsOne(IServiceOne serviceOne)
{
    //etc
}

public void SomeMethodThatNeedsTwo(IServiceTwo serviceTwo)
{
    //etc
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接