Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

ClientBase<T> and a Common Base Class


:P
On this page:

I've been racking my head over a 'generic' problem in relation to a WCF Web service client. I have an component that acts as a Web Service wrapper to proxy calls between a non-.NET client and a WCF Web Service client. The calls are working fine, but we have a rather large number of services that are being called and there's a bit of configuration code that needs to fire for initializing the Web Service client.

The problem is the code is nearly identical for each of the service initializations except for the WCF proxy types, but I had a heck of a time to figure out how to make this code generic so I don't end up duplicating the code into each of the service wrapper classes. That would  create a maintenance nightmare if there's a change to the configuration of the service. Here's the core code that needs to run (at this point - I'm sure there will be more in the future):

private Message_EnrolmentInterfaceClient GetEnrollmentService(string Username, string Password)

{

 

    this.client = new Message_EnrolmentInterfaceClient();

 

    client.Endpoint.Address = new EndpointAddress(InteropApp.Configuration.ServiceAddress);

 

    // *** Use a custom binding if requested

    if (!string.IsNullOrEmpty(this.BindingName))

        client.Endpoint.Binding = new BasicHttpBinding(this.BindingName);

 

    client.ClientCredentials.UserName.UserName = Username;

    client.ClientCredentials.UserName.Password = Password;

 

    // *** Allow acceptance of all certificates

    if (this.IngoreCertificateErrors)

        ServicePointManager.ServerCertificateValidationCallback +=

            delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)

            { return true; };

 

    // *** Don't include TimeStamp header - so server won't expect it back

    // *** This allows results returned without any Soap Headers

    BindingElementCollection elements = client.Endpoint.Binding.CreateBindingElements();

    elements.Find<SecurityBindingElement>().IncludeTimestamp = false;

    client.Endpoint.Binding = new CustomBinding(elements);

 

    return client;

}

The code basically creates an instance of the WCF Proxy then proceeds to set a few properties of the proxy based on configuration settings or properties set on the wrapper class instance.

As I mentioned this code is used for each and every one of the service classes (there are about 10 services that are called) and it's nearly identical.

So Refactor this code, right?  I'd love to but I couldn't easily figure out how to get this to work <s>.

The problem is that there's no simple base class for a WCF Web Service proxy. The base class for this service is ClientBase<MessageEnrollmentInterfaceService> - a generic type which proves a bit twisted for creating code based on generic parameters.

Ideally I would like to create the service wrapper with a generic parameter for the Proxy type but for starters I tried using a base class method with a generic parameter. So for first - faulty -  attempt in BaseInteropService I created this:

protected TResult GetEnrollmentService<T,TResult>(string Username, string Password)

       where T : ClientBase<T>, class

   {

       TResult client = Activator.CreateInstance<T>() as TResult;

 

       client.Endpoint.Address = new EndpointAddress(InteropApp.Configuration.ServiceAddress);

 

       // *** Use a custom binding if requested

       if (!string.IsNullOrEmpty(this.BindingName))

           client.Endpoint.Binding = new BasicHttpBinding(this.BindingName);

 

       client.ClientCredentials.UserName.UserName = Username;

       client.ClientCredentials.UserName.Password = Password;

 

       // *** Allow acceptance of all certificates

       if (this.IngoreCertificateErrors)

           ServicePointManager.ServerCertificateValidationCallback +=

               delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)

               { return true; };

 

       // *** Don't include TimeStamp header - so server won't expect it back

       // *** This allows results returned without any Soap Headers

       BindingElementCollection elements = client.Endpoint.Binding.CreateBindingElements();

       elements.Find<SecurityBindingElement>().IncludeTimestamp = false;

       client.Endpoint.Binding = new CustomBinding(elements);

 

       return client;

   }

and this code does compile on its own. It basically passes the Proxy type in as a parameter and internally uses Activator.CreateInstance<T> to create a new instance. Looks good.

But the problem isn't with the method - it's with actually passing the proper generic parameter of T to the method. You'd think I should be able to do:

Message_EnrolmentInterfaceClient client = this.GetEnrollmentService<Message_EnrolmentInterface>();

The type 'WebServiceInterop.EnrolmentService.Message_EnrolmentInterfaceClient' cannot be used as type parameter 'T' in the generic type or method 'WebServiceInterop.BaseInteropService.GetEnrollmentService<T>()'. There is no implicit reference conversion from
'WebServiceInterop.EnrolmentService.Messager_EnrolmentInterfaceClient' to
'System.ServiceModel.ClientBase<WebServiceInterop.EnrolmentService.Message)EnrolmentInterfaceClient>'. 

