簡體   English   中英

WCF路由服務 - 動態錯誤處理

[英]WCF Routing Service - Dynamic Error Handling

我正在學習使用WCF路由服務可以做些什么。 仍然在'擰緊它,看看它能做什么'階段。

我對路由服務的理解是,當消息通過時,服務將嘗試將其傳遞給備份列表中首先出現的任何端點。 如果失敗了,它將繼續嘗試下一個,然后是下一個,直到任何一個有效或者沒有什么可以嘗試。

我想做的是訪問該失敗事件,以便我可以:

  1. 記錄失敗
  2. 通過電子郵件發送端點失敗的通知
  3. (可選)從備份列表中刪除端點,以便它不會減慢將來的消息流過系統的速度

無法找到如何擴展WCF框架以獲取此特定事件。

這是WCF路由服務可以做的事情嗎? 任何朝着正確方向的推動都將非常感激。


目前,我在IIS下托管了30-ah動態生成的路由服務(或者更准確地說,是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的客戶端/服務器應用程序運行正常。 通信速度較慢,但​​仍在超時限制范圍內。

但是,如果我注釋掉行backupList.Add(realDestination); 從上面的ConfigureRoutingBehavior方法開始,然后對RouteServiceErrorHandler.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做我想做的事情,而不是自己動手。

WCF提供錯誤處理( http://msdn.microsoft.com/en-us/library/ee517422.aspx ),因此您可以創建一個在CommunicationException上激活的函數( http://msdn.microsoft.com/en- us / library / system.servicemodel.communicationexception.aspx )並記錄傳遞給函數的數據中的錯誤代碼。 您可以從那里到郵件生根服務以及您需要的任何其他內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM