Chapter 11. Creating a SOA Case

WHAT'S IN THIS CHAPTER?

  • Defining requirements for the case

  • Developing the complete solution

  • Testing the solution

This chapter is an interactive walkthrough of how to create a solution in SOA architecture with Visual Studio 2010 using WCF. It starts by defining requirements for a case and shows you how to develop the solution, step by step. At the end of this chapter you will have developed a number of services, hosts, and clients as part of the solution. This is a complete example — you can test the process and see it working in action.

THE REQUIREMENTS FOR THE CASE

You need to create services for a car rental company. The company needs a service for managing their fleet of cars, a service to register their customers, and a service to register the rentals.

These three services are considered internal and will be used by their own applications.

Besides these services, they also need an external service accessible by partners of the company that can insert a customer and register a rental with one call.

Operations for the CarManagementService:

  • InsertNewCar: Receives the data for a car and inserts it into a database.

  • RemoveCar: Receives the ID of a car and deletes it from the database.

  • UpdateMileage: Receives the data for a car and updates the mileage for the car in the database.

  • ListCars: Returns all cars with their data.

  • GetCarPicture: Returns a picture for the car with a specified ID.

Operations in the CustomerService:

  • RegisterCustomer: Receives the data for a customer and inserts it into a database.

Operations in the RentalService:

  • RegisterCarRental: Receives all information to register the data for a rental.

  • RegisterCarRentalAsPaid: Receives an ID of the rental and marks the rental as paid in the database.

  • StartRental: Calls the method to indicate that the car was picked up at a given location where the rental starts.

  • StopCarRental: Calls the method to indicate that the car was dropped off at a given location where the rental stops.

  • GetRentalRegistration: Returns all data about the rental.

Operations for the ExternalService:

  • SubmitRentalContract: Receives all the information for a new customer and all the information for the rental. It inserts the new customer and the rental data into a database.

Additional Requirements

Additional requirements for the CarManagementService:

  • There is an enumerated field type for defining whether the car has a manual transmission or an automatic transmission.

  • A number of car types are recognized by the company. Besides regular cars there are luxury cars and sports cars. Luxury cars have all the data elements of a car and a list of luxury items. Sports cars have all the data elements of a car and a horsepower value.

Additional requirements for the RentalService:

  • Errors occurring in this service should be handled carefully. The data should be validated and when the data is not correctly filled in, the client must receive a well-structured message with a functional description of the validation rule.

  • The code for the RegisterCarRentalAsPaid operation should be executed under the credentials of the client using the service.

Additional requirement for the ExternalService:

  • The SubmitRentalContract calls operations in two other services (CustomerService and RentalService) in sequence to register the customer and to register the rental. It's clear that this has to happen in one transaction. The customer is not allowed to be inserted into the database without the rental being registered.

SETTING UP THE SOLUTION

You start by creating an empty Visual Studio solution. You will be creating every project needed in one solution. This makes it easy to test all applications at once and to have it checked in to the source control environment (see Figure 11-1).

FIGURE 11-1

Figure 11.1. FIGURE 11-1

Name the solution TheCarRentalSOACase and place it in the directory C:DataWorkTheCarRentalSOACase.

Create a structure of solution folders to group projects together. Each folder will contain one or more projects. You can add a solution folder by right-clicking the solution and selecting Add Ø New Solution Folder. See Figure 11-2.

These solution folders are needed:

  • Clients (foldername : Clients)

  • Hosts (foldername : Hosts)

  • Interfaces (foldername : Interfaces)

  • Services (foldername : Services)

After adding the four solution folders, your solution should look like Figure 11-3.

FIGURE 11-2

Figure 11.2. FIGURE 11-2

FIGURE 11-3

Figure 11.3. FIGURE 11-3

CREATING THE INTERFACES

Creating the interfaces first is the best approach and is typical for a SOA project. Start by creating the libraries to contain the interfaces. You need four libraries:

  • CarManagementInterface

  • CustomerInterface

  • RentalInterface

  • ExternalInterface

Do this by adding the four Class Libraries projects to the Interfaces folder in the solution. See Figure 11-4.

In the Add New Project dialog box, select Class Library from the list of templates. See Figure 11-5.

FIGURE 11-4

Figure 11.4. FIGURE 11-4

FIGURE 11-5

Figure 11.5. FIGURE 11-5

Do this for each of the four interfaces. Your solution should look like Figure 11-6.

Now you need to add references to the WCF libraries, System.ServiceModel and System.Runtime.Serialization. They contain the attributes needed for the contracts. Adding references is done by right-clicking References in a project in Solution Explorer. See Figure 11-7.

FIGURE 11-6

Figure 11.6. FIGURE 11-6

FIGURE 11-7

Figure 11.7. FIGURE 11-7

This opens the Add Reference dialog box. Select System.ServiceModel and System.Runtime.Serialization and click OK. See Figure 11-8.

FIGURE 11-8

Figure 11.8. FIGURE 11-8

Repeat this step for the other three projects.

Because you selected Class Library as the template, Visual Studio has created a file called Class1.cs containing an empty class in the project. You don't need this file as you're creating interfaces. So you can delete the Class1.cs file in each of the four projects. See Figure 11-9.

Add an Interface file to each project (see Figure 11-10). The Interfaces are ICarManagement, ICustomer, IRental, and IExternalInterface.

FIGURE 11-9

Figure 11.9. FIGURE 11-9

FIGURE 11-10

Figure 11.10. FIGURE 11-10

Open the interface and add the needed using statements to access the System.ServiceModel and System.Runtime.Serialization namespaces:

using System.ServiceModel;
using System.Runtime.Serialization;

Make the interface public by adding the public keyword to the interface declaration:

public interface ICarManagement

Repeat this for each of the interface projects.

Creating the CarManagement Interface

To support the requirement needed, an enumerated field type for the car transmission, add the following code:

[DataContract]
public enum TransmissionTypeEnum
{
  [EnumMember]
  Manual,
  [EnumMember]
  Automatic
}

This is a public enum called TransmissionTypeEnum. The enum must be attributed with the DataContract attribute and each choice in the enum must be attributed with the EnumMember attribute.

Now add code for the Car class:

[DataContract]
public class Car
{
  [DataMember]
  public string BrandName { get; set; }
  [DataMember]
  public string TypeName { get; set; }
  [DataMember]
  public TransmissionTypeEnum Transmission { get; set; }
  [DataMember]
  public int NumberOfDoors { get; set; }
  [DataMember]
  public int MaxNumberOfPersons { get; set; }
  [DataMember]
  public int LitersOfLuggage { get; set; }
}

This class is attributed with the DataContract attribute and each property is attributed as DataMember.

Now you can complete the interface containing the signatures of the service methods:

[ServiceContract]
interface ICarManagement
{
  [OperationContract]
  int InsertNewCar(Car car);

  [OperationContract]
  bool RemoveCar(Car car);

  [OperationContract]
  void UpdateMilage(Car car);

  [OperationContract]
  List<Car> ListCars();

  [OperationContract]
  byte[] GetCarPicture(string carID);
}

Fix any errors and then finish by compiling this class library.

Creating the Customer Interface

Add code to the ICustomer interface. This is the code for the Customer class and the signatures for the operation in the CustomerService:

[ServiceContract]
public interface ICustomer
{
  [OperationContract]
  int RegisterCustomer(Customer customer);
}

[DataContract]
public class Customer
{
  [DataMember]
  public string CustomerName { get; set; }
  [DataMember]
  public string CustomerFirstName { get; set; }
  [DataMember]
  public string CustomerMiddleLetter { get; set; }
  [DataMember]
  public DateTime CustomerBirthDate { get; set; }
}

Creating the Rental Interface

Add code to the IRental interface. This is the code for the RentalRegistration DataContract and the signatures for the operations in the RentalService:

[ServiceContract]
public interface IRental
{
  [OperationContract]
  string RegisterCarRental(RentalRegistration rentalRegistration);

  [OperationContract]
  void RegisterCarRentalAsPaid(string rentalID);

  [OperationContract]
  void StartCarRental(string rentalID);

  [OperationContract]
  void StopCarRental(string rentalID);

  [OperationContract]
  RentalRegistration GetRentalRegistration(string rentalID);
}

[DataContract]
public class RentalRegistration
{
  [DataMember]
  public int CustomerID { get; set; }
  [DataMember]
  public string CarID { get; set; }
  [DataMember]
  public int PickUpLocation { get; set; }
  [DataMember]
  public int DropOffLocation { get; set; }
  [DataMember]
  public DateTime PickUpDateTime { get; set; }
  [DataMember]
  public DateTime DropOffDateTime { get; set; }
  [DataMember]
  public PaymentStatusEnum PaymentStatus { get; set; }
  [DataMember]
  public string Comments { get; set; }
}

[DataContract]
public enum PaymentStatusEnum
{
  [EnumMember(Value = "PUV")]
  PaidUpFrontByVoucher,
  [EnumMember(Value = "PUC")]
  PaidUpFrontByCreditCard,
  [EnumMember(Value = "TPP")]
  ToBePaidAtPickUp,
[EnumMember(Value = "INV")]
  ToBePaidByInvoice
}

[DataContract]
public enum IncludedInsurance
{
  [EnumMember]
  LiabilityInsurance = 1,
  [EnumMember]
  FireInsurance = 2,
  [EnumMember]
  TheftProtection = 4,
  [EnumMember]
  AllRiskInsurance = 1 + 2 + 4
}

Code snippet CreatingaSOACase.zip

Creating the External Interface

The External Interface project reuses the Customer contract and the Rental contract and thus needs a reference to both libraries. Open the Add Reference dialog box and switch to the Projects tab. Select both CustomerInterface and RentalInterface. See Figure 11-11.

FIGURE 11-11

Figure 11.11. FIGURE 11-11

Besides adding using statements for the two system libraries, add using statements for RentalInterface and CustomerInterface:

using RentalInterface;
using CustomerInterface;

The following is code for the ExternalInterface:

[ServiceContract]
public interface IExternalInterface
{
  [OperationContract]
  void SubmitRentalContract(RentalContract rentalContract);
}

[DataContract]
public class RentalContract
{
  [DataMember]
  public string Company { get; set; }
  [DataMember]
  public string CompanyReferenceID { get; set; }
  [DataMember]
  public RentalRegistration RentalRegistration { get; set; }
  [DataMember]
  public Customer Customer { get; set; }
}

The ExternalInterface has one method that receives a parameter. It is structured by a DataContract which has properties for a RentalRegistration and a Customer.

CREATING THE SERVICES

In the solution, add four projects as a class library, one for each service. These projects reference the interface projects and contain the implementation of the logic. Name them CarManagementService, CustomerService, ExternalInterfaceFacade, and RentalService. Your solution should now look like Figure 11-12.

FIGURE 11-12

Figure 11.12. FIGURE 11-12

Add the assemblies, System.ServiceModel and System.Runtime.Serialization, to each of the projects.

Rename the Class1.cs file in each project to the appropriate name. The names of the files are CarManagementImplementation.cs, CustomerServiceImplementation.cs, RentalServiceImplementation.cs, and ExternalInterfaceFacadeImplementation.cs.

Visual Studio asks you if you would like to rename the code element 'Class1'. Click Yes. See Figure 11-13.

The result is seen in Figure 11-14.

FIGURE 11-13

Figure 11.13. FIGURE 11-13

FIGURE 11-14

Figure 11.14. FIGURE 11-14

Add the using statements needed to each of the files:

using System.Runtime.Serialization;
using System.ServiceModel;

For each project, add a reference to the corresponding project containing the interface. Then add a corresponding using statement in each class to access its interface:

using CarManagementInterface;

Implement the interface in the class by adding a column and the name of the interface after the class declarations:

public class CarManagementImplementation : ICarManagement
{
}

Implementing all methods in an interface can be done quickly by right-clicking the name of the interface you just typed in the source code editor and selecting Implement Interface. See Figure 11-15.

FIGURE 11-15

Figure 11.15. FIGURE 11-15

For the CarManagement Interface, the result should look like this:

public int InsertNewCar(Car car)
{
  throw new NotImplementedException();
}

public bool RemoveCar(Car car)
{
  throw new NotImplementedException();
}

public void UpdateMilage(Car car)
{
  throw new NotImplementedException();
}

public List<Car> ListCars()
{
  throw new NotImplementedException();
}

public byte[] GetCarPicture(string carID)
{
  throw new NotImplementedException();
}

Do this for the other three services, making sure to use the corresponding interface for each implementation library, and then build the solution.

CREATING THE HOST

Start by creating a console application as host. This is more convenient for testing and debugging. Later you can create a Windows service which will host the services.

Add a Console application to the solution in the host solution folder. See Figure 11-16.

FIGURE 11-16

Figure 11.16. FIGURE 11-16

Next you add the needed WCF libraries and all the interface and implementation libraries. Add the System.ServiceModel and System.Runtime.Serialization assemblies. Then add all four Interface Projects together with the Implementation Projects. See Figure 11-17.

FIGURE 11-17

Figure 11.17. FIGURE 11-17

To the code of program.cs, add the needed using statements. Both the system namespaces and the four namespaces of the implementation projects are needed:

using System.ServiceModel;
using System.Runtime.Serialization;
using CarManagementService;
using RentalService;
using CustomerService;
using ExternalInterfaceFacade;

Add code to the main method for an exception handler together with statements that write the status of the service to the console. The last line is a console.readkey operation. It is there to make sure the host application keeps running after the services are opened so clients can access them:

Console.WriteLine("ServiceHost");
try
{

  //Open hosts here

}
catch (Exception ex)
{
  Console.WriteLine(ex.Message);
}
Console.WriteLine("Started");
Console.ReadKey();

You need four service hosts. Declare a reference variable for each of the hosts in the program class:

static ServiceHost CarManagementServiceHost;
static ServiceHost CustomerServiceHost;
static ServiceHost RentalServiceHost;
static ServiceHost ExternalServiceHost;

Write the code to instantiate and open each host in the try-catch block of the main method:

CarManagementServiceHost = new ServiceHost(typeof(CarManagementService.
 CarManagementImplementation));
CarManagementServiceHost.Open();

CustomerServiceHost = new ServiceHost(typeof(CustomerService.
 CustomerServiceImplementation));
CustomerServiceHost.Open();

RentalServiceHost = new ServiceHost(typeof(RentalService.
RentalServiceImplementation));
RentalServiceHost.Open();

ExternalServiceHost = new ServiceHost(typeof(ExternalInterfaceFacade.
 ExternalInterfaceFacadeImplementation));
ExternalServiceHost.Open();

Add an application configuration file to the project. Do this by using the Add New Item dialog box. Select Application Configuration File and use the default name, which is App.config. See Figure 11-18.

FIGURE 11-18

Figure 11.18. FIGURE 11-18

Build all solutions. It's important that the host is built correctly. The build will copy the assemblies for the interfaces and the implementations to the bin directory of this project. The binaries are needed as the WCF Configuration Editor needs to have access to them while configuring them.

Right-click the App.config file and select Edit WCF Configuration. See Figures 11-9 and 11-20.

FIGURE 11-19

Figure 11.19. FIGURE 11-19

In the WCF Configuration Editor, click Create a New Service. See Figure 11-21.

FIGURE 11-20

Figure 11.20. FIGURE 11-20

FIGURE 11-21

Figure 11.21. FIGURE 11-21

Click Browse to open the Type Browser dialog box. Navigate into the bin directory and then into the debug directory. The dialog box shows a number of assemblies. Select the CarManagamentService.dll assembly and click Open. See Figure 11-22.

This will show you the implementation class in this assembly. Click CarManagamentService.CarManagementImplementation and click Open. See Figure 11-23.

FIGURE 11-22

Figure 11.22. FIGURE 11-22

FIGURE 11-23

Figure 11.23. FIGURE 11-23

The Add Service wizard proposes the name of the implementation in the dialog box. Click Next.

The wizard decides what the name of the contract is. Do not change this. Click Next.

The wizard now asks you for the communication mode the service is using. Leave the selection on HTTP (see Figure 11-24). Click Next.

The wizard now asks for the method of interoperability, as shown in Figure 11-25. Select Advanced Web Services interoperability and leave the Simplex Communication button selected. Click Next.

FIGURE 11-24

Figure 11.24. FIGURE 11-24

FIGURE 11-25

Figure 11.25. FIGURE 11-25

The wizard asks for the address of your endpoint: http://localhost:9876/CarManagementService. See Figure 11-26.

Click Next and Finish. Save the configuration (File, Save) and exit the WCF Configuration Editor.

If the App.config file was open in an editor, Visual Studio will detect that the App.config file has changed and will show you this dialog box (see Figure 11-27). Click Yes.

FIGURE 11-26

Figure 11.26. FIGURE 11-26

FIGURE 11-27

Figure 11.27. FIGURE 11-27

Check the configuration created. The wizard has configured ws2007HttpBinding as binding for the endpoint. Change this to wsHttpBinding. Be careful, this value is case sensitive. The result should look like this:

<system.serviceModel>
 <services>
  <service name="CarManagementService.CarManagementImplementation">
   <endpoint
     address="http://localhost:9876/CarManagementService"
     binding="wsHttpBinding"
     bindingConfiguration=""
     contract="CarManagementInterface.ICarManagement" />
  </service>
 </services>
</system.serviceModel>

Instead of doing the configuration one by one for the three other services using the WCF Configuration Editor, you can now copy and paste the service tag and change the values by hand. Use the following addresses for the other services:

  • http://localhost:9876/CustomerService

  • http://localhost:9876/RentalService

  • http://localhost:9876/ExternalInterfaceService

Make sure you change the name and the contract correctly. The result should look like this:

<system.serviceModel>
    <services>
      <service name="CarManagementService.CarManagementImplementation">
        <endpoint
          address="http://localhost:9876/CarManagementService"
          binding="wsHttpBinding"
          bindingConfiguration=""
          contract="CarManagementInterface.ICarManagement" />
      </service>
      <service name="CustomerService.CustomerServiceImplementation">
        <endpoint
          address="http://localhost:9876/CustomerService"
          binding="wsHttpBinding"
          bindingConfiguration=""
          contract="CustomerInterface.ICustomer" />
      </service>
      <service name="RentalService.RentalServiceImplementation">
        <endpoint
          address="http://localhost:9876/RentalService"
          binding="wsHttpBinding"
          bindingConfiguration=""
          contract="RentalInterface.IRental" />
      </service>
      <service
       name="ExternalInterfaceFacade.ExternalInterfaceFacadeImplementation">
        <endpoint
          address="http://localhost:9876/ExternalInterfaceService"
          binding="wsHttpBinding"
          bindingConfiguration=""
          contract="ExternalInterface.IExternalInterface" />
      </service>
    </services>
  </system.serviceModel>

Code snippet CreatingaSOACase.zip

Set HostAllServices as the startup project. Do this by right-clicking the HostAllServices project and selecting Set as StartUp Project. See Figure 11-28.

Now you can run the application. This application exposes the services you created. See Figure 11-29.

FIGURE 11-28

Figure 11.28. FIGURE 11-28

FIGURE 11-29

Figure 11.29. FIGURE 11-29

CREATING THE DATABASE

Open Server Explorer in Visual Studio and create a new SQL Server Database (see Figure 11-30). Name the database RentalCarCaseDB.

Add two tables to the database. You can do this by executing this SQL statement or by adding the tables manually:

FIGURE 11-30

Figure 11.30. FIGURE 11-30

CREATE DATABASE [CarRentalCaseDB]
GO
USE [CarRentalCaseDB]
GO
CREATE TABLE [dbo].[Rental]
(
  [RentalID] [int] IDENTITY(1,1) NOT NULL,
  [CustomerID] [int] NULL,
  [CarID] [nvarchar](50) NULL,
  [PickUpLocation] [int] NULL,
  [DropOffLocation] [int] NULL,
  [PickUpDateTime] [datetime] NULL,
  [DropOffDateTime] [datetime] NULL,
  [PaymentStatus] [char](3) NULL,
  [Comments] [nvarchar](1000) NULL,
 CONSTRAINT [PK_Rental] PRIMARY KEY CLUSTERED
 ([RentalID] ASC))
