添加wcf服务引用时,vs.net本来就会帮我们在app.config/web.config里生成各种配置,这没啥好研究的,但本文谈到的配置并不是这个。先看下面的图:
通常,如果采用.NET的WCF技术来架构SOA风格的应用,我们会把项目做一些基本的分层,如上图:
01. contract层:通常定义服务的接口(即服务契约ServiceContract,指明该服务提供了哪些方法可供外部调用)、以及接口方法中传输的Model定义(即:数据契约DataContract,指明方法中的对象参数的Class定义)
02. implementation层:即服务接口的实现
03. host层:wcf最终需要一个宿主环境,如果是web应用,最简单的办法莫过于直接寄宿在IIS上
04. client层:即服务的消费方,如果是b/s应用,通常就是一个web application
实际部署时,一般将wcf服务层和client层分开部署,如下图:
如果并发数随着业务的增长而增长,不管是client层的website,还是服务层的service,加上其它技术,比如集群或负载均衡之类,可以很方便进行扩充。服务的实现逻辑也可以方便的单独的修改替换(前提是服务契约相对稳定)
但如果应用的规模较小,出于成本考虑,完全有可能Service层和Website Client部署在一台机器上,虽然1个IIS上架2个站点完全没有问题,但是总归有点不爽,既然都在一台机器上了,为啥还要自己调用自己,增加无谓的开销呢?
最好是在不修改原来代码的前提下,通过简单的配置文件修改,就能让原来远程调用WCF的方式,改成直接调用本地DLL程序集,反过来也一样,这样就比较灵活了。事实上,我们公司很多项目就是这样处理的,规模小的应用,直接全都部署在一台机器上,等应用规模上去了,再分开部署,代码完全不用动,只要修改相关配置即可。
原理其实非常简单,反射即可,先在Client层的web.config或app.config中,增加类似以下节点:
1 <appSettings> 2 <!--调用方式:Remote远程调用,Local本地调用(注:本地调用时,bin目录下必须有[服务实现类]的dll)--> 3 <add key="CallType" value="Remote"/> 4 <!--本地调用时,程序集的名称--> 5 <add key="AssemblyName" value="sjtu.wcf.demo.implementation"/> 6 <!--本地调用时,[服务实现类]的名称--> 7 <add key="ServiceTypeName" value="sjtu.wcf.demo.implementation.DemoService"/> 8 </appSettings>
CallType就决定了调用方式:“远程调用”或“本地DLL调用”。然后在本地写一个调用的Client类:(注:wcf的调用方式,参考了dudu的文章“享受无止境 - 改进版WCF Client”)
1 using System; 2 using System.Linq.Expressions; 3 using System.Reflection; 4 using System.ServiceModel; 5 using sjtu.wcf.demo.client.configs; 6 7 namespace sjtu.wcf.demo.client 8 { 9 /// <summary> 10 /// Wcf客户端 11 /// </summary> 12 /// <typeparam name="T">ServiceContract接口</typeparam> 13 public class WcfClient<T> where T : class 14 { 15 16 private readonly string assemblyName; 17 private readonly string implTypeName; 18 private readonly string callType; 19 20 public WcfClient() 21 { 22 callType = ConfigHelper.CallType.ToLower(); 23 if (callType == CallType.Local.ToString().ToLower()) 24 { 25 assemblyName = ConfigHelper.AssemblyName; 26 implTypeName = ConfigHelper.ServiceTypeName; 27 } 28 } 29 30 /// <summary> 31 /// 对外提供的Call方法 32 /// </summary> 33 /// <typeparam name="R"></typeparam> 34 /// <param name="expression"></param> 35 /// <returns></returns> 36 public R Call<R>(Expression<Func<T, R>> expression) 37 { 38 if (callType == CallType.Local.ToString().ToLower()) 39 { 40 return InvokeLocalMethod<R>(expression); 41 } 42 return InvokeRemoteMethod<R>(expression); 43 } 44 45 /// <summary> 46 /// 调用本地程序集方法 47 /// </summary> 48 /// <typeparam name="R"></typeparam> 49 /// <param name="operation"></param> 50 /// <returns></returns> 51 private R InvokeLocalMethod<R>(Expression<Func<T, R>> operation) 52 { 53 Assembly asm = Assembly.Load(new AssemblyName(assemblyName)); 54 T t = (T)asm.CreateInstance(implTypeName); 55 R result = operation.Compile().Invoke(t); 56 return result; 57 } 58 59 /// <summary> 60 /// 调用远程wcf方法 61 /// </summary> 62 /// <typeparam name="R"></typeparam> 63 /// <param name="operation"></param> 64 /// <returns></returns> 65 private R InvokeRemoteMethod<R>(Expression<Func<T, R>> operation) 66 { 67 ChannelFactory<T> channelFactory = new ChannelFactory<T>("*"); 68 69 T channel = channelFactory.CreateChannel(); 70 var client = (IClientChannel)channel; 71 client.Open(); 72 R result = operation.Compile().Invoke(channel); 73 try 74 { 75 if (client.State != CommunicationState.Faulted) 76 { 77 client.Close(); 78 } 79 } 80 catch 81 { 82 client.Abort(); 83 } 84 return result; 85 } 86 } 87 }
这样调用时,只需要一行代码即可:
1 var students = new WcfClient<IStudent>().Call(c => c.GetStudents("jerry"));