Learning WCF Chapter1 Creating a New Service from Scratch

时间:2023-12-04 23:52:14

Learning WCF Chapter1  Creating a New Service from Scratch

You’re about to be introduced to the WCF service.
This lab isn’t your typical “Hello World”—it’s “Hello Indigo”!
In this lab,you will learn how to build a new WCF service and in the process learn the minimum requirements of service development and consumption.
Here’s a short list of things you’ll accomplish:

• Create a new service contract and service implementation
• Programmatically configure a service host, its endpoints, and bindings
• Create a client application and open a client channel proxy to invoke the service

Now,before you start thinking “been there,done that,” this simple lab will be slightly different
because I’m going to give you some practical design tips that ensure configurability and appropriate decoupling of service,host,and client.
In addition,I’ll be diving deeper into basic concepts such as services,service contracts,endpoints, bindings, ServiceHost, and channels.

Lab: Creating Clients and Services Programmatically

In this first lab,you will create a new solution with three projects: a service,a host,and a client.
When you run the service host,you’ll expose a single service endpoint.
The client application will access service operations through that endpoint.
You’ll host the service in a console application and invoke the service using a manually constructed proxy.
This lab will teach you the basic requirements for creating,hosting,and consuming a service with WCF.

Creating a new service
The first thing you will do is create a new service contract with a single operation and implement this contract on a new service type.
1. In this lab,everything begins from scratch,so you’ll start by creating a new Visual Studio solution.
Open a new instance of Visual Studio 2008.
Select File ➝New ➝ Project,and from the New Project dialog,create a new Blank Solution in the <YourLearningWCFPath>\Labs\Chapter1 directory.
Name the solution ServiceFromScratch. Verify that the .NET Framework version selected in the dropdown list is set to .NET Framework 3.0 or .NET Framework 3.5.
Click OK to create the empty solution.

Learning WCF Chapter1  Creating a New Service from Scratch

2. Create the service project.
From Solution Explorer,right-click on the solution node and select Add ➝ New Project.
Select the Class Library template,name the project HelloIndigo,and make sure the location path matches the solution at <YourLearningWCFPath>\Labs\Chapter1\ServiceFromScratch.
Click OK to create the new project.

Learning WCF Chapter1  Creating a New Service from Scratch
3. Now you will create your first service contract.
From Solution Explorer,rename the project’s only class file to Service.cs.
Open this file in the code window.
Add a new interface named IHelloIndigoService in Service.cs.
Add a single method to the interface, HelloIndigo, with the signature shown here:

public interface IHelloIndigoService
{
string HelloIndigo( );
}

4. Add a reference to the System.ServiceModel assembly. From Solution Explorer,right-click References and select System.ServiceModel from the list.
You’ll also need to add the following using statement to Service.cs:

using System.ServiceModel;

5. To turn this interface into a service contract,you’ll need to explicitly decorate the interface with the ServiceContractAttribute.
In addition,each method should be decorated with the OperationContractAttribute to include it in the service contract.
In this case,you’ll make IHelloIndigoService a service contract and expose HelloIndigo( ) as its only service operation by applying these attributes as shown here:

[ServiceContract(Namespace="http://www.thatindigogirl.com/samples/2006/06")]
public interface IHelloIndigoService
{
[OperationContract]
string HelloIndigo( );
}

Note:Providing a namespace for the ServiceContractAttribute reduces the possibility of naming collisions with other services. This will be discussed in greater detail in Chapter 2.

6. In the same file,create a service type to implement the service contract.
You can modify the existing class definition,renaming it to HelloIndigoService.
Then add the IHelloIndigoService interface to the derivation list and implement HelloIndigo( ) with the following code:

public class HelloIndigoService : IHelloIndigoService
{
public string HelloIndigo( )
{
return "Hello Indigo";
}
}

7. Compile the service project.

At this point,you’ve created a service contract with a single operation and implemented it on a service type.
The service is complete at this point,but to consume it from a client application, you will need to host it first.

Hosting a service
Next,add a new console application to the solution.
This will be the host application.
You’ll instantiate a ServiceHost instance for the service type and configure a single endpoint.
1. Go to the Solution Explorer and add a new Console Application project to the solution.
Name the new project Host.
2. Add a reference to the System.ServiceModel assembly,and add the following using statement to Program.cs:

using System.ServiceModel;

3. You will be writing code to host the HelloIndigoService type.
Before you can do this, you must add a reference to the HelloIndigo project.
4. Create a ServiceHost instance and endpoint for the service.
Open Program.cs in the code window and modify the Main( ) entry point,adding the code shown in Example 1-1.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel; namespace Host
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(HelloIndigo.HelloIndigoService), new Uri("http://localhost:8000/HelloIndigo")))
{
host.AddServiceEndpoint(typeof(HelloIndigo.IHelloIndigoService), new BasicHttpBinding(), "HelloIndigoService");
host.Open();
Console.WriteLine("Press <ENTER> to terminate the service host");
Console.ReadLine();
}
}
}
}

