WCF 路由服务 - 动态错误处理

Posted

技术标签:

【中文标题】WCF 路由服务 - 动态错误处理【英文标题】:WCF Routing Service - Dynamic Error Handling 【发布时间】:2013-07-18 06:42:43 【问题描述】:

我正在了解 WCF 路由服务可以做什么。仍处于“搞砸它,看看它能做什么”阶段。

我对路由服务的理解是,当消息通过时,服务会尝试将其传递给备份列表中最先出现的端点。如果失败,它会继续尝试下一个,然后是下一个,直到有什么可行或没有什么可尝试的。

我想做的是访问该失败事件,以便我可以:

    记录失败 通过电子邮件发送端点出现故障的通知 可选择从备份列表中删除端点,这样它就不会减慢未来消息流经系统的速度

无法找到如何扩展 WCF 框架来处理此特定事件。

这是 WCF 路由服务可以做的事情吗?任何朝着正确方向轻推将不胜感激。


目前,我在 IIS 下托管了 30 个动态生成的路由服务(或者更准确地说,是 Visual Studio 2010 的 ASP.NET 开发服务器)。我正在 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 和 RoutingServiceHostFactory 是自定义类。请注意,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 接口。但是,我的运气并不好。

请注意,在上面的示例中,我的 RoutingServiceHostFactory 略有变化。我现在将 RoutingServiceErrorHandlerInjector 行为添加到服务描述中。请注意,为了便于说明,我还在备份列表中添加了额外的死端点。

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

我的期望是我应该触发从 deadDestination1 到 deadDestination5 的 ProvideFault 或 HandleError 事件。我在调试器中的 NotImplementedExceptions 上设置了断点。 但该代码永远不会被激活。调用最终到达备份列表末尾的真实地址,我用来测试这个 RoutingService 的客户端/服务器应用程序运行良好。通信速度较慢,但​​仍在超时限制内。

但是,如果我从上面的 ConfigureRoutingBehavior 方法中注释掉 backupList.Add(realDestination); 行,则执行 RoutingServiceErrorHandler.ProvideFault 方法......但它只包含与 deadDestination5 相关的信息。任何可能为 deadDestination1 到 deadDestination4 生成的异常或错误都会消失在我身上。

此外,我已经使用 RedGate 调试器单步执行 RoutingService 的反射代码。这对我来说很棘手,因为我不习惯调试优化的代码,所以几乎没有任何变量可供我实际阅读。但是从目测下面的逻辑步骤来看:

// 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() 抛出的,然后由 RoutingService.BeginProcessRequest catch 子句传递,然后才最终在我的 IErrorHandler 实现中一直激活.

这强烈向我表明,我在这里尝试做的事情不能用 System.ServiceModel.Routing 命名空间的当前实现来完成。请注意,RoutingService 是一个密封类,因此我无法使用自己的基类对其进行扩展以更改此行为,即使我认为这是个好主意(我不这样做)。

但话又说回来,请注意这是一个肤浅的阅读。我很容易出错。事实上,我非常希望被证明是错误的。我更愿意找到一种方法让 RoutingService 做我想做的事情,而不是自己动手。

【问题讨论】:

【参考方案1】:

WCF 提供错误处理 (http://msdn.microsoft.com/en-us/library/ee517422.aspx),因此您可以创建一个在 CommunicationException (http://msdn.microsoft.com/en-us/library/system.servicemodel.communicationexception.aspx) 上激活的函数,并记录传递给函数的数据中的错误代码。您可以从那里转到邮件 root 服务以及您需要的任何其他服务。

【讨论】:

如何访问备份列表中每个端点抛出的每个异常? 在问题中添加了一些额外信息,以显示我迄今为止在捕获这些异常方面的尝试。 @Daniel,看起来最后一段代码只处理可能致命或未处理的异常。

以上是关于WCF 路由服务 - 动态错误处理的主要内容,如果未能解决你的问题,请参考以下文章

如何使用故障契约和 jquery ajax 处理 wcf 错误

将 json 参数传递给 WCF 函数给出错误“服务器在处理请求时遇到错误。有关更多详细信息,请参阅服务器日志”

WCF自定义错误处理(IErrorHandler接口的用法)

在WCF中,自定义Authentication,Validate方法,抛出错误异常时如何在不停止服务的情况下处理异常?

WCF 错误 - 安全处理器无法在消息中找到安全标头

配置 WCF 客户端会出现错误“无法处理消息。这很可能是因为操作