Introduction
This article introduces a way of developing Silverlight and WCF applications in the same Visual Studio solution.
Background
A Silverlight application runs in a sandbox of web browsers, and it is unable to talk to any database data source directly due to security restrictions. Any serious IT application developed on the Silverlight platform will need to communicate with some WCF or Web Service application. This article shows a way to concurrently develop Silverlight and WCF applications in the same Visual Studio solution.
Depending on the organizational structure of the development team, it is not uncommon that the Silverlight application and the WCF application are developed by different programmers, while it is also possible that the same programmer is responsible for both the Silverlight and WCF development. For the latter case, it is desirable that the Silverlight project and the WCF project are included in the same Visual Studio solution, so a single compilation can take care of the code modifications in both projects, thus making debugging and testing more convenient.
The simplest way to achieve this is to add WCF into the Silverlight application's ASP.NET host project. But in the production environment, it is fairly common that the WCF application runs in a different website from the Silverlight's host web application. To better simulate this production environment in the development process, this article will demonstrate concurrent development by creating an independent WCF project in the same Visual Studio solution.
This article is a little comprehensive. If you are not familiar with developing Silverlight applications and hosting them in ASP.NET applications, you can refer to "A Simple Flexible Silverlight Splash Screen" and "Configure Silverlight 3 Applications using the Web.config File from ASP.NET". We will be using some of the techniques introduced in the two articles.
The sample code in this article is developed with Visual Studio 2008 and Silverlight 3. The following is a step by step introduction on how to achieve this concurrent development for Silverlight and WCF applications.
Create an Empty Silverlight Project
The first step is to create an empty Silverlight project. We will name the project "SilverlightWCFDemo". Will will also let Visual Studio's project wizard to create a host ASP.NET project for us and name the host project "SilverlightWCFDemoWeb". We will not go to the details on how to create Silverlight projects in Visual Studio in this article. If you are not familiar with this subject, you can refer to the articles "A Simple Flexible Silverlight Splash Screen" and "Configure Silverlight 3 Applications using the Web.config File from ASP.NET".
Both the Silverlight and the ASP.NET host projects will be created in the same Visual Studio solution by the wizard. We will name the solution the same as the Silverlight project, as "SilverlightWCFDemo".
Change the ASP.NET Project to Host the Silverlight Application from "Default.ASPX"
By default, Visual Studio creates three host files in the host ASP.NET project to host the Silverlight application. To make the code cleaner, we will only keep "Default.aspx" in the ASP.NET project and make it the only host web page. The article "Configure Silverlight 3 Applications using the Web.config File from ASP.NET" has more detailed instructions on this step. After the change, the "Default.aspx" file in the "SilverlightWCFDemoWeb" project will look like the following:
<!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 href="Styles/SilverlightDefault.css" rel="stylesheet" type="text/css" />
-->
<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>
In the above ASP.NET code, you will notice that the styles and the JavaScript scripts generated by Visual Studio used to support the hosting of the Silverlight application are wrapped into style and JavaScript files and linked into the "Default.aspx" file.
Besides hosting the Silverlight application, I also added an "ico" file named "huanhuan.ico" in the "Images" folder and linked it to the "Default.aspx" file to create a shortcut icon for the host ASP.NET application. After the above change, the ASP.NET project should look like the following in the Solution Explorer:
Add a Splash Screen to the Silverlight Application
To make the Silverlight application look better, we will be adding a splash screen to it. You can refer to the article "A Simple Flexible Silverlight Splash Screen" for the details. In this article, we will be adding a XAML file called "Splash.xaml" beyond the "MainPage.xaml" created by Visual Studio by default. We will be using "Splash.xaml" as the splash screen and "MainPage.xaml" as the main Silverlight application user interface control. A image file called "NiagaraFalls.jpg" is also added to be displayed in the splash screen. After these changes, the Silverlight project looks like the following in the Solution Explorer:
"Splash.xaml" is then changed to the following to embed the "NiagaraFalls.jpg" image.
<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>
Add an Empty WCF project "SilverlightWCFDemo" in the Visual Studio Solution and Create the WCF Service
We will be adding an empty WCF project to the Visual Studio solution "SilverlightWCFDemo" using the wizard in Visual Studio, and name the WCF project "WCFDemoService".
After adding the project "WCFDemoService", we will first delete the default WCF service created for us by Visual Studio. We then add a WCF service called "StudentService". In this student service, we will be adding a DataContract
called Student
, which is a class of four public properties. The properties are StudentID
, StudentName
, Score
, and EvaluationTime
. We will also add a OperationContract
called GetStudents
, which is a public function that takes an integer value indicating the number of student records to retrieve from the WCF service. The return value of GetStudents
is a List
of Student
objects filled with dummy data to simulate a list of student records.
The following is the C# code in the public interface IStudentService
and the DataContract
:
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; } }
}
}
The implementation of the interface IStudentService
is in the code-behind file of 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;
}
}
}
By completing the above steps, the project "WCFDemoService" looks like the following in Solution Explorer:
The binding of the WCF service is pretty flexible. In this article, we will be using basicHttpBinding
, so the serviceModel
section in the Web.config file in the "WCFDemoService" project looks like the following:
<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>
This completes the development of the WCF service. Right click on the "WCFDemoService" project in Solution Explorer, we can debug launch the WCF service, and a web browser window opens like the following:
In the address bar of the web browser, we can see the address of the WCF service. When Visual Studio creates the WCF service project, it assigns the port number 4701 to the project. We can make changes to this. In this article, let me not make any changes and use this port number as it is.
In order that the WCF service is accessible from the Silverlight application, we will need to add a "clientaccesspolicy.xml" and make its content like the following:
="1.0" ="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>
In the above XML file, we open the WCF service access to everyone for simplicity. In the production environment, we will need to configure this file so only authorized access is permitted. Detailed information to configure the WCF access can be found from the MSDN web page.
Add Configuration Information in the "SilverlightWCFDemoWeb" Project
At deployment time, the Silverlight application needs to know the address of the WCF service. We will be adding this information to the "Web.config" file of the hosting ASP.NET application project "SilverlightWCFDemoWeb". If you are not familiar with how to configure a Silverlight application from the "Web.config" file of the Silverlight's host ASP.NET application, you can refer to "Configure Silverlight 3 Applications using the Web.config File from ASP.NET".
The following XML section will be added to the "Web.config" file:
<appSettings>
<add key="StudentWCFAddress"
value="http://localhost:4701/StudentService.svc"/>
<add key="Author" value="Song Li"/>
<add key="DevelopmentTime" value="01/08/2010"/>
</appSettings>
The following C# code will be added to the code-behind file of "Default.aspx" in the "SilverlightWCFDemoWeb" project to transfer the configuration information to the Silverlight application.
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);
}
}
}
Consume the WCF Service from the Silverlight Application
Similar to "Configure Silverlight 3 Applications using the Web.config File from ASP.NET", the configuration information passed over to the Silverlight application is stored in a globally accessible public parameter in the "App
" class of the Silverlight application. The Silverlight application will be using the service address stored in DeploymentConfigurations
to access the WCF service.
After adding "WCFDemoService" as a service reference and naming the service reference as "StudentService
", I created a class WCFServiceFactory
in the Silverlight project to create the WCF service channel to make the WCF call cleaner in the Silverlight application.
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;
}
}
}
Due to the multi-threading nature of the WCF call in Silverlight applications, this factory class is implemented as a thread safe singleton. The above class reads the WCF service address passed over from the host ASP.NET application, so the deployment of both the Silverlight application and the WCF application becomes completely flexible.
To demonstrate how to consume the WCF service, we will make changes to the "MainPage.xaml" file like the following:
<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>
In MainPage.xaml, we added a DataGrid
, a ComboBox
, and two Button
objects. The ComboBox
will provide us the number of student records that we want the WCF service to return. In the Click
event of the the button btnMakeWCFCall
, we will be making the WCF call to obtain a list of the student records and show it in the DataGrid
. In the Click
event of the button btnClearWCFCallResult
, we will clear the binding of the student list to the DataGrid
. Given below is the C# code in the code-behind file of "MainPage.xaml":
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);
}
}
}
Run the Silverlight Application
Set "Always Start When Debugging" for both the "SilverlightWCFDemoWeb" and "WCFDemoService" projects to true, and set "SilverlightWCFDemoWeb" as the startup project and "Default.aspx" as the start page. We can see the Silverlight application shown in the web browser after we start debugging/running the application.
Make a selection for the number of records to retrieve, and click on the "Make WCD Call" button. You will see the student records are retrieved from the WCF service and displayed in the Silverlight application.
Points of Interest
- It is not an absolute necessity to develop WCF services and Silverlight applications in the same Visual Studio solution. If you do not want to do it, you can develop them in separate solutions. But if you are responsible for both the WCF and the Silverlight projects, it is definitely a convenience in the development.
- The simplest place to add a WCF service for the Silverlight application is in the ASP.NET host project. But creating a separate WCF project can better simulate some deployment environment when the WCF service is not deployed together with the host ASP.NET application or not even in the same web server.
- There are many ways to consume a WCF service. This article uses only one of them as a personal preference.
- The
WCFServiceFactory
class is used to access the WCF service implemented as a singleton to save initialization time. Normally, it should not be a problem. But if it becomes a problem in a multi-threaded environment, you can change it, so every time a new instance of IStudentService
is returned to you.
- In this article, the WCF binding is
basicHttpBinding
. We have options to use other bindings. The choice of "basicHttpBinding
" is just for simplicity. If performance is a concern, you can choose to use some binary bindings.
- The configuration information in the "Web.config" file of the host ASP.NET application is written as plain text in the "Default.aspx" file, and "clientaccesspolicy.xml" opens the access to the WCF service to every one. All these are just for simplicity in the development process. In a production environment, when security becomes a concern, you need to take care of the security details.
History
This is the first revision of this article.