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

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

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

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

  • 相关阅读:
    c# 方法重载
    c# propertyGrid下拉选项
    c# 枚举类型
    c# socket编程
    读书笔记之ado.net entity framework
    c# delegate的invoke和bejinInvoke的区别
    C# 读书笔记之类与结构体
    c#笔记之启动新线程
    c# listview的使用
    visual studio2013 改变匹配括号的颜色
  • 原文地址:https://www.cnblogs.com/liulun/p/2335138.html
Copyright © 2011-2022 走看看