Служба маршрутизации WCF - динамическая обработка ошибок

Я узнаю о том, что можно сделать с помощью службы маршрутизации WCF. Все еще в фазе "крутись с ним, чтобы посмотреть, что он может сделать".

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

Я хотел бы получить доступ к этому событию, чтобы я мог:

  1. Зарегистрировать ошибку
  2. Отправьте уведомление по электронной почте, что конечная точка не работает
  3. При желании удалите конечную точку из списка резервных копий, чтобы она не замедляла прохождение будущих сообщений через систему.

Возникли проблемы с поиском того, как расширить структуру WCF, чтобы попасть на это конкретное мероприятие.

Может ли это сделать служба маршрутизации WCF? Любой толчок в правильном направлении будет принята с благодарностью.


На данный момент у меня есть 30-ти динамически генерируемые сервисы маршрутизации, размещенные под IIS (или, точнее, ASP.NET Development Server для Visual Studio 2010). Я настраиваю маршруты к сервисам в Global.asax, как показано ниже.

    protected void Application_Start(object sender, EventArgs e)
    {
        List<Type> serviceTypes = ServiceUtility.GetServiceTypes();
        foreach (Type st in serviceTypes)
        {
            string route = String.Format("Services/{0}.svc", ServiceUtility.GetServiceName(st));
            RouteTable.Routes.Add(new ServiceRoute(route, new RoutingServiceHostFactory(st), typeof(System.ServiceModel.Routing.RoutingService)));
        }
    }

ServiceUtility и Routing ServiceHostFactory являются пользовательскими классами. Обратите внимание, что IPolicyService - это интерфейс контракта службы WCF в интересующей сборке.

public static class ServiceUtility
{
    public static List<Type> GetServiceTypes()
    {
        Type policyInterfaceType = typeof(IPolicyService);
        Assembly serviceContractsAssembly = Assembly.GetAssembly(policyInterfaceType);
        Type[] serviceContractsAssemblyTypes = serviceContractsAssembly.GetTypes();

        List<Type> serviceTypes = new List<Type>();
        foreach (Type t in serviceContractsAssemblyTypes)
        {
            if (!t.IsInterface)
                continue;

            object[] attrib = t.GetCustomAttributes(typeof(ServiceContractAttribute), false);
            if (attrib == null || attrib.Length <= 0)
                continue;

            serviceTypes.Add(t);
        }

        return serviceTypes;
    }

    // Other stuff
}

Я создаю свои ServiceHosts следующим образом. Для краткости я опустил некоторые из моих вспомогательных методов.

public class RoutingServiceHostFactory : ServiceHostFactory
{
    private Type BackendServiceType { get; set; }
    private Binding BackendServiceBinding { get; set; }

    public RoutingServiceHostFactory(Type backendServiceType)
    {
        this.BackendServiceType = backendServiceType;
        this.BackendServiceBinding = ServiceUtility.GetBinding(this.BackendServiceType);
    }

    private const string DOMAIN_LIVE = "http://localhost:2521/";
    private const string DOMAIN_DEAD_1 = "http://localhost:2522/";
    private const string DOMAIN_DEAD_2 = "http://localhost:2524/";
    private const string DOMAIN_DEAD_3 = "http://localhost:2525/";
    private const string DOMAIN_DEAD_4 = "http://localhost:2526/";
    private const string DOMAIN_DEAD_5 = "http://localhost:2527/";

    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);

        this.BindEndpoints(host, baseAddresses);
        this.ConfigureRoutingBehavior(host);
        this.ConfigureServiceMetadataBehavior(host);
        this.ConfigureDebugBehavior(host);

        host.Description.Behaviors.Add(new RoutingServiceErrorHandlerInjector());

        return host;
    }

    // Other Stuff

    private void ConfigureRoutingBehavior(ServiceHost host)
    {
        string deadAddress1 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_1, this.BackendServiceType);
        string deadAddress2 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_2, this.BackendServiceType);
        string deadAddress3 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_3, this.BackendServiceType);
        string deadAddress4 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_4, this.BackendServiceType);
        string deadAddress5 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_5, this.BackendServiceType);
        string realAddress = ServiceUtility.GetServiceUrl(DOMAIN_LIVE, this.BackendServiceType);

        RoutingConfiguration rc = new RoutingConfiguration();

        ContractDescription contract = new ContractDescription("IRequestReplyRouter");
        ServiceEndpoint deadDestination1 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress1));
        ServiceEndpoint deadDestination2 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress2));
        ServiceEndpoint deadDestination3 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress3));
        ServiceEndpoint deadDestination4 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress4));
        ServiceEndpoint deadDestination5 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress5));
        ServiceEndpoint realDestination = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(realAddress));

        List<ServiceEndpoint> backupList = new List<ServiceEndpoint>();
        backupList.Add(deadDestination1);
        backupList.Add(deadDestination2);
        backupList.Add(deadDestination3);
        backupList.Add(deadDestination4);
        backupList.Add(deadDestination5);
        backupList.Add(realDestination);

        rc.FilterTable.Add(new MatchAllMessageFilter(), backupList);

        RoutingBehavior rb = new RoutingBehavior(rc);

        host.Description.Behaviors.Add(rb);             
    }

    // Other Stuff
}

