Prerequisites
Implementing Message Contracts
Using Console Applications to host a WCF Service 数据挖掘研究院
Implementing Fault Contracts 数据挖掘研究院
Controlling Instancing Behavior
Controlling the Operations Invocation Sequence 数据挖掘研究院
Conclusion 数据挖掘研究院
In this installment, you have learned the basics of message contracts and fault contracts and seen the steps involved in implementing them in a WCF service. You have also gained a good understanding of how to host WCF services in a .NET console application. Finally, you have seen examples demonstrating the instancing behavior and the flexibility of WCF in allowing you to control the invocation sequence of WCF service operations in a service.
数据挖掘实验室There are times where you might want to enforce a sequence in which the service operations need to be invoked. For example, if your service has a series of operations that depend on an product id, you can set IsInitiating to true for a GetProductID service operation and set all remaining service operations to false. This ensures that each new client obtains an Product ID prior to using the other methods exposed by the service. You can accomplish this through the properties exposed by the OperationContract class. To this the OperationContract class exposes two key properties: IsInitiating and IsTerminating that are key to controlling the sequence of service operations. 数据挖掘实验室
- IsInitiating: The default value of IsInitiating is true, which means that an operation can be the first one called on a channel. You can also set IsInitiating to false to force clients to call another method on the service before they can invoke this one.
- IsTerminating: When you set the IsTerminating to true, WCF will close the channel after the reply is sent to the client application.
The following code shows a simple product service that implements three methods named IsProductInStock, OrderProduct, and GetConfirmationID. By setting the IsInitiating or IsTerminating properties of the OperationContract attribute for each of the methods to true or false, you control the sequence in which the operations need to be invoked. 数据挖掘研究院
using System; 数据挖掘实验室
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Data;
using System.Data.SqlClient;
namespace WCFExamplesConsoleHosting
{
[ServiceContract(Namespace = "http://15seconds.com/WCF", SessionMode =
SessionMode.Required)]
public interface IProductService
{
[OperationContract(IsInitiating = true, IsTerminating = false)]
bool IsProductInStock(Product prod);
[OperationContract(IsInitiating = false, IsTerminating = false)]
string OrderProduct(Product prod);
[OperationContract(IsInitiating = false, IsTerminating = true)] 数据挖掘研究院
int GetConfirmationID(string guid);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class ProductService : IProductService
{
public bool IsProductInStock(Product prod)
{
//Perform processing
return true;
}
public string OrderProduct(Product prod)
{
//Perform processing
return "3F2504E0-4F89-11D3-9A0C-0305E82C3301";
}
public int GetConfirmationID(string guid)
{
//Perform processing
return 1;
}
}
[DataContract]
public class Product
{
[DataMember]
public int ProductID;
[DataMember]
public string Name;
[DataMember]
public Guid Rowguid; 数据挖掘研究院
[DataMember]
public DateTime ModifiedDate;
}
}
To start with, you set the SessionMode property of the ServiceContract attribute to SessionMode.Required to indicate the service operations execute within the scope of a session. 数据挖掘研究院
[ServiceContract(Namespace = "http://15seconds.com/WCF",
SessionMode = SessionMode.Required)]
Then you define the invocation sequence of operations through the IsInitiating and IsTerminating properties. 数据挖掘研究院
[OperationContract(IsInitiating = true, IsTerminating = false)]
bool IsProductInStock(Product prod);
[OperationContract(IsInitiating = false, IsTerminating = false)]
string OrderProduct(Product prod);
[OperationContract(IsInitiating = false, IsTerminating = true)]
int GetConfirmationID(string guid);
Based on the above definition, the right sequence to invoke the operations of the product service would be: IsProductInStock(), OrderProduct() and GetConfirmationID(). If a caller's first call is to any operation other than IsProductInStock(), the channel is refused and an exception is thrown. When a caller initiates a session by calling IsProductInStock(), that caller can terminate the communication session at any time by calling GetConfirmation(). OrderProduct() can be called any number of times during a session. 数据挖掘研究院
Now that you have seen the service code required to establish the sequence in which the operations need to be invoked, let us validate that through the client invocation.
Invoking the Product Service from a Client
As an example, consider the following client code that invokes the OrderProduct() method first without invoking the IsProductInStock() method. 数据挖掘研究院
private void btnProductService_Click(object sender, EventArgs e)
{
ProductServiceClient client = new ProductServiceClient();
MessageBox.Show(client.OrderProduct(new Product()).ToString());
}
The following figure shows the exception generated when you try to invoke the OrderProduct() operation before the IsProductInStock() operation. 数据挖掘研究院
However, if you invoke the operations in the right sequence, as shown below, you will get the desired result.
private void btnProductService_Click(object sender, EventArgs e)
{
ProductServiceClient client = new ProductServiceClient();
MessageBox.Show(client.IsProductInStock(new Product()).ToString());
string guid = client.OrderProduct(new Product());
MessageBox.Show(client.GetConfirmationID(guid).ToString());
}
When you send a message to the service, the message gets dispatched to the instance of the service. The lifetime of this service instance is tied to one or more received messages. The WCF programming model allows you to change this association through the InstanceContextMode property on the ServiceBehaviorAttribute type. Here are the possible settings are: 数据挖掘研究院
- PerCall: This setting creates a new service instance for each client request. When the service type is configured for per-call activation, the service instance exists only while a client call is in progress. As the name suggests, every client request gets a new dedicated service instance. The client calls the proxy and the proxy forwards the call to the service. Windows Communication Foundation creates a service instance and calls the method on it. After the method call returns, if the object implements IDisposable, then WCF calls Dispose() on it.
- PerSession: With this setting, a new instance is created for each new client session, and maintained for the lifetime of that session. When you create a new proxy to a service configured as session-aware, you will get a new dedicated service instance that is independent of any other instances of the same service. That instance will remain in service usually until the client no longer needs it. Note that every client session has one service instance per proxy. If you create another proxy to the same or a different service endpoint, that second proxy will be associated with a new instance and session.
- Single: In this case, a single instance of the service class handles all client requests for the lifetime of the service. As the name suggests, when a service is configured as a singleton, all clients get connected to the same single well-known instance independently of each other, regardless of which endpoint of the service they connect to. The singleton service lives forever, and is only disposed of once the host shuts down. The singleton is created exactly once when the host is created.
Now that you know the different settings, let us look at a simple example to showcase the use of instancing behavior setting. 数据挖掘实验室
using System; 数据挖掘研究院
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Data;
using System.Data.SqlClient;
namespace WCFExamplesConsoleHosting
{
[ServiceContract()]
public interface ICategoryService
{
[OperationContract]
void AddCategory(CategoryAttributes trans);
[OperationContract]
int GetNoOfCalls();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class CategoryService : ICategoryService
{
private Object _sync = new object(); 数据挖掘研究院
private int _noOfCalls;
public void AddCategory(CategoryAttributes cate)
{
string connString =
System.Configuration.ConfigurationManager.
ConnectionStrings["adventureWorks"].ConnectionString;
using (SqlConnection connection = new SqlConnection(connString))
{
connection.Open();
string sql = "Insert into Production.ProductCategory " +
"(Name, rowguid, ModifiedDate) " +
"Values ('" + cate.Name + "','" + cate.guid + "','" +
cate.ModifiedDate + "')";
SqlCommand command = new SqlCommand(sql, connection);
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
}
lock (_sync)
{
_noOfCalls++;
}
}
public int GetNoOfCalls()
{
lock (_sync)
{
return _noOfCalls;
}
}
}
[MessageContract]
public class CategoryAttributes
{
[MessageHeader]
public DateTime ModifiedDate;
[MessageHeader]
public Guid guid;
[MessageBodyMember]
public string Name;
}
}
In the above code, you set the instancing behavior to Singleton meaning that one instance of CategoryService will always be available to service all the client requests.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class CategoryService : ICategoryService
Inside the AddCategory() method, you increment the private variable _noOfCalls by .
lock (_sync) 数据挖掘研究院
{
_noOfCalls++;
}
Then you declare a service operation named GetNoOfCalls() that returns the number of times the AddCategory() method is called by different clients ever since the host started the service. For example, if the CategoryService is called 3 times by three different clients, the below call to the GetNoOfCalls() will return 3.
private void btnNoOfCalls_Click(object sender, EventArgs e) 数据挖掘研究院
{
CategoryServiceClient client = new CategoryServiceClient();
MessageBox.Show(client.GetNoOfCalls().ToString());
}
In addition to the pre-defined settings, you can also implement your own mode of instancing by implementing a custom IInstanceContextProvider interface. By implementing the various methods of this interface, you can have a much finer level of control over the instancing behavior.
数据挖掘研究院There are times where you might want to return custom SOAP faults to clients so that you can provide detailed information about the exceptions generated by the services. To accomplish this, you need to follow the below steps: 数据挖掘研究院
- First define a type using the data contract and specify the fields you want to return as part of the SOAP faults.
- Next you decorate the service operation with the FaultContract attribute and specify the name of the type you defined in the first step.
- Raise the exception from the service by creating an instance of the type (defined in step 1) and set its properties to appropriate values.
To understand the fault contracts, consider the following EmployeeService implementation.
using System; 数据挖掘研究院
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Data;
using System.Data.SqlClient;
namespace WCFExamplesConsoleHosting
{
[ServiceContract]
public interface IEmployeeService
{
[OperationContract]
[FaultContract(typeof(EmployeeException),
Action="http://15seconds.com/exceptions",
ProtectionLevel=ProtectionLevel.EncryptAndSign)]
int AddEmployee(string employeeName, DateTime modifiedDate,
Guid guid); 数据挖掘研究院
}
public class EmployeeService : IEmployeeService
{
public int AddEmployee(string employeeName, DateTime
modifiedDate, Guid guid)
{
try
{
//Perform some processing
}
catch(Exception ex)
{
throw new FaultException<EmployeeException>(new EmployeeException 数据挖掘实验室
("Employee Name 1", "Employee Exception Message 1"));
}
return 1;
}
}
[DataContract(Namespace = "http://15seconds.com/exceptions",
Name = "EmployeeException")]
public class EmployeeException
{
private string _employeeExceptionName;
private string _employeeExceptionMessage;
public EmployeeException(string employeeExceptionName,
string employeeExceptionMessage)
{
_employeeExceptionName = employeeExceptionName;
_employeeExceptionMessage = employeeExceptionMessage;
}
[DataMember(Name = "EmployeeExceptionName", Order = 1)]
public string EmployeeExceptionName
{
get{return _employeeExceptionName;}
set{_employeeExceptionName = value;}
}
[DataMember(Name = "EmployeeExceptionMessage", Order = 2)]
public string EmployeeExceptionMessage
{
get{return _employeeExceptionMessage;}
set{_employeeExceptionMessage = value;}
}
}
}
By decorating the service operation with the FaultContract attribute, you are instructing WCF to include the appropriate fault information in the service metadata (specifically in the XSD and WSDL). 数据挖掘研究院
[OperationContract] 数据挖掘实验室
[FaultContract(typeof(EmployeeException),
Action="http://15seconds.com/exceptions",
ProtectionLevel=ProtectionLevel.EncryptAndSign)]
int AddEmployee(string employeeName, DateTime modifiedDate,
Guid guid);
Once you specify the fault contract, whenever you raise the EmployeeException from the service implementation, the exception will get serialized into custom SOAP fault message, which is then propagated back up to the caller. 数据挖掘研究院
throw new FaultException<EmployeeException>(new EmployeeException 数据挖掘研究院
("Employee Name 1", "Employee Exception Message 1"));
In the above code, the FaultException type is used to throw the EmployeeException to the caller. Now that you have seen the code in the service side, let us briefly look at the client side code required to handle and process the exception.
public static void Main() 数据挖掘实验室
{
EmployeeServiceClient client = new EmployeeServiceClient();
try
{
client.AddEmployee("Test Employee", DateTime.Now,
System.Guid.NewGuid());
MessageBox.Show("Employee added");
}
catch (FaultException<EmployeeException> employeeException)
{
//Process the exception
}
}
Here you catch the EmployeeException and process the details of the exception in the catch block like you would catch any other exception.
数据挖掘实验室To host a service in a console application, you use the ServiceHost class that gives you direct control over the WCF hosting infrastructure. You instantiate ServiceHost based on a particular service type. Let us look at the code required to host the CategoryService in a Console application named WCFExamplesConsoleHosting.
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace WCFExamplesConsoleHosting
{
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost/CategoryService");
ServiceHost host = new ServiceHost(typeof(CategoryService), uri);
host.AddServiceEndpoint(typeof(ICategoryService),
new WSHttpBinding(), uri);
host.Open(); 数据挖掘实验室
Console.WriteLine("Waiting for client invocations");
Console.ReadLine();
host.Close();
}
}
}
To the constructor of the ServiceHost class, you specify the base address for the transport you plan to use in addition to the service type. In this example, you set the base address to http://localhost/CategoryService. This will be used as the base for any relative HTTP addresses that you might specify when adding endpoints.
Uri uri = new Uri("http://localhost/CategoryService"); 数据挖掘研究院
ServiceHost host = new ServiceHost(typeof(CategoryService), uri);
Now you are ready to add the service end point to the ServiceHost object. This is accomplished through the call to the ServiceHost.AddServiceEndPoint() method. For the purposes of this example, the endpoint uses WSHttpBinding and a absolute HTTP address of http://localhost/CategoryService.
host.AddServiceEndpoint(typeof(ICategoryService), 数据挖掘研究院
new WSHttpBinding(), uri);
Once you configure the host with end point information, WCF is now ready to build the runtime needed to support your configuration. This is initiated through the call to the ServiceHost.Open() method. 数据挖掘研究院
host.Open();
Once the call to ServiceHost.Open() returns, the WCF runtime is built and ready to receive messages at the addresses specified by each endpoint. During this process, WCF generates an endpoint listener for each supplied endpoint and the code needed to support the protocols specified by the binding. Note that an endpoint listener is the piece of code that actually listens for incoming messages using the specified transport and address. 数据挖掘研究院
Configuring Service Endpoints in Configuration File 数据挖掘研究院
In the previous section, you have seen the code that demonstrated how to add the service end point information to the host application. Although this approach works for simple scenarios, it is not an ideal approach since hard coding the end point information in the code makes it difficult to change the end point information as needed. You can overcome this drawback by storing the end point information in the configuration file and retrieving it run time. Specifically, you can store this end point information in the <system.serviceModel>/<services>/<service>/<endpoint> element. This approach provides you with the flexibility to change the end point information any time without having to touch the service hosting implementation code.
The following configuration file defines the end point for the CategoryService, which was specified through the call to the AddServiceEndpoint() in the previous section: 数据挖掘研究院
<?xml version="1.0"?>
<configuration>
----
----
<system.serviceModel>
<services>
<service name="WCFExamplesConsoleHosting.CategoryService">
<endpoint address="" binding="wsHttpBinding"
contract="WCFExamplesConsoleHosting.ICategoryService"/>
</service>
</services>
</system.serviceModel>
</configuration>
If you add this app.config file to the WCFExamplesConsoleHosting application and comment out the calls to AddServiceEndpoint(), you'll see the same results in the output. This increases deployment flexibility since the communication details are completely factored out of the compiled code.
Client Implementation 数据挖掘研究院
Now that you have created the service and hosted the service in a console application, let us invoke the service from the client application. To be able to invoke the client application, you first need to create a proxy for the service through the svcutil utility. Part-1 of this series goes into specifics of creating the proxy through the svcutil utility. Once the proxy and the configuration file are created, add them to the client project. The following code shows the code required to invoke the CategoryService through the generated proxy.
private void btnInvoke_Click(object sender, EventArgs e)
{
CategoryServiceClient client = new CategoryServiceClient();
client.AddCategory(DateTime.Now, System.Guid.NewGuid(),
txtCategoryName.Text);
MessageBox.Show("Add Category completed");
}
The above code retrieves the category name entered by the user in a textbox and passes that as an argument to the AddCategory() operation along with the current date time and the guid values. 数据挖掘研究院
数据挖掘研究院There are times where the default SOAP message format provided by the WCF runtime infrastructure does not meet your needs. In that case, you might want to manipulate the format of the actual SOAP message that is being passed between the client and the service. In other words, the message contracts allow you to specify what exactly a service will be passing on the wire. Prior to WCF, the only way to accomplish this is by plugging into the HTTP pipeline and intercepting the messages through SOAP extensions. However with the introduction of WCF message contracts, you now have a finer level of control over the SOAP messages in that you can easily demarcate the portions of the message that should live in the SOAP header and those that should live in the body. In addition, the message contracts also provide you with a way to control how the WSDL is generated for a WCF service. By separating the message into two well defined sections (header and body), you now have the ability to apply different types of processing to these two sections. For example, you can apply different encryption processing to each section. 数据挖掘研究院
Here is an example of a message contract. 数据挖掘研究院
[MessageContract]
public class CategoryAttributes
{
[MessageHeader]
public DateTime ModifiedDate;
[MessageHeader]
public Guid guid;
[MessageBodyMember]
public string Name;
}
To start with, you decorate the name of the class that represents the SOAP message with the keyword MessageContract. When you apply a MessageContract attribute to a type, it is used to serialize a SOAP message. Applying the MessageHeader to data members creates one or more SOAP headers in the WSDL contract for this message. Applying the MessageBodyMember creates one or more SOAP body elements that can be ordered using the System.ServiceModel.MessageBodyMember.Order property of the attribute. For example, to order the Name property to appear before the AnotherProperty, you can use the Order attribute to indicate their specific locations.
[MessageContract] 数据挖掘研究院
public class CategoryAttributes
{
[MessageHeader]
public DateTime ModifiedDate;
[MessageHeader]
public Guid guid;
[MessageBodyMember(Order=1)]
public string Name;
[MessageBodyMember(Order=2)]
public string AnotherProperty;
}
In the above example, the header and body elements are of simple types. However they can also in turn leverage data contracts or serializable types as part of their definition. 数据挖掘实验室
Using the Message Contracts in a Service
Message contracts are associated with service operations as incoming or outgoing messages. To see this in action, let us create a new service named CategoryService and use the CategoryAttributes message contract to pass the arguments to the service. First, create a new Visual C# Console application named WCFExamplesConsoleHosting and add a C# class named CategoryService to the project and modify the class to look as follows:
using System;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Data;
using System.Data.SqlClient;
namespace WCFExamplesConsoleHosting
{
[ServiceContract()]
public interface ICategoryService
{
[OperationContract]
void AddCategory(CategoryAttributes trans);
}
public class CategoryService : ICategoryService
{
public void AddCategory(CategoryAttributes cate)
{
string connString = System.Configuration.ConfigurationManager.
ConnectionStrings["adventureWorks"].ConnectionString; 数据挖掘研究院
using (SqlConnection connection = new
SqlConnection(connString))
{
connection.Open();
string sql = "Insert into Production.ProductCategory "
+ "(Name, rowguid, ModifiedDate) " +
"Values ('" + cate.Name + "','" + cate.guid +
"','" + cate.ModifiedDate + "')"; 数据挖掘实验室
SqlCommand command = new SqlCommand(sql, connection);
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
}
}
}
[MessageContract]
public class CategoryAttributes
{
[MessageHeader]
public DateTime ModifiedDate;
[MessageHeader]
public Guid guid;
[MessageBodyMember]
public string Name;
}
}
To start with, the ICategoryService interface defines a method named AddCategory with CategoryAttributes as the parameter.
[OperationContract]
void AddCategory(CategoryAttributes trans);
Note that whenever you are using message contracts with a service operation, you can't mix and match message contracts with data contract types as return values or arguments for that particular operation. As an example, if you like to return some values from the service, you need to create a corresponding message contract for the return value as well. 数据挖掘研究院
As the name suggests, the AddCategory() method simply adds a new category to the Production.ProductCategory table using the values contained in the CategoryAttributes object.
public void AddCategory(CategoryAttributes cate)
{
First, you retrieve the connection string from the app.config file using the ConfigurationManager class.
string connString = System.Configuration.ConfigurationManager. 数据挖掘研究院
ConnectionStrings["adventureWorks"].ConnectionString;
Then you open the connection to the database and execute the sql statement that inserts the category record to the ProductCategory table. 数据挖掘研究院
using (SqlConnection connection = new 数据挖掘实验室
SqlConnection(connString))
{
connection.Open();
string sql = "Insert into Production.ProductCategory "
+ "(Name, rowguid, ModifiedDate) " +
"Values ('" + cate.Name + "','" + cate.guid +
"','" + cate.ModifiedDate + "')";
SqlCommand command = new SqlCommand(sql, connection);
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
}
}
As you can see from the above, the message contract really acts as a wrapper object for the SOAP message parameters. Now that you have created the service, you are ready to host the service and make it available for consumers. In Part 1 of this series, you have seen how to host the service in IIS. For the purposes of this installment, let us take a look at the steps involved in hosting a WCF service in a Console application. 数据挖掘研究院
数据挖掘研究院- Visual Studio 2005 Professional Edition
- Microsoft .NET Framework 3.0
- Microsoft Visual Studio Code Name "Orcas" Community Technology Preview - Development Tools for .NET Framework 3.0
- Microsoft Windows Software Development Kit

