简体   繁体   中英

Can't access properties of the base class of the class I am using as the type argument in a generic method

Background

I have a third party system that offers SOAP APIs. The class structure of their WSDLs leaves a lot to be desired in general, but one area I'm struggling with is streamlining the retrieval of bindings. Their sample code has around 20 of these methods, one for each endpoint/class/interface:

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;
}

My primary goal is to convert these into a generic method that I can call with just a parameter or two and return the class. (You'll also see in my current code that I'm implementing some dynamic binding code. This is because I have multiple companies to access and their system structure requires bindings per company. This is not relevant to the question.)

The basic structure of all the classes and interfaces is as follows. I have included only the constructor I'm targeting. These are defined in the stub classes created from the WSDLs, and not something I want to directly edit if possible.:

Interfaces
public interface ICreateObjectPortType { }
Classes
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;
    }
}

Here are examples of the other interfaces and classes declared in the stubs. The only common thing between all of them is all of the classes inherit from ClientBase<interface> and interface :

  • IReadObjectPortType and ReadObjectPortTypeClient
  • IUpdateObjectPortType and UpdateObjectPortTypeClient
  • IFindObjectsPortType and FindObjectsPortTypeClient

Here is the SetupHttpHeader method defined in their sample code. I haven't checked to see if it's actually needed yet, but I'm including it for completeness:

 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; } }

My Code

And lastly for code samples, here is my stab at a generic method, the supporting credential setting method, and what it looks like to use them:

 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; }

As you can see, I'm not quite there yet. I have successfully implemented a function parameter in the method in order to call a constructor on T with parameters. It's far more open than I prefer my generics to be, but I don't know of another way to allow this while also restricting what T can be.

The Question

My problem right now is getting the setting of ClientCredentials and calling of SetupHttpHeader to happen in that one generic method as well. Given that there is no base interface for the classes, and the only common ground they have is ClientBase<T> I haven't found a way to treat servicePort inside ConfigureServicePort as something that inherits from ClientBase<T> to get at the necessary properties.

How can I get it so that ConfigureServicePort<T> can treat its T like a ClientBase<T> so that I can get to the properties I need to configure?

I cannot test this as I'd need an actual service, but assuming that you can do this:

public CreateObjectPortTypeClient() { }

which can fail if some conditions aren't met (as specified in the ClientBase<T> constructor):

//
// 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.

If that works for you, you can make your life quite easy.

Let's simplify the situation with 2 services:

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

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

then you can just use this:

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;
}

and then call it simply with:

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

Notice that the two generic parameters are needed as you need to satisfy the generic constraint set by ClientBase<T> .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM