在同一个 Visual Studio 解决方案中并发开发 Silverlight 和 WCF 应用程序
本文介绍了一种在同一个Visual Studio解决方案中开发Silverlight和WCF应用程序的方法。
引言
本文介绍了一种在同一个Visual Studio解决方案中开发Silverlight和WCF应用程序的方法。
背景
Silverlight应用程序在Web浏览器的沙箱环境中运行,由于安全限制,它无法直接访问任何数据库数据源。在Silverlight平台上开发的任何严肃的IT应用程序都需要与某些WCF或Web Service应用程序进行通信。本文展示了一种在同一个Visual Studio解决方案中并发开发Silverlight和WCF应用程序的方法。
根据开发团队的组织结构,Silverlight应用程序和WCF应用程序可能由不同的程序员开发,但也可能由同一位程序员负责Silverlight和WCF的开发。对于后者,最好将Silverlight项目和WCF项目包含在同一个Visual Studio解决方案中,这样一次编译就可以处理两个项目中的代码修改,从而使调试和测试更加方便。
实现这一点的最简单方法是将WCF添加到Silverlight应用程序的ASP.NET宿主项目中。但在生产环境中,WCF应用程序通常运行在一个与Silverlight宿主Web应用程序不同的网站中。为了在开发过程中更好地模拟生产环境,本文将通过在同一个Visual Studio解决方案中创建一个独立的WCF项目来演示并发开发。
本文内容比较全面。如果您不熟悉开发Silverlight应用程序并将其托管在ASP.NET应用程序中,可以参考“一个简单灵活的Silverlight启动画面”和“使用ASP.NET的Web.config文件配置Silverlight 3应用程序”。我们将使用这两篇文章中介绍的一些技术。
本文中的示例代码是使用Visual Studio 2008和Silverlight 3开发的。以下是关于如何实现Silverlight和WCF应用程序的并发开发的步骤介绍。
创建一个空的Silverlight项目
第一步是创建一个空的Silverlight项目。我们将项目命名为“SilverlightWCFDemo”。我们还将让Visual Studio的项目向导为我们创建一个宿主ASP.NET项目,并将宿主项目命名为“SilverlightWCFDemoWeb”。本文将不详细介绍如何在Visual Studio中创建Silverlight项目。如果您对此不熟悉,可以参考文章“一个简单灵活的Silverlight启动画面”和“使用ASP.NET的Web.config文件配置Silverlight 3应用程序”。
向导将在同一个Visual Studio解决方案中创建Silverlight项目和ASP.NET宿主项目。我们将解决方案命名为与Silverlight项目相同的名称,“SilverlightWCFDemo”。
将ASP.NET项目更改为从“Default.ASPX”宿主Silverlight应用程序
默认情况下,Visual Studio会在宿主ASP.NET项目中创建三个宿主文件来承载Silverlight应用程序。为了使代码更整洁,我们将只保留宿主项目中的“Default.aspx”文件,并使其成为唯一的宿主网页。文章“使用ASP.NET的Web.config文件配置Silverlight 3应用程序”提供了有关此步骤的更详细说明。更改后,“SilverlightWCFDemoWeb”项目中的“Default.aspx”文件将如下所示
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Silverlight WCF Demonstration</title>
<link rel="SHORTCUT ICON" href="Images/huanhuan.ico" />
<!-- Link the Microsoft generated style -->
<link href="Styles/SilverlightDefault.css" rel="stylesheet" type="text/css" />
<!-- Link the Microsoft generated JAVA scripts -->
<script src="JS/Silverlight.js" type="text/javascript"></script>
<script src="JS/SilverlightDefault.js" type="text/javascript"></script>
</head>
<body>
<form id="form1" runat="server">
<div>
<object data="data:application/x-silverlight-2,"
type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/SilverlightWCFDemo.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="3.0.40624.0" />
<param name="autoUpgrade" value="true" />
<asp:Literal ID="ParamInitParams" runat="server"></asp:Literal>
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0"
style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181"
alt="Get Microsoft Silverlight"
style="border-style:none"/>
</a>
</object>
<iframe id="_sl_historyFrame"
style="visibility:hidden;height:0px;width:0px;border:0px">
</iframe>
</div>
</form>
</body>
</html>
在上面的ASP.NET代码中,您会注意到Visual Studio用于支持Silverlight应用程序托管的样式和JavaScript脚本被封装到样式和JavaScript文件中,并链接到“Default.aspx”文件中。
除了托管Silverlight应用程序外,我还将一个名为“huanhuan.ico”的“ico”文件添加到了“Images”文件夹中,并将其链接到“Default.aspx”文件,为宿主ASP.NET应用程序创建快捷方式图标。更改后,解决方案资源管理器中的ASP.NET项目应如下所示
为Silverlight应用程序添加启动画面
为了让Silverlight应用程序看起来更好,我们将为其添加一个启动画面。您可以参考文章“一个简单灵活的Silverlight启动画面”了解详情。在本文中,除了Visual Studio默认创建的“MainPage.xaml”之外,我们还将添加一个名为“Splash.xaml”的XAML文件。我们将使用“Splash.xaml”作为启动画面,而“MainPage.xaml”作为主要的Silverlight应用程序用户界面控件。还添加了一个名为“NiagaraFalls.jpg”的图像文件,用于在启动画面中显示。完成这些更改后,Silverlight项目在解决方案资源管理器中应如下所示
然后修改“Splash.xaml”以嵌入“NiagaraFalls.jpg”图像。
<UserControl x:Class="SilverlightWCFDemo.Splash"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Cursor="Wait">
<Grid x:Name="LayoutRoot" Background="White">
<Image Source="Images/NiagaraFalls.jpg" Width="600" />
</Grid>
</UserControl>
在Visual Studio解决方案中添加一个空的WCF项目“SilverlightWCFDemo”并创建WCF服务
我们将使用Visual Studio中的向导,向Visual Studio解决方案“SilverlightWCFDemo”添加一个空的WCF项目,并将WCF项目命名为“WCFDemoService”。
添加项目“WCFDemoService”后,我们将首先删除Visual Studio为我们创建的默认WCF服务。然后,我们添加一个名为“StudentService”的WCF服务。在该学生服务中,我们将添加一个名为Student
的DataContract
,它是一个具有四个公共属性的类。这些属性是StudentID
、StudentName
、Score
和EvaluationTime
。我们还将添加一个名为GetStudents
的OperationContract
,它是一个公共函数,接受一个整数值,表示要从WCF服务检索的学生记录数。GetStudents
的返回值是一个Student
对象的List
,其中包含模拟的学生记录列表。
以下是公共接口IStudentService
和DataContract
中的C#代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFDemoService
{
[ServiceContract]
public interface IStudentService
{
[OperationContract]
List<Student> GetStudents(int NoOfRecords);
}
[DataContract]
public class Student
{
private int studentID;
private string studentName;
private int score;
private DateTime evaluationTime;
[DataMember(Order = 1)]
public int StudentID { get { return studentID; }
set { studentID = value; } }
[DataMember(Order = 2)]
public string StudentName { get { return studentName; }
set { studentName = value; } }
[DataMember(Order = 3)]
public int Score { get { return score; }
set { score = value; } }
[DataMember(Order = 4)]
public DateTime EvaluationTime { get { return evaluationTime; }
set { evaluationTime = value; } }
}
}
接口IStudentService
的实现位于StudentService.svc的后台代码文件中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFDemoService
{
public class StudentService : IStudentService
{
public List<Student> GetStudents(int NoOfRecords)
{
List<Student> studentList = new List<Student>();
Random rd = new Random();
for (int Idex = 1; Idex <= NoOfRecords; Idex++)
{
Student student = new Student();
student.StudentID = Idex;
student.StudentName = "Student Name No." + Idex.ToString();
student.Score = (int) (60 + rd.NextDouble() * 40);
student.EvaluationTime = System.DateTime.Now;
studentList.Add(student);
}
return studentList;
}
}
}
完成上述步骤后,“WCFDemoService”项目在解决方案资源管理器中的外观如下
WCF服务的绑定非常灵活。在本文中,我们将使用basicHttpBinding
,因此“WCFDemoService”项目中的Web.config文件中的serviceModel
部分如下所示
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="WCFDemoService.StudentServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="WCFDemoService.StudentServiceBehavior"
name="WCFDemoService.StudentService">
<endpoint address="" binding="basicHttpBinding"
contract="WCFDemoService.IStudentService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
至此,WCF服务开发完成。右键单击解决方案资源管理器中的“WCFDemoService”项目,我们可以调试启动WCF服务,此时会打开一个Web浏览器窗口,如下所示
在Web浏览器的地址栏中,我们可以看到WCF服务的地址。当Visual Studio创建WCF服务项目时,它会为项目分配端口号4701。我们可以对此进行更改。在本文中,我将不进行任何更改,并按原样使用此端口号。
为了使Silverlight应用程序能够访问WCF服务,我们需要添加一个“clientaccesspolicy.xml”文件,并使其内容如下
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers="*">
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
在上面的XML文件中,为了简单起见,我们将WCF服务的访问权限开放给所有人。在生产环境中,您需要配置此文件,以便只允许授权访问。有关配置WCF访问的详细信息,可以在MSDN网页中找到。
在“SilverlightWCFDemoWeb”项目中添加配置信息
部署时,Silverlight应用程序需要知道WCF服务的地址。我们将此信息添加到宿主ASP.NET应用程序项目“SilverlightWCFDemoWeb”的“Web.config”文件中。如果您不熟悉如何通过Silverlight宿主ASP.NET应用程序的“Web.config”文件来配置Silverlight应用程序,可以参考“使用ASP.NET的Web.config文件配置Silverlight 3应用程序”。
以下XML节将添加到“Web.config”文件中
<appSettings>
<add key="StudentWCFAddress"
value="https://:4701/StudentService.svc"/>
<add key="Author" value="Song Li"/>
<add key="DevelopmentTime" value="01/08/2010"/>
</appSettings>
以下C#代码将添加到“SilverlightWCFDemoWeb”项目中“Default.aspx”的后台代码文件中,以便将配置信息传递给Silverlight应用程序。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections.Specialized;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Configuration;
using System.Text;
namespace SilverlightWCFDemoWeb
{
public partial class Default : System.Web.UI.Page
{
private void SaveSilverlightDeploymentSettings(Literal litSettings)
{
NameValueCollection appSettings = ConfigurationManager.AppSettings;
StringBuilder SB = new StringBuilder();
SB.Append("<param name=\"InitParams\" value=\"");
int SettingCount = appSettings.Count;
for (int Idex = 0; Idex < SettingCount; Idex++)
{
SB.Append(appSettings.GetKey(Idex));
SB.Append("=");
SB.Append(appSettings[Idex]);
SB.Append(",");
}
SB.Remove(SB.Length - 1, 1);
SB.Append("\" />");
litSettings.Text = SB.ToString();
}
protected void Page_Load(object sender, EventArgs e)
{
Response.Cache.SetCacheability(HttpCacheability.NoCache);
SaveSilverlightDeploymentSettings(ParamInitParams);
}
}
}
从Silverlight应用程序中消费WCF服务
与“使用ASP.NET的Web.config文件配置Silverlight 3应用程序”类似,传递给Silverlight应用程序的配置信息存储在Silverlight应用程序的“App
”类中的一个全局可访问的公共参数中。Silverlight应用程序将使用存储在DeploymentConfigurations
中的服务地址来访问WCF服务。
在添加“WCFDemoService”作为服务引用并将其命名为“StudentService
”后,我在Silverlight项目中创建了一个名为WCFServiceFactory
的类来创建WCF服务通道,从而使Silverlight应用程序中的WCF调用更加简洁。
using System;
using System.Net;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ServiceModel;
using System.Collections.Generic;
namespace SilverlightWCFDemo
{
public class WCFServiceFactory
{
private static WCFServiceFactory _thisInstance = null;
private static object _threadLock = new Object();
private App application;
private StudentService.IStudentService _iStudentService;
public StudentService.IStudentService iStudentService
{ get { return _iStudentService; } }
private WCFServiceFactory()
{
application = (App)Application.Current;
BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
EndpointAddress endpointAddress = new EndpointAddress(
application.DeploymentConfigurations["StudentWCFAddress"]);
_iStudentService = new ChannelFactory<StudentService.IStudentService>
(basicHttpBinding, endpointAddress).CreateChannel();
}
public static void InitializaFactory()
{
lock (_threadLock)
if (_thisInstance == null)
_thisInstance = new WCFServiceFactory();
}
public static WCFServiceFactory GetInstance()
{
InitializaFactory();
return _thisInstance;
}
}
}
由于Silverlight应用程序中WCF调用的多线程特性,此类被实现为一个线程安全的单例。上面的类读取从宿主ASP.NET应用程序传递过来的WCF服务地址,因此Silverlight应用程序和WCF应用程序的部署变得完全灵活。
为了演示如何消费WCF服务,我们将对“MainPage.xaml”文件进行如下修改
<UserControl
xmlns:data="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
x:Class="SilverlightWCFDemo.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"
FontFamily="Verdana">
<Grid x:Name="LayoutRoot" Margin="20, 20, 20, 20">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="15"/>
<RowDefinition Height="*"/>
<RowDefinition Height="45"/>
<RowDefinition Height="45"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="Concurrent development of Silverlight &
WCF in the Same Visual Studio Solution"
HorizontalAlignment="Center"
FontSize="14" Foreground="Brown"
FontWeight="Bold" />
<TextBlock x:Name="lblAuthorInformation" Grid.Row="1"
HorizontalAlignment="Center"
FontSize="10" Foreground="Green"
FontWeight="Bold" />
<data:DataGrid Grid.Row="2"
x:Name="dgResult" AutoGenerateColumns="True"
HeadersVisibility="All" GridLinesVisibility="All"
RowHeight="25" RowBackground="LightPink"
ColumnHeaderHeight="50"
IsReadOnly="True" CanUserResizeColumns="True"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="10, 10, 10, 10"/>
<Grid x:Name="SelectionRoot" Grid.Row="3"
HorizontalAlignment="Right"
Margin="10, 10, 10, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="250"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontWeight="Bold">
Number of Student Records to Retrieve
</TextBlock>
<ComboBox x:Name="cmbNoOfStudentRecords" Grid.Column="2">
<ComboBoxItem IsSelected="True" Tag="*"
Content="** Please Select **" />
<ComboBoxItem Tag="5" Content="5" />
<ComboBoxItem Tag="10" Content="10" />
<ComboBoxItem Tag="15" Content="15" />
<ComboBoxItem Tag="20" Content="20" />
</ComboBox>
</Grid>
<Grid x:Name="ButtonRoot" Grid.Row="4" HorizontalAlignment="Right"
Margin="10, 5, 10, 5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="250"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="btnClearWCFCallResult" Grid.Column="0" FontSize="12"
Content="Clear WCF Call Result"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Click="btnClearWCFCallResult_Click"/>
<Button x:Name="btnMakeWCFCall" Grid.Column="2" FontSize="12"
Content="Make WCF Call"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Click="btnMakeWCFCall_Click"/>
</Grid>
</Grid>
</UserControl>
在MainPage.xaml中,我们添加了一个DataGrid
、一个ComboBox
和两个Button
控件。ComboBox
将提供我们要WCF服务返回的学生记录数。在按钮btnMakeWCFCall
的Click
事件中,我们将调用WCF以获取学生记录列表并将其显示在DataGrid
中。在按钮btnClearWCFCallResult
的Click
事件中,我们将清除学生列表到DataGrid
的绑定。下面是“MainPage.xaml”后台代码文件中的C#代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ServiceModel;
using System.Collections.ObjectModel;
using System.Text;
namespace SilverlightWCFDemo
{
public partial class MainPage : UserControl
{
private IDictionary<string, string> DeploymentConfigurations;
public MainPage()
{
InitializeComponent();
App application = (App)Application.Current;
DeploymentConfigurations = application.DeploymentConfigurations;
StringBuilder SB = new StringBuilder();
SB.Append("Developed by ");
SB.Append(DeploymentConfigurations["Author"]);
SB.Append(" on ");
SB.Append(DeploymentConfigurations["DevelopmentTime"]);
lblAuthorInformation.Text = SB.ToString();
this.Cursor = Cursors.Arrow;
}
private void btnClearWCFCallResult_Click(object sender, RoutedEventArgs e)
{
dgResult.ItemsSource = null;
GC.Collect();
}
private void btnMakeWCFCall_Click(object sender, RoutedEventArgs e)
{
ComboBoxItem aItem =
(ComboBoxItem)cmbNoOfStudentRecords.SelectedItem;
if (aItem.Tag.ToString() == "*")
{
MessageBox.Show("Please select the number " +
"of the student records to retrieve");
return;
}
int NoofStudentRecord = System.Convert.ToInt16(aItem.Tag.ToString());
AsyncCallback aSyncCallBack = delegate(IAsyncResult result)
{
ObservableCollection<StudentService.Student> studentList;
try
{
studentList = ((StudentService.IStudentService)
result.AsyncState).EndGetStudents(result);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{ dgResult.ItemsSource = studentList;});
}
catch (Exception ex)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
MessageBox.Show(ex.Message));
return;
}
};
WCFServiceFactory.GetInstance().iStudentService.BeginGetStudents(
NoofStudentRecord, aSyncCallBack,
WCFServiceFactory.GetInstance().iStudentService);
}
}
}
运行Silverlight应用程序
将“SilverlightWCFDemoWeb”和“WCFDemoService”项目的“Always Start When Debugging”(调试时始终启动)设置为true,并将“SilverlightWCFDemoWeb”设置为启动项目,“Default.aspx”设置为起始页。当您开始调试/运行应用程序时,我们可以在Web浏览器中看到Silverlight应用程序。
选择要检索的记录数,然后单击“Make WCD Call”按钮。您将看到学生记录已从WCF服务检索并在Silverlight应用程序中显示。
关注点
- 在同一个Visual Studio解决方案中开发WCF服务和Silverlight应用程序并非绝对必要。如果您不想这样做,可以在单独的解决方案中开发它们。但如果您负责WCF和Silverlight项目,那么在开发过程中确实会更加便捷。
- 为Silverlight应用程序添加WCF服务的最简单位置是ASP.NET宿主项目。但创建一个单独的WCF项目可以更好地模拟某些部署环境,当WCF服务不与宿主ASP.NET应用程序一起部署,甚至不在同一台Web服务器上时。
- 消费WCF服务的方式有很多种。本文仅根据个人偏好使用其中一种。
WCFServiceFactory
类用于访问作为单例实现的WCF服务,以节省初始化时间。通常情况下,这不会有问题。但在多线程环境中,如果出现问题,您可以将其更改,以便每次都返回一个新的IStudentService
实例。- 在本文中,WCF绑定是
basicHttpBinding
。我们还可以选择使用其他绑定。“basicHttpBinding
”的选择仅是为了简单起见。如果性能是关注点,您可以选择使用一些二进制绑定。 - 宿主ASP.NET应用程序的“Web.config”文件中的配置信息以明文形式写入“Default.aspx”文件,并且“clientaccesspolicy.xml”将WCF服务的访问权限开放给所有人。所有这些都只是为了在开发过程中简化操作。在生产环境中,当安全性成为问题时,您需要处理安全细节。
历史
这是本文的第一个修订版。