GO
CREATE TABLE [dbo].[Customer](
  [CustomerID] [int] IDENTITY(1,1) NOT NULL,
  [CustomerName] [nvarchar](50) NULL,
  [CustomerFirstName] [nvarchar](50) NULL,
 CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED
([CustomerID] ASC))
GO

IMPLEMENTING THE SERVICE

Now you'll add code to the implementation to make your services work.

This is not the complete business logic behind the services as this book is focusing on WCF only. Instead you'll write temporary code so the operations can return meaningful answers to clients. For the implementation of InsertCustomer and RegisterCarRental, use a LINQ to SQL model to insert the data into a database.

Creating Database Access for the CustomerService and the RentalService

Add a LINQ to SQL Classes file to both the CustomerService and the RentalService (see Figure 11-31). Both CustomerService and RentalService access the same database in this case.

In a real production environment these would probably be two different databases, but using one database is more convenient for this walkthrough. At CustomerService, add a LINQ to SQL Classes and name it DataClassesCustomer.dbml (see Figure 11-32).

FIGURE 11-31

Figure 11.31. FIGURE 11-31

FIGURE 11-32

Figure 11.32. FIGURE 11-32

Drag and drop the Customer table into the designer.

Do this also for the RentalService. Create a LINQ to SQL model named DataClassesRental.dbml and drag and drop the Rental Table into it.

Now you can add the implementation code to the CustomerService. Open the CustomerServiceImplementation.cs file. In the code for the RegisterCustomer method, delete the throw statement and replace it with the following code:

using (DataClassesCustomerDataContext ctx = new DataClassesCustomerDataContext())
{
  Customer customerToInsert;
  customerToInsert = new Customer();
  customerToInsert.CustomerName = customer.CustomerName;
  customerToInsert.CustomerFirstName = customer.CustomerFirstName;
  ctx.Customers.InsertOnSubmit(customerToInsert);
  ctx.SubmitChanges();
  return customerToInsert.CustomerID;
}

This code opens the LINQ to SQL DataContext, creates a new customer, and inserts and submits the customer to the database.

Add similar code to insert a rental into the database in the implementation of the RentalService. Open RentalServiceImplementation.cs and replace the throw statement with the following code:

Console.WriteLine("RegisterCarRental");
using (DataClassesRentalDataContext ctx = new DataClassesRentalDataContext())
{
  Rental rentalToInsert;
  rentalToInsert = new Rental();
  rentalToInsert.CustomerID = rentalRegistration.CustomerID;
  rentalToInsert.CarID = rentalRegistration.CarID;
  rentalToInsert.Comments = rentalRegistration.Comments;
  ctx.Rentals.InsertOnSubmit(rentalToInsert);
  ctx.SubmitChanges();
}

return "OK";

Creating the CarManagement Service

Here you add code to the CarManagementService. Start by adding a using statement for the system.IO namespace. You need this namespace to read an image from a file:

using System.IO;

Implement the operations with the following code. Make sure you use a path that refers to a file that exists in the GetCarPicture operation:

public int InsertNewCar(Car car)
{
  Console.WriteLine("InsertNewCar " + car.BrandName + " " + car.TypeName);
  return 1;
}

public bool RemoveCar(Car car)
{
  Console.WriteLine("RemoveCar " + car.BrandName + " " + car.TypeName);
  return true;
}

public void UpdateMilage(Car car)
{
  Console.WriteLine("UpdateMilage " + car.BrandName + " " + car.TypeName);
}

public List<Car> ListCars()
{
  Console.WriteLine("ListCars");
  List<Car> listCars;
  listCars = new List<Car>();
  listCars.Add(new Car {
       BrandName = "XXX",
       Transmission = TransmissionTypeEnum.Automatic,
       TypeName = "YYY" });
  listCars.Add(new Car {
       BrandName = "XXX",
       Transmission = TransmissionTypeEnum.Automatic,
       TypeName = "YYY" });

  return listCars;
}

public byte[] GetCarPicture(string carID)
{
  Console.WriteLine("GetCarPicture");

  byte[] buff;

  string pathToPicture;
  pathToPicture = @"C:DataWCFBookCodeSOACasePicsCarExample.jpg";

  FileStream fileStream =
   new FileStream(pathToPicture, FileMode.Open,FileAccess.Read);
  BinaryReader binaryReader = new BinaryReader(fileStream);

  buff = binaryReader.ReadBytes((int)fileStream.Length);
  return buff;
}

In this implementation the InsertCar, RemoveCar, and UpdateMileage operations do not interact with a database. They just write a status line to the console. The ListCars operation creates and returns a hardcoded list of cars. The GetCarPicture operation reads the image in a file and returns this as an array of bytes. It's up to the client to transform these bytes to an image again.

EXPOSING METADATA

Now you're able to host the service by adding configuration to expose the metadata. Exposing metadata allows Visual Studio to download the WSDL file to create the needed proxies. Allowing a service to expose its metadata can be done by configuration.

You'll do this for the CarManagementService only. You'll see other ways to create proxies for the other services in this chapter.

Open the App.config of the HostAllServices applications with the WCF Configuration Editor. Look for the Service Behaviors node and click the New Service Behavior Configuration link. See Figure 11-33.

FIGURE 11-33

Figure 11.33. FIGURE 11-33

Name the behavior ExposeMetaDataBehavior. See Figure 11-34.

FIGURE 11-34

Figure 11.34. FIGURE 11-34

Add a behavior element by clicking the Add button. This opens a list of behavior elements. See Figure 11-35.

Select the serviceMetadata element and click Add. Notice the element is now added to the ExposeMetaDataBehavior node in the treeview on the right.

Click this new node and edit the serviceMetadata attributes. Enter http://localhost:9876/CarManagement/MEX as the HttpGetUrl and set the HttpGetEnabled property to True. See Figure 11-36.

FIGURE 11-35

Figure 11.35. FIGURE 11-35

FIGURE 11-36

Figure 11.36. FIGURE 11-36

Now refer CarManagementService to this new behavior. Click CarManagementService in the tree and set the BehaviorConfiguration to ExposeMetaDataBehavior. See Figure 11-37.

FIGURE 11-37

Figure 11.37. FIGURE 11-37

Save the configuration, close the WCF Configuration Editor, and reload the App.config file.

The configuration in the App.config should now include a serviceBehavior tag like this:

<behaviors>
  <serviceBehaviors>
    <behavior name="ExposeMetaDataBehavior">
      <serviceMetadata
        httpGetEnabled="true"
        httpGetUrl="http://localhost:9876/CarManagement/MEX" />
    </behavior>
  </serviceBehaviors>
</behaviors>

The configuration of the CarManagementService should now look like the following — notice the behaviorConfiguration attribute of the service tag:

