WCF ChannelFactory и каналы - кэширование, повторное использование, закрытие и восстановление

У меня есть следующая запланированная архитектура для моей клиентской библиотеки WCF:

  • использование ChannelFactory вместо сгенерированных svcutil прокси, потому что мне нужно больше контроля, а также я хочу сохранить клиента в отдельной сборке и избежать регенерации при изменении моей службы WCF
  • нужно применить поведение с инспектором сообщений к моей конечной точке WCF, чтобы каждый канал мог отправлять свой собственный токен аутентификации
  • моя клиентская библиотека будет использоваться из интерфейса MVC, поэтому мне придется подумать о возможных проблемах с потоками
  • Я использую .NET 4.5 (может быть, у него есть помощники или новые подходы для лучшей реализации клиентов WCF?)

Я прочитал много статей о различных отдельных частях, но я все еще не понимаю, как правильно собрать все это вместе. У меня есть следующие вопросы:

  1. как я понимаю, рекомендуется кешировать ChannelFactory в статической переменной, а потом получать из нее каналы, не так ли?
  2. является ли поведение конечной точки специфичным для всей фабрики ChannelFactory, или я могу применить свое поведение аутентификации для каждого канала отдельно? Если поведение характерно для всей фабрики, это означает, что я не могу хранить какую-либо информацию о состоянии в моих объектах поведения конечной точки, потому что один и тот же токен аутентификации будет повторно использоваться для каждого канала, но, очевидно, я хочу, чтобы каждый канал имел свой собственный токен аутентификации для текущий пользователь. Это означает, что мне нужно будет вычислить токен внутри моего поведения конечной точки (я могу сохранить его в HttpContext, и мое поведение инспектора сообщений просто добавит его в исходящие сообщения).
  3. мой клиентский класс одноразовый (реализует IDispose). Как правильно расположить канал, зная, что он может быть в любом возможном состоянии (не открыт, открыт, сбой ...)? Я просто утилизирую это? Должен ли я прервать его, а затем утилизировать? Закрыть его (но, возможно, он еще не открылся), а затем утилизировать?
  4. Что делать, если при работе с каналом возникла какая-то ошибка? Неисправен только канал или вся фабрика ChannelFactory?

Думаю, строка кода говорит больше, чем о тысяче слов, так что вот моя идея в виде кода. Я пометил все свои вопросы выше знаком "???" в коде.

public class MyServiceClient : IDisposable
{
    // channel factory cache
    private static ChannelFactory<IMyService> _factory;
    private static object _lock = new object();

    private IMyService _client = null;
    private bool _isDisposed = false;

     /// <summary>
    /// Creates a channel for the service
    /// </summary>
    public MyServiceClient()
    {
        lock (_lock)
        {
            if (_factory == null)
            {
                // ... set up custom bindings here and get some config values

                var endpoint = new EndpointAddress(myServiceUrl);
                _factory = new ChannelFactory<IMyService>(binding, endpoint);

                // ???? do I add my auth behavior for entire ChannelFactory 
                // or I can apply it for individual channels when I create them?
            }
        }

        _client = _factory.CreateChannel();
    }

    public string MyMethod()
    {
        RequireClientInWorkingState();
        try
        {
            return _client.MyMethod();
        }
        catch
        {
            RecoverFromChannelFailure();
            throw;
        }
    }

    private void RequireClientInWorkingState()
    {
        if (_isDisposed)
            throw new InvalidOperationException("This client was disposed. Create a new one.");

        // ??? is it enough to check for CommunicationState.Opened && Created?
        if (state != CommunicationState.Created && state != CommunicationState.Opened)
            throw new InvalidOperationException("The client channel is not ready to work. Create a new one.");
    }

    private void RecoverFromChannelFailure()
    {
        // ??? is it the best way to check if there was a problem with the channel?
        if (((IChannel)_client).State != CommunicationState.Opened)
        {
            // ??? is it safe to call Abort? won't it throw?
            ((IChannel)_client).Abort();
        }

        // ??? and what about ChannelFactory? 
        // will it still be able to create channels or it also might be broken and must be thrown away? 
        // In that case, how do I clean up ChannelFactory correctly before creating a new one?
    }

    #region IDisposable

    public void Dispose()
    {    
        // ??? is it how to free the channel correctly?
        // I've heard, broken channels might throw when closing 
        // ??? what if it is not opened yet?
        // ??? what if it is in fault state?
        try
        {
            ((IChannel)_client).Close();
        }
        catch
        {
           ((IChannel)_client).Abort();              
        }

        ((IDisposable)_client).Dispose();

        _client = null;
        _isDisposed = true;
    }

    #endregion
}

person JustAMartin    schedule 27.01.2013    source источник
comment
В итоге я получил реализацию, почти аналогичную приведенной выше, и, похоже, она работает нормально. Я добавил код в RecoverFromChannelFailure для работы со сломанной фабрикой: lock (_lock){ if (_factory.State != CommunicationState.Opened) {_factory.Abort();_factory = null;}}; а также у меня есть метод Initialize, который проверяет, исчезла ли фабрика, а затем создает новую.   -  person JustAMartin    schedule 05.12.2013
comment
Что касается аутентификации, у меня получился пользовательский MessageInterceptorBehavior : IEndpointBehavior, IClientMessageInspector, IDispatchMessageInspector, в котором есть методы AfterReceiveRequest, которые вызываются WCF как на стороне сервера, так и на стороне клиента.   -  person JustAMartin    schedule 05.12.2013
comment
Спасибо за обновление! Я могу забыть о сломанной фабрике. Между прочим, у меня были некоторые проблемы с повторным использованием клиентского канала: частые, но случайные исключения TCP 995, обнаруженные в трассировке; вот почему я спросил. В конце концов, повторное использование фабрики, но воссоздание клиентских каналов каждый раз решало эту проблему для меня. Поскольку нижележащие TCP-соединения были объединены в пул, кажется, что это не требует больших затрат, хотя я не измерял.   -  person henginy    schedule 05.12.2013