Example 1-1. Code to programmatically initialize the ServiceHost

This code initializes a ServiceHost instance specifying the service type and a base address where relative service endpoints can be located.
It also adds a single relative endpoint for the service.
In this case,a base address is provided for HTTP protocol,and the relative endpoint uses one of the standard bindings, BasicHttpBinding, based on HTTP protocol.
5. Compile and run the host to verify that it works.
From Solution Explorer,rightclick on the Host project node and select “Set as Startup Project.”
Run the project (F5),and you should see console output similar to that shown in Figure 1-17.

Learning WCF Chapter1  Creating a New Service from Scratch

Figure 1-17. Console output for the host application
6. Stop debugging and return to Visual Studio.
You now have a host application for the service.
When it is running clients will be able to communicate with the service.
The next step is to create a client application.

Creating a proxy to invoke a service
Now you will create a new console application to test the service.
To do this,the client requires metadata from the service and information about its endpoint.
This information will be used to initialize a client proxy that can invoke service operations.

1. Go to Solution Explorer and add a new Console Application to the solution.
Name the new project Client.
2. As you might expect,this project also requires a reference to System.ServiceModel.
Add this reference and add the following using statement to Program.cs:

using System.ServiceModel;

3. Copy the service contract to the client.
First,add a new class to the Client project,naming the file ServiceProxy.cs.
Open this new file in the code window and add the IHelloIndigoService contract metadata as shown in Example 1-2.

Example 1-2. Service contract metadata for the client

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel; namespace Client
{
[ServiceContract(Namespace = "http://www.thatindigogirl.com/samples/2006/06")]
public interface IHelloIndigoService
{
[OperationContract]
string HelloIndigo();
}
}

This service contract supplies the necessary metadata to the client,describing namespaces and service operation signatures.
4. Now you can add code to invoke the service endpoint.
Open Program.cs and modify the Main( ) entry point by adding the code as shown in Example 1-3.

Example 1-3. Code to invoke a service through its generated proxy

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel; namespace Client
{
class Program
{
static void Main(string[] args)
{
EndpointAddress ep = new EndpointAddress("http://localhost:8000/HelloIndigo/HelloIndigoService");
IHelloIndigoService proxy = ChannelFactory<IHelloIndigoService>.CreateChannel(new BasicHttpBinding(), ep);
string s = proxy.HelloIndigo();
Console.WriteLine(s);
Console.WriteLine("Press <ENTER> to terminate Client.");
Console.ReadLine();
}
}
}

This code uses the ChannelFactory to create a new channel to invoke the service.
This strongly typed channel reference acts as a proxy.
The code also initializes an EndpointAddress with the correct address and binding expected by the service endpoint.
5. Test the client and service.
Compile the solution and run the Host project first,followed by the Client project.
The Client console output should look similar to that shown in Figure 1-18.

Learning WCF Chapter1  Creating a New Service from Scratch

In the next few sections,I will explain in more detail the steps you completed and
the features you explored in the lab.

Assembly Allocation
The first thing I’d like to touch on is the allocation of assemblies when you create a new solution that includes services.
For example,in this lab you created a new solution with three projects: one for the service,another for the host,and another for the client.
Note that the service definition is decoupled from the host project.
This is an approach I always recommend because it allows you to host the same service in multiple environments.
For example,you may need to expose a service behind the firewall over TCP,and yet also allow remote,interoperable clients to consume it over HTTP.
These two approaches require distinct hosting environments (specifically,a Windows service and IIS,as I will discuss in Chapter 4 at length).
For simplicity,many examples may couple service and host,but this is merely a convenience—not a practical approach.
As such,at a minimum I recommend that you always create a separate project for service contracts and services.

Services are the window through which business functionality is invoked,but business logic has no place in the service assembly.
Business functionality should never be coupled with the service implementation because it is possible that multiple services and applications may need to reuse the same business logic.
Furthermore,while you may use services to reach that functionality in most cases,what if you needed to expose an Enterprise Service component to interoperate with a particular application or system?
If business logic is stored in its own assemblies,this type of sharing is made easy.

Another reason to decouple business logic from service implementation is to improve manageability and versioning.
The service tier may need to coordinate logging activities and exception handling around calls to business components,
and the service tier may need to be versioned,while business components and associated functionality have not changed.
For this reason,I always recommend that business components,data access components,and other dependencies of the business tier also represent a separate set of assemblies in your solution.
Figure 1-19 illustrates this breakdown from a high level.

Learning WCF Chapter1  Creating a New Service from Scratch

Figure 1-19 illustrates this breakdown from a high level.

