天天看點

Wcf通訊基礎架構方案(三)——用戶端

假設定義了一個服務契約:

[ServiceContract(Namespace = "WcfExtension.Services.Interface")]
    public interface ITestService
    {
        [OperationContract]
        int Add(int x, int y);

        [OperationContract]
        [ServiceKnownType(typeof(TestContract))]
        ITestContract TestData(ITestContract tc);
        
        [OperationContract]
        List<string> AddList(List<string> args);
    }      

首先看一下,用戶端是怎麼使用的:

Console.WriteLine(WcfServiceLocator.Create<ITestService>().Add(1, 3));      

WcfServiceLocator.Create()出來的就是一個接口,可以保持這個引用以後調用,也可以每次直接這麼寫,那麼來看看WcfServiceLocator:

public static T Create<T>() where T : class
        {
            return (T)(new ServiceRealProxy<T>().GetTransparentProxy());
        }

        public static IWcfLogService GetLogService()
        {
            if (enableRemoteLogService)
                return (IWcfLogService)(new LogServiceRealProxy().GetTransparentProxy());
            else
                return new LocalLogService();
        }      

Create方法很簡單,隻是傳回一個ServiceRealProxy的透明代理。這個之後會詳細說,下面單獨有一個GetLogService專門用于擷取日志服務。這裡通過心跳來判斷遠端的日志服務是否可用,如果不可用直接傳回本地的日志服務。之是以日志服務的真實代理和其它業務服務的真實代理不同是因為,在業務服務中我們需要加入很多橫切日志,在日志服務中不需要:

public override IMessage Invoke(IMessage msg)
        {
            using (var client = WcfServiceClientFactory.CreateServiceClient<T>())
            {
                var channel = client.Channel;
                IMethodCallMessage methodCall = (IMethodCallMessage)msg;
                IMethodReturnMessage methodReturn = null;
                object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];
                methodCall.Args.CopyTo(copiedArgs, 0);

                bool isSuccessuful = false;
                var stopwatch = Stopwatch.StartNew();

                try
                {
#if DEBUG
                    Console.WriteLine("開始調用:" + methodCall.MethodName);
#endif
                    object returnValue = methodCall.MethodBase.Invoke(channel, copiedArgs);

#if DEBUG
                    if (ClientApplicationContext.Current != null)
                        Console.WriteLine("這個請求由" + ClientApplicationContext.Current.ServerMachineName + "處理完成,服務端版本号:" + ClientApplicationContext.Current.ServerVersion);
                    Console.WriteLine("結束調用:" + methodCall.MethodName);

#endif
                    methodReturn = new ReturnMessage(returnValue,
                                                    copiedArgs,
                                                    copiedArgs.Length,
                                                    methodCall.LogicalCallContext,
                                                    methodCall);
                    isSuccessuful = true;
                }
                catch (Exception ex)
                {
                    var excepionID = ClientApplicationContext.Current.ServerExceptionID ?? "";
                    if (ex.InnerException != null)
                    {
#if DEBUG
                        Console.WriteLine("調用服務端方法出現異常:" + ex.InnerException.Message);
#endif
                        var exception = ex.InnerException;
                        if (exception is FaultException)
                        {
                            exception = new Exception("Failed to call this Wcf service, remote exception message is:" + exception.Message);
                            exception.HelpLink = "Please check this exception via ID : " + excepionID;
                        }

                        if (WcfLogManager.Current().ExceptionInfo.Client.Enabled)
                        {

                            var log = WcfLogProvider.GetClientExceptionInfo(
                                typeof(T).FullName,
                                ClientApplicationContext.Current.RequestIdentity,
                                "ServiceRealProxy.Invoke",
                                exception, excepionID);
                            WcfServiceLocator.GetLogService().Log(log);

                        }
                        methodReturn = new ReturnMessage(exception, methodCall);
                    }
                    else
                    {
                        Console.WriteLine("調用方法出現異常:" + ex.Message);
                        if (WcfLogManager.Current().ExceptionInfo.Client.Enabled)
                        {
                            var log = WcfLogProvider.GetClientExceptionInfo(
                                typeof(T).FullName,
                                ClientApplicationContext.Current.RequestIdentity,
                                "ServiceRealProxy.Invoke",
                                ex, excepionID);
                            WcfServiceLocator.GetLogService().LogWithoutException(log);
                            methodReturn = new ReturnMessage(ex, methodCall);
                        }
                    }
                }
                finally
                {
                    if (WcfLogManager.Current<T>().InvokeInfo.Client.Enabled)
                    {
                        var log = WcfLogProvider.GetClientInvokeLog(
                           typeof(T).FullName,
                           "ServiceRealProxy.Invoke",
                           stopwatch.ElapsedMilliseconds,
                           isSuccessuful,
                           methodCall.MethodName,
                           ClientApplicationContext.Current);
                        WcfServiceLocator.GetLogService().LogWithoutException(log);
                    }
                }
                return methodReturn;
            }
        }      