Ответы (1)


Думаю, лучше поздно, чем никогда ... и похоже, что у автора это работает, это может помочь будущим пользователям WCF.

1) ChannelFactory организует канал, который включает в себя все поведения для канала. Создание канала с помощью метода CreateChannel «активирует» канал. Фабрики каналов можно кэшировать.

2) Вы формируете фабрику каналов с помощью привязок и поведения. Эта форма доступна всем, кто создает этот канал. Как вы отметили в своем комментарии, вы можете прикреплять инспекторы сообщений, но более распространенным случаем является использование заголовка для отправки пользовательской информации о состоянии в службу. Вы можете прикреплять заголовки через OperationContext.Current

using (var op = new OperationContextScope((IContextChannel)proxy))
{
    var header = new MessageHeader<string>("Some State");
    var hout = header.GetUntypedHeader("message", "urn:someNamespace");
    OperationContext.Current.OutgoingMessageHeaders.Add(hout);
}

3) Это мой общий способ избавления от клиентского канала и фабрики (этот метод является частью моего класса ProxyBase)

public virtual void Dispose()
{
    CloseChannel();
    CloseFactory();
}

protected void CloseChannel()
{
    if (((IChannel)_client).State == CommunicationState.Opened)
    {
        try
        {
            ((IChannel)_client).Close();
        }
        catch (TimeoutException /* timeout */)
        {
            // Handle the timeout exception
            ((IChannel)innerChannel).Abort();
        }
        catch (CommunicationException /* communicationException */)
        {
            // Handle the communication exception
            ((IChannel)_client).Abort();
        }
    }
}

protected void CloseFactory()
{
    if (Factory.State == CommunicationState.Opened)
    {
        try
        {
            Factory.Close();
        }
        catch (TimeoutException /* timeout */)
        {
            // Handle the timeout exception
            Factory.Abort();
        }
        catch (CommunicationException /* communicationException */)
        {
            // Handle the communication exception
            Factory.Abort();
        }
    }
}

4) WCF откажет каналу, а не фабрике. Вы можете реализовать логику повторного подключения, но для этого потребуется создавать и извлекать клиентов из некоторой настраиваемой ProxyBase, например.

protected I Channel
{
    get
    {
        lock (_channelLock)
        {
            if (! object.Equals(innerChannel, default(I)))
            {
                ICommunicationObject channelObject = innerChannel as ICommunicationObject;
                if ((channelObject.State == CommunicationState.Faulted) || (channelObject.State == CommunicationState.Closed))
                {
                    // Channel is faulted or closing for some reason, attempt to recreate channel
                    innerChannel = default(I);
                }
            }

            if (object.Equals(innerChannel, default(I)))
            {
                Debug.Assert(Factory != null);
                innerChannel = Factory.CreateChannel();
                ((ICommunicationObject)innerChannel).Faulted += new EventHandler(Channel_Faulted);
            }
        }

        return innerChannel;
    }
}

5) Не используйте повторно каналы. Открыть, сделать что-нибудь, закрыть - это нормальная модель использования.

6) Создайте общий базовый класс прокси и извлеките из него всех своих клиентов. Это может быть полезно, например, повторное подключение, использование логики предварительного / последующего вызова, получение событий с фабрики (например, сбой, открытие)

7) Создайте свой собственный CustomChannelFactory, это даст вам дополнительный контроль над поведением фабрики, например. Установите таймауты по умолчанию, установите различные параметры привязки (MaxMessageSizes) и т. Д.

public static void SetTimeouts(Binding binding, TimeSpan? timeout = null, TimeSpan? debugTimeout = null)
        {
            if (timeout == null)
            {
                timeout = new TimeSpan(0, 0, 1, 0);
            }
            if (debugTimeout == null)
            {
                debugTimeout = new TimeSpan(0, 0, 10, 0);
            }
            if (Debugger.IsAttached)
            {
                binding.ReceiveTimeout = debugTimeout.Value;
                binding.SendTimeout = debugTimeout.Value;
            }
            else
            {
                binding.ReceiveTimeout = timeout.Value;
                binding.SendTimeout = timeout.Value;
            }
        }
person Petar Vučetin    schedule 02.03.2014
comment
@TCC вы пробовали другие альтернативы? вы использовали хорошие шаблоны и методы? - person Kiquenet; 06.07.2016
comment
применяется к ServiceClient? как MyUserServiceClient : System.ServiceModel.ClientBase<Portal.Admin.MobileServicesUsuario.IUsuarioService>, Portal.Admin.MobileServicesUsuario.IUsuarioService - person Kiquenet; 06.07.2016
comment
Есть ли образцы кода с использованием хороших шаблонов и практик для CustomChannelFactory и common proxy base class, re-connect logic и ProxyBase class? - person Kiquenet; 06.07.2016
comment
Отличный ответ! Я знаю, что это старый пост, но, возможно, вы все еще можете ответить на этот вопрос: следует ли избавляться от ChannelFactory одновременно с Channel? Вы упомянули кеширование Factory (для повторного использования между прокси-серверами), поэтому не лучше ли иметь некоторую логику, позволяющую избавляться от фабрики только тогда, когда больше нет активных каналов? - person progLearner; 26.01.2021