Background
The Windows Communication Foundation (WCF) is an application programming interface in the .NET Framework for building connected, service-oriented applications. A WCF client has two ways to access the functions provided by WCF services. They are synchronous and asynchronous WCF calls.This article is an example to show you how to access WCF services in both ways, synchronously as well as asynchronously. This article assumes that the readers have some basic understandings of the WCF services. If you are not familiar with WCF services, this link is a good place to get you started. In this article, I will first build a WCF service. I will then show you how to access this service. The following is a screen shot of this example Visual Studio solution in the solution explorer:
- The "
WCFServiceImplementation" project is a class library that implements the WCF service. - The "
WCFServiceHost" project is an ASP.NET web application that hosts the WCF service implemented in the "WCFServiceImplementation" project. - The "
WCFCallExample" project is a WPF application, where I will demonstrate the two ways to access the WCF service.
The Implementation of the WCF Service
WCFServiceImplementation" is a class library. The entire WCF service is implemented in this project in the "StudentService.cs" file:using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFServiceImplementation
{
[ServiceContract]
public interface IStudentService
{
[OperationContract]
List<Student> GetStudents(int NoOfStudents);
}
[DataContract]
public class Student
{
[DataMember(Order = 1)]
public int StudentID { get; set; }
[DataMember(Order = 2)]
public string StudentName { get; set; }
[DataMember(Order = 3)]
public int Score { get; set; }
[DataMember(Order = 4)]
public DateTime EvaluationTime { get; set; }
}
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);
}
System.Threading.Thread.Sleep(10000);
return studentList;
}
}
}
This class library defines the following:- A "data contract" "
Student",which is the class to store the information for an individual student. - A "service contract" "Interface" "
IStudentService". This "Interface" defines what functions the WCF service should expose to the clients. In this case, it exposes an "operation contract" "GetStudents".
IStudentService" is implemented in the "StudentService" class. When accessing the WCF service, a WCF client can make a WCF call to the "GetStudents" method by passing the number of the student records to the proxy method at the client side. The "GetStudents" method then randomly generates a List of "Student" objects and sends the List of these objects to the caller. You may notice that "System.Threading.Thread.Sleep(10000)" is added to the "GetStudents"
method to artificially delay the response of the WCF service, so we can
see the difference between the synchronous and asynchronous WCF calls
at the client side.The WCF Service Host Application "WCFServiceHost"
The WCF service is hosted in the ASP.NET application project "WCFServiceHost", which is shown as the following in the solution explorer:WCFServiceImplementation" class library, the "StudentService.svc" file is as simple as the following: <%@ ServiceHost Language="C#" Debug="true"
Service="WCFServiceImplementation.StudentService" %>
The configuration of the WCF service in the "Web.config" file is the following:<system.serviceModel>
<services>
<service behaviorConfiguration="WCFServiceHost.ServiceBehavior"
name="WCFServiceImplementation.StudentService">
<endpoint address="" binding="basicHttpBinding"
contract="WCFServiceImplementation.IStudentService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WCFServiceHost.ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
If we now right-click on the file "StudentService.svc" and
select "View in Browser" in the solution explorer, a web browser window
is open showing that the WCF service is functioning. The end-point address of the WCF service is shown in the address bar of the browser:The Example WCF Client Application "WCFCallExample"
The following is the simple WPF application "WCFCallExample" shown in the solution explorer. This application is the example client application to access the WCF service built before.StudentService" is added to the project using the "Adding service reference" utility provided by the Visual Studio. After adding the "StudentService" reference, the Visual Studio will generate all the client proxies
for accessing the WCF service. When adding this reference, you will
need to click the "Advanced ..." button and open the "Service Reference
Settings" window. You need to make sure that the "Generate asynchronous
operations" checkbox is checked:The "WPF user control" "
WCFCallExample" implemented in the "WCFCallExample.xaml"
and its code-behind file will be used to demonstrate both synchronous
and asynchronous calls to the WCF service using the proxies generated.
The "WCFCallExample.xaml" file is shown as the following: <UserControl x:Class="WFCCallExample.WCFCallExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:Toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="23" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Style="{StaticResource SmallBold}">
Select the number of the students:
</TextBlock>
<ComboBox Margin="10,0,0,0" Name="cmbNumberOfStudents" Width="80"
SelectionChanged="cmbNumberOfStudents_SelectionChanged" />
<Button Margin="10,0,0,0" Width="150" Click="WCFServiceCall_Click">
Make WCF service call</Button>
<Button Margin="10,0,0,0" Width="150" Click="ClickMe_Click">
Click me!</Button>
</StackPanel>
<Toolkit:DataGrid Margin="0,10,0,10" AutoGenerateColumns="False"
Grid.Row="1" Name="DGStudent" IsReadOnly="True">
<Toolkit:DataGrid.Columns>
<Toolkit:DataGridTextColumn Header="StudentID"
Binding="{Binding StudentID}" />
<Toolkit:DataGridTextColumn Header="StudentName"
Binding="{Binding StudentName}" />
<Toolkit:DataGridTextColumn Header="Score" Binding="{Binding Score}" />
<Toolkit:DataGridTextColumn Header="EvaluationTime"
Binding="{Binding EvaluationTime}" />
</Toolkit:DataGrid.Columns>
</Toolkit:DataGrid>
</Grid>
</UserControl>
This "XAML" file defines the following major functional "UI elements":- A "ComboBox" to select the number of the students to retrieve from the WCF service.
- A "DataGrid" to display the information of the students obtained from the WCF service.
- A "Button" labeled as "Make WCF service call" to issue the WCF calls.
- A "Button" labeled as "Click me!" to pop up a simple "Messagebox". This button is used to show if the UI thread is blocked by the WCF call.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ServiceModel;
namespace WFCCallExample
{
/// <summary>
/// Interaction logic for WCFCallExample.xaml
/// </summary>
public partial class WCFCallExample : UserControl
{
private bool AsynchronousCall = false;
public WCFCallExample(bool AsynchronousCall)
{
InitializeComponent();
this.AsynchronousCall = AsynchronousCall;
cmbNumberOfStudents.Items.Add("****");
cmbNumberOfStudents.SelectedIndex = 0;
for (int Idex = 5; Idex <= 30; Idex = Idex + 5)
{
cmbNumberOfStudents.Items.Add(Idex);
}
}
private void ClickMe_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("OK, I am clicked ...");
}
private void WCFServiceCall_Click(object sender, RoutedEventArgs e)
{
if (cmbNumberOfStudents.SelectedValue.ToString() == "****")
{
MessageBox.Show("Please select the number of the students to retrieve");
return;
}
DGStudent.ItemsSource = null;
int NumberOfStudents =
System.Convert.ToInt16(cmbNumberOfStudents.SelectedValue);
if (AsynchronousCall) { MakeAsynchronousCall(NumberOfStudents); }
else { MakeSynchronousCall(NumberOfStudents); }
}
private void cmbNumberOfStudents_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
DGStudent.ItemsSource = null;
}
private void MakeSynchronousCall(int NumberOfStudents)
{
StudentService.StudentServiceClient WCFClient =
new WFCCallExample.StudentService.StudentServiceClient();
try
{
WCFClient.Open();
List<StudentService.Student> Students =
WCFClient.GetStudents(NumberOfStudents);
DGStudent.ItemsSource = Students;
}
catch (Exception ex){ MessageBox.Show(ex.Message); }
finally
{
if (WCFClient.State ==
System.ServiceModel.CommunicationState.Opened)
{
WCFClient.Close();
}
}
}
private void MakeAsynchronousCall(int NumberOfStudents)
{
BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
StudentService.StudentServiceClient c =
new WFCCallExample.StudentService.StudentServiceClient();
EndpointAddress endpointAddress = c.Endpoint.Address;
StudentService.IStudentService iStudentService =
new ChannelFactory<StudentService.IStudentService>
(basicHttpBinding, endpointAddress).CreateChannel();
AsyncCallback aSyncCallBack =
delegate(IAsyncResult result)
{
try
{
List<StudentService.Student> Students =
iStudentService.EndGetStudents(result);
this.Dispatcher.BeginInvoke((Action)delegate
{ DGStudent.ItemsSource = Students; });
}
catch (Exception ex)
{
this.Dispatcher.BeginInvoke((Action)delegate
{ MessageBox.Show(ex.Message); });
}
};
try
{
iStudentService.BeginGetStudents(NumberOfStudents,
aSyncCallBack, iStudentService);
} catch (Exception ex) { MessageBox.Show(ex.Message); }
}
}
}
This code-behind file does the following:- It defines a
privateboolean type instance variable "AsynchronousCall", which will be initiated in the constructor of the class. This boolean variable will be used by the event handling method "WCFServiceCall_Click" to decide if a synchronous call or an asynchronous call should be made to the WCF service when the "Make WCF service call" button is clicked. - If a synchronous call should be made, the method "
MakeSynchronousCall" is used. It will issue a synchronous WCF call and bind the list of the student information from the WCF service to the "DataGrid". - If an asynchronous call should be made, the method "
MakeAsynchronousCall" is used. It will issue an asynchronous call and bind the list of the student information from the WCF service to the "DataGrid" in the "AsyncCallback" "delegate".
This "user control" "
WCFCallExample" is hosted in the application's main window "MainWnd.xaml": <Window x:Class="WFCCallExample.MainWnd"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Style="{StaticResource WindowDefault}"
Title="{Binding Path=ApplicationName, Mode=OneTime}"
WindowStartupLocation="CenterScreen"
WindowState="Maximized"
Icon="Images/Rubik-Cube.ico">
<Grid VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" Margin="5,20,5,5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Style="{StaticResource AppHeader}"
HorizontalAlignment="Center"
Text="{Binding Path=ApplicationName, Mode=OneTime}" />
<StackPanel Grid.Row="1" Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock xml:space="preserve"
Style="{StaticResource AuthorInformation}">
Developed by </TextBlock>
<TextBlock xml:space="preserve"
Style="{StaticResource AuthorInformation}"
Text="{Binding Path=Author, Mode=OneTime}"></TextBlock>
<TextBlock xml:space="preserve"
Style="{StaticResource AuthorInformation}"> on </TextBlock>
<TextBlock xml:space="preserve"
Style="{StaticResource AuthorInformation}"
Text="{Binding Path=DevelopentDate, Mode=OneTime}">
</TextBlock>
<TextBlock xml:space="preserve"
Style="{StaticResource AuthorInformation}"> Version
</TextBlock>
<TextBlock xml:space="preserve"
Style="{StaticResource AuthorInformation}"
Text="{Binding Path=Version, Mode=OneTime}"></TextBlock>
</StackPanel>
</Grid>
<Grid Grid.Row="1" x:Name="MainContent" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="5, 2, 5, 10">
<TabControl FontSize="12" x:Name="ApplicationMainTab">
<TabItem
HeaderTemplate="{StaticResource TabItemHeaderTemplate}">
<TabItem.Header>
Call WCF Services Synchronously
</TabItem.Header>
</TabItem>
<TabItem
HeaderTemplate="{StaticResource TabItemHeaderTemplate}">
<TabItem.Header>
Call WCF Services Asynchronously
</TabItem.Header>
</TabItem>
</TabControl>
</Grid>
</Grid>
<TextBlock Grid.Row="1" Text="{Binding Path=Copyright, Mode=OneTime}"
HorizontalAlignment="Center" Style="{StaticResource SmallBold}"
Foreground="Silver" />
</Grid>
</Window>
The above "XAML" file defines two "tab items" in a "TabControl". Each of the "tab items" will host an instance of the "user control" created in the "WCFCallExample.xaml"
file. One instance will be initiated to make synchronous WCF calls and
the other will be initiated to make asynchronous WCF calls by setting
the boolean instance variable "AsynchronousCall" accordingly.The code-behind file of the "MainWnd.xaml" file is the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WFCCallExample.Properties;
namespace WFCCallExample
{
/// <summary>
/// Interaction logic for MainWnd.xaml
/// </summary>
public partial class MainWnd : Window
{
public MainWnd()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWnd_Loaded);
}
void MainWnd_Loaded(object sender, RoutedEventArgs e)
{
DataContext = Settings.Default;
((TabItem)ApplicationMainTab.Items[0]).Content
= new WCFCallExample(false);
((TabItem)ApplicationMainTab.Items[1]).Content
= new WCFCallExample(true);
}
}
}
The "MainWnd_Loaded" event handler initiates two instances of the "WCFCallExample" "user control" objects and associates each of them to the appropriate "tab item". One instance of the "user control" object is initiated to make synchronous calls, and the other is initiated to make asynchronous calls.Run the Application
Now, we finish the development of this example application. Set the "WCFCallExample" project as the "startup project" and press "F5", you can debug run the application. Let us first take a look at the "Call WCF Services Synchronously" tab:Let us now take a look at the "Call WCF Services Asynchronously" tab:
Points of Interest
- This article is an example to show you how to build a WCF service and how to call the WCF service synchronously and asynchronously as well.
- When building the WCF service, I separated the implementation and the host of the service. This is not always necessary. This article only shows you that you can separate the concerns of the implementation and the hosting, if you want do it.
- To call a WCF service synchronously, you can simply use the proxy method created by the Visual Studio, similar to calling a local method.
- To call a WCF service asynchronously, you will need to first create an "AsyncCallback" "delegate" and pass this delegate to the asynchronous proxy method generated by the Visual Studio. Since the "AsyncCallback" "delegate" is executed in a worker thread different from the UI thread, any access to the "UI elements" needs to be "Dispatched".
- From the application point of view, either way has its advantages and disadvantages. For most applications, after the calling of a WCF service, the application will be in an intermediate state until the response from the service is received. During this time, it may be ideal to block the UI thread to prevent user actions to get into any un-predictable results. In this case, a synchronous call may be appropriate. In some other scenarios, particularly when we work on some "SOA" applications, we may need to make multiple service calls at the same time and let the services to work in parallel. In this case, asynchronous WCF calls will be the ideal.
- One thing just came to my mind. To make an asynchronous call, you probably do not need to have the asynchronous proxies. You can well fork a worker thread by yourself and let the thread to do a synchronous call. You will achieve the same effect as using the asynchronous proxies.
- If you are not familiar with WCF services, this link is a good place to get you started.
No comments:
Post a Comment