首页 > 解决方案 > 无法访问我在泛型方法中用作类型参数的类的基类的属性

问题描述

背景

我有一个提供 SOAP API 的第三方系统。他们的 WSDL 的类结构总体上还有很多不足之处,但我正在努力解决的一个领域是简化绑定的检索。他们的示例代码有大约 20 个这样的方法,每个端点/类/接口一个:

public static ICreateObjectPortType GetCreateObjectHttpBinding()
{
    var servicePort = new CreateObjectPortTypeClient();

    servicePort.Endpoint.ListenUri = GetServiceUrl("ReadObject");

    servicePort.ClientCredentials.UserName.UserName = SDK.Username;
    servicePort.ClientCredentials.UserName.Password = SDK.Password;

    SetupHttpHeader(servicePort.InnerChannel);
    return servicePort;
}

我的主要目标是将这些转换为一个通用方法,我可以只用一个或两个参数调用它并返回类。(您还会在我当前的代码中看到我正在实现一些动态绑定代码。这是因为我要访问多家公司,并且它们的系统结构需要每个公司的绑定。这与问题无关。)

所有类和接口的基本结构如下。我只包含了我要定位的构造函数。这些是在从 WSDL 创建的存根类中定义的,如果可能的话,我不想直接编辑。:

接口
public interface ICreateObjectPortType { }
课程
public partial class CreateObjectPortTypeClient : ClientBase<ICreateObjectPortType>, ICreateObjectPortType
{
    public CreateObjectPortTypeClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 
            base(endpointConfigurationName, remoteAddress)
    {
    }
}

以下是存根中声明的其他接口和类的示例。它们之间唯一的共同点是所有类都继承自ClientBase<interface>and interface

这是SetupHttpHeader他们的示例代码中定义的方法。我还没有检查它是否真的需要,但为了完整起见,我将其包括在内:

public static void SetupHttpHeader(IClientChannel serviceChannel)
{
    using (new OperationContextScope(serviceChannel))
    {
        // Add a HTTP Header to an outgoing request
        var requestMessage = new HttpRequestMessageProperty();
        requestMessage.Headers["Authorization"] = "Basic " + System.Convert.ToBase64String(Encoding.UTF8.GetBytes(SDK.Username + ":" + SDK.Password));
        OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
    }
}

我的代码

最后,对于代码示例,这是我对通用方法、支持凭证设置方法以及使用它们的样子的尝试:

public static T ConfigureServicePort<T>(Func<string,EndpointAddress, T> test, string serviceName) where T : class
{
    var endpointName = serviceName + "HttpPort";
    var endpointAddress = new EndpointAddress($"https://hcmiller.myprintdesk.com:443/rpc/company:public/services/{serviceName}");

    var servicePort = test(endpointName, endpointAddress);
    return servicePort;
}

public static void SetClientCredentials(this ClientCredentials creds)
{
    creds.UserName.UserName = SDK.Username;
    creds.UserName.Password = SDK.Password;
}

public static ICreateObjectPortType GetCreateObjectHttpBinding()
{
    var servicePort = ConfigureServicePort((x, y) => new CreateObjectPortTypeClient(x, y), "CreateObject");
    servicePort.ClientCredentials.SetClientCredentials();
    SetupHttpHeader(servicePort.InnerChannel);
    return servicePort;
}

如您所见,我还没有完全到那里。我已经成功地在方法中实现了一个函数参数,以便T使用参数调用构造函数。它比我更喜欢我的泛型要开放得多,但我不知道有另一种方法可以允许这样做,同时也限制T可以是什么。

问题

我现在的问题是在该通用方法中也进行设置ClientCredentials和调用。SetupHttpHeader鉴于这些类没有基本接口,它们唯一的共同点是ClientBase<T>我还没有找到一种方法将servicePort内部ConfigureServicePort视为继承自ClientBase<T>以获取必要属性的东西。

我怎样才能得到它,以便ConfigureServicePort<T>可以像对待它T一样对待它ClientBase<T>,以便我可以访问我需要配置的属性?

标签: c#generics

解决方案


我无法对此进行测试,因为我需要实际的服务,但假设您可以这样做:

public CreateObjectPortTypeClient() { }

如果不满足某些条件(如ClientBase<T>构造函数中指定),则可能会失败:

//
// Summary:
//     Initializes a new instance of the System.ServiceModel.ClientBase`1 class using
//     the default target endpoint from the application configuration file.
//
// Exceptions:
//   T:System.InvalidOperationException:
//     Either there is no default endpoint information in the configuration file, more
//     than one endpoint in the file, or no configuration file.

如果这对你有用,你可以让你的生活变得很轻松。

让我们用 2 个服务来简化情况:

public interface IService1 { }
public class Service1 : ClientBase<IService1>, IService1 { }

public interface IService2 { }
public class Service2 : ClientBase<IService2>, IService2 { }

那么你可以使用这个:

public static TClient ConfigureServicePort<TClient, TService>(string serviceName)
    where TClient : ClientBase<TService>, new()
    where TService : class
{
    var servicePort = new TClient();
    servicePort.Endpoint.Address = new EndpointAddress($"https://hcmiller.myprintdesk.com:443/rpc/company:public/services/{serviceName}");
    servicePort.Endpoint.Name = serviceName + "HttpPort";

    servicePort.ClientCredentials.SetClientCredentials();
    SetupHttpHeader(servicePort.InnerChannel);

    return servicePort;
}

然后简单地调用它:

var service1Client = X.ConfigureServicePort<Service1, IService1>("Service1");
var service2Client = X.ConfigureServicePort<Service2, IService2>("Service2");

请注意,需要两个泛型参数,因为您需要满足由 设置的泛型约束ClientBase<T>


推荐阅读