一个更简单的通用ClientBase工厂



在阅读了带有接口混乱的C#通用ClientBase之后,我设法创建了一个简化代码的Soap Web服务工厂:

private T ClientMaker<TInterface, T>(string username, string password, string address)
where TInterface : class 
where T : ClientBase<TInterface>, TInterface
{
var binding = new BasicHttpBinding();
binding.MaxBufferPoolSize = int.MaxValue;
binding.MaxBufferSize = int.MaxValue;
binding.MaxReceivedMessageSize = int.MaxValue;
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
EndpointAddress ordersEndpoint = new EndpointAddress(address);
T client = Activator.CreateInstance(typeof(T), new object[] { binding, ordersEndpoint }) as T;
client.ClientCredentials.UserName.UserName = username;
client.ClientCredentials.UserName.Password = password;
return client;
}

并且是这样使用的:

var client = ClientMaker<CreateWebOrder.WEB_Functions_Port, CreateWebOrder.WEB_Functions_PortClient>(user, pass, endpointBase + "Codeunit/WEB_Functions");

CreateWebOrder.WEB_Functions_Port is the interface implemented by CreateWebOrder.WEB_Functions_PortClient

我不太喜欢的一件事是,我需要提供接口和Web服务的类/类型,所以我想知道是否有一种方法可以通过从类型参数中找出接口或类来解决这个问题。

考虑到返回类型是ClientBase,并且由于提供的类型参数而已知TInterface,为什么我需要提供"PortClient"?

理想情况下,我想给工厂打电话,只带一个类型参数,但我不知道这是否可能是

简短回答

该功能已经开箱即用。给定一个带注释的接口:

[ServiceContract()]
interface IMath
{
[OperationContract()]
double Add(double A, double B);
}

您可以为特定的绑定和端点创建一个工厂:

BasicHttpBinding myBinding = new BasicHttpBinding();
//configure the binding ........ then 
EndpointAddress myEndpoint = new EndpointAddress("http://localhost/MathService/Ep1");
ChannelFactory<IMath> myChannelFactory = new ChannelFactory<IMath>(myBinding, myEndpoint);
// Create a channel.
IMath wcfClient1 = myChannelFactory.CreateChannel();
double s = wcfClient1.Add(3, 39);

详细解释

WCF已经为所有这些东西提供了工厂,事实上,它有太多的工厂,而没有足够的文档——早在2008年,人们就认为每个人都会从符合WS-*标准的WSDL生成客户端代理,并通过XML进行配置。

那时,每个人都认为XML是一切的解决方案,就像5年前,或者2年前,每个人认为JSON是解决方案一样。

客户端使用行为。行为将端点和绑定结合在一起。所有这些都可以在代码中创建,并根据需要注入。WCF提供了从app.config/web.config文件加载设置的基础结构。不幸的是,这个配置基础结构不能很容易地定制,How To文档也没有以编程方式显示构建通道的整个过程,因为它被认为是advanced的情况。

绑定、端点、消息信封、正文等的工厂。没有人想过要对所有这些进行文档化,因为他们认为每个人都会使用工具来生成代理。

例如,WCF客户端概述显示了如何创建接口和客户端基础,但5个构造函数中有4个使用默认配置或期望使用配置名称。不过,最后一个同时接受Binding和EndpointAddress。无需硬编码,例如:


public partial class SampleServiceClient : System.ServiceModel.ClientBase<ISampleService>, ISampleService
{
public SampleServiceClient(Binding binding, EndpointAddress remoteAddress)
:base(binding, remoteAddress)
{
}
}

每次生成代码都是不切实际的,尤其是当行为需要更改时。例如,添加加密,或检查/修改自定义标头。因此WCF为"高级"场景提供了一个ChannelFactory。代理也使用它来缓存Behavior、绑定和端点实例。这些文档位于"高级"部分,尽管介绍了如何:使用ChannelFactory。

从文档的例子来看,它很简单:

[ServiceContract()]
interface IMath
{
[OperationContract()]
double Add(double A, double B);
}

