Silverlight从其它系统获取外部数据的常规途径无非下面2种:
1、直接远程加载文本或xml文件 (直接请求ashx/aspx,然后在ashx/aspx上输出信息也可以归入这一类)
2、通过wcf/webService取得数据
(当然,sl跟本机的sl之间也能交换数据,但这个用处有限,此外通过socket也能拿到数据,但是socket要玩好并不容易,难度系数有点高,本文不做讨论)
而返回的数据格式,最常用的通常为"xml"、"json字符串"(或普通字符串) 或 "最原始的Stream"
今天在学习FluorineFx(一个开源的免费项目),并查看它的演示示例时,意外发现FluorineFx也支持silverlight!
与webService采用的soap协议不同:FluorineFx支持Adobe的AMF0,AMF3,RTMP协议,能方便的与Adobe几乎所有的通讯技术交互,这为silverlight与flash/flex交互提供了可能。(详见百度百科FluorineFX http://baike.baidu.com/view/1654458.htm?fr=ala0_1)
下面简单说下silverlight中使用FluorineFx的大概步骤:
基本上 silverlight本身只是一种UI技术,自身并无太强的的数据处理能力(独立存储虽然提供了数据存储和检索能力,但能力实在有限),要获取数据只能借助其它系统或技术,所以我们先把其它系统做好:
1、先用VS.Net(我用的是vs2010)创建一个Library项目,起名为ServiceLib,并在里面创建一个TestLib.cs的类,代码如下:
01 |
using System.ComponentModel;
|
02 |
using System.Data;
|
03 |
using FluorineFx;
|
04 |
|
05 |
namespace ServiceLib
|
06 |
{ |
07 |
[RemotingService]
|
08 |
[Description( "Test Service" )]
|
09 |
public class TestLib
|
10 |
{
|
11 |
[DataTableType( "SliverlightApp.Person" )]
|
12 |
public DataTable GetPersonList()
|
13 |
{
|
14 |
DataTable tbl = new DataTable();
|
15 |
tbl.Columns.Add( "Name" , typeof ( string ));
|
16 |
tbl.Columns.Add( "Age" , typeof (System.Int32));
|
17 |
tbl.Rows.Add( "菩提树下的杨过" , 30);
|
18 |
tbl.Rows.Add( "小龙女" , 100);
|
19 |
return tbl;
|
20 |
}
|
21 |
}
|
22 |
} |
当然,这个项目要引用FluorineFx程序集,该项目的主要用意在于把"取数据"的业务逻辑封装在这一层,以方便重用。
2、再创建一个webApplication,起名为WebApp,同样要添加FluorineFx.dll的引用
2.1 然后创建一个名为Gateway.aspx的文件,这样就行了,不用添加任何多余的代码(这个文件作为调用FluorineFx的网关)
2.2 在根目录下,创建目录Web-INF/flex (即二层目录),然后在flex目录下,放置一个services-config.xml,内容如下:
01 |
<? xml version = "1.0" encoding = "utf-8" ?>
|
02 |
< services-config >
|
03 |
< services >
|
04 |
< service id = "remoting-service" class = "flex.messaging.services.RemotingService" |
05 |
|
06 |
messageTypes = "flex.messaging.messages.RemotingMessage" >
|
07 |
< destination id = "fluorine" >
|
08 |
< channels >
|
09 |
< channel ref = "my-amf" />
|
10 |
</ channels >
|
11 |
< properties >
|
12 |
< source >*</ source >
|
13 |
</ properties >
|
14 |
</ destination >
|
15 |
</ service >
|
16 |
</ services >
|
17 |
|
18 |
< channels >
|
19 |
< channel-definition id = "my-amf" class = "mx.messaging.channels.AMFChannel" >
|
20 |
< endpoint uri = "http://{server.name}:{server.port}/{context.root}/Gateway.aspx" |
21 |
|
22 |
class = "flex.messaging.endpoints.AMFEndpoint" />
|
23 |
< properties >
|
24 |
<!-- <legacy-collection>true</legacy-collection> -->
|
25 |
</ properties >
|
26 |
</ channel-definition >
|
27 |
</ channels >
|
28 |
</ services-config >
|
照抄就好了,不用管太多。基本上这个配置的作用就相当于添加wcf(svc文件)后,系统自动在web.config中增加的配置节点,用于提供一些必要的配置信息.
2.3修改web.config的httpModules节点为以下内容
1 |
< httpModules >
|
2 |
< add name = "ScriptModule" type = "System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
|
3 |
< add name = "FluorineGateway" type = "FluorineFx.FluorineGateway, FluorineFx" />
|
4 |
</ httpModules >
|
2.4 添加对ServiceLib项目的引用
3、最后创建一个silverlight项目,添加FluorineFx.dll引用,命名为SliverlightApp
注意:FluorineFx.dll有二个版本,一个用于webform,一个专用于silverlight(本文最后会给出下载)
通常用vs.net创建一个silverlight项目时,会提示你是否把该项目承载于一个webApplication项目中,以方便测试,这里直接指定第2步中的webApp为承载项目(即相当于webApp项目添加对SliverlightApp的引用)
在silverlight中访问FluorineFx的关键代码如下:
01 |
using FluorineFx;
|
02 |
using FluorineFx.AMF3;
|
03 |
using FluorineFx.Messaging.Api.Service;
|
04 |
using FluorineFx.Net;
|
05 |
|
06 |
... |
07 |
|
08 |
//点击按钮时,开始调用 |
09 |
private void btnFluorineFx_Click( object sender, RoutedEventArgs e)
|
10 |
{
|
11 |
NetConnection _netConnection = new NetConnection();
|
12 |
_netConnection.ObjectEncoding = ObjectEncoding.AMF3;
|
13 |
_netConnection.NetStatus += new NetStatusHandler(_netConnection_NetStatus);
|
14 |
_netConnection.Connect( "http://localhost:1718/Gateway.aspx" );
|
15 |
_netConnection.Call( "ServiceLib.TestLib.GetPersonList" , new GetPersonHandler( this ));
|
16 |
|
17 |
}
|
18 |
|
19 |
//状态回调 |
20 |
private void _netConnection_NetStatus( object sender, NetStatusEventArgs e)
|
21 |
{
|
22 |
string level = e.Info[ "level" ] as string ;
|
23 |
this .Dispatcher.BeginInvoke(() => { this .txtResult.Text = "level:" + level + ",code:" + e.Info
|
24 |
|
25 |
[ "code" ] as String; });
|
26 |
|
27 |
}
|
28 |
|
29 |
|
30 |
//数据回调处理 |
31 |
private class GetPersonHandler : IPendingServiceCallback
|
32 |
{
|
33 |
MainPage page;
|
34 |
|
35 |
public GetPersonHandler(MainPage page)
|
36 |
{
|
37 |
this .page = page;
|
38 |
}
|
39 |
|
40 |
public void ResultReceived(IPendingServiceCall call)
|
41 |
{
|
42 |
page.Dispatcher.BeginInvoke(() => { page.txtResult.Text = "" ; });
|
43 |
object result = call.Result;
|
44 |
ArrayCollection items = result as ArrayCollection;
|
45 |
foreach ( object item in items)
|
46 |
{
|
47 |
Person p = item as Person; //注意:这里直接将数据反序列化为Person了
|
48 |
|
49 |
page.Dispatcher.BeginInvoke(() => { page.txtResult.Text += p.ToString() + ";" ; });
|
50 |
|
51 |
|
52 |
}
|
53 |
|
54 |
}
|
55 |
}
|
当然还有一个数据实体类Person.cs
01 |
using System;
|
02 |
|
03 |
namespace SliverlightApp
|
04 |
{ |
05 |
public class Person
|
06 |
{
|
07 |
public string Name { set ; get ; }
|
08 |
public int Age { set ; get ; }
|
09 |
|
10 |
public override string ToString() {
|
11 |
return "name:" + Name + ",age:" + Age.ToString();
|
12 |
}
|
13 |
}
|
14 |
} |
4、最后回过头来,在webApp中把(创建silverlight项目时自动生成的)SliverlightAppTestPage.aspx设置为启动页测试就行了
整个解决方案的目录结构如下:
分析:
传统的soap协议是采用xml格式的,而xml格式的最大问题就是数据太大,比如一个普通的"hello world"字符串,经过xml格式封装后,可能变成<string>hello world</string>,再加上文件头部的xml文档声明,传输数据量最终会增加不少。
为了改进,Adobe发明了AMF0/AMF3协议,AMF是Adobe独家开发出来的通信协议,它采用二进制压缩,序列化、反序列化、传输数据,从而为Flash 播放器与Flash Remoting网关通信提供了一种轻量级的、高效能的通信方式。
所以FluorineFx相对于基于soap协议的web service/wcf而言,应该是效率会更高,不过我们也应该看到微软的进步:wcf在传输数据时,除了xml格式,还可以用json格式甚至直接最原始的stream流格式。为了比较,我在代码中还特意加了test.svc 以json格式返回数据,用于跟fluorinefx做下对比比(xml格式就懒得比较了,传输数据量肯定要大于json格式)--test.svc里的具体代码如下:
001 |
using System.Collections.Generic;
|
002 |
using System.Data;
|
003 |
using System.IO;
|
004 |
using System.ServiceModel;
|
005 |
using System.ServiceModel.Activation;
|
006 |
using System.ServiceModel.Web;
|
007 |
using System.Text;
|
008 |
|
009 |
namespace WebApp
|
010 |
{ |
011 |
[ServiceContract(Namespace = "" )]
|
012 |
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
|
013 |
public class test
|
014 |
{
|
015 |
|
016 |
/// <summary>
|
017 |
/// 利用系统自动封装成json格式
|
018 |
/// </summary>
|
019 |
/// <returns></returns>
|
020 |
[OperationContract]
|
021 |
[WebInvoke(Method = "GET" , ResponseFormat = WebMessageFormat.Json)]
|
022 |
public List<SliverlightApp.Person> GetPersonList()
|
023 |
{
|
024 |
List<SliverlightApp.Person> lst = new List<SliverlightApp.Person>();
|
025 |
lst.Add( new SliverlightApp.Person() { Name = "菩提树下的杨过" , Age = 30 });
|
026 |
lst.Add( new SliverlightApp.Person() { Name = "小龙女" , Age = 100 });
|
027 |
return lst;
|
028 |
}
|
029 |
|
030 |
/// <summary>
|
031 |
/// 自己封装成json格式
|
032 |
/// </summary>
|
033 |
/// <returns></returns>
|
034 |
[OperationContract]
|
035 |
[WebInvoke(Method = "GET" )]
|
036 |
public Stream GetPersonList2()
|
037 |
{
|
038 |
DataTable tbl = new DataTable();
|
039 |
tbl.Columns.Add( "Name" , typeof ( string ));
|
040 |
tbl.Columns.Add( "Age" , typeof (System.Int32));
|
041 |
tbl.Rows.Add( "菩提树下的杨过" , 30);
|
042 |
tbl.Rows.Add( "小龙女" , 100);
|
043 |
return GetStream(CreateJsonParameters(tbl));
|
044 |
}
|
045 |
|
046 |
|
047 |
/// <summary>
|
048 |
/// 将datatable转化成json字符串
|
049 |
/// </summary>
|
050 |
/// <param name="dt"></param>
|
051 |
/// <returns></returns>
|
052 |
private string CreateJsonParameters(DataTable dt)
|
053 |
{
|
054 |
StringBuilder JsonString = new StringBuilder();
|
055 |
if (dt != null && dt.Rows.Count > 0)
|
056 |
{
|
057 |
JsonString.Append( "{ " );
|
058 |
JsonString.Append( "\"Head\":[ " );
|
059 |
for ( int i = 0; i < dt.Rows.Count; i++)
|
060 |
{
|
061 |
JsonString.Append( "{ " );
|
062 |
for ( int j = 0; j < dt.Columns.Count; j++)
|
063 |
{
|
064 |
if (j < dt.Columns.Count - 1)
|
065 |
{
|
066 |
JsonString.Append( "\"" + dt.Columns[j].ColumnName.ToString().Replace( "\"" , "\\\"" ) + "\":" + "\"" + dt.Rows[i][j].ToString().Replace( "\"" , "\\\"" ) + "\"," );
|
067 |
}
|
068 |
else if (j == dt.Columns.Count - 1)
|
069 |
{
|
070 |
JsonString.Append( "\"" + dt.Columns[j].ColumnName.ToString().Replace( "\"" , "\\\"" ) + "\":" + "\"" + dt.Rows[i][j].ToString().Replace( "\"" , "\\\"" ) + "\"" );
|
071 |
}
|
072 |
}
|
073 |
|
074 |
if (i == dt.Rows.Count - 1)
|
075 |
{
|
076 |
JsonString.Append( "} " );
|
077 |
}
|
078 |
else
|
079 |
{
|
080 |
JsonString.Append( "}, " );
|
081 |
}
|
082 |
}
|
083 |
JsonString.Append( "]}" );
|
084 |
return JsonString.ToString();
|
085 |
}
|
086 |
else
|
087 |
{
|
088 |
return null ;
|
089 |
}
|
090 |
}
|
091 |
|
092 |
/// <summary>
|
093 |
/// 辅助方法,用于输出流
|
094 |
/// </summary>
|
095 |
/// <param name="str"></param>
|
096 |
/// <returns></returns>
|
097 |
private Stream GetStream( string str)
|
098 |
{
|
099 |
MemoryStream ms = new MemoryStream();
|
100 |
StreamWriter sw = new StreamWriter(ms);
|
101 |
sw.AutoFlush = true ;
|
102 |
sw.Write(str);
|
103 |
ms.Position = 0;
|
104 |
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain" ;
|
105 |
return ms;
|
106 |
}
|
107 |
}
|
108 |
} |
这是用httpwatch在firefox下测试的结果:
如果用最原始的stream方法封装json数据,返回的数据为
如果用系统提供的json自动封装,返回的数据为
而FluorineFx是以二进制返回的,不方便直接观察字符串,只能直接反序列化为Peron类,就不贴出结果了。
从运行图的Received列上可以看出:“FluorineFx返回的数据大小-375” 要小于“wcf默认封装的json数据-389”,但大于“开发者自行处理的json数据大小-312”
再比较Time列,FluorineFx所用的时间是最小的(当然多测试几次,结果稍有不同,但经过我的多次观察,FluorineFx所花的时间始终是最小的)
综合比较下来:FluorineFx传输的数量小,传输时间短,整体效率是不错的,确实是silverlight/.net与其它系统高效传输数据的可选方式之一。
文中所用源代码下载:http://cid-2959920b8267aaca.office.live.com/self.aspx/Silverlight/FluorineFx.rar