Порт 2521 имеет действующий веб-сайт на другом конце, на котором размещены некоторые службы WCF. Другие порты, упомянутые выше, ничего не слушают.

Для контекста, вот мой Web.config для сайта маршрутизации. Заметьте, что тайм-ауты и тому подобное - просто результат моих ошибок, не принимайте их слишком серьезно.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />    
    <bindings>
      <wsHttpBinding>
        <binding
          name="TestBinding"
          allowCookies="True"
          closeTimeout="00:04:00"
          openTimeout="00:00:10"
          receiveTimeout="00:05:00"
          sendTimeout="00:05:00"
          maxReceivedMessageSize="15728640">
          <security>
            <message establishSecurityContext="true" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>  
</configuration>

РЕДАКТИРОВАТЬ

В ответ на ответ TheDoctor ниже я подумала, что мне следует подробнее рассказать о том, что я делаю с этим пробным решением с тех пор, как я первоначально разместила сообщение. Я попытался реализовать интерфейс IErrorHandler. Однако мне не очень повезло с этим.

Обратите внимание, что в приведенном выше примере мой Routing ServiceHostFactory немного изменился. Теперь я добавляю поведение Routing ServiceErrorHandlerInjector к описанию сервиса. Обратите внимание, что я также добавил дополнительные тупиковые точки в свой резервный список для иллюстрации.

public class RoutingServiceErrorHandlerInjector : IServiceBehavior
{
    #region IServiceBehavior Members

    public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {

    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher chanDisp in serviceHostBase.ChannelDispatchers)
        {
            chanDisp.ErrorHandlers.Add(new RoutingServiceErrorHandler());
        }
    }

    public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
    {

    }

    #endregion
}

public class RoutingServiceErrorHandler : IErrorHandler
{
    #region IErrorHandler Members

    public bool HandleError(Exception error)
    {
        throw new NotImplementedException(error.Message, error);

    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        throw new NotImplementedException(error.Message, error);
    }

    #endregion
}

Я ожидал, что я должен вызвать событие ProvideFault или HandleError для deadDestination1 через deadDestination5. У меня есть точки останова на NotImplementedExceptions выше в моем отладчике. Но этот код никогда не активируется. В конце концов, вызовы доходят до реального адреса в конце списка резервного копирования, и клиент-серверное приложение, которое я использую для тестирования этой службы Routing Service, работает нормально. Связь медленнее, но все еще хорошо в пределах тайм-аута.

Однако, если я закомментирую строку backupList.Add(realDestination); из метода ConfigureRoutingBehavior, описанного выше, метод Routing ServiceErrorHandler.ProvideFault приведен ниже... Но он содержит только информацию, относящуюся к deadDestination5. Любые исключения или ошибки, которые могли быть сгенерированы для deadDestination1 через deadDestination4, просто исчезают у меня.

Кроме того, я попробовал отладчик RedGate, следуя отраженному коду для Routing Service. Это было сложно для меня, так как я не привык к отладке оптимизированного кода, так что мне едва ли были доступны какие-либо переменные для чтения. Но от глазного шага до логики ниже:

// This has been taken from System.ServiceModel.Routing.RoutingService
// via the RedGate decompiler - unsure about it's ultimate accuracy.
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed), ServiceBehavior(AddressFilterMode=AddressFilterMode.Any, InstanceContextMode=InstanceContextMode.PerSession, UseSynchronizationContext=false, ValidateMustUnderstand=false)]
public sealed class RoutingService : ISimplexDatagramRouter, ISimplexSessionRouter, IRequestReplyRouter, IDuplexSessionRouter, IDisposable
{   
    [OperationBehavior(Impersonation=ImpersonationOption.Allowed), TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    IAsyncResult IRequestReplyRouter.BeginProcessRequest(Message message, AsyncCallback callback, object state)
    {
        return this.BeginProcessRequest<IRequestReplyRouter>(message, callback, state);
    }

    private IAsyncResult BeginProcessRequest<TContract>(Message message, AsyncCallback callback, object state)
    {
        IAsyncResult result;
        try
        {
            System.ServiceModel.Routing.FxTrace.Trace.SetAndTraceTransfer(this.ChannelExtension.ActivityID, true);
            result = new ProcessRequestAsyncResult<TContract>(this, message, callback, state);
        }
        catch (Exception exception)
        {
            if (TD.RoutingServiceProcessingFailureIsEnabled())
            {
                TD.RoutingServiceProcessingFailure(this.eventTraceActivity, OperationContext.Current.Channel.LocalAddress.ToString(), exception);
            }
            throw;
        }
        return result;
    }
}

Соответствующие разделы из System.ServiceModel.Routing.ProcessRequestAsyncResult показаны ниже. Они также отладки через RedGate, поэтому не могут быть изменены. Я верю, что RedGate и выпущенный источник от Microsoft точны. #hesaiddubiously

internal class ProcessRequestAsyncResult<TContract> : TransactedAsyncResult
{        
    public ProcessRequestAsyncResult(RoutingService service, Message message, AsyncCallback callback, object state) : base(callback, state)
    {
        this.allCompletedSync = true;
        this.service = service;
        this.messageRpc = new System.ServiceModel.Routing.MessageRpc(message, OperationContext.Current, service.ChannelExtension.ImpersonationRequired);
        if (TD.RoutingServiceProcessingMessageIsEnabled())
        {
            TD.RoutingServiceProcessingMessage(this.messageRpc.EventTraceActivity, this.messageRpc.UniqueID, message.Headers.Action, this.messageRpc.OperationContext.EndpointDispatcher.EndpointAddress.Uri.ToString(), (this.messageRpc.Transaction != null) ? "True" : "False");
        }
        try
        {
            EndpointNameMessageFilter.Set(this.messageRpc.Message.Properties, service.ChannelExtension.EndpointName);
            this.messageRpc.RouteToSingleEndpoint<TContract>(this.service.RoutingConfig);
        }
        catch (MultipleFilterMatchesException exception)
        {
            throw System.ServiceModel.Routing.FxTrace.Exception.AsError(new ConfigurationErrorsException(System.ServiceModel.Routing.SR.ReqReplyMulticastNotSupported(this.messageRpc.OperationContext.Channel.LocalAddress), exception));
        }
        while (this.StartProcessing())
        {
        }
    }

