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

2023-12-20

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

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

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

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

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

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


目前,我有 30 个动态生成的路由服务托管在 IIS(或更准确地说,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>

EDIT

为了回应下面 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 的客户端/服务器应用程序运行良好。通信速度较慢,但​​仍在超时限制之内。

但是,如果我注释掉该行backupList.Add(realDestination);从上面的ConfigureRoutingBehavior方法,然后执行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 发布的源代码是准确的。 #他说得很可疑

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 完成我想要它做的事情,而不是必须自己动手。


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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

WCF 路由服务 - 动态错误处理 的相关文章

随机推荐

  • SignalR 背板的可靠性如何?

    对于所有消息是否会到达所有订阅节点的问题 SignalR Backplane 的可靠性如何 底层是否使用可靠的协议 或者消息是否有可能丢失 显然 例如 由于某些网络问题 一个节点可能会关闭一段时间 当再次可达时 SignalR Backpl
  • 如何将 int 变量分配给 int 数组 int C++? [复制]

    这个问题在这里已经有答案了 我想实现数学运算并写入数组 我以 int 形式发送参数 但错误提示数组下标的类型 int int 无效 string clients 5 2 string products 7 string strPrices
  • 在 Web Api 2 中使用 Url.Link 和属性路由

    我想在使用 webapi 2 时向我的 http 响应添加 Location 标头 下面的方法展示了如何使用命名路由来执行此操作 有谁知道是否可以使用作为 webapi 2 的一部分发布的属性路由功能创建 Url Link string u
  • PHP 致命错误:允许的内存大小 1073741824 字节耗尽(尝试分配 16777216 字节)

    I ran 作曲家安装 on my 16 GBMacbook Pro 我得到了这个 distributor portal composer install Loading composer repositories with package
  • 循环跳过偶数

    I 代表全局变量 即名称 I 代表函数内部和外部相同的变量 当 I 1 时 事实首先被调用 这是第一个写入的值 这个值是 传递给函数的虚拟参数 N 同样的 I 现在被 Fact 内部的 DO 循环赋予初始值 2 但由于它大于 N 所以不执行
  • 增加504超时错误

    有什么方法可以使错误 504 网关超时更长 如果可以的话 如何以及更改它的文件位于何处 我在centos 6上使用nginx 根据您拥有的网关类型 您应该使用类似以下内容的内容 proxy read timeout 600s 检查文档 ht
  • 在 Yii2 中,如何从视图文件中的渲染中排除布局?

    我有一个管理员登录页面 我想在没有布局的情况下呈现它 如何在 Yii2 中渲染视图而不渲染主布局 这可以使用以下方法完成renderPartial method 您可以从官方文档中获取更多信息 这是a link http www yiifr
  • 如何使用 symfony2 包含 stripe 客户端 api。如何在 symfony2 中包含没有类的文件

    我试图将此文件包含在我的 symfony2 项目中 该项目是一个包含一堆 require 语句的文件 不幸的是 该文件不包含类 我不想为所有包含我需要的类的包含文件手动编写命名空间 所以我想知道如何以包含我需要的其他文件的方式包含该文件 我
  • CSS 关键帧仅适用于 Chrome

    我正在尝试使用关键帧制作一个简单的动画 但它仅适用于 Chrome 这是代码 为了更短的帖子 我只包含一次关键帧代码 keyframes logokf 0 background image url gfx logo1 png 20 back
  • 使用 mysql 别名从 2 个表中选择列

    我有 2 个表 table a 和 table b 两者都包含一个名为 open 的列 table a open 36 99 36 85 36 40 36 33 36 33 table b open 4 27 4 46 4 38 4 22
  • 无法使用 Flask 服务器在 IIS 上运行 dash 应用程序

    我的 IIS Windows Server 2016 上有两个网站 都使用 Dash 和 Flask 第一个是最小的working由 app py 和 web config 组成的示例 由于某种原因 我无法让第二个站点正常工作 下面附有两个
  • 如何处理 d3.layout.stack() 中缺少数据点的图层

    我正在使用 d3 stack 创建堆积面积图 但如果每层中的项目数量不相等 则会出现错误 我从这样的数据数组开始 key Group1 value date key Group1 value date key Group1 value da
  • 响应所有方法调用的 Python 类的实例

    有没有办法创建一个实例响应任意方法调用的类 我知道有一个特殊的方法 getattr self attr 当有人尝试访问实例的属性时会调用它 我正在寻找类似的东西 使我也能够拦截方法调用 期望的行为看起来像这样 class A object
  • 有没有一种算法可以检测两幅图像之间的差异?

    我正在寻找一种算法或库 可以发现两个图像之间的差异 例如在 查找错误 游戏中 并输出包含这些更改的边界框的坐标 我对 Python C 或几乎任何其他语言的算法持开放态度 如果您只是想显示差异 那么您可以使用下面的代码 FastBitmap
  • 如何从 R 中的 xlsx 文件中检测“删除线”样式

    我必须检查包含 的数据删除线 在 R 中导入 excel 文件时的格式 我们有什么方法可以检测到它们吗 欢迎使用 R 和 Python 方法 R 溶液 the tidyxl 包可以帮助你 例如 temp xlsx 其中数据位于第一张纸的 A
  • 如何在 Eclipse 中使用 Android 操作系统 VirtualBox 作为设备

    我在这里找到了有关如何运行 Android 操作系统的教程 http www javacodegeeks com 2010 06 install android os on pc with html http www javacodegee
  • jQuery 文档.ready

    我对 jQuery 中的 document ready 有点困惑 你什么时候在里面定义javascript函数 document ready 什么时候不呢 将所有 javascript 代码放入 document ready 中是否足够安全
  • gnuplot 文件有标准的文件扩展名吗?

    我见过 gnu plt and gplot作为 gnuplot 脚本的文件扩展名 我知道 Linux 不关心文件扩展名 但是什么扩展名最普遍地向人类声明 我是一个 gnuplot 脚本 正如罗曼 珀森博士和尼尔布都指出的那样这篇维基教科书文
  • 使用 XSD 架构进行 Xml 验证

    以下代码帮助我使用 XSD 架构验证 XML 文件 XmlReaderSettings settings new XmlReaderSettings settings Schemas Add null xsdFilePath setting
  • WCF 路由服务 - 动态错误处理

    我正在了解 WCF 路由服务可以做什么 仍处于 摆弄它看看它能做什么 阶段 我对路由服务的理解是 当消息通过时 该服务将尝试将其传递到备份列表中首先出现的端点 如果失败 它将继续尝试下一个 然后再尝试下一个 直到有东西起作用或者没有什么可以