Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to optimize WCF CreateFactory in System.ServiceModel.ChannelFactory?

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.

like image 796
Andreas Savva Avatar asked May 17 '18 14:05

Andreas Savva


1 Answers

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.

like image 84
Paul Turner Avatar answered Oct 13 '22 00:10

Paul Turner