這就是ServiceRealProxy的Invoke方法實作了,可以看到:

1) 在這裡我們using了WcfServiceClientFactory.CreateServiceClient傳回的包裝後的Channel,讓每一次調用都可以正常關閉信道。

2) 在這裡我們加入了調用方法成功後的日志,以及出錯時的異常日志。

再來看一下WcfChannelWrapper:

internal sealed class WcfChannelWrapper<T> : IDisposable where T : class
    {
        private readonly T channel;

        public WcfChannelWrapper(T channel)
        {
            this.channel = channel;
        }

        public T Channel { get { return channel; } }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private bool disposed;

        private void Dispose(bool disposing)
        {
            if (disposed) return;
            if (disposing)
            {
                var commObj = channel as ICommunicationObject;
                if (commObj != null)
                {
                    try
                    {
                        commObj.Close();
                    }
                    catch (CommunicationException)
                    {
                        commObj.Abort();
                    }
                    catch (TimeoutException)
                    {
                        commObj.Abort();
                    }
                    catch (Exception)
                    {
                        commObj.Abort();
                        throw;
                    }
                }

            }

            disposed = true;
        }

        ~WcfChannelWrapper()
        {
            Dispose(false);
        }

        #endregion

    }      

使用最佳實踐推薦的方式來關閉信道。ServiceRealProxy的另一個好處是,不再需要這麼調用服務:

using (var scf = WcfServiceClientFactory.CreateServiceClient<IWcfConfigService>())
                {
                    return scf.Channel.GetWcfClientEndpoint(serviceContractType.FullName, versionstring, WcfLogProvider.MachineName);
                }      

使用的直接是契約接口,不需要scf.Channel.xx(),不需要using。最後,來看看LogService的真實代理,幹淨多了:

public override IMessage Invoke(IMessage msg)
        {
            using (var client = WcfServiceClientFactory.CreateServiceClient<IWcfLogService>())
            {
                var channel = client.Channel;
                IMethodCallMessage methodCall = (IMethodCallMessage)msg;
                IMethodReturnMessage methodReturn = null;
                object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];
                methodCall.Args.CopyTo(copiedArgs, 0);
                try
                {
                    object returnValue = methodCall.MethodBase.Invoke(channel, copiedArgs);
                    methodReturn = new ReturnMessage(returnValue,
                                                    copiedArgs,
                                                    copiedArgs.Length,
                                                    methodCall.LogicalCallContext,
                                                    methodCall);
                }
                catch (Exception ex)
                {
                    if (ex.InnerException != null)
                    {
                        LocalLogService.Log(ex.InnerException.ToString());
                        methodReturn = new ReturnMessage(ex.InnerException, methodCall);
                    }
                    else
                    {
                        LocalLogService.Log(ex.ToString());
                        methodReturn = new ReturnMessage(ex, methodCall);
                    }
                }
                return methodReturn;
            }
        }      

即使出錯也不會再調用日志服務,而是記錄本地日志,實作了一個比較簡單的本地日志服務:

static LocalLogService()
        {
            var logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log");
            if (!Directory.Exists(logPath))
                Directory.CreateDirectory(logPath);
            Debug.Listeners.Add(new TextWriterTraceListener(logPath + "/log" + DateTime.Now.ToString("yyyyMMdd") + ".txt"));
            Debug.AutoFlush = true;
        }

        public static void Log(string text)
        {
            Debug.WriteLine("================================== START ======================================");
            Debug.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            Debug.WriteLine(text);
            Debug.WriteLine("=================================== END =======================================");
        }      

下一節會詳細介紹下四種日志消息:啟動日志、調用日志、收發消息日志和異常日志。

作者:

lovecindywang

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。