<service
 behaviorConfiguration="ExposeMetaDataBehavior"
 name="CarManagementService.CarManagementImplementation">
  <endpoint
   address="http://localhost:9876/CarManagementService"
   binding="wsHttpBinding"
   bindingConfiguration=""
   contract="CarManagementInterface.ICarManagement" />
</service>

Now you can test the metadata endpoint. Start the HostAllServices application and start a browser. After the HostAllServices application is up, browse to http://localhost:9876/CarManagement/MEX. The browser should show you the WSDL file, as in Figure 11-38.

FIGURE 11-38

Figure 11.38. FIGURE 11-38

CREATING THE CARMANAGEMENT CLIENT

Add a WindowsForms application to the Client solution folder called CarApplication. This application uses the CarService.

Start the HostAllServices application outside Visual Studio instead of running it from within Visual Studio. You can do this by navigating to the bindebug directory of the project and starting HostAllServices.exe.

In Visual Studio, click Add Service Reference in the CarManagementClient application. Type the address of the metadata endpoint in the Add Service Reference dialog box: http://localhost:9876/CarManagement/MEX.

Set the namespace of the service reference to CarService and click the Go button. Visual Studio downloads the WSDL and shows the available operations. See Figure 11-39.

FIGURE 11-39

Figure 11.39. FIGURE 11-39

Click the Advanced button to access the Service Reference Settings dialog box. Set the Collection type to System.Collections.Generic.LinkedList. See Figure 11-40. This is needed for Visual Studio to generate generic lists instead of arrays in the proxies for all collection types found in the contract.

Click OK twice. The code for the proxies and the configuration for the client endpoints are now generated. You can check this by clicking the Show All Files button in Solution Explorer. See Figure 11-41.

FIGURE 11-40

Figure 11.40. FIGURE 11-40

FIGURE 11-41

Figure 11.41. FIGURE 11-41

Now you can close the HostAllServices application and check the result in Visual Studio. Open the App.config file and look up the configuration for the client endpoint. For testing purposes, remark the <identity> tag for the generated client endpoint. The result should look like this:

<client>
    <endpoint address="http://localhost:9876/CarManagementService"
        binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ICarManagement"
        contract="CarService.ICarManagement" name="WSHttpBinding_ICarManagement">
        <!--<identity>
            <userPrincipalName value="XXXYYY" />
        </identity>-->
    </endpoint>
</client>

Continue creating the application by adding user interface components to the form. For this walkthrough you need to add three buttons: a listbox, a textbox with the multiline property set to true, and a picturebox. It should look like Figure 11-42.

FIGURE 11-42

Figure 11.42. FIGURE 11-42

The three buttons will call the ListCars operation, the InsertNewCar operation, and the GetCarPicture operation. The listbox is used to show the results; the picturebox is there to show the picture of the car; and the textbox is there to show error messages, should they occur.

Add the following code for the ListCars button:

try
{
  CarService.CarManagementClient client;
client = new CarService.CarManagementClient();
  List<CarService.Car> listCars;
  listCars = client.ListCars();
  foreach (CarService.Car car in listCars)
  {
    listBox1.Items.Add(car.BrandName + " " + car.TypeName);
  }
}
catch (Exception ex)
{
  textBox1.Text = ex.Message;
}

This code instantiates the proxy called CarManagementClient, calls the ListCars operation, and adds all cars returned to the listbox.

Add code for the InsertNewCar button:

try
{
  CarService.CarManagementClient client;
  client = new CarService.CarManagementClient();
  CarService.Car car;
  car = new CarApplication.CarService.Car();
  car.BrandName = "BMW";
  car.TypeName = "320d"
  int newCarID;
  newCarID = client.InsertNewCar(car);
}
catch (Exception ex)
{
  textBox1.Text = ex.Message;
}

This code creates a new car, specifies the content of its properties, and calls the InsertNewCar operation in the service.

Add code for the GetCarPicture button:

try
{
  CarService.CarManagementClient client;
  client = new CarService.CarManagementClient();
  byte[] buff;
  buff = client.GetCarPicture("C67872");
  TypeConverter typeConverter;
  typeConverter = TypeDescriptor.GetConverter(typeof(Bitmap));
  Bitmap bitmap = (Bitmap)typeConverter.ConvertFrom(buff);
  pictureBox1.Image = bitmap;
}
catch (Exception ex)
{
  textBox1.Text = ex.Message;
}

This code calls the GetCarPicture operation, receives the array of bytes, converts them to a bitmap, and shows the bitmap in the picturebox.

You need to make changes to the configuration of the host to allow receiving the pictures correctly. To get a picture from the service with a size bigger than the default, you need to add a binding specification in the App.config file of the HostAllServices application and specify the maxReceivedMessageSize property for the binding.

Open the App.config file and add the following code in the system.serviceModel tag:

<system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="AllowBigMessageSize" maxReceivedMessageSize="999999">
        </binding>
      </wsHttpBinding>
    </bindings>

  </system.serviceModel>

This adds a binding configuration called AllowBigMessageSize. Use this binding configuration in the endpoint configuration of the CarManagementService. See the configuration code here:

<service
   behaviorConfiguration="ExposeMetaDataBehavior"
   name=
    "CarManagementService.CarManagementImplementation">
  <endpoint
    address="http://localhost:9876/CarManagementService"
    binding="wsHttpBinding"
    bindingConfiguration="AllowBigMessageSize"
    contract="CarManagementInterface.ICarManagement" />
</service>

Also change the configuration of the client so it accepts large messages. Open the App.config of the CarApplication and set the maxReceivedMessageSize of the WsHttpBinding to 999999 and also set the maxArrayLength of the readerQuotas to 999999. This should be enough for all images:

<wsHttpBinding>
    <binding name="WSHttpBinding_ICarManagement" closeTimeout="00:01:00"
        openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
        bypassProxyOnLocal="false" transactionFlow="false"
        hostNameComparisonMode="StrongWildcard"
        maxBufferPoolSize="524288" maxReceivedMessageSize="999999"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
        allowCookies="false">
        <readerQuotas maxDepth="32" maxStringContentLength="8192"
            maxArrayLength="999999"
            maxBytesPerRead="4096"
            maxNameTableCharCount="16384" />
        <reliableSession ordered="true" inactivityTimeout="00:10:00"
            enabled="false" />
        <security mode="Message">
            <transport clientCredentialType="Windows" proxyCredentialType="None"
                realm="" />
            <message clientCredentialType="Windows"
                negotiateServiceCredential="true"
                algorithmSuite="Default" establishSecurityContext="true" />
        </security>
    </binding>
</wsHttpBinding>

Now you can test both client and service. To make sure Visual Studio starts both projects, change the properties of the solution so it has multiple startup projects. Right-click the solution and select Properties. See Figure 11-43. Select Multiple startup projects and set the Actions of the CarApplication and the HostAllServices to Start.

