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 ======================================="); }
下一节会详细介绍下四种日志消息:启动日志、调用日志、收发消息日志和异常日志。
欢迎大家阅读我的极客时间专栏《Java业务开发常见错误100例》【全面避坑+最佳实践=健壮代码】