The problem is that the way I have defined the generic parameter as ClientBase<T> as the input and this is not working when trying to pass that value as an input for a generic parameter. Generic parameters don't work as input for a generic parameter. Ugh...

The bummer here is that there's no common non-generic base type/interface that you can cast a WCF proxy to to get at the various Proxy properties like EndPoint, Channel, Credential etc. The immediate ancestor is ClientBase<T> where T is the service interface.

After a bunch of back and forth I ended with the following code that is kinda ugly but works using two generic parameters on the method - one for the interface and one for the service type.

protected TClient GetServiceClient<TClient,TInterface>(string Username, string Password)           

    where TClient : class

    where TInterface : class

{

    TClient result = Activator.CreateInstance<TClient>() as TClient;

    ClientBase<TInterface> client = result as ClientBase<TInterface>;

 

    client.Endpoint.Address = new EndpointAddress(InteropApp.Configuration.ServiceAddress);

 

    // *** Use a custom binding if requested

    if (!string.IsNullOrEmpty(this.BindingName))

        client.Endpoint.Binding = new BasicHttpBinding(this.BindingName);

 

    client.ClientCredentials.UserName.UserName = Username;

    client.ClientCredentials.UserName.Password = Password;

 

    // *** Allow acceptance of all certificates

    if (this.IngoreCertificateErrors)

        ServicePointManager.ServerCertificateValidationCallback +=

            delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)

            { return true; };

 

    // *** Don't include TimeStamp header - so server won't expect it back

    // *** This allows results returned without any Soap Headers

    BindingElementCollection elements = client.Endpoint.Binding.CreateBindingElements();

    elements.Find<SecurityBindingElement>().IncludeTimestamp = false;

    client.Endpoint.Binding = new CustomBinding(elements);

 

    return result;

}

And then creating the instance with:

// *** Always Ensure that an instance of the service proxy is loaded (this.client)               

FaCSIAEnrolmentInterface_Binding_FaCSIAEnrolmentInterfaceClient client =       this.GetServiceClient<Message_EnrolmentInterfaceClient,
                      Message_EnrolmentInterface>();

I need the two parameter because I need an instance for the type in order to instantiate and the interface so I can use ClientBase<ChannelInterface> to make the call.

This is quite ugly but it removes the redundancy.

Next I thought it'd be nice to move the generic code to the class level so the generic parameters only have to be set once during class creation and then forget about it:

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.AutoDual)]

public abstract class BaseInteropService<TClient,TInterface>

        where TClient : class

        where TInterface : class

...

   protected static TClient GetEnrollmentService(string Username, string Password)           

   {

       TClient result = Activator.CreateInstance<TClient>() as TClient;

       ClientBase<TInterface> client = result as ClientBase<TInterface>;

 

       client.Endpoint.Address = new EndpointAddress(InteropApp.Configuration.ServiceAddress);

 

       // *** Use a custom binding if requested

       if (!string.IsNullOrEmpty(this.BindingName))

           client.Endpoint.Binding = new BasicHttpBinding(this.BindingName);

 

       client.ClientCredentials.UserName.UserName = Username;

       client.ClientCredentials.UserName.Password = Password;

 

       // *** Allow acceptance of all certificates

       if (this.IngoreCertificateErrors)

           ServicePointManager.ServerCertificateValidationCallback +=

               delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)

               { return true; };

 

       // *** Don't include TimeStamp header - so server won't expect it back

       // *** This allows results returned without any Soap Headers

       BindingElementCollection elements = client.Endpoint.Binding.CreateBindingElements();

       elements.Find<SecurityBindingElement>().IncludeTimestamp = false;

       client.Endpoint.Binding = new CustomBinding(elements);

 

       return result;

   }

}

That works fine in .NET but unfortunately won't work for my specific interface because it has to be called over COM. Anything published to COM cannot be or derive from a generic type. Bummer.

So in the end I went back to the original generic methods on the base class in addition I require adding a proxy typed client reference and a GetClient() method that simpifies retrieving the Proxy in one place. Yes - still redundnant code but it's pretty 'generic' - nothing happening there that's likely to change and be a maintenance headache:

/// <summary>

/// Wrapper for the Enrolment Service that is COM

/// accessible from FoxPro.

/// </summary>   

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.AutoDual)]

public class InteropEnrolmentService : BaseInteropService

