Обрабатывать постоянный клиент WCF, вводящий состояние с ошибкой

У нас есть служба WCF, которую мы потребляем из веб-приложения. Клиент, который мы используем, был создан с использованием опции "Добавить ссылку на службу" Visual Studio. Поскольку это веб-приложение, и поскольку характер приложения, скорее всего, приведет к относительно коротким сеансам, мы решили создать экземпляр клиента, когда пользователь входит в систему и сохраняет его в течение всего сеанса, а затем обрабатывать его, когда сеанс проходит.

Это подводит меня к моему вопросу - мы пытаемся решить лучший способ обработки клиентского канала, входящего в состояние Faulted. После поиска по некоторым причинам мы пришли к следующему:

if(client.State = CommuncationState.Faulted)
{
    client = new Client();
}

try
{
    client.SomeMethod();
}
catch //specific exceptions left out for brevity
{
    //logging or whatever we decide to do
    throw;
}

Это, однако, не работает из-за того, что, по крайней мере, в нашем случае, даже если служба недоступна, клиент покажет состояние Open, пока вы на самом деле не попытаетесь сделать вызов, используя это, при котором затем он переходит в состояние Faulted.

Итак, это оставляет нам сделать что-то еще. Другой вариант, с которым мы столкнулись, заключался в следующем:

try
{
    client.SomeMethod();
}
catch
{
    if(client.State == CommunicationState.Faulted)
    {
        //we know we're faulted, try it again
        client = new Client();
        try
        {
            client.SomeMethod();
        }
        catch
        {
            throw;
        }
    }
    //handle other exceptions
}

Но это пахнет. Очевидно, мы могли бы избежать этого, используя нового клиента и избавляясь от него для каждого вызова. Это кажется ненужным, но если это правильный путь, то я предполагаю, что мы выберем. Итак, каков наилучший способ изящно описать, находится ли клиент в неисправном состоянии, а затем что-то делать? Должны ли мы просто получать нового клиента для каждого звонка?

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

Ответы

Ответ 1

Чтобы ответить на ваш вопрос, вы можете обработать Faulted event свойства ChannelFactory следующим образом:

client.ChannelFactory.Faulted += new EventHandler(ChannelFactory_Faulted);

Это должно позволить вам выполнять любые протоколирования/очистки, которые вам нужно выполнить.

Как общая рекомендация, вы не должны оставлять канал открытым в течение всего сеанса, поэтому убедитесь, что вы закрываете канал правильно (прерывание после исключения) после того, как вы закончите с ним.

Также, если это возможно, рассмотрите НЕ использование Visual Studio Add Service Reference или, по крайней мере, очистку создаваемого кода/конфигурации. Я рекомендую, чтобы, если вы хотите использовать прокси-реализацию, создайте свой собственный, создав ClientBase или используйте ChannelFactory. Поскольку вы упоминаете класс-оболочку, я бы рекомендовал вам использовать ChannelFactory и обрабатывать Faulted event для ваших нужд очистки.

Ответ 2

Попробуйте обработать событие .Faulted на прокси-сервере клиента, например:

((ICommunicationObject)client).Faulted += new EventHandler(client_Faulted);

private void client_Faulted(object sender, EventArgs e)
{
    client = new Client();
    ((ICommunicationObject)client).Faulted += new EventHandler(client_Faulted);
}

Он должен инициировать мгновенные ошибки канала, что дает вам возможность повторно открыть его.

Вы также должны обернуть каждый вызов методу client в блоке try-catch и, возможно, даже обернуть его в цикле while(), который повторяет вызов n раз, затем регистрирует сбой. EG:

bool succeeded = false;
int triesLeft = 3;
while (!succeeded && triesLeft > 0)
{
    triesLeft--;
    try
    {
        client.SomeMethod();
        succeeded = true;
    }
    catch (exception ex)
    {
        logger.Warn("Client call failed, retries left: " + triesLeft;
    }
}
if (!succeeded)
    logger.Error("Could not call web service");

В моем коде я дошел до использования ManualResetEvent, чтобы заблокировать цикл while() до тех пор, пока обработчик события client_Faulted не сможет воссоздать прокси-сервер client.