    private bool StartProcessing()
    {
        bool flag = false;
        SendOperation operation = this.messageRpc.Operations[0];
        this.currentClient = this.service.GetOrCreateClient<TContract>(operation.CurrentEndpoint, this.messageRpc.Impersonating);
        if (TD.RoutingServiceTransmittingMessageIsEnabled())
        {
            TD.RoutingServiceTransmittingMessage(this.messageRpc.EventTraceActivity, this.messageRpc.UniqueID, "0", this.currentClient.Key.ToString());
        }
        try
        {
            Message message;
            if ((this.messageRpc.Transaction != null) && operation.HasAlternate)
            {
                throw System.ServiceModel.Routing.FxTrace.Exception.AsError(new ConfigurationErrorsException(System.ServiceModel.Routing.SR.ErrorHandlingNotSupportedReqReplyTxn(this.messageRpc.OperationContext.Channel.LocalAddress)));
            }
            if (operation.AlternateEndpointCount > 0)
            {
                message = this.messageRpc.CreateBuffer().CreateMessage();
            }
            else
            {
                message = this.messageRpc.Message;
            }
            operation.PrepareMessage(message);
            IAsyncResult result = null;
            using (base.PrepareTransactionalCall(this.messageRpc.Transaction))
            {
                using (IDisposable disposable = null)
                {
                    try
                    {
                    }
                    finally
                    {
                        disposable = this.messageRpc.PrepareCall();
                    }
                    result = this.currentClient.BeginOperation(message, this.messageRpc.Transaction, base.PrepareAsyncCompletion(ProcessRequestAsyncResult<TContract>.operationCallback), this);
                }
            }
            if (!base.CheckSyncContinue(result))
            {
                return flag;
            }
            if (this.OperationComplete(result))
            {
                base.Complete(this.allCompletedSync);
                return flag;
            }
            return true;
        }
        catch (Exception exception)
        {
            if (Fx.IsFatal(exception))
            {
                throw;
            }
            if (!this.HandleClientOperationFailure(exception))
            {
                throw;
            }
            return true;
        }
    }
}

На мой взгляд, мне кажется, что ProcessRequestAsyncResult выполняет пошаговое выполнение списка резервных копий с помощью метода ProcessRequestAsyncResult.StartProcessing. Тем не менее, StartProcess(), по-видимому, не генерирует каждое исключение, а скорее выборочно выбирает, генерировать исключения или нет.

Кажется, что только StartProcess () на самом деле генерирует только исключение для окончательного мертвого адреса, а затем передается предложением Routing Service.BeginProcessRequest, чтобы затем, наконец, полностью перейти к активации в моей реализации IErrorHandler.

Это настоятельно подсказывает мне, что то, что я пытаюсь сделать здесь, не может быть сделано с текущей реализацией пространства имен System.ServiceModel.Routing. Обратите внимание, что Routing Service является запечатанным классом, поэтому я не могу расширить его собственным базовым классом, чтобы изменить это поведение, даже если я думал, что это хорошая идея (а я нет).

Но опять же, обратите внимание, что это поверхностное чтение. Я легко могу ошибаться. На самом деле, я бы очень хотел оказаться неправым. Я бы предпочел найти способ заставить Routing Service делать то, что я от него хочу, вместо того, чтобы кататься самостоятельно.

1 ответ

WCF обеспечивает обработку ошибок ( http://msdn.microsoft.com/en-us/library/ee517422.aspx), поэтому вы можете создать функцию, которая активируется в CommunicationException ( http://msdn.microsoft.com/en-us/library/system.servicemodel.communicationexception.aspx) и записывает коды ошибок из данных, переданных в функцию. Вы можете перейти оттуда к службе рутирования почты и тому, что вам еще нужно.

Другие вопросы по тегам