Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / WPF

How to Call WCF Services Synchronously and Asynchronously

Rate me:
Please Sign up or sign in to vote.
4.91/5 (48 votes)
16 Jul 2010CPOL9 min read 296.4K   9.1K   167   36
This article is an example to show you how to call WCF services synchronously as well as asynchronously.

Introduction

This article is an example to show you how to call WCF services synchronously as well as asynchronously.

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:

VisualStudio.jpg

This Visual Studio solution has three .NET projects:

  • 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.

This Visual Studio solution is developed in C# in Visual Studio 2008. We will take a look at how the WCF service is implemented first.

The Implementation of the WCF Service

VisualStudioImplementation.jpg

The .NET project "WCFServiceImplementation" is a class library. The entire WCF service is implemented in this project in the "StudentService.cs" file:

C#
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:

The "service contract" "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:

VisualStudioHost.jpg

In this project, the WCF service is hosted in the "StudentService.svc" file. Since the entire implementation of the WCF service is completed in the "WCFServiceImplementation" class library, the "StudentService.svc" file is as simple as the following:

ASP.NET
<%@ ServiceHost Language="C#" Debug="true"
   Service="WCFServiceImplementation.StudentService" %>

The configuration of the WCF service in the "Web.config" file is the following:

XML
<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:

ViewInBrowser.jpg

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.

VisualStudioWPFApplicationFull.jpg

The "Service Reference" "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:

ProxyGenerateAsync.jpg

By checking this checkbox, Visual Studio will generate the proxies to support both synchronous and asynchronous calls to the WCF service.

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:

XML
<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.

The code-behind file of the "WCFCallExample.xaml" file is the following:

C#
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 private boolean 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".

Both synchronous and asynchronous calls to the WCF service are pretty simple by using the proxies generated by the Visual Studio. When making a synchronous call, the UI thread that issues the call will be blocked until the WCF service sends the results back to the client. When an asynchronous call is made, the UI thread sends the WCF request and forks a worker thread to wait for the response from the WCF service. The UI thread is not blocked and remains responsive to other user interactions. When the response from the WCF service comes back, the "AsyncCallback" "delegate" is executed to process the WCF service response.

This "user control" "WCFCallExample" is hosted in the application's main window "MainWnd.xaml":

XML
 <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:

C#
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:

WCFCallSynchronized.jpg

Select the number of the students to retrieve from the WCF service and click the "Make WCF service call" button, a synchronous WCF call is made and the student list is retrieved from the WCF service. During the time when you wait for the WCF service to respond to your call, you can click on the "Click me!" button, you will notice that the UI thread of the WPF application is blocked, and the application will not respond to your button click until the WCF call is finished.

Let us now take a look at the "Call WCF Services Asynchronously" tab:

WCFCallAsynchronized.jpg

After selecting the number of the students to retrieve and clicking the "Make WCF service call" button, an asynchronous WCF call is made. If you can click on the "Click me!" button, you will notice that the UI thread is not blocked and the "MessageBox" telling you the button is clicked shows up immediately even before the WCF service responds back to the WCF call.

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.

History

This is the first revision of this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I have been working in the IT industry for some time. It is still exciting and I am still learning. I am a happy and honest person, and I want to be your friend.

Comments and Discussions

 
GeneralMy vote of 4 Pin
Manas_Kumar5-Dec-15 20:29
professionalManas_Kumar5-Dec-15 20:29 
QuestionHow do I achieve Parallel processing When web service is called in For loop? Pin
Member 949605028-Feb-14 21:45
Member 949605028-Feb-14 21:45 
GeneralMy vote of 5 Pin
Uday P.Singh9-Jul-13 23:48
Uday P.Singh9-Jul-13 23:48 
QuestionThanks for the insight ... but there is always another question Pin
GPDeveloper22-Jun-13 16:00
GPDeveloper22-Jun-13 16:00 
AnswerRe: Thanks for the insight ... but there is always another question Pin
Dr. Song Li24-Jun-13 3:56
Dr. Song Li24-Jun-13 3:56 
GeneralRe: Thanks for the insight ... but there is always another question Pin
Hasan Habib Surzo26-Aug-13 3:53
Hasan Habib Surzo26-Aug-13 3:53 
GeneralMy vote of 5 Pin
rmadhavan0031-May-13 19:04
rmadhavan0031-May-13 19:04 
GeneralEasiest way to create and consume WCF Services in asp.net Pin
Lalit24rocks14-May-13 21:41
Lalit24rocks14-May-13 21:41 
QuestionI am not getting the Dispatcher method. Pin
S.S.Cheral7-Apr-13 21:36
S.S.Cheral7-Apr-13 21:36 
QuestionAsynchronous Call functionality in WCF Pin
Lalita Pophale31-Mar-13 1:13
Lalita Pophale31-Mar-13 1:13 
AnswerRe: Asynchronous Call functionality in WCF Pin
Dr. Song Li31-Mar-13 4:26
Dr. Song Li31-Mar-13 4:26 
GeneralRe: Asynchronous Call functionality in WCF Pin
Lalita Pophale31-Mar-13 6:47
Lalita Pophale31-Mar-13 6:47 
GeneralMy vote of 5 Pin
Lalita Pophale27-Mar-13 3:41
Lalita Pophale27-Mar-13 3:41 
GeneralMy vote of 5 Pin
Lalita Pophale27-Mar-13 3:34
Lalita Pophale27-Mar-13 3:34 
Excellent Atricle.
Help to start multithreading in wcf
GeneralExcellent Article Pin
Abhinesh M27-Feb-13 1:22
Abhinesh M27-Feb-13 1:22 
GeneralRe: Excellent Article Pin
Dr. Song Li27-Feb-13 3:57
Dr. Song Li27-Feb-13 3:57 
Questionis new client required for the async call Pin
Chasler Pathko9-Feb-13 8:27
Chasler Pathko9-Feb-13 8:27 
AnswerRe: is new client required for the async call Pin
Dr. Song Li9-Feb-13 9:47
Dr. Song Li9-Feb-13 9:47 
GeneralMy vote of 5 Pin
GregoryW28-Nov-12 22:45
GregoryW28-Nov-12 22:45 
GeneralRe: My vote of 5 Pin
Dr. Song Li29-Nov-12 3:50
Dr. Song Li29-Nov-12 3:50 
GeneralMy vote of 5 Pin
Danology18-Oct-12 22:41
Danology18-Oct-12 22:41 
GeneralRe: My vote of 5 Pin
Dr. Song Li19-Oct-12 3:48
Dr. Song Li19-Oct-12 3:48 
GeneralMy vote of 5 Pin
MehtaVikas26-Jun-12 22:14
MehtaVikas26-Jun-12 22:14 
GeneralThanks for clarifying the client-side configuration Pin
Bryan DeYoung2-Jun-12 9:31
Bryan DeYoung2-Jun-12 9:31 
QuestionThanks a lot Pin
rashmirjois9-May-12 20:14
rashmirjois9-May-12 20:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.