Making Asynchronous Calls to WCF Services from ASP.NET
When ASP.NET receives a synchronous request, it assigns a thread to the request from a thread pool. The thread remains blocked till the request is completed. Since the thread pool has got a limited number of threads, time consuming operations like pulling data from database can bring down the performance of the application. If there are multiple such time consuming operations, the scalability can go in for a toss.
ASP.NET 2.0 and above makes it simple to create Asynchronous pages. Unlike the synchronous operation, a thread is fetched from the pool but is returned back to the pool when the async operation begins. When the async operation completes, a thread is again grabbed from the pool to complete the process. This means that threads do not have to remain occupied for time consuming operations, thereby increasing the scalability of the application.
I had recently written an article to Access an AJAX Enabled WCF Service using ASP.NET and Client Script. A dotnetcurry.com user mailed back asking if there was a way to call a WCF service asynchronously. In this article, we will see how to create a WCF service and then consume it asynchronously using ASP.NET. Asynchronous tasks can be performing using AddOnPreRenderCompleteAsync or RegisterAsyncTask methods of the Page class. We will be using the RegisterAsyncTask in this example. Follow these steps:
Step 1: Create an ASP.NET 3.5 website
Right click Project > Add New Item > WCF Service > Type name as ‘NorthwindService.svc’ > Add.
For simplicity sake, I have not hosted the service on IIS. I am using the server that comes built-in with Visual Studio 2008. The server automatically allocates a port number. If you would like to keep the port number fixed, Select the project in the Solution Explorer > Go to the Properties window > Set 'Use Dynamic Ports' to False > Set the Port number. I am using port 64258. Build the Solution.
Step 2: We will query the 'Products' table in the Northwind database and return a list of Product names. We will create a Product class for this purpose. Now C# 3.0/ VB 9.0 introduce features like the ‘Anonymous type’ to cut down on the code required to create and instantiate a class using ‘the old way’. However, I prefer creating a class in case I need to pass in the result to a webservice or to a different part of your application. Right click the project > Add > Class > we will call our class ‘Products’
C#
///<summary>
/// Summary description for Products
///</summary>
public class Products
{
public Int32 ProductID { get; set; }
public string ProductName { get; set; }
}
VB.NET
Public Class Products
Private privateProductID As Int32
Public Property ProductID() As Int32
Get
Return privateProductID
End Get
Set(ByVal value As Int32)
privateProductID = value
End Set
End Property
Private privateProductName As String
Public Property ProductName() As String
Get
Return privateProductName
End Get
Set(ByVal value As String)
privateProductName = value
End Set
End Property
End Class
Step 3: In your web.config, create <connectionStrings> section as shown below:
<connectionStrings>
<add name="NorthwindConnectionString" connectionString="Data Source=(local);Initial Catalog=Northwind;Integrated Security=True"/>
</connectionStrings>
Step 4: Now move to the ServiceContract in the INorthwindService.cs or .vb file and replace the DoWork() method with ProductList() method as shown below:
C#
[ServiceContract]
public interface INorthwindService
{
[OperationContract]
List<Products> ProductList();
}
VB.NET
<ServiceContract> _
Public Interface INorthwindService
<OperationContract> _
Function ProductList() As List(Of Products)
End Interface
Go back to your NorthwindService.cs or vb and implement the ProductList() method. Write the following code:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;
using System.Data;
using System.Data.SqlClient;
// NOTE: If you change the class name "NorthwindService" here, you must also update the reference to "NorthwindService" in Web.config.
public class NorthwindService : INorthwindService
{
public List<Products> ProductList()
{
string nwConn = System.Configuration.ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString;
var prodList = new List<Products>();
using (SqlConnection conn = new SqlConnection(nwConn))
{
const string sql = @"SELECT ProductID, ProductName FROM Products";
conn.Open();
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
SqlDataReader dr = cmd.ExecuteReader(
CommandBehavior.CloseConnection);
if (dr != null)
while (dr.Read())
{
var prods = new Products
{
ProductID = dr.GetInt32(0),
ProductName = dr.GetString(1)
};
prodList.Add(prods);
}
// Simulating a delayed asynchronous task.
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5.0));
return prodList;
}
}
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.Serialization
Imports System.ServiceModel
Imports System.Text
Imports System.ServiceModel.Activation
Imports System.Data
Imports System.Data.SqlClient
' NOTE: If you change the class name "NorthwindService" here, you must also update the reference to "NorthwindService" in Web.config.
Public Class NorthwindService
Implements INorthwindService
Public Function ProductList() As List(Of Products)
Dim nwConn As String = System.Configuration.ConfigurationManager.ConnectionStrings("NorthwindConnectionString").ConnectionString
Dim prodList = New List(Of Products)()
Using conn As New SqlConnection(nwConn)
Const sql As String = "SELECT ProductID, ProductName FROM Products"
conn.Open()
Using cmd As New SqlCommand(sql, conn)
Dim dr As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)
If dr IsNot Nothing Then
Do While dr.Read()
Dim prods = New Products With {.ProductID = dr.GetInt32(0), .ProductName = dr.GetString(1)}
prodList.Add(prods)
Loop
End If
' Simulating a delayed asynchronous task.
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5.0))
Return prodList
End Using
End Using
End Function
The code shown above connects to the Northwind database and pulls the list of ProductID and ProductName from the Products table. The return type is a List<> of type Products. You can test this service by right clicking the NorthwindService.svc > View in Browser.
Click the WSDL (http://localhost:64258/ASPWCFAsync/NorthwindService.svc?wsdl) to view the operations and bindings.
Call this WCF Service asynchronously using ASP.NET
Once the WCF Service has been created, it’s time to consume this service asynchronously using ASP.NET. Follow these steps:
Step 5: Right click Project > Add Service Reference > Enter the url where the service is running ‘http://localhost:64258/ASPWCFAsync/NorthwindService.svc’ > Click the Go button and the service will be displayed as shown below along with the operations it supports.
I have renamed the Namespace field as ‘ProductReference’. Click on the ‘Advanced’ button and check the checkbox ‘Generate asynchronous operations’ > Click OK.
Click OK again to add the Service Reference.
You will observe that a new folder called ‘App_WebReferences’ gets created which contains class and methods to call the service.
Step 6: Let’s now write some code to consume the service asynchronously. Go to the Default.aspx page. The first step is to add ‘Async=true’ to the Page directive as shown below. Setting the Async attribute on the page directive to 'true' avoids the thread executing the page to be blocked until the async tasks are completed.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Async="true" Inherits="_Default" %>
Now drag and drop a Button control and a ListBox to the page. Generate a Click event handler for the button and in the click event, write the following code:
C#
protected void Button1_Click(object sender, EventArgs e)
{
PageAsyncTask pat = new PageAsyncTask(BeginProductRetrieveAsync, EndProductRetrieveAsync, null, null);
Page.RegisterAsyncTask(pat);
}
IAsyncResult BeginProductRetrieveAsync(object sender, EventArgs e, AsyncCallback acb, object extraData)
{
nor = new ProductReference.NorthwindServiceClient();
return nor.BeginProductList(acb, extraData);
}
void EndProductRetrieveAsync(IAsyncResult ar)
{
var prods = new List<Products>();
ListBox1.DataSource = nor.EndProductList(ar);
ListBox1.DataTextField = "ProductName";
ListBox1.DataValueField = "ProductID";
ListBox1.DataBind();
}
VB.NET
Private nor As ProductReference.NorthwindServiceClient = Nothing
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim pat As New PageAsyncTask(AddressOf BeginProductRetrieveAsync, AddressOf EndProductRetrieveAsync, Nothing, Nothing)
Page.RegisterAsyncTask(pat)
End Sub
Private Function BeginProductRetrieveAsync(ByVal sender As Object, ByVal e As EventArgs, ByVal acb As AsyncCallback, ByVal extraData As Object) As IAsyncResult
nor = New ProductReference.NorthwindServiceClient()
Return nor.BeginProductList(acb, extraData)
End Function
Private Sub EndProductRetrieveAsync(ByVal ar As IAsyncResult)
Dim prods = New List(Of Products)()
ListBox1.DataSource = nor.EndProductList(ar)
ListBox1.DataTextField = "ProductName"
ListBox1.DataValueField = "ProductID"
ListBox1.DataBind()
End Sub
We have used the PageAsyncTask class that contains information about an asynchronous task registered to a page. The parameters of the PageAsyncTask is as follows:
param1: handler to call when beginning an async task
param2: handler to call when task is completed successfully
param3: handler to call when the operation timeouts.
param4: object representing state of the task.
param5: bool representing if task can be processed in parallel with other async tasks.
Note: An Asynchronous task by default will time out if the operations does not complete within 45 seconds. You can specify your own time out using the Page directive <%@ Page AsyncTimeout="40" %>
A PageAsyncTask object must be registered to the page through the RegisterAsyncTask method. This is done using Page.RegisterAsyncTask(pat);
The BeginProductRetrieveAsync and EndProductRetrieveAsync are the handlers that are called when beginning and completing an asyc task respectively. The ListBox is bound to the results in the EndProductRetrieveAsync handler.
Run the application. You will see that on the button click, a List of ProductName is fetched asynchronously from the WCF service. I would recommend users to read this article by Jeff Prosise where he has covered up Asynchronous Pages in detail. I hope you liked this article and I thank you for viewing it.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of
DotNetCurry,
DNC Magazine for Developers,
SQLServerCurry and
DevCurry. He has also authored a couple of books
51 Recipes using jQuery with ASP.NET Controls and
The Absolutely Awesome jQuery CookBook.
Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.
Get in touch with him on Twitter @suprotimagarwal or at LinkedIn