FIGURE 11-43

Figure 11.43. FIGURE 11-43

Start the application by running the solution and executing the operations by clicking the buttons. See Figure 11-44. Watch the console of the ServiceHost to see if the operations are called correctly.

FIGURE 11-44

Figure 11.44. FIGURE 11-44

CREATING THE RENTALAPPLICATION

In the client solution folder, add a Windows form application called RentalApplication. Add the System.ServiceModel and System.Runtime.Serialization assemblies to this application.

For this client you will not create the proxies by adding a service reference referring to a WSDL. Instead you'll refer to the RentalInterface project itself as this project also has all the necessary metadata in the form of WCF attributes. This approach is easier and more flexible but only valid if the client is also a .NET environment. With non-.NET clients you need the WSDL file.

Add a reference to the RentalInterface to the application. See Figure 11-45.

FIGURE 11-45

Figure 11.45. FIGURE 11-45

Because the proxies are being created manually, you need to add a class to the application called RentalProxy. This class is the proxy to be created manually instead of generating it by Visual Studio.

Add the using statements to the class like this:

using System.ServiceModel;
using System.Runtime.Serialization;

Also add a using statement to the namespace of the referenced RentalInterface:

using RentalInterface;

Make the class public, make it inherit from ClientBase<RentalInterface.IRental>, and let it implement the IRental interface. The code should look like this:

public class RentalProxy : ClientBase<IRental>,IRental
{
  #region IRental Members

  public string RegisterCarRental(RentalRegistration rentalRegistration)
  {
    throw new NotImplementedException();
  }

  public void RegisterCarRentalAsPaid(string rentalID)
  {
    throw new NotImplementedException();
  }

  public void StartCarRental(string rentalID)
{
    throw new NotImplementedException();
  }

  public void StopCarRental(string rentalID)
  {
    throw new NotImplementedException();
  }

  public RentalInterface.RentalRegistration GetRentalRegistration(string rentalID)
  {
    throw new NotImplementedException();
  }

  #endregion
}

Now create a constructor for the class that calls the constructor of the base class with a string as parameter. This string is the name of an endpoint in the configuration file. You will create this endpoint later.

public RentalProxy()
  : base("RentalServiceEndpoint")
{

}

Implement each method with code that calls the corresponding method on the channel property. The channel property is a protected member in the ClientBase<T> class with a generic type used in the class declaration. As it is protected, it is only accessible in classes that inherit from the class where it is declared. In your case, it is the IRental interface. This results in the channel having the same methods as the implementation class. As you see, a proxy is simply a class at the client side with the same methods as the class at the service side. Therefore it implements the interface. The proxy's responsibility is simply to pass the parameters it gets from its caller to the channel and return the output from the channel back to the caller.

After implementing the interface, delete the throw statement for each method and replace it as follows:

public string RegisterCarRental(RentalRegistration rental)
{
  return Channel.RegisterCarRental(rental);
}

public void RegisterCarRentalAsPaid(string rentalID)
{
  Channel.RegisterCarRentalAsPaid(rentalID);
}

public void StartCarRental(string rentalID)

{
  Channel.StartCarRental(rentalID);
}

public void StopCarRental(string rentalID)
{
  Channel.StopCarRental(rentalID);
}

public RentalRegistration GetRentalRegistration(string rentalID)
{
  return Channel.GetRentalRegistration(rentalID);
}

As you're not taking the add service reference approach, you need to add and configure the App.config file yourself. Add an App.config file to the application and edit the file with the WCF Configuration Editor. See Figure 11-46.

FIGURE 11-46

Figure 11.46. FIGURE 11-46

Add the client configuration by clicking the Create a New Client link. A wizard starts up asking how to create the configuration. See Figure 11-47.

Select the From Service Config option and browse to the App.config file of the HostAllServices application. This is C:DataWorkTheCarRentalSOACaseTheCarRentalSOACaseHostAllServicesapp.config.

Select the endpoint to which the client will talk. In the drop-down you'll find all four endpoints. See Figure 11-48. Select the RentalService endpoint.

FIGURE 11-47

Figure 11.47. FIGURE 11-47

FIGURE 11-48

Figure 11.48. FIGURE 11-48

The wizard now asks you for a name to identify your client configuration. This name is RentalServiceEnpoint. See Figure 11-49. This is the string you specified in the constructor of the proxy. This is how WCF knows where to look for the configuration when the proxy is instantiated.

FIGURE 11-49

Figure 11.49. FIGURE 11-49

Click Next, click Finish, save the configuration, and close the WCF Configuration Editor. Reload the App.config file in Visual Studio.

For testing purposes, delete the <identity> tag in the generated configuration. The configuration should look like this:

<system.serviceModel>
  <client>
    <endpoint
        address="http://localhost:9876/RentalService" binding="wsHttpBinding"
        bindingConfiguration=""
        contract="RentalInterface.IRental"
        name="RentalServiceEndpoint">
    </endpoint>
  </client>
</system.serviceModel>

For the user interface of the application, add two buttons to the form. See Figure 11-50. These buttons will execute the code to call to the proxy.

FIGURE 11-50

Figure 11.50. FIGURE 11-50

Following is the code for the RegisterCarRental application:

try
{
  RentalProxy rentalProxy;
rentalProxy = new RentalProxy();
  RentalInterface.RentalRegistration rentalRegistration;
  rentalRegistration = new RentalInterface.RentalRegistration();
  rentalRegistration.CustomerID = 1;
  rentalRegistration.CarID = "123767";

  rentalRegistration.DropOffLocation = 1327;
  rentalRegistration.DropOffDateTime = System.DateTime.Now;

  rentalRegistration.PickUpLocation = 7633;
  rentalRegistration.PickUpDateTime = System.DateTime.Now;

  rentalProxy.RegisterCarRental(rentalRegistration);

}
catch (Exception ex)
{
  MessageBox.Show(ex.Message);
}

Here is the code for the RegisterCarRentalAsPaid button:

try
{
  RentalProxy rentalProxy;
  rentalProxy = new RentalProxy();

  rentalProxy.RegisterCarRentalAsPaid("1876893");
}
catch (Exception ex)
{
  MessageBox.Show(ex.Message);
}

To test this, change the solution properties, and set the RentalApplication and the HostAllServices as startup. Run the applications, call the operations by clicking the buttons, and check the database to verify records are correctly inserted.

ADDING CORRECT ERROR HANDLING

At this moment the applications have no correct error handling. A good error-handling strategy starts with defining fault contracts in the interfaces.

Open the IRental.cs file and add a DataContract for the faults:

[DataContract(Name = "RentalRegisterFault",
              Namespace = "FaultContracts/RentalRegisterFault")]
