Как оптимизировать WCF CreateFactory в System.ServiceModel.ChannelFactory? - PullRequest
0 голосов
/ 17 мая 2018

Моя текущая реализация использует класс ClientBase для создания канала для вызовов WCF, выполняемых сторонним API. Для этого стороннего API требуется сертификат X509Certificate2, а также ClientCredentials для аутентификации.

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

Клиентский экземпляр и запрос можно посмотреть здесь:

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

К сожалению, процесс создания канала для вызова WCF занимает более 9 секунд. Используя профилировщик DoTrace от ReSharper, я вижу, что код задерживается по следующему методу: System.ServiceModel.Description.XmlSerializer.OperationBehavior + Reflecto.EnsureMessageInfos

Полная трассировка стека вызовов, совершаемых в System.ServiceModel , приведена ниже.

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

Я уже пытался использовать sgen.exe для создания сборки сериализации XML в надежде, что это улучшит производительность сериализатора. К сожалению, это никак не отразилось.

Я также нашел в сети несколько подходов, которые рекомендуют кэшировать каналы или фабрики каналов, такие как здесь http://www.itprotoday.com/microsoft-visual-studio/wcf-proxies-cache-or-not-cache. Однако эти подходы не работают для этой реализации, поскольку с фабрикой каналов связаны клиентские учетные данные. Это потребует кэширования фабрики каналов или канала для каждого клиента, что нереально.

Кто-нибудь знает способ предотвратить отражение ChannelFactory над объектами Request и Response при его создании? Любая помощь, которую кто-либо может оказать по этому вопросу, будет принята с благодарностью.

1 Ответ

0 голосов
/ 03 июня 2019

Я не знаю ни одного механизма, который позволил бы вам обойти поведение, которое вы здесь видите. По сути, именно так и было спроектировано ChannelFactory: оно требует значительных разовых затрат на рефлексию и составление стека каналов, чтобы дать вам дешевую процедуру создания экземпляров каналов. Вы должны повторно использовать фабрику, если хотите сэкономить 9 секунд.

Обычно я бы рекомендовал использовать встроенное кэширование из ChannelFactory, связанное с экземплярами клиента, но это становится недействительным в тот момент, когда вы касаетесь свойства ClientCredentials.

Я бы предположил, что вам действительно нужно рассмотреть возможность кэширования каждого ChannelFactory для каждого клиента. Если у вас нет буквально десятков тысяч наборов учетных данных, это нереальная перспектива. Действительно, именно так работают системы HTTP в .NET для предварительной авторизации запросов.

...