Introduction
This article presents an example to host WCF services in an Azure Worker Role.
Background
When you work with the Microsoft "Azure" platform, you may often want to put a "WCF" service in the "cloud". In most cases, the ideal place to host your WCF services should be a "Web Role". But occasionally, you may find that putting the WCF services in a "Worker Role" may better fit your needs. This article is to present an example to show you how to host WCF services in an Azure Worker Role.
You can learn this method by watching a nice "Youtube" video by "Rafael Godinho". I like his video, but I am unable to understand his language. This article though, is to present a slightly different example using the same method. If you do not have a problem understanding his language, I will recommend you to skip this article, but directly go to Rafael Godinho's "Blog'.
The attached Visual Studio 2010 solution has 4 projects:
- The "
LoanCalculatorContracts
" project is a class library. It defines the "Data Contract" and the "Service Contract" for the WCF service. - The "
WCFWorkerRole
" project is a "Windows Azure Project". It has only one worker role called "WCFWorker
". - The "
WCFWorker
" project is the implementation of the worker role. It implements the WCF service defined in the "LoanCalculatorContracts
" project. It also sets up the WCF service to run in the worker role. - The "
WPFTestApplication
" project is a simple "WPF" client application that is used to test the WCF service.
If you want to download and run the attached Visual Studio solution, you will need to install the "Azure SDK". To install the SDK, you will need to enable the "Internet Information Service" and you will need to have one of the required versions of "SQL Server" on your computer. This Microsoft web site has detailed instructions on how to install the Azure SDK.
In this article, I will first present the data and service contracts of the WCF service. I will then present the implementation of the WCF service and how we can set it up to run in an Azure worker role. At the end, I will present the WPF client application to show you how we can consume the WCF service.
The Data and Service Contracts
Both the data and service contracts of the WCF service are defined in the "Contracts.cs" file in the "LoanCalculatorContracts
" project.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace LoanCalculatorContracts
{
public class LoanInformation
{
public double Amount { get; set; }
public double InterestRateInPercent { get; set; }
public int TermInMonth { get; set; }
}
public class PaymentInformation
{
public double MonthlyPayment { get; set; }
public double TotalPayment { get; set; }
}
[ServiceContract]
public interface ILoanCadulator
{
[OperationContract]
PaymentInformation Calculate(LoanInformation loan);
}
}
The WCF service is to implement a loan payment calculator. The "Operation Contract" "Calculate
" takes an object of type "LoanInformation
" and returns an object of type "PaymentInformation
". The class "LoanInformation
" has the information about the loan and the "PaymentInformation
" class has the calculated payment information.
The Implementation of the WCF Service
The WCF service is implemented in the "LoanCalculatorImplementation.cs" file in the "WCFWorker
" project.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LoanCalculatorContracts;
using System.ServiceModel;
namespace WCFWorker
{
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
class LoanCalculatorImplementation : ILoanCadulator
{
public PaymentInformation Calculate(LoanInformation loan)
{
double monthlyInterest
= Math.Pow((1.0 + loan.InterestRateInPercent / 100.0), 1.0 / 12.0)
- 1.0;
double num = loan.Amount * monthlyInterest;
double den = 1.0
- (1.0/(Math.Pow(1.0 + monthlyInterest, (double)loan.TermInMonth)));
double monthlyPayment = num / den;
double totalPayment = monthlyPayment * (double)loan.TermInMonth;
return new PaymentInformation()
{
MonthlyPayment = monthlyPayment,
TotalPayment = totalPayment
};
}
}
}
The "LoanCalculatorImplementation
" class implements the service contract interface "ILoanCadulator
" to calculate the payment information. I have made my effort to ensure the algorithm to be "correct", but I do not give you the guarantee because the correctness of the payment calculation is really not important for the purpose of this article. If you want to estimate your mortgage payment, I will recommend you to get some more professional advice.
Host the WCF Service in the Worker Role
The code to host the WCF service in the worker role is implemented in the "WorkerRole.cs" file in the "WCFWorker
" project.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using System.ServiceModel;
using LoanCalculatorContracts;
namespace WCFWorker
{
public class WorkerRole : RoleEntryPoint
{
public override void Run()
{
Trace.WriteLine("WCFWorker entry point called", "Information");
while (true)
{
Thread.Sleep(10000);
Trace.WriteLine("Working", "Information");
}
}
public override bool OnStart()
{
ServicePointManager.DefaultConnectionLimit = 12;
CreateServiceHost();
return base.OnStart();
}
private ServiceHost serviceHost;
private void CreateServiceHost()
{
serviceHost = new ServiceHost(typeof(LoanCalculatorImplementation));
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
RoleInstanceEndpoint externalEndPoint =
RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["WCFEndpoint"];
string endpoint = String.Format("net.tcp://{0}/LoanCalculator",
externalEndPoint.IPEndpoint);
serviceHost.AddServiceEndpoint(typeof(ILoanCadulator), binding, endpoint);
serviceHost.Open();
}
}
}
To host the WCF service, the method "CreateServiceHost
" initiates a "ServiceHost" to connect it to the WCF service implementation. It also sets the "Binding" and the "Endpoint" for the "ServiceHost". The "CreateServiceHost
" is called in the "OnStart
" method. The above code is fairly simple, except that we may need to pay some attention to the following two things:
- The "ServiceHost" is declared as a class instance variable. If we declare it in the "
CreateServiceHost
" method as a local variable, when the calling of the method "CreateServiceHost
" finishes, the "ServiceHost" is out of the scope and will be garbage collected. - The "RoleInstanceEndpoint" in the above code is obtained from the "
ServiceConfiguration
" file in the "Windows Azure Project" "WCFWorkerRole
".
To configure the "RoleInstanceEndpoint", we can double click the worker role "WCFWorker
" in the "Roles" folder in the "WCFWorkerRole
" project to bring up the configuration panel.
In this example, I added the endpoint "WCFEndpoint
" to the worker role. So the endpoint address of the WCF service will be "net.tcp://127.0.0.1:9191/LoanCalculator" in the development environment.
The WPF Client Application
In order to test the WCF service, I created a simple window WPF application. The "XAML" code for the single window application is implemented in the "MainWindow.xaml" file in the WPFTestApplication
"" project.
<Window x:Class="WPFTestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Worker Role WCF Service Test Client"
FontFamily="Calibri"
Height="350" Width="525">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Azure Worker Role WCF Loan Calculator"
FontSize="18" FontWeight="SemiBold" Foreground="Brown" />
<Border Grid.Row="1"
BorderThickness="1" Margin="0, 5, 0, 0"
BorderBrush="Blue" CornerRadius="5">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Text="Total loan ($)" />
<TextBlock Grid.Row="1" Grid.Column="0"
Margin="0, 0, 10, 0"
Text="Anual interest rate (%)" />
<TextBlock Grid.Row="2" Grid.Column="0"
Text="Term (month)" />
<ComboBox Grid.Row="0" Grid.Column="1"
x:Name="cmbTotalLoan"
SelectionChanged="cmbbox_SelectionChanged"
HorizontalAlignment="Stretch" />
<ComboBox Grid.Row="1" Grid.Column="1"
x:Name="cmbAnualInterest"
SelectionChanged="cmbbox_SelectionChanged"
HorizontalAlignment="Stretch" />
<ComboBox Grid.Row="2" Grid.Column="1"
x:Name="cmbTermInMonth"
SelectionChanged="cmbbox_SelectionChanged"
HorizontalAlignment="Stretch" />
</Grid>
<TextBlock Grid.Row="1" HorizontalAlignment="Right"
x:Name="txtCalculationResult"
Foreground="Green"
Margin="0, 5, 5, 5" Text="NA" />
<Button Grid.Row="2" Content="Calculate" Click="Calculate_Click" />
</Grid>
</Border>
</Grid>
</Window>
The XAML code declared the following visual components:
- Three "Comboboxes" for the users to select the total loan amount, the annual interest rate, and the term of the loan.
- A button for the users to trigger the WCF call to make the payment calculation.
- The result of the payment calculation will be displayed by the "TextBlock" named "
txtCalculationResult
".
For a production quality WPF application, we should implement at least some level of "MVVM" pattern. But for this simple test application, I just implemented the programming logic in the code-behind file of the "MainWindow.xaml" file.
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 LoanCalculatorContracts;
using System.ServiceModel;
namespace WPFTestApplication
{
public partial class MainWindow : Window
{
private string serviceUrl = "net.tcp://127.0.0.1:9191/LoanCalculator";
private string DefaultResultText;
private ILoanCadulator GetAProxy()
{
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
EndpointAddress endpointAddress
= new EndpointAddress(serviceUrl);
return new ChannelFactory<ILoanCadulator>
(binding, endpointAddress).CreateChannel();
}
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs args)
{
cmbTotalLoan.Items.Clear();
cmbAnualInterest.Items.Clear();
cmbTermInMonth.Items.Clear();
cmbTotalLoan.Items.Add("** Please select **");
for (int i = 1; i <= 5; i++)
{
cmbTotalLoan.Items.Add(100000 * i);
}
cmbTotalLoan.SelectedIndex = 0;
cmbAnualInterest.Items.Add("** Please select **");
for (int i = 1; i <= 5; i++)
{
cmbAnualInterest.Items.Add(3 + i);
}
cmbAnualInterest.SelectedIndex = 0;
cmbTermInMonth.Items.Add("** Please select **");
for (int i = 1; i <= 5; i++)
{
cmbTermInMonth.Items.Add(12 * 10 * i);
}
cmbTermInMonth.SelectedIndex = 0;
DefaultResultText = "Please select the amount, the interest rate,"
+ " the term and click the \"Calculate\" button";
txtCalculationResult.Text = DefaultResultText;
}
private void cmbbox_SelectionChanged
(object sender, SelectionChangedEventArgs e)
{
txtCalculationResult.Text = DefaultResultText;
txtCalculationResult.Foreground = Brushes.Green;
}
private void Calculate_Click(object sender, RoutedEventArgs e)
{
string amount = cmbTotalLoan.SelectedItem.ToString();
string interestRateInPercent = cmbAnualInterest.SelectedItem.ToString();
string termInMonth = cmbTermInMonth.SelectedItem.ToString();
if (amount == "** Please select **")
{
MessageBox.Show("Please select the total loan.");
return;
}
if (interestRateInPercent == "** Please select **")
{
MessageBox.Show("Please select the annual interest rate");
return;
}
if (termInMonth == "** Please select **")
{
MessageBox.Show("Please select the term");
return;
}
LoanInformation loan = new LoanInformation()
{
Amount = Convert.ToDouble(amount),
InterestRateInPercent = Convert.ToDouble(interestRateInPercent),
TermInMonth = Convert.ToInt32(termInMonth)
};
string resultText = null;
try
{
PaymentInformation payment = GetAProxy().Calculate(loan);
resultText = "Monthly payment: $"
+ payment.MonthlyPayment.ToString()
+ ", Total payment: $" + payment.TotalPayment.ToString();
}
catch (Exception ex)
{
MessageBox.Show("Error when calculating the payments - " + ex.Message);
return;
}
txtCalculationResult.Text = resultText;
txtCalculationResult.Foreground = Brushes.Brown;
}
}
}
In the "Loaded" event, the three "Comboboxes" are initiated with some test values. In the "Click" event of the button, a WCF call is made to make the payment calculation. If the WCF call is successful, the result is displayed by the "TextBlock" "txtCalculationResult
". The client proxy of the WCF service is created by the "GetAProxy
" method using the endpoint address that we have configured in the worker role.
Now we finish implementing both the WCF service and the WPF test client. We can then test run the application.
Run the Application
To run a "Windows Azure Project" in the Visual Studio, we need to start the Visual Studio as an administrator.
Since we are testing both the WPF client application and the WCF service in the Azure worker role, we need to start both the WPF client application "WPFTestApplication
" and the "Windows Azure Project" "WCFWorkerRole
". To let them both start when we debug run the application, we can right click the solution file and bring up the "Properties" window.
We need to select the "Multiple startup projects" option and mark both "WCFWorkerRole
" and "WPFTestApplication
" project to "Start".
When we start to debug run the application, the WPF application should normally start up faster than the Azure project. We need to wait until the Azure project is fully started to issue the WCF service call. The following picture shows the result of a call to the WCF service.
If you trust my calculation, you can see that your total payment for a 30 year half million loan with a 6% annual interest rate is well over one million. Banking industry is pretty profitable, isn't it?
Points of Interest
- This article presented an example to host WCF services in an Azure Worker Role.
- For most practical applications, the best place to host the WCF services should be the web roles. But you may find that hosting the WCF services in a worker role may provide some advantages under certain circumstances.
- If you want to run the code in your own Visual Studio, you will need to install the "Azure SDK". In this example, I configured the worker role to listen to the port "9191". When you run this example in your computer, you need to make sure that no other application is listening to this port. Otherwise, the worker role won't be able to start.
- In this example, the binding and endpoint configurations on both the service and client sides are done in the C# code. You can also use the "Web.config" and "App.config" files to make the configurations. If you want to do it in the configuration files, you can refer to Rafael Godinho's video.
- This article only shows how to configure the WCF service to use "NetTcpBinding". If you want to use "HttpBinding", you can take a look at this article.
- In this example, the WCF service does not expose any "Metadata" by itself, so both the service implementation project "
WCFWorker
" and the client project "WPFTestApplication
" need to reference the contract definition project "LoanCalculatorContracts
". - I hope you like my postings and I hope this article can help you one way or the other.
History
This is the first revision of this article.