{

 

    #region Class Configuration

 

    /// <summary>

    /// Internal instance of the Web Service Proxy. Once loaded

    /// this client instance is cached for reuse. Set to null to

    /// clear explicitly or release the class reference

    /// </summary>

    private Message_EnrolmentInterfaceClient client = null;

 

    /// <summary>

    /// Gets an instance of the Enrolment Service Proxy

    /// </summary>

    /// <returns></returns>

    public override bool GetClient()

    {

        if (this.client != null)

            return true;

 

        this.client = this.GetServiceClient<Message_EnrolmentInterfaceClient,

                                            Message_EnrolmentInterface>();

        if (this.client == null)

        {

            this.SetError("Unable to load client instance");

            return false;

        }

 

        return true;

    }

 

    #endregion

 

    /// <summary>

    /// Create a new Enrolment against the service. Takes an Enrollment

    /// input record and returns       

    public Message_CreateEnrolment_Output CreateEnrolment(Message_CreateEnrolment_Input input)

    {

        try

        {

            // *** Always Ensure that an instance of the service proxy is loaded (this.client)               

            if (!this.GetClient())               

                return null;

 

            // *** Call the actual CreateEnrolment method on the service

            Message_CreateEnrolment_Output output = client.CreateEnrolment(input);

 

            return output;

        }

        catch (Exception ex)

        {

            this.SetError(ex.Message);

        }

 

        return null;

    }

 

... more implementation methods

}

Somehow I don't feel very good about this like I'm missing something simple, most likely in the WCF inheritance/interface structure that would have allowed me to make a factory method like this easier to create...

Posted in WCF  

The Voices of Reason


 

Donnie Hale
December 14, 2007

# re: ClientBase&lt;T&gt; and a Common Base Class

I don't think you have to use Activator.CreateInstance to get a new TClient, as long as you add "new" to the "where" constraints for TClient. If you do that, I'm pretty sure you can then just say "new TClient()";

Rick Strahl
December 14, 2007

# re: ClientBase&lt;T&gt; and a Common Base Class

@Donnie - usually you'd be right, but it doesn't work in this case. If I use new() for TClient I get an error:

'TClient' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TClient' in the generic type or method

The type passed actually has neither of these qualities the message complains about, but it still throws. With just using class it works.

Rick Strahl
December 14, 2007

# re: ClientBase&lt;T&gt; and a Common Base Class

Duh! It does work - I just failed to change the generic parameter in another method that calls this one with the same generic parameters. Changed only the latter so the inbound first parameter was not set to new(). Switched and it works.

This sort of thing gets tricky with generics - hard to visualize the flow and difficult to trace backwards especially in this case where the error is showing what seems to be an unrelated error message.

Sujit D'Mello
February 21, 2008

# re: ClientBase&lt;T&gt; and a Common Base Class

This was really usefull in my scenario where I wanted to distribute my client code without requiring any WCF configuration. However, the standard WCF proxy will fail if you use the parameterless constructor without a default binding in the config file. To work around the problem, I made the following change:

TClient result = Activator.CreateInstance<TClient>() as TClient;

was changed to

TClient result = Activator.CreateInstance(typeof(TClient), new object[] { new WSHttpBinding(), new EndpointAddress(InteropApp.Configuration.ServiceAddress) }) as TClient;

Now the code works with no WCF config!

Sujit

christian
June 29, 2008

# re: ClientBase&lt;T&gt; and a Common Base Class

a wee improvement:

instead of:
public abstract class BaseInteropService<TClient,TInterface>

        where TClient : class

        where TInterface : class 


you can do:

public abstract class BaseInteropService<TClient,TInterface>

        where TClient : ClientBase<TInterface>

        where TInterface : class 

Daniel Q
May 31, 2012

# re: ClientBase&lt;T&gt; and a Common Base Class

Pretty late to comment on this, probably not very usefull now, but I ran into the same issue about the conversion of types bettween service type and ClientBase<IService>... this happens because I was not using the interface defined on the service wrapper generated code, but from my own contract class library. So yes, it's posible to define TClient as a ClientBase<TInterface> generic, like cristian pointed.

Regards.

Logan M
December 26, 2013

# re: ClientBase&lt;T&gt; and a Common Base Class

Thanks a lot! This post helped me when I was struck with the same kind of issue!!

Lisa B
September 03, 2014

# re: ClientBase&lt;T&gt; and a Common Base Class

Thank you! Your post helped me clean up some ugly code!

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024