There may be times when it is desirable to share the service contracts with client applications.
In that case,service contracts and service implementations may also be decoupled.
This makes it possible to share the metadata of the service without sharing the implementation.

Note:Later in this chapter,you’ll see a scenario in which the service contract and service are decoupled.

Defining a Service
The first step in creating a service is to define a service contract.
You create a service contract by applying the ServiceContractAttribute to an interface or type.
Methods on the interface or type will not be included in the service contract until the OperationContractAttribute is applied.
In a typical service contract,all methods will be included in the contract—after all,the entire reason for defining a service contract is to expose operations as part of a service.
Business interfaces should not be directly converted into service contracts.
Likewise,business components should not be directly converted to services.
The service tier should instead be explicitly defined with the sole purpose of exposing public functionality and should internally consume business components,
rather than embed business logic with the service implementation.

When you implement a service contract as an interface,the service type implements this interface.
In this lab,the service implements a single service contract,IHelloIndigoService.
This contract exposes a single operation, HelloIndigo( ).

An alternative to this approach is to apply both the ServiceContractAttribute and the OperationContractAttribute directly to the service type.
Example 1-4 shows the changes you would make to the lab to achieve this.
Here is a summary of those changes:

When you apply the ServiceContractAttribute to the service type,the service type name becomes the official name of the service contract.
Thus,this is the name that must be provided when you create a new endpoint (see AddServiceEndpoint( )).

On the client side,the service contract can still be represented as an interface (the client only requires metadata) but the name of that interface (the service contract)must match the new service contract name,
HelloIndigoService—instead of IHelloIndigoService.
To update the lab,you can rename the interface at the client,or specify a value for the Name property of the ServiceContactAttribute as shown in Example 1-4.
Service contracts are discussed in Chapter 2.

Note:The following sample illustrates the coupling of service contracts with service type: <YourLearningWCFPath>\Sample\ServiceContracts\ServiceContractOnServiceType.