private void Run()
{
BasicHttpBinding myBinding = new BasicHttpBinding();
EndpointAddress myEndpoint = new EndpointAddress("http://localhost/MathService/Ep1");
ChannelFactory<IMath> myChannelFactory = new ChannelFactory<IMath>(myBinding, myEndpoint);
// Create a channel.
IMath wcfClient1 = myChannelFactory.CreateChannel();
double s = wcfClient1.Add(3, 39);
}

我为什么按相反的顺序写这些例子

正是因为ChannelFactory"隐藏"在高级功能中。我首先找到了编程客户端示例,然后是指向ClientFactory的Configuring client Behaviors,最后是How to:Use the ChannelFactory。

首先需要清理签名。减少参数数量。以《清洁代码》一书为参考,一个参数已经是一个参数太多了。如果您可以实现反向控制,这是有意义的,但在这种情况下,这实际上意味着创建一个POCO/DTO/Class来传递数据。这满足打开/关闭原理

public class EndpointDetails{
public string UserName {get; set;}  
public string Password {get; set;}
public string Address {get; set;}
}

然后

private T ClientMaker<TInterface, T>(EndpointDetails endpointDetails)

"ClienMaker"对我来说并没有任何意义,而且具有误导性。在C#中,我们应该熟悉代码的含义,以便有意义地命名事物(也来自Clean code)-看完代码后,我会将其命名为MakeClientWithSettings(),因为这就是我建议它在下面的代码中要做的事情。

此外,这不是一家工厂。。。不要那样称呼它来混淆事情。如果是一家工厂,您只需调用Create(),无参数!现在它将如何利用DI(参考工厂可能是什么)来创建一切

现在要清理所有泛型,正如您正确指出的那样,这有点过分——这完全取决于所需的依赖关系。

在我看来,在那一种方法中发生的事情太多了

  • 这意味着类中存在多个关注点(违反了单一责任)
  • 有人真的抓住救命稻草让东西变得"通用">

所以我可以尝试帮助你重构它,但我不知道代码的上下文,所以这就是我想到的-这可能不会编译,所以你需要调整。我只是在记事本中手动完成的。

本质上,这里的jist是

  • 您传入的Generic也可以传出
  • 这里唯一类似工厂的地方是,我们正在手动更新
  • 具有泛型的返回类型是通过推断检查的类型

    private ClientBase<TInterface> MakeClientWithSettings<TInterface>(EndpointDetails endpointDetails)
    where TInterface : class 
    {
    var binding = new BasicHttpBinding();
    binding.MaxBufferPoolSize = int.MaxValue;
    binding.MaxBufferSize = int.MaxValue;
    binding.MaxReceivedMessageSize = int.MaxValue;
    binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
    EndpointAddress ordersEndpoint = new EndpointAddress(endpointDetails.Address);
    //T client = Activator.CreateInstance(typeof(T), new object[]{binding, ordersEndpoint}) as T;
    var client = new ClientBase<T>(binding, ordersEndpoint);
    client.ClientCredentials.UserName.UserName = endpointDetails.Username;
    client.ClientCredentials.UserName.Password = endpointDetails.Password;
    return client;
    }
    

因此,根据Panagiotis Kanavos的评论,这似乎真的应该以不同的方式进行,而疼痛的根源是试图迫使它以另一种方式进行。

听起来您需要利用依赖项注入容器来处理所需范围中的依赖项,PerRequest或PerSession(瞬态),并且WCF中已经有一个工厂可以为您完成所有绑定。

这是伪代码(我不知道你们使用的客户端的任何东西?可能是你们需要使用或和接口的另一个类)

services.AddTransient<BasicHttpBinding>(() => new BasicHttpBinding(*))
services.AddTransient<EndpointAddress>(() = EndpointAddress(*))
services.AddTransient<Client?>(ClientFactory) <-- the dependencies here are the ones you need to inject above

然后,您只需在需要的地方使用客户端,将其注入类的构造函数中

public OrderSystem(Client? client){
client.DoReqeust()
}

最新更新