My current implementation is utilizing the ClientBase class to create a channel for WCF calls made to a third party API. This third party API requires a X509Certificate2 certificate as well as ClientCredentials to be authenticated.
public class HeaderAdder : ContextBoundObject, IClientMessageInspector
{
public bool RequestFailedDueToAuthentication;
public string UserName { get; set; }
public string Password { get; set; }
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var property = new UserNameHeader
{
Password = Password,
UserName = UserName
};
request.Headers.Add(MessageHeader.CreateHeader("UserNameHeader", "test", property));
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
RequestFailedDueToAuthentication = reply.ToString().Contains("ErrorCode>-4<");
}
}
public class CustomEndpointBehavior : IEndpointBehavior
{
private readonly HeaderAdder _headerAdder;
public CustomEndpointBehavior(HeaderAdder headerAdder)
{
_headerAdder = headerAdder;
}
public void Validate(ServiceEndpoint endpoint)
{
//throw new NotImplementedException();
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
//throw new NotImplementedException();
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
//throw new NotImplementedException();
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
var credentials = endpoint.Behaviors.Find<ClientCredentials>();
if (!string.IsNullOrEmpty(credentials.UserName.Password))
{
_headerAdder.UserName = credentials.UserName.UserName;
_headerAdder.Password = credentials.UserName.Password;
clientRuntime.ClientMessageInspectors.Add(_headerAdder);
}
}
}
Client instantiation and request can be seen here:
var client = new TestClient()
{
ClientCredentials =
{
UserName =
{
UserName = "testing",
Password = "testing"
},
UseIdentityConfiguration = true
}
};
client.ClientCredentials?.ClientCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindByIssuerName,
"Testing");
client.ChannelFactory.Endpoint.EndpointBehaviors.Add(
new CustomEndpointBehavior(new HeaderAdder()));
var request = new Request();
client.Get(request);
Unfortunately the process of creating a Channel for the WCF call takes over 9 seconds to complete. Using ReSharper's doTrace profiler I am able to see that the code is being held up on the following method: System.ServiceModel.Description.XmlSerializer.OperationBehavior+Reflecto.EnsureMessageInfos
A full stack trace of the calls being made in System.ServiceModel can be seen below.
System.ServiceModel.ClientBase`1.get_Channel
System.ServiceModel.ClientBase`1.CreateChannelInternal
System.ServiceModel.ClientBase`1.CreateChannel
System.ServiceModel.ChannelFactory`1.CreateChannel
System.ServiceModel.ChannelFactory`1.CreateChannel(EndpointAddress, Uri)
System.ServiceModel.ChannelFactory.EnsureOpened
System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan)
System.ServiceModel.ChannelFactory.OnOpening
System.ServiceModel.ChannelFactory.CreateFactory
System.ServiceModel.Channels.ServiceChannelFactory.BuildChannelFactory(ServiceEndpoint, Boolean)
System.ServiceModel.Description.DispatcherBuilder.BuildProxyBehavior(ServiceEndpoint, out BindingParameterCollection)
System.ServiceModel.Description.DispatcherBuilder.ApplyClientBehavior(ServiceEndpoint, ClientRuntime)
System.ServiceModel.Description.DispatcherBuilder.BindOperations(ContractDescription, ClientRuntime, DispatchRuntime)
System.ServiceModel.Description.XmlSerializerOperationBehavior.ApplyClientBehavior(OperationDescription, ClientOperation)
System.ServiceModel.Description.XmlSerializerOperationBehavior.CreateFormatter
System.ServiceModel.Description.XmlSerializerOperationBehavior+Reflector.EnsureMessageInfos
I have already tried using sgen.exe to create an XML serialization assembly in hopes that it would improve the serializer's performance. Unfortunately, it had no effect.
I have also found several approaches online that recommend caching Channels, or Channel Factories such as here http://www.itprotoday.com/microsoft-visual-studio/wcf-proxies-cache-or-not-cache. However, these approaches do not work for this implementation because the Channel Factory has Client Credentials associated with it. This would require caching of a Channel Factory or Channel for each client which is unrealistic.
Does anyone know of a way to prevent the ChannelFactory from reflecting over the Request and Response objects when it is created? Any assistance anyone can be provide on this issue would greatly appreciated.
I don't know of any mechanism that will let you bypass the behaviour you're seeing here. This is intrinsically how ChannelFactory
was designed: it does the heavy one-off costs of reflection and composing the channel stack to give you a cheap routine to create channel instances. You must re-use the factory if you want to save your 9 seconds.
Normally I'd suggest using the built-in caching of the ChannelFactory
associated with client instances, but this is invalidated the moment you touch the ClientCredentials
property.
I would suggest that you really do need to consider caching each ChannelFactory
on a per-client basis. Unless you have literal tens of thousands of sets of credentials, it's not an unrealistic prospect. Indeed, this is how the HTTP systems in .NET work to pre-authorise requests.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With