Example 1-4. Changes that support defining the service contract with the service type
// HelloIndigo Project – Service.cs     //取消接口,直接在服务类型上加ServiceContract和在方法加OperationContract
[ServiceContract(Namespace = "http://www.thatindigogirl.com/samples/2006/06")]
public class HelloIndigoService
{
[OperationContract]
public string HelloIndigo( )
{
return "Hello Indigo";
}
}
// Host Project – Program.cs   //接口不存在了,需要使用类型
host.AddServiceEndpoint(typeof(HelloIndigo.HelloIndigoService), new BasicHttpBinding( ),
"HelloIndigoService");
// Client Project – ServiceProxy.cs    //需要显示指定服务类型
[ServiceContract(Name="HelloIndigoService", Namespace = "http://www.thatindigogirl.com/
samples/2006/06")]
public interface IHelloIndigoService
{
[OperationContract]
string HelloIndigo( );
}

Hosting a Service
Any managed process can host services. Within that process,you can create one or more ServiceHost instances,each associated with a particular service type and exposing one or more endpoints for that type.
This lab shows you how to host a service by creating an instance of the ServiceHost type for the HelloIndigoService type within a console application.

Before opening the ServiceHost instance,you can also provide it with base addresses if you are planning to create relative endpoints.
In order to reach the service,at least one endpoint is required.
To programmatically supply base addresses to the ServiceHost,you can pass them to the constructor.
The ServiceHost type also provides an AddServiceEndpoint( ) method to create endpoints as shown here (from Example 1-1):
using (ServiceHost host = new ServiceHost(typeof(HelloIndigo.HelloIndigoService),new Uri("http://localhost:8000/HelloIndigo")))
{
host.AddServiceEndpoint(typeof(HelloIndigo.IHelloIndigoService),new BasicHttpBinding( ), "HelloIndigoService");
// other code
}

In a simple scenario,the ServiceHost need only know its service type and associated endpoints where the service can be reached.
This information is used to create a server channel that can receive and process messages.
The channel is created when you call the Open( ) method on the ServiceHost instance.
This creates a channel listener to receive messages for the service through its associated endpoints.
The receiving channel processes incoming messages,invokes service operations,and processes responses.
When the Close( ) method is called,the channel is gracefully disposed of after processing any remaining requests.
In Example 1-1, Close( ) is automatically called when code block associated with the using statement ends.

Note:The using statement can be applied to any type that implements IDisposable.
At the end of the statement, Dispose( ) is called within a try…finally block to ensure cleanup even in the case of an exception.
Dispose() calls Close()—but if the channel has faulted (faults are discussed in Chapter 8) Abort() should be called instead of Close().
Thus,the using statement is convenient for simple code samples, but not a general best practice.

Exposing Service Endpoints
Endpoints expose service functionality at a particular address.
Each endpoint is associated with a particular contract and a set of protocols as defined by the binding configuration.
For each service,one or more endpoints may be exposed if multiple contracts are present or if multiple protocols are desired to access service functionality.
Figure 1-20 illustrates how the ServiceHost instance exposes endpoints to clients and how the proxy invokes service operations at a particular endpoint.

Learning WCF Chapter1  Creating a New Service from Scratch

Figure 1-20. ServiceHost exposes endpoints and client proxies target a specific endpoint

As the lab illustrates,to create a service endpoint you provide an address,a binding,and a contract.

Addresses
The address can be a complete URI or a relative address like that used in the lab.
The following shows you how to initialize an endpoint with a complete URI without supplying a base address to the ServiceHost:
using (ServiceHost host = new ServiceHost(typeof(HelloIndigo.HelloIndigoService)))
{
host.AddServiceEndpoint(typeof(HelloIndigo.IHelloIndigoService),new BasicHttpBinding( ), "http://localhost:8000/HelloIndigo/HelloIndigoService");
// other code
}

If you supply a relative address it is concatenated with the ServiceHost base address for the matching protocol.
The following illustrates providing an HTTP base address to the ServiceHost constructor and providing a relative address to AddServiceEndpoint( ):
using (ServiceHost host = new ServiceHost(typeof(HelloIndigo.HelloIndigoService),new Uri("http://localhost:8000/HelloIndigo")))
{
host.AddServiceEndpoint(typeof(HelloIndigo.IHelloIndigoService),new BasicHttpBinding( ), "HelloIndigoService");
// other code
}
// Resulting endpoint address
http://localhost:8000/HelloIndigo/HelloIndigoService

In practice,a base address should be supplied for each transport protocol over which the service can be accessed—for example,HTTP,TCP,named pipes,or MSMQ.
In the event an endpoint address includes a complete URI,the base address will be ignored.

Note:Using relative endpoint addressing makes it possible to modify the base URI to move all associated relative endpoints to a new domain or port.
This can simplify the deployment process.

Bindings
The binding provided to an endpoint can be any of the standard bindings supplied by the service model.
In this example,a new instance of the standard BasicHttpBinding is used to initialize the endpoint:
host.AddServiceEndpoint(typeof(HelloIndigo.IHelloIndigoService),new BasicHttpBinding( ), "HelloIndigoService");
The choice of binding defines the communication channel.
For an endpoint,BasicHttpBinding,for example,supports requests over HTTP protocol sent in text format without any additional protocols for addressing,reliable messaging,security,or transactions.

Note:In this chapter,you will employ other standard bindings,but you
should look to Chapter 3 for an in-depth discussion of bindings,channels, and overall service model architecture.

Contracts
Each endpoint is associated with a particular service contract that determines the operations available at the endpoint.
Only one service contract exists in this lab,but a service with multiple contracts could expose a different endpoint for each contract it wants to make accessible to clients.

Creating a Client Proxy
Clients use a proxy to consume a service endpoint.
A proxy can be created manually using a channel factory,or it can be generated using tools.
This lab explores the former and shows you the bare necessities required to communicate with a service:
• The address of the service endpoint
• The protocols required to communicate with the service endpoint, or the binding
• The service contract metadata as described by the service contract associated with the endpoint

Essentially,the client proxy requires information about the service endpoint it wishes to consume.
In this lab,you learned how to manually create the proxy using ChannelFactory<T>, as shown here:

EndpointAddress ep = new EndpointAddress("http://localhost:8000/HelloIndigo/HelloIndigoService");
IHelloIndigoService proxy = ChannelFactory<IHelloIndigoService>.CreateChannel(new BasicHttpBinding( ), ep);

ChannelFactory<T> is a service model type that can generate the client proxy and underlying channel stack.
You provide the address,binding,and service contract type and call CreateChannel( ) to generate the channel stack discussed earlier.
In this lab,you made a copy of the service contract (not the implementation) in the client application in order to supply it as the generic parameter type to ChannelFactory<T>.
The address and binding supplied matched those of the service.
The result is that the client proxy knows where to send messages,what protocols to use,and which operations it can call.

In order for communication between client and service to succeed,the binding must be equivalent to the binding specified for the service endpoint.
Equivalence means that the transport protocol is the same,the message-encoding format is the same,and any additional messaging protocols used at the service to serialize messages are also used at the client.
This lab achieves this by applying the same standard binding,BasicHttpBinding,at the client and service—thus,they are equivalent.
Another requirement for successful communication is that the service contract used to initialize the proxy has equivalent operation signatures and namespace definitions.
This is achieved in this lab by making an exact copy of the service contract at the client.

Note: You may be wondering: how can the client discover the correct address,binding,and contract associated with a service endpoint?
In the next lab,you’ll learn how to generate client proxies and configuration to consume a service without having access to the service code base.