题外话:
对不住各位,本打算年前把这个系列写完,结果由于杂务缠身一直推到年后
我特别痛恨我自己!我觉得不但对不起各位!也对不起自己。
最近烦躁不安,不能专心向学。也不知道如何是好。
……
好吧,言归正传
说个前提条件:
此项目虽然使用了silverlight 4.0
但是服务端只能在dotNet3.5下运行
这也是我们为什么自己实现riaService的原因
实体层设计
由于有这个限制条件,我们设计的实体层也有所区别
如下图为实体层的程序集(只有MenuM实体类,其他实体类未加入。)
下面来看一下实体层MenuM的代码
namespace RTMDemo.Model { [DataContract] public class MenuM : Entity { public string _MenuName; public string _Url; public string _MenuDes; public int _OrderNum; [DataMember] public Guid Id { get; set; } [DataMember] [Display(Name = "菜单名称")] [Required(ErrorMessage="不能为空")] public string MenuName { get { return _MenuName; } set { ValidateProperty("MenuName", value); _MenuName = value; } } [DataMember] public Guid ParentId { get; set; } [DataMember] [Display(Name = "菜单路径")] [Required(ErrorMessage = "不能为空")] public string Url { get { return _Url; } set { ValidateProperty("Url", value); _Url = value; } } [DataMember] [Display(Name = "菜单帮助")] public string MenuDes { get { return _MenuDes; } set { ValidateProperty("Help", value); _MenuDes = value; } } [DataMember] [Display(Name = "菜单排序")] [RegularExpression("[1-9]+[0-9]", ErrorMessage = "只能输入数字")] public int OrderNum { get { return _OrderNum; } set { ValidateProperty("MenuName", value); _OrderNum = value; } } } }
这里有几点需要说明
1:
特性[DataContract]与[DataMember]标记
是为了客户端与服务端传输数据的时候序列化与反序列化引入的
2:
MenuM类继承自Entity类
然而在.net 3.5中是没有Entity类的
那么我们就创建了这个类(就是Attr文件夹下的 Entity.cs类)
namespace System.ServiceModel.DomainServices.Client { [DataContract] public class Entity { public void ValidateProperty(string PropertyName, object value) { } } }
这个类虽然在这里看上去没什么用
但是在silverlight客户端用处就非常大(等会会说道为silverlight客户端自动生成实体类型,silverlight 4.0是有Entity类的)
3:
[Display(Name = "菜单名称")]
如上:Display特性在dotNet3.5中也是不存在的
同理,我们创建了DisplayAttribute特性,也是为了使用Silverlight4.0的客户端特性
namespace System.ComponentModel.DataAnnotations { public sealed class DisplayAttribute : Attribute { public string Name { get; set; } } }
为客户端动态生成服务代理和实体类型
使用过Silverlight RIA Service的人一定都知道
每次编译的时候都会在Silverlight程序集中生成如下目录和文件
此文件就包含了服务代理和实体类型
那么为了达到与RIA Service一样的效果
我们为服务端程序集增加了VS2010的后期生成事件命令行
如下图所示
命令行代码为
$(SolutionDir)RTMDemo.Compile\bin\Debug\RTMDemo.Compile.exe
其中
$(SolutionDir)为宏,指解决方案的目录(定义为驱动器 + 路径);包括尾部的反斜杠“\”。
更多生成事件命令行的宏请参见这里:http://msdn.microsoft.com/zh-cn/library/42x5kfw4(v=vs.90).aspx
这个命令行的意思是
在编译完服务端类库后
执行RTMDemo.Compile\bin\Debug\RTMDemo.Compile.exe
也就是这个类库的生成文件
那么我们来看一下这个程序集中的主要工作
1.保存目录路径以备读取和写入
[DllImport("Kernel32.dll")] public static extern string GetCommandLineA(); //实体的路径 static string mPath; //服务的路径 static string sPath; //客户端的路径 static string tarPath; [STAThread] static void Main() { var rtstr = @"RTMDemo.Compile\bin\Debug\RTMDemo.Compile.exe"; //获取命令行参数 var arg = GetCommandLineA().Replace("\"",""); mPath = arg.Replace(rtstr,"RTMDemo.Model"); sPath = arg.Replace(rtstr, @"RTMDemo.Host\bin\RTMDemo.Host.dll"); tarPath = arg.Replace(rtstr, @"RTMDemo.Frame\Generated_Code\RTMDemo.Host.g.cs"); AddModels(); AddService(); }
2.添加实体类型
static void AddModels() { var di = new DirectoryInfo(mPath); var sb = new StringBuilder(Environment.NewLine); foreach (var fi in di.GetFiles()) { var pname = Path.GetFileNameWithoutExtension(fi.Name); if (!pname.EndsWith("M")) { continue; } var sr = new StreamReader(fi.FullName); var content = sr.ReadToEnd(); var matche = Regex.Match(content, @"\[DataContract(.|\n)*}"); var str = matche.Value; str = str.TrimEnd('}'); sb.Append(Environment.NewLine); sb.AppendFormat(" {0}", str); sr.Close(); } sb.Append(Environment.NewLine); WriteToTar("实体", sb.ToString()); }
此端代码大意为:
遍历实体类库文件夹内的文件,
读取文件名以M结尾的文件(约定实体类名必须以M结尾)
然后按正则匹配[DataContract]以后的内容
把这些内容保存起来以备写入目标文件
3.添加服务代理
static void AddService() { var ass = Assembly.LoadFile(sPath); var types = ass.GetTypes(); var sb = new StringBuilder(Environment.NewLine); foreach (var ty in types) { if (!ty.Name.EndsWith("Service")) { continue; } sb.AppendLine(string.Format("public class {0}", ty.Name)); sb.AppendLine("{"); sb.AppendLine("public event ServiceEventHandler Completed;"); var methods = ty.GetMethods(); foreach (var me in methods) { if (me.DeclaringType != ty) { continue; } sb.AppendFormat("public void {0}(", me.Name); var Parameters = me.GetParameters(); var ps = new StringBuilder(); for (var i = 0; i < Parameters.Length; i++) { var tn = GetTypeName(Parameters[i].ParameterType); var dh = (i == Parameters.Length - 1?"":","); sb.AppendFormat("{0} {1}{2}", tn, Parameters[i].Name,dh); ps.AppendFormat(" ,{0}", Parameters[i].Name); } sb.AppendFormat("){0}", Environment.NewLine); sb.AppendLine("{"); sb.AppendLine("var si = new ServiceInvoker();"); sb.AppendLine("si.Completed += new ServiceEventHandler(si_Completed);"); var rtp = GetTypeName(me.ReturnType); if (rtp == "Void") { sb.AppendLine(string.Format("si.PrepareInvoke(\"{0}\", \"{1}\", {2} {3});", ty.Name, me.Name, "null", ps.ToString())); } else { sb.AppendLine(string.Format("si.PrepareInvoke(\"{0}\", \"{1}\", typeof({2}) {3});", ty.Name, me.Name, rtp, ps.ToString())); } sb.AppendLine("si.InvokeService();"); sb.AppendLine("}"); } sb.AppendLine("void si_Completed(object sender, ServiceEventArgs e)"); sb.AppendLine("{"); sb.AppendLine("Completed(sender, e);"); sb.AppendLine("}"); sb.AppendLine("}"); } sb.Append(Environment.NewLine); WriteToTar("服务", sb.ToString()); }
获取服务端类信息与获取实体类信息不同
获取服务端类信息使用了反射
我们反射出类的名字,类中的方法名,参数名,参数类型,返回值类型等
来生成形如下面这样的服务端代理
public class MenuService { public event ServiceEventHandler Completed; public void GetAllMenu() { var si = new ServiceInvoker(); si.Completed += new ServiceEventHandler(si_Completed); si.PrepareInvoke("MenuService", "GetAllMenu", typeof(List<MenuM>)); si.InvokeService(); } public void DelMenu(Guid Id) { var si = new ServiceInvoker(); si.Completed += new ServiceEventHandler(si_Completed); si.PrepareInvoke("MenuService", "DelMenu", null, Id); si.InvokeService(); } public void AddMenu(MenuM m) { var si = new ServiceInvoker(); si.Completed += new ServiceEventHandler(si_Completed); si.PrepareInvoke("MenuService", "AddMenu", null, m); si.InvokeService(); } public void UpdateMenu(MenuM m) { var si = new ServiceInvoker(); si.Completed += new ServiceEventHandler(si_Completed); si.PrepareInvoke("MenuService", "UpdateMenu", null, m); si.InvokeService(); } void si_Completed(object sender, ServiceEventArgs e) { Completed(sender, e); } }
至于ServiceInvoker是什么,我们将在下一节内容中介绍
注意:这样生成服务端代理暂不支持生成服务端方法的重载代理
在获取参数或返回值类型的时候,
会遇到获取泛型类型的情况(如:List~<….>,就不能把这些拼到字符串内去)
我们使用诸如下面的函数来做简单判别
public static string GetTypeName(Type t) { if (t.Name.StartsWith("List")) { var gtype = t.GetGenericArguments().FirstOrDefault(); var result = string.Format("List<{0}>", gtype.Name); return result; } return t.Name; }
4.写入代理文件
原始的代理文件模版如下
//------------------------------------------------------------------------------ // <auto-generated> // 此代码由工具生成。 // 对此文件的更改可能会导致不正确的行为, // 并且如果重新生成代码,这些更改将会丢失。 // </auto-generated> //------------------------------------------------------------------------------ namespace RTMDemo.Frame.Client { using System; using System.Collections.Generic; using System.Runtime.Serialization; using RTMDemo.Frame.Common; using System.ComponentModel.DataAnnotations; using System.ComponentModel; using System.ServiceModel.DomainServices.Client; #region 实体 #endregion #region 服务 #endregion }
写入代理文件主要是匹配两个region
然后插入之前生成的字符串
static void WriteToTar(string reg, string content) { reg = string.Format(@"(?<=#region {0})(.|\n)*?(?=#endregion)", reg); var tsr = new StreamReader(tarPath); var tcontent = tsr.ReadToEnd(); tsr.Close(); tcontent = Regex.Replace(tcontent, reg, content); var tarStream = new StreamWriter(tarPath); tarStream.Write(tcontent); tarStream.Close(); }
调用此函数的代码如下
WriteToTar("服务", sb.ToString());
至此就把服务代理和实体类型都拷贝到客户端去了
在下一节我们介绍怎么使用这些内容
………………………………………….写文章不容易…………………请务必点个推荐吧………………………………………………