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...
Other Posts you might also like