今日更新本系列最后一篇,Silverlight訪問控制台宿主WCF,我們分別介紹tcp信道和http信道兩種方式。無論用IIS宿主還是控制台等程序宿主,對於WCF服務代碼沒有任何區別,但由於是Silverlight來進行調用,就會面臨一個跨域的問題,我們今天也主要是來演示如何解決這個問題的,其思路就是在WCF端增加一個跨域服務,當客戶端Silverlight調用的時候,Silverlight就會通過跨域服務得到跨域文件,這個方法也是之前在網上查找了大量的資料后,學習到的,在此記下並與大家分享。
首先,我們要知道跨域文件放到什么地方?前幾篇IIS宿主的時候已經講過,如果通過http綁定方式訪問,跨域文件則需要放到http協議指定的端口下面(例如http://localhost:8080,則應該放到8080端口網站下),而通過net.Tcp綁定的時候,跨域文件應該放到80端口的網站下面,這是Silverlight的官方解釋。好了話不多說,看代碼,同樣先展示一下項目結構。
項目結構
結構和以前一樣,四層結構:
類庫 | 說明 | 項目引用 |
LxContracts | 數據契約及操作契約 | System.Runtime.Serialization System.ServiceModel System.ServiceModel.Web |
LxSerivces | WCF服務的實現 | LxContracts [項目] System.Runtime.Serialization System.ServiceModel |
LxWcfHost | 控制台程序宿主WCF,需要修改Framework版本為.Net Framework 4.0 | LxContracts [項目] LxSerivces [項目] System.ServiceModel |
SilverlightClient | Silverlight客戶端進行服務的調用 |
代碼實現:
類庫LxContracts(包括數據契約student.cs 操作契約IStudent.cs 和 跨域契約IDomain.cs)

using System.Runtime.Serialization; namespace LxContracts { [DataContract] public class Student { /// <summary> /// 學生編號 /// </summary> [DataMember] public int StuId { get; set; } /// <summary> /// 學生姓名 /// </summary> [DataMember] public string StuName { get; set; } /// <summary> /// 所在班級 /// </summary> [DataMember] public string ClassName { get; set; } /// <summary> /// 聯系電話 /// </summary> [DataMember] public string TelPhoneNum { get; set; } } }

using System.ServiceModel; using System.Collections.Generic; namespace LxContracts { [ServiceContract] public interface IStudent { [OperationContract] List<Student> GetStudent(); } }
關鍵是這個跨域契約

using System.ServiceModel; using System.Runtime.Serialization; using System.ServiceModel.Channels; using System.ServiceModel.Web; namespace LxContracts { [ServiceContract] public interface IDomain { [OperationContract] [WebGet(UriTemplate = "ClientAccessPolicy.xml")] Message ProvidePolicyFile(); } }
類庫LxServicers(集合類StudentList.cs 服務類StudentService.cs 和 契約服務DomainService.cs)

using LxContracts; using System.Collections.Generic; namespace LxServices { public class StudentList : List<Student> { public StudentList() { this.Add(new Student() { StuId = 1, StuName = "小明", ClassName = "計算機一班", TelPhoneNum = "123456" }); this.Add(new Student() { StuId = 2, StuName = "小紅", ClassName = "計算機二班", TelPhoneNum = "234567" }); this.Add(new Student() { StuId = 2, StuName = "小蘭", ClassName = "計算機三班", TelPhoneNum = "890123" }); } } }

using LxContracts; using System.Collections.Generic; namespace LxServices { public class StudentService : IStudent { public List<Student> GetStudent() { //實際情況應該為從數據庫讀取 //本例手動生成一個StudentList StudentList ListStuent = new StudentList(); return ListStuent; } } }
關鍵是這個實現跨域服務

using LxContracts; using System.IO; using System.Xml; using System.ServiceModel.Channels; namespace LxServices { public class DomainService : IDomain { public System.ServiceModel.Channels.Message ProvidePolicyFile() { MemoryStream ms = new MemoryStream(); using (FileStream fs = File.OpenRead(@"clientaccesspolicy.xml")) { int length = (int)fs.Length; byte[] data = new byte[length]; fs.Position = 0; fs.Read(data, 0, length); ms = new MemoryStream(data); } XmlReader reader = XmlReader.Create(ms); Message result = Message.CreateMessage(MessageVersion.None, "", reader); return result; } } }
OK,服務代碼已經編寫好了,我們來進行控制台宿主,我們用配置文件先演示http方式宿主
宿主控制台LxWcfHost(我們為該控制台添加一App.config 文件 和 clientaccesspolicy.xml 跨域文件,並把跨域文件的屬性中的“復制到輸出目錄” 設置成為 “始終復制”)
此跨域文件把兩種跨域文件進行了合並,可以同時支持http和Tcp

<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="*"> <domain uri="*" /> </allow-from> <grant-to> <socket-resource port="4502-4534" protocol="tcp" /> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>

using System; using System.ServiceModel; namespace LxWcfHost { class Program { static void Main(string[] args) { ServiceHost host = new ServiceHost(typeof(LxServices.StudentService)); ServiceHost crossDomainserviceHost = new ServiceHost(typeof(LxServices.DomainService)); host.Opened += delegate { Console.WriteLine("服務已經啟動,按任意鍵終止..."); }; crossDomainserviceHost.Opened += delegate { Console.WriteLine("跨域服務已經啟動,按任意鍵終止..."); }; crossDomainserviceHost.Open(); host.Open(); Console.ReadKey(); host.Close(); host.Abort(); crossDomainserviceHost.Close(); crossDomainserviceHost.Abort(); } } }
使用Http綁定方式的App.config文件的配置如下:

<?xml version="1.0"?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="LxBehavior"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="false" /> </behavior> </serviceBehaviors> <endpointBehaviors> <behavior name="DomainServiceBehavior"> <webHttp/> </behavior> </endpointBehaviors> </behaviors> <services> <service name="LxServices.StudentService" behaviorConfiguration="LxBehavior"> <endpoint address="" binding="basicHttpBinding" contract="LxContracts.IStudent" /> <host> <baseAddresses> <add baseAddress="http://localhost:9090/StudentSrv" /> </baseAddresses> </host> </service> <service name="LxServices.DomainService"> <endpoint address="" behaviorConfiguration="DomainServiceBehavior" binding="webHttpBinding" contract="LxContracts.IDomain" /> <host> <baseAddresses> <add baseAddress="http://localhost:9090/" /> </baseAddresses> </host> </service> </services> <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> </system.serviceModel> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> </startup> </configuration>
注意:配置文件中的跨域服務的基地址是和WCF服務的基地址端口是一樣的都是9090
至此,我們的控制台宿主程序和WCF服務代碼都已經完成,我們來啟動一下服務:
SilverlightClient 客戶端代碼:

<UserControl xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="SilverlightClient.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"> <Grid x:Name="LayoutRoot" Background="White"> <sdk:DataGrid x:Name="dgStudnet" Grid.Row="0" AutoGenerateColumns="False"> <sdk:DataGrid.Columns> <sdk:DataGridTextColumn Header="學生編號" Width="80" Binding="{Binding StuId}" /> <sdk:DataGridTextColumn Header="學生姓名" Width="100" Binding="{Binding StuName}" /> <sdk:DataGridTextColumn Header="所在班級" Width="120" Binding="{Binding ClassName}" /> <sdk:DataGridTextColumn Header="電話號碼" Width="100" Binding="{Binding TelPhoneNum}" /> </sdk:DataGrid.Columns> </sdk:DataGrid> </Grid> </UserControl>
我們為該Silverlight客戶添加服務引用輸入:http://localhost:9090/StudentSrv

using System; using System.Windows.Controls; using System.Collections.ObjectModel; using SilverlightClient.WCF.StudentSrv; namespace SilverlightClient { public partial class MainPage : UserControl { ObservableCollection<Student> listStudent; public MainPage() { InitializeComponent(); listStudent = new ObservableCollection<Student>(); this.Loaded += new System.Windows.RoutedEventHandler(MainPage_Loaded); } void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e) { StudentClient proxyClient = new StudentClient(); proxyClient.GetStudentCompleted += new EventHandler<GetStudentCompletedEventArgs>(proxyClient_GetStudentCompleted); proxyClient.GetStudentAsync(); } void proxyClient_GetStudentCompleted(object sender, GetStudentCompletedEventArgs e) { if (e.Error == null) { listStudent = e.Result; this.dgStudnet.ItemsSource = listStudent; } } } }
OK,我們來調試一下,Silverlight客戶端進行一下調用WCF服務,結果如下圖:
Http綁定方式已經可以成功調用了,我們如何使用TCP方式呢,很簡單,我們只要修改一下App.config文件即可完成,如下:

<?xml version="1.0"?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="LxBehavior"> <serviceMetadata httpGetEnabled="false"/> <serviceDebug includeExceptionDetailInFaults="false"/> </behavior> </serviceBehaviors> <endpointBehaviors> <behavior name="DomainServiceBehavior"> <webHttp/> </behavior> </endpointBehaviors> </behaviors> <bindings> <netTcpBinding> <binding name="LxBinding"> <security mode="None"/> </binding> </netTcpBinding> </bindings> <services> <service name="LxServices.StudentService" behaviorConfiguration="LxBehavior"> <endpoint address="StudentService" binding="netTcpBinding" bindingConfiguration="LxBinding" contract="LxContracts.IStudent"/> <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/> <host> <baseAddresses> <add baseAddress="net.tcp://localhost:4505/"/> </baseAddresses> </host> </service> <service name="LxServices.DomainService"> <endpoint address="" behaviorConfiguration="DomainServiceBehavior" binding="webHttpBinding" contract="LxContracts.IDomain" /> <host> <baseAddresses> <add baseAddress="http://localhost:80/" /> </baseAddresses> </host> </service> </services> <serviceHostingEnvironment multipleSiteBindingsEnabled="true"/> </system.serviceModel> <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
注意:配置文件中的跨域服務的基地址已經變成了 http://localhost:80 端口了
我們將先前的Silverlight客戶端服務引用重新配置或者刪除后重新添加:
然后,再次運行Silverlight客戶端程序,也會得到相應的結果。
到此為止,Silverlight與WCF通信的常用方式,已經全部演示完畢,也是對自己的學習進行了一次總結並與大家進行分享,希望與大家共同交流,共同探討。