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

时间:2013-07-19 04:51:28

标签: c# wcf wcf-routing custom-error-handling

我正在学习使用WCF路由服务可以做些什么。仍然在与它拧紧,看它能做什么'相。

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

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

  1. 记录失败
  2. 通过电子邮件发送端点失败的通知
  3. (可选)从备份列表中删除端点,以便它不会减慢来自系统的未来消息的速度
  4. 无法找到如何扩展WCF框架以获取此特定事件。

    这是WCF路由服务可以做的事情吗?任何朝着正确方向的推动都会非常感激。


    目前,我在IIS下托管了30-beh动态生成的路由服务(或更确切地说,是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和微软发布的消息来源是准确的。 #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 个答案:

答案 0 :(得分:0)

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