Introduction Windows Communication Foundation Part5 (Last)
Posted On Tuesday, February 24, 2009 at at 5:10 PM by UnknownBài viết này được dịch từ Chapter 25 - INTRODUCING WINDOWS COMMUNICATION FOUNDATION của sách Pro C# 2008 and the NET 3.5 Platform Fourth Edition.
Hosting the WCF Service As a Windows Service
- Cũng dễ dàng nhận thấy rằng nếu host một WCF service trong một Console application hay trong một GUI desktop application không thích hợp lắm trong môi trường server bởi vì người ta thường muốn chương trình host phải chạy liên tục ở backgroud và luôn available cho các client request. Bởi vậy dù bạn vẫn có thể minimize host application xuống taskbar, nhưng vẫn có khả năng bạn sẽ vô tình shutdown chương trình host dẫn đến mọi kết nối của client sẽ bị đứt.
* Tuy ta có thể làm một chương trình desktop application chạy bình thường mà không cần main Windows, nhưng nó vẫn cần phải được execute bởi một người nào đó khi login vào Windows. Trong khi đó Windows service có thể start dễ dàng mà không cần ai login vào Windows.
- Một đặc điểm dễ thấy nhất của Windows service là nó có thể start ngay sau khi machine vừa khởi động. Ngoài ra như đã nói, một Windows service có thể chạy ngầm dưới background và không cần đến một tương tác nào của người dùng Windows.
- Chúng ta sẽ thử build một service như vậy bằng cách sử dụng Windows service project template. Ta hãy tạo một project dạng Windows service tên là MathWindowsServiceHost (Hình 1). Sau đó hãy đổi tên Service1.cs thành MathWinService.cs
Hình 1: Creating a Windows service to host our WCF service
Các bạn đang xem bài viết về Introduction WCF từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)
Specifying the ABCs in Code
- Tương tự như những lần trước, ta cũng add reference tới những assembly như System.ServiceModel.dll và project MathServiceLibrary. Chúng ta cần tạo ra một ServiceHost bên trong function OnStart() như sau:
public partial class MathWinService : ServiceBase
{
// A member variable of type ServiceHost.
private ServiceHost myHost;
public MathWinService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
// Just to be really safe.
if (myHost != null)
{
myHost.Close();
myHost = null;
}
Uri address = new Uri("http://localhost:8888/MathServiceLibrary");
// Create the host.
myHost = new ServiceHost(typeof(MathService), address);
WSHttpBinding binding = new WSHttpBinding();
Type contract = typeof(IBasicMath);
// Add this endpoint.
myHost.AddServiceEndpoint(contract, binding, address);
AddMexEndpoint(myHost);
// Open the host.
myHost.Open();
}
protected void AddMexEndpoint(ServiceHost myHost)
{
ServiceMetadataBehavior smb = myHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
// If not, add one
if (smb == null)
smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
myHost.Description.Behaviors.Add(smb);
// Add MEX endpoint
myHost.AddServiceEndpoint(
ServiceMetadataBehavior.MexContractName,
MetadataExchangeBindings.CreateMexHttpBinding(),
"mex"
);
}
protected override void OnStop()
{
// Shut down the host.
if (myHost != null)
myHost.Close();
}
}
{
// A member variable of type ServiceHost.
private ServiceHost myHost;
public MathWinService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
// Just to be really safe.
if (myHost != null)
{
myHost.Close();
myHost = null;
}
Uri address = new Uri("http://localhost:8888/MathServiceLibrary");
// Create the host.
myHost = new ServiceHost(typeof(MathService), address);
WSHttpBinding binding = new WSHttpBinding();
Type contract = typeof(IBasicMath);
// Add this endpoint.
myHost.AddServiceEndpoint(contract, binding, address);
AddMexEndpoint(myHost);
// Open the host.
myHost.Open();
}
protected void AddMexEndpoint(ServiceHost myHost)
{
ServiceMetadataBehavior smb = myHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
// If not, add one
if (smb == null)
smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
myHost.Description.Behaviors.Add(smb);
// Add MEX endpoint
myHost.AddServiceEndpoint(
ServiceMetadataBehavior.MexContractName,
MetadataExchangeBindings.CreateMexHttpBinding(),
"mex"
);
}
protected override void OnStop()
{
// Shut down the host.
if (myHost != null)
myHost.Close();
}
}
Code 1: Code method OnStart
- Đoạn code ở trên chỉ thể hiện cách build một service host từ code, bạn hoàn toàn có thể sử dụng file config để làm tất cả những việc này.
Enabling MEX
- Chúng ta cũng có thể enable MEX từ code như trên (Trong code sample bên dưới có implement MEX endpoint). Nhưng dễ dàng nhất là thêm một file App.config vào Windows service project như sau:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>
<configuration>
<system.serviceModel>
<!-- A behavior definition for MEX -->
<behaviors>
</system.serviceModel>
<services>
</services>
<service name="MathServiceLibrary.MathService" behaviorConfiguration ="MathServiceMEXBehavior">
</service>
<!-- Enable the MEX endpoint -->
<endpoint address="mex"
<!-- Need to add this so MEX knows the address of our service -->
<host>
</host>
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
contract="IMetadataExchange" />
<!-- Need to add this so MEX knows the address of our service -->
<host>
<baseAddresses>
</baseAddresses>
<add baseAddress ="http://localhost:8080/MathService"/>
</baseAddresses>
</host>
</service>
</services>
<!-- A behavior definition for MEX -->
<behaviors>
<serviceBehaviors>
</behaviors>
<behavior name="MathServiceMEXBehavior" >
</serviceBehaviors>
<serviceMetadata httpGetEnabled="true" />
</behavior>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Code 2: MEX settings
Creating a Windows Service Installer
- Một Windows service khi install thì sẽ được đăng kí vào hệ điều hành. Do đó, bạn cần phải tạo một project installer cho nó. Project installer này sẽ chứa những phần code cần thiết để bạn đăng kí service này vào hệ thống. Để tạo ra một installer, bạn right-click lên màn hình design của MathWinService và chọn “Add Installer” (Hình 2)
Hình 2: Thêm installer cho Windows service
- Sau khi chọn “Add Installer”, bạn sẽ thấy có hai components được thêm vào màn hình designer. Thành phần đầu tiên (mặc định được đặt tên là serviceProcessInstaller1) sẽ được dùng để install một service mới vào hệ thống. Bạn hãy chọn nó, và sử dụng cửa sổ Properties để set property “Account” thành “LocalSystem”
- Thành phần thứ hai (mặc định được đặt tên là serviceInstaller1) sẽ thực sự install service của bạn. Tương tự, hãy sử dụng cửa sổ Properties, đổi “ServiceName” thành “Math Order Service” (Đây là một friendly name của service khi đăng kí vào hệ thống, bạn sẽ thấy nó trong chương trình services.msc của Windows), sau đó set “StartType” thành “Automatic”, bạn cũng có thể set giá trị cho Description nếu muốn. Sau cùng, hãy build lại project.
Installing the Windows Service
- Một Windows service có thể được install bằng một chương trình setup quen thuộc (như một file msi installer) hoặc bằng chương trình installutil.exe command-line tool. Nếu bạn muốn sử dụng cách thứ 2, hãy dùng Visual Studio 2008 command prompt, đổi thư mục hiện hành vô \bin\Debug trong project MathWindowsServiceHost. Và gõ lệnh sau đây:
installUtil MathWindowsServiceHost.exe
- Nếu như install thành công, bạn sẽ thấy friendly name của Windows service này hiển thị bên trong Services list của hệ thống. Bạn có thể mở chương trình này trong Administrator Tools (bên trong Control Panel) hoặc sử dụng lệnh services.msc /s
Hình 3: Xem windows service của bạn sau khi đã install vào hệ thống
- Bây giờ service của bạn đã chạy, bạn có thể build một client application để gọi thử service này. Nếu nó chưa chạy (như hình trên là chưa chạy), bạn hãy start nó.
Invoking a Service Asynchronously
- Bạn hãy tạo một Console Application và đặt tên là MathClient. Sau đó add Service Reference đến Windows service vừa nãy. Nhưng đừng có click button OK vội mà hãy click vào button “Advanced…” như trong hình dưới đây:
Hình 4: Reference our MathService
- Khi click button “Advanced”, bạn sẽ thấy những configuration khác cho proxy. Ta có thể chọn để generate code để có thể call những “remote methods” asynchronous (bất đồng bộ, gọi xong không phải chờ mà có thể thực hiện những dòng lệnh khác, khi nào remote method trả kết quả về sẽ có nơi để nhận và xử lý kết quả đó) bằng cách check vào “Generate Asynchoronous Operators”.
- Có một option khác trong dialog box này đáng quan tâm là Add Web Reference. Nếu bạn đã từng sử dụng XML webservice trong VS.NET 2005 hay trước đó, bạn có thể thấy Add Web Reference thay vì Add Service Reference. Nếu bạn click vào button này, proxy code sẽ được generate theo kiểu cũ để có thể gọi những web service *.asmx.
- Những option khác được sử dụng để control quá trình generate các data contracts mà ta sẽ bàn tới sau. Bạn hãy chọn “Generate Asynchronous Operators” rồi click OK để quay về Visual Studio IDE.
Hình 5: Advanced client-side proxy configuration options
- Lúc này, proxy code sẽ chứa những methods giúp bạn gọi các members của service contract bằng cách sử dụng Begin/End asynchoronous invocation pattern. Sau đây là code sample :
namespace MathClient
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** The Async Math Client *****\n");
using (BasicMathClient proxy = new BasicMathClient())
{
proxy.Open();
// Add numbers in an async manner, using a lambda expression.
IAsyncResult result = proxy.BeginAdd(2, 3,
ar =>
{
Console.WriteLine("2 + 5 = {0}", proxy.EndAdd(ar));
}, null);
while (!result.IsCompleted)
{
Thread.Sleep(200);
Console.WriteLine("Client working...");
}
}
Console.ReadLine();
}
}
}
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** The Async Math Client *****\n");
using (BasicMathClient proxy = new BasicMathClient())
{
proxy.Open();
// Add numbers in an async manner, using a lambda expression.
IAsyncResult result = proxy.BeginAdd(2, 3,
ar =>
{
Console.WriteLine("2 + 5 = {0}", proxy.EndAdd(ar));
}, null);
while (!result.IsCompleted)
{
Thread.Sleep(200);
Console.WriteLine("Client working...");
}
}
Console.ReadLine();
}
}
}
Code 3: Gọi remote methods asynchoronous
Designing WCF Data Contracts
- Ở phần cuối này chúng ta sẽ tìm hiểu thêm về Data Contracts. WCF services trước bao gồm một method rất đơn giản và chỉ sử dụng các type built-in của CLR. Khi bạn sử dụng bất kì web-service binding types (basicHttpBindings, wsHttpinding, …), dữ liệu vào và ra được tự động format thành XML elements nhờ vào System.Runtime.Serialization.XmlFormatter trong assembly System.Runtime.Serialization.dll. Nếu bạn sử dụng TCP binding (chẳng hạn như netTcpBinding), các parameters và dữ liệu return lại được format theo dạng binary.
- Tuy nhiên, khi bạn tạo những service contract có sử dụng những kiểu dữ liệu (struct/class) mà bạn build, những kiểu dữ liệu này phải được defined như là một data contract. Vậy, một data contract là một kiểu dữ liệu được đánh dấu bằng attribute [DataContract]. Mỗi field mà bạn có sử dụng của DataContract sẽ được đánh dấu bằng attribute [DataMember]. Nếu như một field nào không được đánh dấu bằng attribute [DataMember], nó sẽ không được serialized bởi WCF runtime.
- Để minh họa thêm về data contracts, ta hãy tạo một WCF service khác. Sample lần này sẽ được tạo bằng WCF service project template. Chúng ta hãy nhớ lại rằng loại WCF service này sẽ được tự động host trong IIS, và chúng sẽ hoạt động như một .NET XML web service. Khi đã nắm được tính chất của các WCF service như vậy, bạn sẽ ít gặp phải problem khi port một WCF service có sẵn vào IIS virtual directory.
Using the Web-Centric WCF Service Project Template
- Bạn hãy tạo new ASP.NET website hoặc ASP.NET webservice application, rồi xóa hết những thứ mà project template này sinh ra như các file cs, các file asmx:
Hình 6: Tạo một project web để host service trong IIS
* Trong sample gốc của sách, họ sử dụng đến code của một phần của chương khác nên tui sẽ dùng một sample khác sử dụng database NorthWind.
- Trong ví dụ này tui sẽ tạo một class library và call library này trong project web. Bạn hãy tạo một WCF Service Library project, lưu ý là đổi tên file App.config hoặc xóa nó đi để khi start project web, nó không tự động start WCF Service host luôn. Bây giờ ta tạo service contract INorthWindService.cs và code của service contract như sau:
[ServiceContract]
public interface INorthWindService
{
[OperationContract]
Customer GetCustomer(string customerId);
[OperationContract]
void AddCustomer(Customer customer);
[OperationContract]
void AddCustomer(string customerId, string companyName, string contactName, string contactTitle, string address, string city, string region,
string postalCode, string country, string phone, string fax);
}
public interface INorthWindService
{
[OperationContract]
Customer GetCustomer(string customerId);
[OperationContract]
void AddCustomer(Customer customer);
[OperationContract]
void AddCustomer(string customerId, string companyName, string contactName, string contactTitle, string address, string city, string region,
string postalCode, string country, string phone, string fax);
}
Code 4: NorthWind service contract
- Interface bên trên gồm 3 methods, một trong số đó returns một mảng các object Customer. Sau đây là code của lớp Customer:
[DataContract]
public class Customer
{
public Customer()
{ }
private string _CustomerID;
[DataMember]
public string CustomerID
{
get { return _CustomerID; }
set { _CustomerID = value; }
}
private string _CompanyName;
[DataMember]
public string CompanyName
{
get { return _CompanyName; }
set { _CompanyName = value; }
}
private string _ContactName;
[DataMember]
public string ContactName
{
get { return _ContactName; }
set { _ContactName = value; }
}
private string _ContactTitle;
[DataMember]
public string ContactTitle
{
get { return _ContactTitle; }
set { _ContactTitle = value; }
}
private string _Address;
[DataMember]
public string Address
{
get { return _Address; }
set { _Address = value; }
}
private string _City;
[DataMember]
public string City
{
get { return _City; }
set { _City = value; }
}
private string _Region;
[DataMember]
public string Region
{
get { return _Region; }
set { _Region = value; }
}
private string _PostalCode;
[DataMember]
public string PostalCode
{
get { return _PostalCode; }
set { _PostalCode = value; }
}
private string _Country;
[DataMember]
public string Country
{
get { return _Country; }
set { _Country = value; }
}
private string _Phone;
[DataMember]
public string Phone
{
get { return _Phone; }
set { _Phone = value; }
}
private string _Fax;
[DataMember]
public string Fax
{
get { return _Fax; }
set { _Fax = value; }
}
}
Code 5: Code data contract Customer
- Nếu bạn cứ implement interface như thông thường, tạo một host và call những methods này từ client, bạn sẽ bị runtime exception . Lý do là mỗi method exposed từ service phải có tên duy nhất, ở đây ta có 2 method có cùng tên là AddCustomer. Overloading không work trong web service specifications. Nhưng thật may mắn là [OperationContract] attribute support một property là Name giúp bạn đổi tên của method này trong WSDL description. Chúng ta hãy update method này như sau trong service contract:
[ServiceContract]
public interface INorthWindService
{
...
[OperationContract(Name="AddCustomerDetails")]
void AddCustomer(Customer customer);
}
public interface INorthWindService
{
...
[OperationContract(Name="AddCustomerDetails")]
void AddCustomer(Customer customer);
}
Code 6: Sử dụng property Name trong OperationContract
Implementing the Service Contract
- Bạn hãy download code sample ở link bên dưới của bài viết này để xem code hoàn chỉnh của solution. Database sử dụng là NorthWind, sẽ tự động attach vào SQL EXPRESS của bạn, nếu trong database server của bạn đã có sẵn database NorthWind thì hãy sửa lại connection string trong file Web.config.
The Role of the *.svc File
- Khi bạn tạo một WCF service theo style của web-service, bạn sẽ thấy trong project có một file tên là *.svc. File này được sử dụng bởi IIS để host service của bạn; nó mô tả tên và location của service implementation. Chúng ta đã đổi tên của WCF types nên phải đổi luôn contents của file Service.svc như sau:
<%@ ServiceHost Language="C#"
Debug="true"
Service="nthoai.blogspot.com.WCFSample.ServiceLibary.NorthWindService"
Factory="System.ServiceModel.Activation.ServiceHostFactory" %>
Service="nthoai.blogspot.com.WCFSample.ServiceLibary.NorthWindService"
Factory="System.ServiceModel.Activation.ServiceHostFactory" %>
Code 7: File service.svc
- Các bạn chú ý rằng khi host service của mình trong IIS, thì trong file service.svc bắt buộc có một attribute là Factory. Attribute này mặc định ta có thể để là System.ServiceModel.Activation.ServiceHostFactory. Người ta thường extend class ServiceHostFactory để thêm vào một số xử lý đặc biệt như logging,… Trong ví dụ này chúng ta sẽ để giá trị mặc định cho attribute Factory.
Updating the Web.config File
- Bước cuối cùng là update <system.serviceModel> trong web.config:
<system.serviceModel>
</system.serviceModel>
<services>
</services>
<behaviors>
</behaviors>
<service name="nthoai.blogspot.com.WCFSample.ServiceLibary.NorthWindService"
behaviorConfiguration="WCFSample.ServiceLibary.NorthWindServiceBehavior">
</service>
behaviorConfiguration="WCFSample.ServiceLibary.NorthWindServiceBehavior">
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<endpoint address="" binding="wsHttpBinding" contract="nthoai.blogspot.com.WCFSample.ServiceLibary.INorthWindService">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically. -->
</endpoint>
<!-- Metadata Endpoints -->
<!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
<!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<!-- Unless fully qualified, address is relative to base address supplied above -->
<endpoint address="" binding="wsHttpBinding" contract="nthoai.blogspot.com.WCFSample.ServiceLibary.INorthWindService">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically. -->
<identity>
</identity>
<dns value="localhost"/>
</identity>
</endpoint>
<!-- Metadata Endpoints -->
<!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
<!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
</serviceBehaviors>
<behavior name="WCFSample.ServiceLibary.NorthWindServiceBehavior">
</behavior>
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False"/>
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Code 8: File web.config
Testing the Service
- Bây giờ chúng ta có thể build bất kì một chương trình client nào để call thử service này. Ở đây, ta sẽ thử bằng chương trình WcfTestClient như đã giới thiệu ở phần trước (Trong code sample, address của service sẽ khác so với trong bài viết):
- Hình dưới đây sẽ là kết quả khi ta gọi method GetCustomer()
Hình 7: Kết quả gọi method GetCustomer
Tổng kết:
- Loạt bài này giới thiệu sơ lược các tính năng và cách sử dụng Windows Communication Foundation (WCF) API. WCF là một trong 3 công nghệ mới của .NET 3.0. Như đã giới thiệu, động cơ chính của sự ra đời WCF là để thống nhất các công nghệ .NET distributed được giới thiệu trước đây. Một WCF service hoàn chỉnh được xác định bởi address, bindings và contracts nên sẽ rất dễ nhớ khi ta chú ý các từ đầu tiên của các thành phần này: ABC.
- Một WCF application điển hình cần 3 assemblies. Assembly đầu tiên sẽ định nghĩa service contract và service types, các thành phần này sẽ tạo thành những chức năng của service. Assembly này kế tiếp sẽ được host trong một chương trình nào đó, một IIS virtual directory hay một Windows service. Cuối cùng, một client assembly sẽ sử dụng một tool để generate các proxy code và client config để có thể thực hiện việc gọi các remote methods.
- Trong các bài viết cũng có nhắc đến một số các WCF programming tool như SvcConfigEditor.exe (giúp bạn modify các file config dễ dàng trên GUI), WcfTestClient.exe application (giúp bạn test nhanh bất kì một WCF service nào), và nhiều Visual Studio 2008 WCF project templates.
(Hết)
Code sample : Download
Xem Introduction Windows Communication Foundation Part1
Xem Introduction Windows Communication Foundation Part2
Xem Introduction Windows Communication Foundation Part3
Xem Introduction Windows Communication Foundation Part4
Khi host WCf trên Windows services nên chú ý vấn đề multihost,trong các ví dụ trên mạng thường chỉ đề cập đến host 1 WCF service (ServiceHost) lên windows services nhưng thực tế có khi ta cần host nhiều ServiceHost trên cùng 1 Windows Services , dẫn tới lỗi phát sinh vì windows services chỉ chấp nhận xử lý cho 1 host duy nhất .
Qua loạt bài anh đã post lên, chúng ta hầu như chỉ sử dụng WCF ở local, việc tạo ServiceHost & ServiceClient sẽ rất dễ dàng.
Giả sử một ServiceHost đặt tại server A (qua IP tĩnh), như anh đã giới thiệu mình có 2 dạng host: host WCF trong Windows Service và host WCF lên IIS qua file svc. Một ServiceClient ở server B (bất kỳ ở bên ngoài, cũng thông qua IP tĩnh internet).
Câu hỏi đặt ra: Những vấn đề gì cần giải quyết để ServiceClient kết nối với ServiceHost? Hai cách host trên, khả năng cách nào sẽ public service của mình ra ngoài tốt nhất?
Mong được anh trả lời
Xin cảm ơn
Khoa Diep
Dũng bên GeeksShip nhắn bạn thoại share link ở up.vietgeeks.com nhé
Dũng bên GeeksShip nhắn bạn thoại share link ở up.vietgeeks.com nhé