public class RentalRegisterFault
{
  [DataMember]
  public string FaultDescription { get; set; }
[DataMember]
  public int FaultID { get; set; }
}

Now add the FaultContract attribute to every method in the IRental interface. The interface now looks like this:

[ServiceContract]
public interface IRental
{
  [OperationContract]
  [FaultContract(typeof(RentalRegisterFault))]
  string RegisterCarRental(RentalRegistration rentalRegistration);

  [OperationContract]
  [FaultContract(typeof(RentalRegisterFault))]
  void RegisterCarRentalAsPaid(string rentalID);

  [OperationContract]
  [FaultContract(typeof(RentalRegisterFault))]
  void StartCarRental(string rentalID);

  [OperationContract]
  [FaultContract(typeof(RentalRegisterFault))]
  void StopCarRental(string rentalID);

  [OperationContract]
  [FaultContract(typeof(RentalRegisterFault))]
  RentalRegistration GetRentalRegistration(string rentalID);
}

The next step is to add the logic to detect exceptions in the service implementation. This is done at two levels. The first level is checking the incoming parameter to determine whether it's a null value or not. In case of a null value, you're throwing a FaultException of type RentalRegisterFault. Then you surround the code to insert the rental registration with a try...catch. When an error occurs, you're also throwing a FaultException of type RentalRegisterFault in the catch handler.

The changed code for the implementation is as follows:

public string RegisterCarRental(RentalRegistration rentalRegistration)
{
  Console.WriteLine("RegisterCarRental");

  if (rentalRegistration == null)
  {
    RentalRegisterFault fault;
    fault = new RentalRegisterFault();
    fault.FaultID = 1;
    fault.FaultDescription = "Input is not valid, got null value";
    throw new FaultException<RentalRegisterFault>(fault, "");
  }

  try
{
    using (DataClassesRentalDataContext ctx = new DataClassesRentalDataContext())
    {
      Rental rentalToInsert;
      rentalToInsert = new Rental();
      rentalToInsert.CustomerID = rentalRegistration.CustomerID;
      rentalToInsert.CarID = rentalRegistration.CarID;
      rentalToInsert.Comments = rentalRegistration.Comments;
      ctx.Rentals.InsertOnSubmit(rentalToInsert);
      ctx.SubmitChanges();
      return "OK";
    }
  }
  catch (Exception ex)
  {
    RentalRegisterFault fault;
    fault = new RentalRegisterFault();
    fault.FaultID = 123;
    fault.FaultDescription = "An error occurred while inserting the registration";
    throw new FaultException<RentalRegisterFault>(fault, "");
  }
}

Code snippet CreatingaSOACase.zip

At the client side, it's advisable to catch on four types of exceptions:

  • A FaultException of type RentalRegisterFault

  • The generic FaultException

  • The EndpointNotFoundException

  • The CommunicationException

The changed code for the button in the client application is shown here:

try
{
  RentalProxy rentalProxy;
  rentalProxy = new RentalProxy();

  RentalInterface.RentalRegistration rentalRegistration;
  rentalRegistration = new RentalInterface.RentalRegistration();
  rentalRegistration.CustomerID = 1;
  rentalRegistration.CarID = "123767";

  rentalRegistration.DropOffLocation = 1327;
  rentalRegistration.DropOffDateTime = System.DateTime.Now;

  rentalRegistration.PickUpLocation = 7633;
  rentalRegistration.PickUpDateTime = System.DateTime.Now;

  rentalProxy.RegisterCarRental(rentalRegistration);

}
catch (FaultException<RentalInterface.RentalRegisterFault> rentalRegisterFault)
{
  MessageBox.Show("rentalRegisterFault " + rentalRegisterFault.Message);
}
catch (FaultException faultException)
{
  MessageBox.Show("faultException " + faultException.Message);
}
catch (EndpointNotFoundException endpointNotFoundException)
{
  MessageBox.Show("endpointNotFoundExc " + endpointNotFoundException.Message);
}
catch (CommunicationException communicationException)
{
  MessageBox.Show("communicationException" + communicationException.Message);
}

Code snippet CreatingaSOACase.zip

IMPERSONATING THE CLIENT

Another requirement was that the call to the RegisterCarRentalAsPaid method in the RentalService needs to be impersonated. This means the implementation code in the service should run under the credentials of the user behind the client application.

To do this, open the RentalServiceImplementation.cs file. Add a using statement to the System.Security.Principal namespace. This makes the WindowsIdentity class available. The WindowsIdentity class is useful to get the name of the user running the code. In this way you can test if the impersonation is really happening:

using System.Security.Principal;

You need to add an OperationBehavior attribute at the RegisterCarRentalAsPaid method in the implementation and set the impersonation parameter of the attribute to Required:

[OperationBehavior(Impersonation = ImpersonationOption.Required)]

The complete method becomes this:

[OperationBehavior(Impersonation = ImpersonationOption.Required)]
public void RegisterCarRentalAsPaid(string rentalID)
{
  Console.WriteLine("RegisterCarRentalAsPaid " + rentalID);
  Console.WriteLine("  WindowsIdentity : {0} ", WindowsIdentity.GetCurrent().Name);
}

Testing this can be done by starting the RentalApplication at the command prompt with the runas tool. With the runas tool you can specify that another user besides the one logged in is running the application. Open a command prompt and navigate to the directory where the RentalApplication is present. Execute the following command:

runas /user:UserX RentalApplication.exe

Here you specify that RentalApplication.exe runs under the credentials of a user called UserX. You can use any username in your domain for which you know the password. The runas tool asks you for this password and then starts the application.

EXTENDING THE CARMANAGEMENT INTERFACE TO ACCEPT THE SUBTYPES OF CARS

Another requirement allows that operations can exchange subclasses of the car class. Add two classes that inherit from car to the ICarManagement interface like this:

[DataContract]
public class LuxuryCar : Car
{
  [DataMember]
  List<LuxuryItems> LuxuryItemsList { get; set; }
}

[DataContract]
public class LuxuryItems
{
  [DataMember]
  public string ItemName { get; set; }
  [DataMember]
  public string ItemDescription { get; set; }
}

[DataContract]
public class SportsCar : Car
{
  [DataMember]
  public int HorsePower { get; set; }
}

Specify the KnownType attribute at the Car class for each possible subclass. Using this attribute allows that the returnvalues for the ListCars method can be of types that inherit from the class Car. In our case these are types LuxuryCar and SportsCar:

[DataContract]
[KnownType(typeof(LuxuryCar))]
[KnownType(typeof(SportsCar))]
public class Car
{
  [DataMember]
  public string BrandName { get; set; }
  [DataMember]
public string TypeName { get; set; }
  [DataMember]
  public TransmissionTypeEnum Transmission { get; set; }
  [DataMember]
  public int NumberOfDoors { get; set; }
  [DataMember]
  public int MaxNumberOfPersons { get; set; }
  [DataMember]
  public int LitersOfLuggage { get; set; }
}

