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

 

至此就把服務代理和實體類型都拷貝到客戶端去了

在下一節我們介紹怎么使用這些內容

 

 

………………………………………….寫文章不容易…………………請務必點個推薦吧………………………………………………


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM