在 Azure 工作角色中托管 WCF 服务
本文将介绍在 Azure 工作角色中托管 WCF 服务的示例。
引言
本文将介绍在 Azure 工作角色中托管 WCF 服务的示例。
背景
在使用 Microsoft "Azure" 平台时,您可能经常希望将 "WCF" 服务部署到 "云" 中。在大多数情况下,托管 WCF 服务的理想位置应该是 "Web Role"。但有时,您可能会发现将 WCF 服务部署到 "Worker Role" 可能更适合您的需求。本文将介绍一个示例,说明如何在 Azure 工作角色中托管 WCF 服务。
您可以通过观看 "Youtube" 上 Rafael Godinho 的精彩视频来学习此方法。我喜欢他的视频,但无法听懂他的语言。本文将介绍一个使用相同方法的略有不同的示例。如果您听懂他的语言没有问题,我建议您跳过本文,直接访问 Rafael Godinho 的 "博客"。

附带的 Visual Studio 2010 解决方案包含 4 个项目
- "
LoanCalculatorContracts
" 项目是一个类库。它定义了 WCF 服务的 "数据契约" 和 "服务契约"。 - "
WCFWorkerRole
" 项目是一个 "Windows Azure 项目"。它只有一个名为 "WCFWorker
" 的工作角色。 - "
WCFWorker
" 项目是工作角色的实现。它实现了 "LoanCalculatorContracts
" 项目中定义的 WCF 服务。它还设置了在工作角色中运行的 WCF 服务。 - "
WPFTestApplication
" 项目是一个简单的 "WPF" 客户端应用程序,用于测试 WCF 服务。
如果您想下载并运行附带的 Visual Studio 解决方案,您需要安装 "Azure SDK"。要安装 SDK,您需要启用 "Internet Information Service",并且您的计算机上需要安装一个必需版本的 "SQL Server"。 Microsoft 网站 "这里" 提供了有关如何安装 Azure SDK 的详细说明。
在本文中,我将首先介绍 WCF 服务的数据契约和服务契约。然后,我将介绍 WCF 服务的实现以及如何将其设置为在 Azure 工作角色中运行。最后,我将介绍 WPF 客户端应用程序,向您展示如何使用 WCF 服务。
数据和服务的契约
WCF 服务的数据契约和服务契约都定义在 "LoanCalculatorContracts
" 项目的 "Contracts.cs" 文件中。
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; }
// This is the annual interest
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);
}
}
WCF 服务将实现一个贷款还款计算器。"Operation Contract" "Calculate
" 接受一个 "LoanInformation
" 类型的对象,并返回一个 "PaymentInformation
" 类型的对象。 "LoanInformation
" 类包含贷款信息,而 "PaymentInformation
" 类包含计算出的还款信息。
WCF 服务的实现
WCF 服务实现在 "WCFWorker
" 项目的 "LoanCalculatorImplementation.cs" 文件中。
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)
{
// First calculate the monthly interest rate from the annual rate
double monthlyInterest
= Math.Pow((1.0 + loan.InterestRateInPercent / 100.0), 1.0 / 12.0)
- 1.0;
// Calculate monthly payment
double num = loan.Amount * monthlyInterest;
double den = 1.0
- (1.0/(Math.Pow(1.0 + monthlyInterest, (double)loan.TermInMonth)));
double monthlyPayment = num / den;
// Calculate the total payment
double totalPayment = monthlyPayment * (double)loan.TermInMonth;
return new PaymentInformation()
{
MonthlyPayment = monthlyPayment,
TotalPayment = totalPayment
};
}
}
}
"LoanCalculatorImplementation
" 类实现了服务契约接口 "ILoanCadulator
" 来计算还款信息。我已经尽力确保算法是 "正确的",但我不能保证,因为还款计算的正确性对于本文的目的来说并不重要。如果您想估算您的抵押贷款还款额,我建议您寻求更专业的建议。
在工作角色中托管 WCF 服务
在工作角色中托管 WCF 服务的代码实现在 "WCFWorker
" 项目的 "WorkerRole.cs" 文件中。
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();
}
}
}
要托管 WCF 服务,"CreateServiceHost
" 方法会初始化一个 "ServiceHost" 来将其连接到 WCF 服务实现。它还会为 "ServiceHost" 设置 "Binding" 和 "Endpoint"。 "CreateServiceHost
" 在 "OnStart
" 方法中被调用。上述代码相当简单,但我们需要注意以下两点:
- "ServiceHost" 被声明为类实例变量。如果我们将其作为局部变量声明在 "
CreateServiceHost
" 方法中,当调用 "CreateServiceHost
" 方法完成后,"ServiceHost" 将超出作用域并被垃圾回收。 - 上面代码中的 "RoleInstanceEndpoint" 是从 "Windows Azure 项目" "
WCFWorkerRole
" 的 "ServiceConfiguration
" 文件中获取的。
要配置 "RoleInstanceEndpoint",我们可以双击 "WCFWorkerRole
" 项目 "Roles" 文件夹中的工作角色 "WCFWorker
" 来调出配置面板。

