在园子里混迹多年,始终保持着“只看帖不回帖”的习惯,看了很多,学了很多,却从不敢写些东西贴出来,一来没什么可写的,二来水平不够,怕误人子弟……
最近在做一个MVC+WCF+EF的项目,遇到问题不少,但大多数问题都是前人遇到并解决了的,感谢园子里的大牛们的无私奉献。
俗话说“礼尚往来”,我也在此分享一个最近在项目中遇到的问题,就是远程调用时的Expression表达式的序列化问题的初始解决方案,希望抛出的这块石头能引出完美的钻石来,同时第一次写博客,请大家多多赐教……
为了说明问题,我将用一个简单的示例来演示,文章的最后会有示例的源代码下载。
示例说明:
演示项目还是使用传统的四层结构:
WCF服务契约:契约很简单,一个Member类的Model,一个通过表达式来查找Member信息的GetMember(Expression<Func<Member, bool>>)方法
1 using System;
2 using System.Linq.Expressions;
3 using System.Runtime.Serialization;
4 using System.ServiceModel;
5
6 namespace Liuliu.Wcf.IContract
7 {
8 [ServiceContract]
9 public interface IAccountContract
10 {
11 [OperationContract]
12 Member GetMember(Expression<Func<Member, bool>> predicate);
13 }
14
15 [DataContract]
16 public class Member
17 {
18 [DataMember]
19 public int MemberID { get; set; }
20
21 [DataMember]
22 public string UserName { get; set; }
23
24 [DataMember]
25 public string Email { get; set; }
26 }
27 }
为简化客户端的调用操作,在Contracts项目中添加一个WcfHelper辅助类
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.ServiceModel;
5 using System.Text;
6
7 namespace Liuliu.Wcf.IContract.Helper
8 {
9 public class WcfHelper
10 {
11 public static TReturn InvokeService<TContract, TReturn>(Func<TContract, TReturn> func)
12 {
13 var channelFactory = new ChannelFactory<TContract>("*");
14 var proxy = channelFactory.CreateChannel();
15 var iproxy = proxy as ICommunicationObject;
16 if (iproxy == null)
17 {
18 throw new ArgumentException("执行远程方法时服务契约代理协议为空。");
19 }
20 try
21 {
22 iproxy.Open();
23 var result = func(proxy);
24 iproxy.Close();
25 return result;
26 }
27 catch (CommunicationException)
28 {
29 iproxy.Abort();
30 throw;
31 }
32 catch (TimeoutException)
33 {
34 iproxy.Abort();
35 throw;
36 }
37 catch (Exception)
38 {
39 iproxy.Close();
40 throw;
41 }
42 }
43 }
44 }
服务的实现也非常简单,就是从数据源DataSource中按条件查找相关信息并返回
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Linq.Expressions;
5 using Liuliu.Wcf.IContract;
6
7 namespace Liuliu.Wcf.Services
8 {
9 public class AccountService : IAccountContract
10 {
11 private readonly static List<Member> DataSource = new List<Member>
12 {
13 new Member {MemberID = 3, UserName = "zhangsan", Email = "zhangsan@abc.com"},
14 new Member {MemberID = 4, UserName = "lisi", Email = "lisi@abc.com"},
15 new Member {MemberID = 5, UserName = "wangwu", Email = "wangwu@abc.com"},
16 new Member {MemberID = 6, UserName = "zhaoliu", Email = "zhaoliu@abc.com"}
17 };
18
19 public Member GetMember(Expression<Func<Member, bool>> predicate)
20 {
21 return DataSource.SingleOrDefault(predicate.Compile());
22 }
23 }
24 }
服务端以一个控制台的程序来承载,第一次写博客,可以力求完美一点^_^
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.ServiceModel;
5
6 using Liuliu.Wcf.Services;
7
8 namespace Liuliu.Wcf.Hosting
9 {
10 class Program
11 {
12 private static readonly List<ServiceHost> OpenedHosts = new List<ServiceHost>();
13
14 private static void Main(string[] args)
15 {
16 try
17 {
18 var accountHost = new ServiceHost(typeof(AccountService));
19 var hosts = new List<ServiceHost> { accountHost };
20 CreateHosting(hosts);
21 OpenHosting(hosts);
22 Console.WriteLine("按任意键关闭服务。");
23 Console.ReadKey();
24 CloseHosting(OpenedHosts);
25
26 }
27 catch (Exception e)
28 {
29 Console.WriteLine(e);
30 Console.ReadLine();
31 }
32 }
33
34 private static void OpenHosting(IEnumerable<ServiceHost> hosts)
35 {
36 foreach (var host in hosts)
37 {
38 try
39 {
40 host.Open();
41 if (!OpenedHosts.Contains(host))
42 {
43 OpenedHosts.Add(host);
44 }
45 }
46 catch (Exception)
47 {
48 foreach (var openedHost in OpenedHosts)
49 {
50 openedHost.Close();
51 }
52 throw;
53 }
54 }
55 }
56
57 private static void CreateHosting(IEnumerable<ServiceHost> hosts)
58 {
59 hosts.ToList().ForEach(host =>
60 {
61 host.Opened += host_Opened;
62 host.Closed += host_Closed;
63 host.UnknownMessageReceived += host_UnknownMessageReceived;
64 });
65 }
66
67 private static void CloseHosting(IEnumerable<ServiceHost> hosts)
68 {
69 hosts.ToList().ForEach(host => host.Close());
70 }
71
72 static void host_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e)
73 {
74 var host = (ServiceHost)sender;
75 Console.WriteLine("{0}收到未知消息。", host.Description.ConfigurationName);
76 }
77
78 static void host_Closed(object sender, EventArgs e)
79 {
80 var host = (ServiceHost)sender;
81 Console.WriteLine("{0}已成功关闭。", host.Description.ConfigurationName);
82 }
83
84 static void host_Opened(object sender, EventArgs e)
85 {
86 var host = (ServiceHost)sender;
87 Console.WriteLine("{0}服务启动完毕\n监听地址:{1}", host.Description.ConfigurationName, host.Description.Endpoints.First().ListenUri);
88 }
89 }
90 }
当然现在服务端还不能启动,还需要必要的配置信息,在Hosting项目中添加一个App.Config来进行配置,可以通过VS2010中菜单“工具” - “WCF服务配置编辑器” 来进行可视化添加
为了简化配置,这里不进行调用的验证了,设置 clientCredentialType="None"
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NewBinding0">
<security>
<message clientCredentialType="None" />
</security>
</binding>
</netTcpBinding>
</bindings>
<services>
<service name="Liuliu.Wcf.Services.AccountService">
<endpoint address="net.tcp://127.0.0.1:9000/account" binding="netTcpBinding"
bindingConfiguration="NewBinding0" contract="Liuliu.Wcf.IContract.IAccountContract" />
</service>
</services>
</system.serviceModel>
</configuration>
有了前面的WcfHelper的辅助,客户端调用就非常惬意了,由UserName查找Member信息并显示结果的Email信息
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Linq.Expressions;
5 using System.Text;
6
7 using Liuliu.Wcf.IContract;
8 using Liuliu.Wcf.IContract.Helper;
9
10 namespace Liuliu.Wcf.Client
11 {
12 class Program
13 {
14 static void Main(string[] args)
15 {
16 Expression<Func<Member, bool>> predicate = m => m.UserName == "张三";
17 var result = WcfHelper.InvokeService<IAccountContract, Member>(wcf => wcf.GetMember(predicate));
18 Console.WriteLine(result.Email);
19 }
20 }
21 }
当然,还要客户端的配置信息,由“WCF服务配置编辑器”工具可直接由上面配置的服务端配置来生成客户端配置
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NewBinding0">
<security>
<message clientCredentialType="None" />
</security>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://127.0.0.1:9000/account" binding="netTcpBinding"
bindingConfiguration="NewBinding0" contract="Liuliu.Wcf.IContract.IAccountContract"
name="" kind="" endpointConfiguration="" />
</client>
</system.serviceModel>
</configuration>
至此,演示项目的基本架构搭建完毕,理论上运行的话应该能顺利启动,右键解决方案 - 设置启动项目,设置启动为多项目启动:
当我们信心满满的点下启动按键之后,结果却不是我们所期待的,服务端在执行到OpenHosting(hosts)的时候引发了异常:
View Code
由异常信息我们可以很明确的看到,System.Linq.Expressions.Expression类不支持序列化操作,以Expression作为查询条件的参数传递是行不通的。
此路不通,我们只能另辟蹊径了
以“Expression”,“序列化”,“Lambada”等为关键字百度Google了一把,终于还是收获不少,找到了Expression Tree Serializer 这根救命稻草
有路了就要往下走,后面还会遇到什么问题,且听下回分解……
最后,把本文的源代码发上来以供参考,注意:为了保持现场,这个源码包的程序并不能运行
LambadaSerializeDemo01.rar
作者:郭明锋
出处:http://www.cnblogs.com/guomingfeng
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。