zoukankan      html  css  js  c++  java
  • SilverLight企业应用框架设计【四】实体层设计+为客户端动态生成服务代理(自己实现RiaService)

    题外话:

    对不住各位,本打算年前把这个系列写完,结果由于杂务缠身一直推到年后

    我特别痛恨我自己!我觉得不但对不起各位!也对不起自己。

    最近烦躁不安,不能专心向学。也不知道如何是好。

    ……

    好吧,言归正传

    说个前提条件:

    此项目虽然使用了silverlight 4.0

    但是服务端只能在dotNet3.5下运行

    这也是我们为什么自己实现riaService的原因

    实体层设计

    由于有这个限制条件,我们设计的实体层也有所区别

    如下图为实体层的程序集(只有MenuM实体类,其他实体类未加入。)

    image

    下面来看一下实体层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程序集中生成如下目录和文件

    image

    此文件就包含了服务代理和实体类型

    那么为了达到与RIA Service一样的效果

    我们为服务端程序集增加了VS2010的后期生成事件命令行

    如下图所示

    image

    命令行代码为

    $(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

    也就是这个类库的生成文件

    image

    那么我们来看一下这个程序集中的主要工作

    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());

    至此就把服务代理和实体类型都拷贝到客户端去了

    在下一节我们介绍怎么使用这些内容

    ………………………………………….写文章不容易…………………请务必点个推荐吧………………………………………………

  • 相关阅读:
    笔记44 Hibernate快速入门(一)
    tomcat 启用https协议
    笔记43 Spring Security简介
    笔记43 Spring Web Flow——订购披萨应用详解
    笔记42 Spring Web Flow——Demo(2)
    笔记41 Spring Web Flow——Demo
    Perfect Squares
    Factorial Trailing Zeroes
    Excel Sheet Column Title
    Excel Sheet Column Number
  • 原文地址:https://www.cnblogs.com/liulun/p/2335138.html
Copyright © 2011-2022 走看看