虽然flex跟.net交互的首选是
FluorineFx,但是如果在某些特定情况下(比如服务端是现成的,不允许修改,或者服务端开发方不懂FluorineFx为何物),这时webService还是挺有用的。
WebService完全可以用"以BasicHttpBinding方式运行的WCF"代替。经过我的实际测试:对于基本类型(比如int,string,datetime以及它们对应的arrry以list),flex调用时能正确识别并“翻译”成as3中对应的int,String,Date以及Array类型,而复杂类型(比如自己在c#中定义的实体类或DataTable),flex调用时会报错,这类复杂类型我建议在wcf中用序列化技术处理成String后再返回。
考虑到xml格式序列化后的信息量比较大,我倾向于选择json这种轻量级的格式,而且在.net4.0中新增了System.Runtime.Serialization.Json;能处理大多数的复杂对象序列化(但是DataTable处理不了)
为了方便起见,我把一些序列化/反序列化的操作封装了一下:
using System; using System.IO; using System.Runtime.Serialization.Json; using System.Text; using System.Data; namespace Helper { public static class Utils { /// <summary> /// 将对象序列化成json字符串(注:obj的类定义中要加正确的可序列化标志) /// </summary> /// <param name="obj"></param> /// <returns></returns> public static string ToJsonString(object obj) { string result = string.Empty; DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(obj.GetType()); using (MemoryStream ms = new MemoryStream()) { jsonSerializer.WriteObject(ms, obj); result = Encoding.UTF8.GetString(ms.ToArray()); } return result; } /// <summary> /// json字符串反序列为对象 /// </summary> /// <param name="jsonString"></param> /// <param name="objType"></param> /// <returns></returns> public static Object ToJsonObject(string jsonString, Type objType) { Object result = null; DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(objType); using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString))) { result = jsonSerializer.ReadObject(ms); } return result; } /// <summary> /// 将DataTable序列化成json字符串 /// </summary> /// <param name="dt"></param> /// <returns></returns> public static string ToJsonString(this DataTable dt) { StringBuilder JsonString = new StringBuilder(); //Exception Handling if (dt != null && dt.Rows.Count > 0) { JsonString.Append("{ "); JsonString.Append("\"Rows\":[ "); for (int i = 0; i < dt.Rows.Count; i++) { JsonString.Append("{ "); for (int j = 0; j < dt.Columns.Count; j++) { if (j < dt.Columns.Count - 1) { JsonString.Append("\"" + dt.Columns[j].ColumnName.ToString().Replace("\"", "\\\"") + "\":" + "\"" + dt.Rows[i][j].ToString().Replace("\"", "\\\"") + "\","); } else if (j == dt.Columns.Count - 1) { JsonString.Append("\"" + dt.Columns[j].ColumnName.ToString().Replace("\"", "\\\"") + "\":" + "\"" + dt.Rows[i][j].ToString().Replace("\"", "\\\"") + "\""); } } /**/ /*end Of String*/ if (i == dt.Rows.Count - 1) { JsonString.Append("} "); } else { JsonString.Append("}, "); } } JsonString.Append("]}"); return JsonString.ToString(); } else { return null; } } } }
不过,在开始正文之前,先提醒一下System.Runtime.Serialization.Json在序列化中要注意的问题:
比如有一个类Person,定义如下:
[Serializable] public class Person { private string _name; private int _age; private float _salary; public string Name { set { _name = value; } get { return _name; } } public int Age { set { _age = value; } get { return _age; } } public float Salary { set { _salary = value; } get { return _salary; } } }
对象
Person p = new Person(){Age=30, Name="jimmy.yang", Salary=5000};
序列后的字符串为
{"
_age":30,"
_name":"jimmy.yang","
_salary":5000}
注意:这里并不是我所期待的{" Age":30," Name":"jimmy.yang"," Salary":5000},其实出现这样的结果也可以理解,因为属性的set,get内部就是方法调用,因此最终序列化的只是私有字段。但是如果把[Serializable]标志去掉,确能得到正确的结果:{"Age":30,"Name":"jimmy.yang","Salary":5000} 不知道这个算不算是System.Runtime.Serialization.Json的一个bug.
实际flex应用中,用于传输的实体类99%以上保存的只是一些常规的基元类型(即int,string,date之类),所以为了避免上面提到的问题,我建议:
1、实体类定义中只使用基本类型,去掉[Serializable]
2、或者直接把实例成员用类似public string Name;的方式暴露出来,不过估计大数多酷爱OO的同学们要吐血了.
1、实体类定义中只使用基本类型,去掉[Serializable]
2、或者直接把实例成员用类似public string Name;的方式暴露出来,不过估计大数多酷爱OO的同学们要吐血了.
ok,切入正题吧:
1、先创建一个asp.net项目,然后添加一个wcf service,文件命名为:Sample.svc,对应的后端代码文件Sample.svc.cs内容如下:
using System.Collections.Generic; using System.Data; using System; using Helper; using Entity; namespace WcfApp { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Sample" in code, svc and config file together. public class Sample : ISample { public string ObjectTest() { return Utils.ToJsonString(new Person() { Age=30, Name="jimmy.yang", Salary=3000 }); } public string DataTableTest() { DataTable tbl = new DataTable(); tbl.Columns.Add("Name"); tbl.Columns.Add("Age"); tbl.Rows.Add("Jimmy.Yang", 30); tbl.Rows.Add("Mike", 20); return tbl.ToJsonString(); } public string ListObjectTest() { return Utils.ToJsonString( new List<Person>() { new Person() { Age = 20, Name = "张三", Salary = 5000 }, new Person() { Age = 30, Name = "李四", Salary = 8000 } } ); } } }
2、创建flex项目,然后在Data/Services面板中,添加一个webSerivce的引用
点击Next之后,出现下面的界面
设置wcf所在的WSDL URI后,一路next,最终Data/WebServices面板会变成下面这样
注意上图中右侧工具栏上的几个小按钮,自己去试试吧,会有意外发现哦
同时flex会生成几个as类文件(类似于wcf中svcutil.exe在client端自动生成的cs文件)
剩下的事情,就很容易了,在mxml中测试一番,代码如下:
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:sample="services.sample.*"> <fx:Script> <![CDATA[ import com.adobe.serialization.json.JSONDecoder; import mx.controls.Alert; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import valueObjects.Person; private function dataTableTest():void { DataTableTestResult.token = sample.DataTableTest(); DataTableTestResult.addEventListener(ResultEvent.RESULT,onDataTableTestResult); DataTableTestResult.addEventListener(FaultEvent.FAULT,onDataTableTestFault); } private function onDataTableTestFault(e:FaultEvent):void { Alert.show("dataTableTest调用失败,result="+ e); trace("dataTableTest.Fault=",e); } private function onDataTableTestResult(e:ResultEvent):void { this.txtDataTable.text = "dataTableTest调用成功,结果:" + e.result; } private function listObjectTest():void { ListObjectTestResult.token = sample.ListObjectTest(); ListObjectTestResult.addEventListener(ResultEvent.RESULT,onListObjectTestResult); ListObjectTestResult.addEventListener(FaultEvent.FAULT,onListObjectFault); } private function onListObjectTestResult(e:ResultEvent):void { this.txtList.text = "listObjectTest调用成功,结果:" + e.result; var jsonDecoder:JSONDecoder = new JSONDecoder(e.result.toString(),true); var obj:Object = jsonDecoder.getValue(); //把结果转化为强类型的集合 var list:Vector.<Person> = new Vector.<Person>(); for(var i:int=0;i<obj.length;i++) { list[i] = new Person(obj[i].Name,obj[i].Age,obj[i].Salary); } trace(list); } private function onListObjectFault(e:FaultEvent):void { Alert.show("listObjectTest调用失败,result="+ e); trace("listObjectTest.Fault=",e); } private function objectTest():void { ObjectTestResult.token = sample.ObjectTest(); ObjectTestResult.addEventListener(ResultEvent.RESULT,onObjectTestResult); ObjectTestResult.addEventListener(FaultEvent.FAULT,onObjectTestFault); } private function onObjectTestResult(e:ResultEvent):void { this.txtObject.text = "objectTest调用成功,返回值:" + e.result; var jsonDecoder:JSONDecoder = new JSONDecoder(e.result.toString(),true); var obj:Object = jsonDecoder.getValue(); var p:Person = new Person(obj.Name,obj.Age,obj.Salary); this.txtObject.text += "\n" + "p.Age=" + p.Age + ",p.Name=" + p.Name + ",p.Salary=" + p.Salary; } private function onObjectTestFault(e:FaultEvent):void { this.txtObject.text = "objectTest调用失败,原因="+ e; } private function doClick():void { objectTest(); listObjectTest(); dataTableTest(); } ]]> </fx:Script> <fx:Declarations> <s:CallResponder id="ArrayTestResult"/> <sample:Sample id="sample" fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)" showBusyCursor="true"/> <s:CallResponder id="DataTableTestResult"/> <s:CallResponder id="ListObjectTestResult"/> <s:CallResponder id="ObjectTestResult"/> </fx:Declarations> <s:Panel right="10" left="10" bottom="10" top="10" title="WCF 调用实例"> <s:layout> <s:BasicLayout/> </s:layout> <mx:VDividedBox left="10" bottom="40" right="10" top="0"> <s:TextArea height="33%" width="100%" id="txtObject"> </s:TextArea> <s:TextArea height="33%" width="100%" id="txtList"> </s:TextArea> <s:TextArea height="33%" width="100%" id="txtDataTable"> </s:TextArea> </mx:VDividedBox> <s:Button label="Call Wcf" horizontalCenter="0" id="btnCall" click="doClick()" bottom="10"/> </s:Panel> </s:Application>
后记:在实际开发中发现flex ide环境对于wcf的wsdl解析要比asmx慢不止N倍,但是一旦解析完成,生成相应的as类后,在运行时二耆速度相同。