Fork me on GitHub

【Expression 序列化】WCF的简单使用及其Expression Lambada的序列化问题初步解决方案(一)

在园子里混迹多年,始终保持着“只看帖不回帖”的习惯,看了很多,学了很多,却从不敢写些东西贴出来,一来没什么可写的,二来水平不够,怕误人子弟……

最近在做一个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.Runtime.Serialization.InvalidDataContractException
  Message=无法序列化类型“System.Linq.Expressions.Expression`1[System.Func`2[Liuliu.Wcf.IContract.Member,System.Boolean]]”。请考虑将其标以 DataContractAttribute 特性,并将其所有要序列化的成员标以 DataMemberAttribute 特性。如果类型为集合,则请考虑将其标以 CollectionDataContractAttribute 特性。有关其他受支持的类型,请参见 Microsoft .NET Framework 文档。
  Source=System.Runtime.Serialization
  StackTrace:
       在 System.Runtime.Serialization.DataContract.DataContractCriticalHelper.ThrowInvalidDataContractException(String message, Type type)
       在 System.Runtime.Serialization.DataContract.DataContractCriticalHelper.CreateDataContract(Int32 id, RuntimeTypeHandle typeHandle, Type type)
       在 System.Runtime.Serialization.DataContract.DataContractCriticalHelper.GetDataContractSkipValidation(Int32 id, RuntimeTypeHandle typeHandle, Type type)
       在 System.Runtime.Serialization.XsdDataContractExporter.GetSchemaTypeName(Type type)
       在 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.ValidateDataContractType(Type type)
       在 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.CreatePartInfo(MessagePartDescription part, OperationFormatStyle style, DataContractSerializerOperationBehavior serializerFactory)
       在 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.CreateMessageInfo(DataContractFormatAttribute dataContractFormatAttribute, MessageDescription messageDescription, DataContractSerializerOperationBehavior serializerFactory)
       在 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter..ctor(OperationDescription description, DataContractFormatAttribute dataContractFormatAttribute, DataContractSerializerOperationBehavior serializerFactory)
       在 System.ServiceModel.Description.DataContractSerializerOperationBehavior.GetFormatter(OperationDescription operation, Boolean& formatRequest, Boolean& formatReply, Boolean isProxy)
       在 System.ServiceModel.Description.DataContractSerializerOperationBehavior.System.ServiceModel.Description.IOperationBehavior.ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
       在 System.ServiceModel.Description.DispatcherBuilder.BindOperations(ContractDescription contract, ClientRuntime proxy, DispatchRuntime dispatch)
       在 System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost)
       在 System.ServiceModel.ServiceHostBase.InitializeRuntime()
       在 System.ServiceModel.ServiceHostBase.OnBeginOpen()
       在 System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)
       在 System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
       在 System.ServiceModel.Channels.CommunicationObject.Open()
       在 Liuliu.Wcf.Hosting.Program.OpenHosting(IEnumerable`1 hosts) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Hosting\Program.cs:行号 52
       在 Liuliu.Wcf.Hosting.Program.Main(String[] args) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Hosting\Program.cs:行号 21
  InnerException: 

 

由异常信息我们可以很明确的看到,System.Linq.Expressions.Expression类不支持序列化操作,以Expression作为查询条件的参数传递是行不通的。
此路不通,我们只能另辟蹊径了

以“Expression”,“序列化”,“Lambada”等为关键字百度Google了一把,终于还是收获不少,找到了Expression Tree Serializer 这根救命稻草

有路了就要往下走,后面还会遇到什么问题,且听下回分解……

 最后,把本文的源代码发上来以供参考,注意:为了保持现场,这个源码包的程序并不能运行

LambadaSerializeDemo01.rar



posted @ 2012-04-10 00:30  郭明锋  阅读(3828)  评论(8编辑  收藏  举报