WCF路由服务 – 动态error handling

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

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

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

  1. 记录失败
  2. 通过电子邮件发送端点失败的通知
  3. (可选)从备份列表中删除端点,以便它不会减慢将来的消息流过系统的速度

无法找到如何扩展WCF框架以获取此特定事件。

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


目前,我在IIS下托管了30-beh动态生成的路由服务(或者更确切地说,是Visual Studio 2010的ASP.NET开发服务器)。 我正在设置Global.asax中服务的路由,如下所示。

protected void Application_Start(object sender, EventArgs e) { List 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 GetServiceTypes() { Type policyInterfaceType = typeof(IPolicyService); Assembly serviceContractsAssembly = Assembly.GetAssembly(policyInterfaceType); Type[] serviceContractsAssemblyTypes = serviceContractsAssembly.GetTypes(); List serviceTypes = new List(); 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 backupList = new List(); 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。 请注意,超时等等只是我拧紧的结果,不要太认真。

                   

编辑

为了回应TheDoctor在下面的回答,我想我应该扩展自我最初发布以来我一直在尝试使用这个尝试过的解决方案。 我试过实现IErrorHandler接口。 但是,我没有太多运气。

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

 public class RoutingServiceErrorHandlerInjector : IServiceBehavior { #region IServiceBehavior Members public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection 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的客户端/服务器应用程序运行正常。 通信速度较慢,但​​仍在超时限制范围内。

但是,如果我注释掉行backupList.Add(realDestination); 从上面的ConfigureRoutingBehavior方法开始,然后对RouteServiceErrorHandler.ProvideFault方法进行了validation……但它只包含与deadDestination5相关的信息。 可能已经为deadDestination1到deadDestination4生成的任何exception或错误都消失了。

此外 ,我已经开始使用RedGate调试器逐步完成RoutingService的reflection代码。 这对我来说很棘手,因为我不习惯调试优化代码,因此几乎没有任何变量可供我实际阅读。 但从以下逻辑的逐步观察:

 // 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(message, callback, state); } private IAsyncResult BeginProcessRequest(Message message, AsyncCallback callback, object state) { IAsyncResult result; try { System.ServiceModel.Routing.FxTrace.Trace.SetAndTraceTransfer(this.ChannelExtension.ActivityID, true); result = new ProcessRequestAsyncResult(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 : 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(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(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.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()似乎没有抛出每个exception,而是选择性地选择是否抛出exception。

似乎只有最终死地址的exception实际上是由StartProcess()引发的,然后由RoutingService.BeginProcessRequest catch子句传递,然后最终使它一直到我的IErrorHandler实现中的激活。

这强烈告诉我,我在这里尝试做的事情无法通过System.ServiceModel.Routing命名空间的当前实现来完成。 请注意,RoutingService是一个密封类,所以即使我认为这是一个好主意(我不这样做),我也无法使用我自己的基类扩展它来改变这种行为。

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

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