首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WCF 服務調用程式設計技巧:以委派封裝通道生命週期管理

     編輯:關於.NET

在進行基於會話通道的 WCF 服務調用時,受限於並發通道數量,必須及時關閉通道;遇到特定異常則需強制中止。正如我們不讓開發人員手動管理資料庫連接,也不應讓他們手動創建、開啟、中止和關閉通道——這些基礎設施工作應由框架自動完成。本文介紹如何透過程式設計技巧,讓開發者忽略「通道」的存在,像呼叫普通物件一樣進行服務調用。

一、傳統服務調用方式的痛點

若透過 ChannelFactory<TChannel> 建立代理,典型的調用模式如下:將服務調用包裹在 using 區塊中,並以 try/catch 處理異常。捕獲 TimeoutException 或 CommunicationException 時呼叫 Abort() 中止通道;using 結束時 Dispose() 會進一步呼叫 Close() 關閉通道。

class Program
{
    static void Main(string[] args)
    {
        using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
        {
            ICalculator calculator = channelFactory.CreateChannel();
            using (calculator as IDisposable)
            {
                try
                {
                    Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
                }
                catch (TimeoutException)
                {
                    (calculator as ICommunicationObject).Abort();
                    throw;
                }
                catch (CommunicationException)
                {
                    (calculator as ICommunicationObject).Abort();
                    throw;
                }
            }
        }
        Console.Read();
    }
}

這種寫法雖正確,但若在每處調用都重複,會導致程式碼臃腫、大量重複,且頻繁建立 ChannelFactory<TChannel> 影響效能。

二、利用委派封裝異常處理與通道關閉

我們可建立公共方法統一處理重複邏輯:ChannelFactory 的建立、快取、服務代理的創建/中止/關閉、異常處理。以下 ServiceInvoker 類別提供兩個靜態 Invoke 多載,分別對應無回傳值與有回傳值的服務操作。

using System;
using System.Collections.Generic;
using System.ServiceModel;
namespace Artech.Lib
{
    public class ServiceInvoker
    {
        private static Dictionary<string, ChannelFactory> channelFactories = new Dictionary<string, ChannelFactory>();
        private static object syncHelper = new object();

        private static ChannelFactory<TChannel> GetChannelFactory<TChannel>(string endpointConfigurationName)
        {
            ChannelFactory<TChannel> channelFactory = null;
            if (channelFactories.ContainsKey(endpointConfigurationName))
            {
                channelFactory = channelFactories[endpointConfigurationName] as ChannelFactory<TChannel>;
            }

            if (null == channelFactory)
            {
                channelFactory = new ChannelFactory<TChannel>(endpointConfigurationName);
                lock (syncHelper)
                {
                    channelFactories[endpointConfigurationName] = channelFactory;
                }
            }
            return channelFactory;
        }

        public static void Invoke<TChannel>(Action<TChannel> action, TChannel proxy)
        {
            ICommunicationObject channel = proxy as ICommunicationObject;
            if (null == channel)
            {
                throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy");
            }
            try
            {
                action(proxy);
            }
            catch (TimeoutException)
            {
                channel.Abort();
                throw;
            }
            catch (CommunicationException)
            {
                channel.Abort();
                throw;
            }
            finally
            {
                channel.Close();
            }
        }

        public static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, TChannel proxy)
        {
            ICommunicationObject channel = proxy as ICommunicationObject;
            if (null == channel)
            {
                throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy");
            }
            try
            {
                return function(proxy);
            }
            catch (TimeoutException)
            {
                channel.Abort();
                throw;
            }
            catch (CommunicationException)
            {
                channel.Abort();
                throw;
            }
            finally
            {
                channel.Close();
            }
        }

        public static void Invoke<TChannel>(Action<TChannel> action, string endpointConfigurationName)
        {
            Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
            Invoke<TChannel>(action, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());
        }

        public static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, string endpointConfigurationName)
        {
            Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
            return Invoke<TChannel, TResult>(function, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());
        }
    }
}

為避免頻繁建立 ChannelFactory,透過字典快取已建立的實例。服務調用以委派(Action<TChannel>、Func<TChannel, TResult>)表示,呼叫端只需傳入端點組態名稱,調用碼瞬間簡潔:

using System;
using Artech.Lib;
using Artech.WcfServices.Contracts;
namespace Artech.WcfServices.Clients
{
    class Program
    {
        static void Main(string[] args)
        {
            int result = ServiceInvoker.Invoke<ICalculator, int>(calculator => calculator.Add(1, 2), "calculatorservice");
            Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result);
            Console.Read();
        }
    }
}

三、改進 ServiceInvoker 以符合分層架構

為進一步封裝調用細節,通常會建立「服務代理層」。將 ServiceInvoker 改為泛型類別 ServiceInvoker<TChannel>,繼承原靜態類別,並提供實例方法。再定義基類 ServiceProxyBase<TChannel>,持有 ServiceInvoker<TChannel> 實例,具體代理類別繼承此基類並實作服務契約介面,內部委派給 Invoker 執行。

using System;
namespace Artech.Lib
{
    public class ServiceInvoker<TChannel> : ServiceInvoker
    {
        public string EndpointConfigurationName { get; private set; }

        public ServiceInvoker(string endpointConfigurationName)
        {
            Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
            this.EndpointConfigurationName = endpointConfigurationName;
        }

        public void Invoke(Action<TChannel> action)
        {
            Invoke<TChannel>(action, this.EndpointConfigurationName);
        }

        public TResult Invoke<TResult>(Func<TChannel, TResult> function)
        {
            return Invoke<TChannel, TResult>(function, this.EndpointConfigurationName);
        }
    }
}

namespace Artech.Lib
{
    public class ServiceProxyBase<TChannel>
    {
        public virtual ServiceInvoker<TChannel> Invoker { get; private set; }

        public ServiceProxyBase(string endpointConfigurationName)
        {
            Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
            this.Invoker = new ServiceInvoker<TChannel>(endpointConfigurationName);
        }
    }
}

具體代理範例:

using Artech.Lib;
using Artech.WcfServices.Contracts;
namespace Artech.WcfServices.Clients
{
    public class CalculatorProxy : ServiceProxyBase<ICalculator>, ICalculator
    {
        public CalculatorProxy() : base(Constants.EndpointConfigurationNames.CalculatorService) { }

        public int Add(int x, int y)
        {
            return this.Invoker.Invoke<int>(calculator => calculator.Add(x, y));
        }
    }

    public class Constants
    {
        public class EndpointConfigurationNames
        {
            public const string CalculatorService = "calculatorservice";
        }
    }
}

消費端(如 Presenter 層)即可直接實例化代理並呼叫方法,所有通道管理細節均被封裝:

using System;
using Artech.Lib;
using Artech.WcfServices.Contracts;
namespace Artech.WcfServices.Clients
{
    class Program
    {
        static void Main(string[] args)
        {
            CalculatorProxy calculatorProxy = new CalculatorProxy();
            int result = calculatorProxy.Add(1, 2);
            Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result);
            Console.Read();
        }
    }
}

四、方案限制

此方案一大限制:服務操作不可包含 ref 或 out 參數,因為這兩類參數無法作為匿名方法的參數。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved