假设定义了一个服务契约:
[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 =======================================");
}
下一节会详细介绍下四种日志消息:启动日志、调用日志、收发消息日志和异常日志。