65.9K
CodeProject 正在变化。 阅读更多。
Home

在同一个 Visual Studio 解决方案中并发开发 Silverlight 和 WCF 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (6投票s)

2010年1月25日

CPOL

10分钟阅读

viewsIcon

33484

downloadIcon

408

本文介绍了一种在同一个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项目应如下所示

PJSilverlightWCFDemoWebJPG

为Silverlight应用程序添加启动画面

为了让Silverlight应用程序看起来更好,我们将为其添加一个启动画面。您可以参考文章“一个简单灵活的Silverlight启动画面”了解详情。在本文中,除了Visual Studio默认创建的“MainPage.xaml”之外,我们还将添加一个名为“Splash.xaml”的XAML文件。我们将使用“Splash.xaml”作为启动画面,而“MainPage.xaml”作为主要的Silverlight应用程序用户界面控件。还添加了一个名为“NiagaraFalls.jpg”的图像文件,用于在启动画面中显示。完成这些更改后,Silverlight项目在解决方案资源管理器中应如下所示

PJSilverlightWCFDemoJPG

然后修改“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服务。在该学生服务中,我们将添加一个名为StudentDataContract,它是一个具有四个公共属性的类。这些属性是StudentIDStudentNameScoreEvaluationTime。我们还将添加一个名为GetStudentsOperationContract,它是一个公共函数,接受一个整数值,表示要从WCF服务检索的学生记录数。GetStudents的返回值是一个Student对象的List,其中包含模拟的学生记录列表。

以下是公共接口IStudentServiceDataContract中的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”项目在解决方案资源管理器中的外观如下

PJWCFDemoServiceJPG

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浏览器窗口,如下所示

WCFServiceDebugJPG

在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服务返回的学生记录数。在按钮btnMakeWCFCallClick事件中,我们将调用WCF以获取学生记录列表并将其显示在DataGrid中。在按钮btnClearWCFCallResultClick事件中,我们将清除学生列表到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应用程序。

RunJPG

选择要检索的记录数,然后单击“Make WCD Call”按钮。您将看到学生记录已从WCF服务检索并在Silverlight应用程序中显示。

关注点

  1. 在同一个Visual Studio解决方案中开发WCF服务和Silverlight应用程序并非绝对必要。如果您不想这样做,可以在单独的解决方案中开发它们。但如果您负责WCF和Silverlight项目,那么在开发过程中确实会更加便捷。
  2. 为Silverlight应用程序添加WCF服务的最简单位置是ASP.NET宿主项目。但创建一个单独的WCF项目可以更好地模拟某些部署环境,当WCF服务不与宿主ASP.NET应用程序一起部署,甚至不在同一台Web服务器上时。
  3. 消费WCF服务的方式有很多种。本文仅根据个人偏好使用其中一种。
  4. WCFServiceFactory类用于访问作为单例实现的WCF服务,以节省初始化时间。通常情况下,这不会有问题。但在多线程环境中,如果出现问题,您可以将其更改,以便每次都返回一个新的IStudentService实例。
  5. 在本文中,WCF绑定是basicHttpBinding。我们还可以选择使用其他绑定。“basicHttpBinding”的选择仅是为了简单起见。如果性能是关注点,您可以选择使用一些二进制绑定。
  6. 宿主ASP.NET应用程序的“Web.config”文件中的配置信息以明文形式写入“Default.aspx”文件,并且“clientaccesspolicy.xml”将WCF服务的访问权限开放给所有人。所有这些都只是为了在开发过程中简化操作。在生产环境中,当安全性成为问题时,您需要处理安全细节。

历史

这是本文的第一个修订版。

© . All rights reserved.