在此示例中,我向工作角色添加了端点 "WCFEndpoint
"。因此,在开发环境中,WCF 服务的端点地址将是 "net.tcp://127.0.0.1:9191/LoanCalculator"。
WPF 客户端应用程序
为了测试 WCF 服务,我创建了一个简单的窗口 WPF 应用程序。单窗口应用程序的 "XAML" 代码实现在 WPFTestApplication
"" 项目的 "MainWindow.xaml" 文件中。
<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>
XAML 代码声明了以下可视化组件:
- 三个 "Comboboxes",供用户选择贷款总额、年利率和贷款期限。
- 一个按钮,供用户触发 WCF 调用以进行还款计算。
- 还款计算结果将由名为 "
txtCalculationResult
" 的 "TextBlock" 显示。
对于生产级别的 WPF 应用程序,我们至少应该实现某种程度的 "MVVM" 模式。但对于这个简单的测试应用程序,我只是在 "MainWindow.xaml" 文件的代码隐藏文件中实现了编程逻辑。
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
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private string serviceUrl = "net.tcp://127.0.0.1:9191/LoanCalculator";
private string DefaultResultText;
// This is how we get a proxy to access the WCF service
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;
}
}
}
在 "Loaded" 事件中,三个 "Comboboxes" 使用一些测试值进行了初始化。在按钮的 "Click" 事件中,会进行 WCF 调用以进行还款计算。如果 WCF 调用成功,结果将由 "TextBlock" "txtCalculationResult
" 显示。WCF 服务的客户端代理是通过 "GetAProxy
" 方法创建的,该方法使用了我们在工作角色中配置的端点地址。
现在我们完成了 WCF 服务和 WPF 测试客户端的实现。然后我们可以测试运行该应用程序。
运行应用程序
要在 Visual Studio 中运行 "Windows Azure 项目",我们需要以管理员身份启动 Visual Studio。

由于我们同时测试 WPF 客户端应用程序和 Azure 工作角色中的 WCF 服务,因此我们需要启动 WPF 客户端应用程序 "WPFTestApplication
" 和 "Windows Azure 项目" "WCFWorkerRole
"。为了让它们在调试运行时同时启动,我们可以右键单击解决方案文件并调出 "Properties" 窗口。

我们需要选择 "Multiple startup projects" 选项,并将 "WCFWorkerRole
" 和 "WPFTestApplication
" 项目都标记为 "Start"。
当我们开始调试运行应用程序时,WPF 应用程序通常会比 Azure 项目启动得更快。我们需要等待 Azure 项目完全启动后才能发出 WCF 服务调用。下图显示了对 WCF 服务的调用的结果。

如果您信任我的计算,那么您可以看到,对于一笔 50 万、30 年、6% 年利率的贷款,您的总还款额将超过一百万。银行业利润丰厚,不是吗?
关注点
- 本文介绍了在 Azure 工作角色中托管 WCF 服务的示例。
- 对于大多数实际应用,托管 WCF 服务的最佳位置应该是 Web 角色。但在某些情况下,您可能会发现将 WCF 服务托管在工作角色中可以提供一些优势。
- 如果您想在自己的 Visual Studio 中运行代码,您需要安装 "Azure SDK"。在本示例中,我将工作角色配置为监听端口 "9191"。当您在计算机上运行此示例时,您需要确保没有其他应用程序正在监听该端口。否则,工作角色将无法启动。
- 在本示例中,服务和客户端两端的绑定和端点配置都在 C# 代码中完成。您也可以使用 "Web.config" 和 "App.config" 文件来进行配置。如果您想在配置文件中进行配置,可以参考 Rafael Godinho 的视频。
- 本文仅介绍了如何配置 WCF 服务以使用 "NetTcpBinding"。如果您想使用 "HttpBinding",您可以参考本文。
- 在本示例中,WCF 服务本身不公开任何 "Metadata",因此服务实现项目 "
WCFWorker
" 和客户端项目 "WPFTestApplication
" 都需要引用契约定义项目 "LoanCalculatorContracts
"。 - 我希望您喜欢我的文章,希望本文能以某种方式帮助您。
历史
这是本文的第一个修订版。