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

题外话:

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

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

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

……

好吧,言归正传

说个前提条件:

此项目虽然使用了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());

 

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

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

 

 

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

时间: 2024-09-17 15:59:32

SilverLight企业应用框架设计【四】实体层设计+为客户端动态生成服务代理(自己实现RiaService)的相关文章

SilverLight企业应用框架设计【五】客户端调用服务端(使用JSON传递数据,自己实现RESTful Web服务)

来个索引 SilverLight企业应用框架设计[四]实体层设计+为客户端动态生成服务代理(自己实现RiaService) SilverLight企业应用框架设计[三]服务端设计 SilverLight企业应用框架设计[二]框架画面 SilverLight企业应用框架设计[一]整体说明   在上一节中讲到的自动生成的服务代理类核心代码,如下 public event ServiceEventHandler Completed; public void GetAllMenu() { var si

Silverlight企业应用框架设计【六】自定义系统菜单(使用自己的DataForm)

索引 SilverLight企业应用框架设计[五]客户端调用服务端(使用JSON传递数据,自己实现RESTful Web服务) SilverLight企业应用框架设计[四]实体层设计+为客户端动态生成服务代理(自己实现RiaService) SilverLight企业应用框架设计[三]服务端设计 SilverLight企业应用框架设计[二]框架画面 SilverLight企业应用框架设计[一]整体说明   首先我们设计的窗体如下 xaml代码如下: <location:BasePage x:Cl

SilverLight企业应用框架设计【一】整体说明

Silverlight企业应用框架设计[六]自定义系统菜单(使用自己的DataForm) SilverLight企业应用框架设计[五]客户端调用服务端(使用JSON传递数据,自己实现RESTful Web服务) SilverLight企业应用框架设计[四]实体层设计+为客户端动态生成服务代理(自己实现RiaService) SilverLight企业应用框架设计[三]服务端设计 SilverLight企业应用框架设计[二]框架画面 SilverLight企业应用框架设计[一]整体说明 闲言碎语~

SilverLight企业应用框架设计【二】框架画面

框架画面分为上中下三层 由下面一个Grid控件完成布局 <Grid x:Name="LayoutRoot"> <Grid.RowDefinitions> <RowDefinition Height="60"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height=

SilverLight企业应用框架设计【三】服务端设计

一:缓存服务类型与方法 客户端请求的时候 为了方便的知道请求的类型与类型所包含的方法 我们把服务类型和方法缓存到静态字典中了 代码如下 public class WCFRouteTable { static Dictionary<string, Type> routeService; static Dictionary<string, MethodInfo> routeMethods; static WCFRouteTable() { routeService = new Dict

WinForm企业应用框架设计【一】界限划分与动态创建WCF服务(no svc!no serviceActivations!)

WinForm企业应用框架设计[一]界限划分与动态创建WCF服务(no svc!no serviceActivations!) WinForm企业应用框架设计[二]团队内部的约定和客户端按约定识别WCF服务 WinForm企业应用框架设计[三]框架窗体设计:动态创建菜单: WinForm企业应用框架设计[四]动态创建业务窗体 WinForm企业应用框架设计[五]系统登录以及身份验证+源码 先来张图片!我们这个系列就是要做一个这样的框架!    我曾写过几个"系列"的东西,如 PL/SQ

WinForm企业应用框架设计【五】系统登录以及身份验证+源码

索引 WinForm企业应用框架设计[一]界限划分与动态创建WCF服务(no svc!no serviceActivations!) WinForm企业应用框架设计[二]团队内部的约定和客户端按约定识别WCF服务 WinForm企业应用框架设计[三]框架窗体设计:动态创建菜单: WinForm企业应用框架设计[四]动态创建业务窗体 WinForm企业应用框架设计[五]系统登录以及身份验证+源码 闲话休提~ 一:登录的画面与客户端逻辑 为了在打开程序的时候先弹出登录窗体 我们修改了主窗体的构造函数

net-.NET中,随着业务的增加,关于实体层类的设计 疑问

问题描述 .NET中,随着业务的增加,关于实体层类的设计 疑问 三层架构里面,当页面功能增多时,我们会对数据层和逻辑层做一定的改动, 同时也会对实体层做改变,请问对实体层类增加一些属性字段,是再在实体层中写一个 类 还是在原来的用户类里面添加字段?? 解决方案 这个看你的需求了,如果是Person类,而你要在Person类里添加一个人的胳膊的属性的话,你可以将胳膊定义成一个类,然后在Person类里引用这个胳膊的. 如果是分散的属性,你可以自定义一个类,也可以直接在类里添加的.

WinForm企业应用框架设计【三】框架窗体设计;动态创建菜单;

要不是我的朋友乔乔==乔不死跟我聊到领域驱动设计~ 我也不会发现第一篇中关于"充血实体"的错误说法(至少~我写文章的时候~内心的想法是错的~) 我个人不是很喜欢领域驱动设计~感觉这种思路(我们暂且叫它思路~虽然它有一些既有的原则和模式) 重点要求架构师深入到业务领域中去~ 但是在国内往往很难真正的与领域专家做深入交流~ 架构师划分的领域模型和聚合往往与真实的情况差别较大~ 即使划分的较好~新的业务和变化的业务也另设计师非常头疼~ 另外 设计师很难将庞大复杂的业务抽象成领域模型 往往需要