You can now change the implementation of the ListCars method so it also includes a SportsCar in the list as return, like here:

public List<Car> ListCars()
{
  Console.WriteLine("ListCars");
  List<Car> listCars;
  listCars = new List<Car>();
  listCars.Add(new Car {
                BrandName = "Audi",
                Transmission = TransmissionTypeEnum.Automatic,
                TypeName = "A4" });
  listCars.Add(new Car {
                BrandName = "Volkswagen",
                Transmission = TransmissionTypeEnum.Automatic,
                TypeName = "Golf" });
  listCars.Add(new SportsCar {
                BrandName = "Ferrari",
                Transmission = TransmissionTypeEnum.Automatic,
                TypeName = "XXXX", HorsePower= 600 });

  return listCars;
}

Using the KnownType attribute results in a new version of the contract, so there is a new version of the WSDL file. This new WSDL file now also includes the structure of the two types inheriting from the Car type. You need to update the clients using this contract.

To update the service reference in the CarApplication, start the HostAllServices application outside Visual Studio again. Right-click the existing CarService service reference and select Update Service Reference. See Figure 11-51.

FIGURE 11-51

Figure 11.51. FIGURE 11-51

This generates the proxies again and the two subtypes will now be known in the client.

To test this, close the HostAllServices application and change the startup projects in the solution to the CarApplication and the HostAllServices applications. Try to get the list of cars again. The result should now also include cars of one of the subtypes.

IMPLEMENTING THE EXTERNALINTERFACEFACADE

The implementation of the ExternalInterface has one method. This method is called SubmitRentalContract and receives a parameter that has the data for a new customer and the rental registration. The purpose of the SubmitRentalContract method is to insert the new customer and register a rental for it in one single transaction. The method calls the RegisterCustomer method in the CustomerService and the RegisterCarRental method in the RentalService.

Using the ExternalInterfaceFacade

The ExternalInterfaceFacade calls to these two services by using the named pipes binding. This can be done by creating a channel to the services with a ChannelFactory and specifying the type of binding and its address in code instead of configuration. This is yet another way of creating proxies. Instead of adding a service reference or creating a proxy inheriting from the ClientBase class manually, you're now using the ChannelFactory which can create proxies dynamically at runtime based on an interface.

Defining the scope of the transaction is done by a using block in which you instantiate a TransactionScope. After calling both methods you flag the scope as being complete.

In the ExternalInterfaceFacade project, add a reference to System.Transactions. See Figure 11-52.

FIGURE 11-52

Figure 11.52. FIGURE 11-52

Open the ExternalInterfaceFacadeImplementation.cs file and add a using statement to the System.Transactions namespace:

using System.Transactions;

Add a reference to both the CustomerInterface and the RentalInterface libraries in the ExternalInterfaceFacade project. See Figure 11-53.

FIGURE 11-53

Figure 11.53. FIGURE 11-53

Add this code to the implementation of the SubmitRentalContract method:

using (TransactionScope scope =
        new TransactionScope(TransactionScopeOption.RequiresNew))
{

 NetNamedPipeBinding netNamedPipeBinding;
 netNamedPipeBinding = new NetNamedPipeBinding();
 netNamedPipeBinding.TransactionFlow = true;

 CustomerInterface.ICustomer customerServiceChannel;
 customerServiceChannel = ChannelFactory<CustomerInterface.ICustomer>.
  CreateChannel(netNamedPipeBinding, new
  EndpointAddress("net.pipe://localhost/customerservice"));

 int newCustomerID;
 newCustomerID =
 customerServiceChannel.RegisterCustomer(rentalContract.Customer);
rentalContract.RentalRegistration.CustomerID = newCustomerID;

 RentalInterface.IRental rentalServiceChannel;
 rentalServiceChannel = ChannelFactory<RentalInterface.IRental>.
  CreateChannel(netNamedPipeBinding, new
  EndpointAddress("net.pipe://localhost/rentalservice"));

 rentalServiceChannel.RegisterCarRental(rentalContract.RentalRegistration);

 scope.Complete();

}

In this code you start a transaction in a using block. In the using block you're instantiating a netNamedPipeBinding with the transactionFlow flag set to true and using this binding to create two channels, one for the CustomerService and one for the RentalService. First the RegisterCustomer operation in the CustomerService is called. After this the RegisterCarRental operation in the RentalService is called. When both operations are called you complete the transaction.

Setting Transaction Support at the Methods Participating in the Transaction

Now you need to indicate at the two operations that they require a transaction and that they can complete the transaction automatically.

Add a OperationBehavior attribute to the RegisterCustomer method in the implementation of the CustomerService. Set both TransactionAutoComplete and TransactionScopeRequired to true:

[OperationBehavior( TransactionAutoComplete = true,
                    TransactionScopeRequired = true)]
public int RegisterCustomer(CustomerInterface.Customer customer)
{
  //...
}

Add an OperationBehavior attribute to the RegisterCustomer method in the implementation of the CustomerService. Set both TransactionAutoComplete and TransactionScopeRequired to true:

[OperationBehavior( TransactionAutoComplete = true,
                    TransactionScopeRequired = true)]
public string RegisterCarRental(RentalRegistration rentalRegistration)
{
  //...
}

Configuring Additional Endpoints for the servicehost

Add a netNamedPipeBinding endpoint to the CustomerService in the App.config of the HostAllServices application:

<service name="CustomerService.CustomerServiceImplementation">
...
  <endpoint
    address="net.pipe://localhost/customerservice"
    binding="netNamedPipeBinding"
    bindingConfiguration=" SupportTransactionsNetNamedBinding"
    contract="CustomerInterface.ICustomer" />
</service>

Do this also for the RentalService:

<service name="RentalService.RentalServiceImplementation">
...
  <endpoint
    address="net.pipe://localhost/rentalservice"
    binding="netNamedPipeBinding"
    bindingConfiguration="SupportTransactionsNetNamedBinding"
    contract="RentalInterface.IRental" />
</service>

These endpoints refer to a bindingConfiguration called SupportTransactionsNetNamedBinding. You need to configure the binding as follows:

<bindings>
  <netNamedPipeBinding>
    <binding name="supportTransactionsNetNamedBinding" transactionFlow="true">
    </binding>
  </netNamedPipeBinding>
</bindings>

Back in the code, add a TransactionFlow attribute on the RegisterCarRental operation of the IRental interface with the transactionFlowOption set to Allowed:

[OperationContract]
[FaultContract(typeof(RentalRegisterFault))]
[TransactionFlow(TransactionFlowOption.Allowed)]
string RegisterCarRental(RentalRegistration rentalRegistration);

Do this also for the RegisterCustomer method in the ICustomer interface:

[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
int RegisterCustomer(Customer customer);
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.252.56