Programming Indigo: The Programming Model
David Pallmann
July 2005
Summary: In this excerpted chapter from his upcoming book, Programming Indigo, David Pallmann describes the Indigo programming model for building service-oriented applications. (50 printed pages)
Setting Up for Indigo
The Development Process
The Programming Model
The Service Model
Levels of Programming
Programming Exercise: "Hello, World"
Summary
It claims to be fully automatic, but actually you have to push this little button here.
— Gentleman John Killian
In Chapter 2, you learned about the fundamental concepts behind Indigo. Now it's time to introduce you to the programming model. The best way to learn a platform is to use it, so let's plunge in and start writing some code.
After completing this chapter, you will:
To set up for Indigo development, you must install Indigo and its prerequisites. Indigo is part of WinFX, the Microsoft Windows managed code development framework. You need all of the following to program with Indigo:
If you have WAP and Microsoft Visual Studio 2005 installed, you already have all of the preceding items.
Indigo is part of the Microsoft "Longhorn" wave of technologies and will be included with future versions of Windows. As of this writing, you can also use Indigo with all editions of the following earlier operating systems:
Indigo is not compatible with versions of Windows earlier than these. You can check which version of Windows a computer is running by opening System in Control Panel. The Windows version is displayed on the General tab.
Caution The version and edition of Windows that you use might limit the communication functionality of your programs. For example, Windows XP Home and Professional Editions limit simultaneous connections from other computers to 5 and 10, respectively.
Indigo requires the .NET Framework 2.0. If the framework is not present on your operating system, you can download it from Microsoft at http://msdn.microsoft.com/netframework. Another way to obtain the .NET Framework is to install Visual Studio 2005 as your development environment.
If you're not sure what version of the .NET Framework is on your computer, you can find out by opening Add Or Remove Programs in Control Panel. If you don't see Microsoft .NET Framework 2.0 listed, you need to install the framework.
While installing the framework, you can also install the MSDN documentation library, which is highly recommended. This book assumes that you have access to both the .NET Framework documentation and the MSDN documentation.
Indigo is part of WinFX, the managed code framework for Windows development. It includes Indigo as well as other technologies. WinFX will be included with the "Longhorn" edition of Windows but is also available as a download for supported operating systems as part of WAP.
You can check whether WAP is installed (and its version, if it is installed) by opening Add Or Remove Programs in Control Panel and examining the list of installed software.
WAP can be downloaded from Microsoft at http://msdn.microsoft.com/webservices.
The recommended environment for Indigo development is Visual Studio .NET 2005. Visual Studio provides unparalleled support for Web, desktop, and device development in multiple languages. Its integrated development environment (IDE) includes a full-featured code editor, Microsoft IntelliSense, visual designers, and visual debugging. Visual Studio is integrated with Indigo, providing a rich and seamless development experience.
Solution development in Indigo combines traditional elements of object-oriented programming with some new considerations. The chief skill you need is the ability to think in a service-oriented manner. To enjoy the benefits discussed in Chapter 1, you should avoid design decisions that violate the tenets of service orientation (SO).
To design a service properly, you must take into account its role in relation to other services. Some key questions to answer include the following:
If you can answer these questions concretely, you are ready to begin designing your service.
A fundamental part of service design is determining contracts. Service contracts describe the operations a service can perform. Data contracts define information structures passed to and from service operations. Message contracts customize the organization of messages, which might be necessary for interoperability. You must define contracts, bindings, and endpoints for the services you create, and you must be aware of the contracts, bindings, and endpoints of other services you plan to access.
Developers can choose which part of a service to create first, the code or the contract. In the code-first approach, the service implementation code is written before the metadata (WSDL and XSD) is created. In the contract-first approach, the metadata is the starting point and the code follows. Each approach has its implications. Developers willing to work at a lower level have a third option, contractless or late-bound development.
In the code-first approach, you begin by writing the implementation code for your service. The contract results from how you write your code. Once your code is complete, you can generate metadata describing your service. The metadata can be provided live by the service itself through a metadata exchange (MEX) endpoint, or you can create WSDL and XSD files from the service.
Not all class types can be converted to XSD schema, which illustrates one disadvantage of the code-first approach: if you choose types carelessly, you might prevent a smooth transition to WSDL and then XSD. You can find out more about Indigo conversions between types and XSD in Chapter 5.
In the contract-first approach, you begin with service metadata (WSDL and XSD) and the code follows. You might be implementing the service itself or creating a client to access the service. In either case, you can generate code from the service metadata, which then gives you a big helping hand in writing your program.
You can obtain service metadata in several ways. Ideally, you can retrieve metadata from a running service's MEX endpoint. Alternatively, you can obtain WSDL and XSD files via the Web or e-mail.
Another choice for developers is to not bother with contracts at all. You can choose this route if you are willing to work directly with messages. If your service operations just send and receive messages, Indigo does not have to make assumptions about the kind of messages your service works with. This requires your programs to work at a lower level than is usually necessary. You have to create messages to send and interpret messages that are received. You might use this technique for a service that accepts any kind of message for processing, such as a router.
Whether you use code-first or contract-first development, you can benefit from code generation. Most developers find metadata such as WSDL and XSD complex and cumbersome to work with directly, so it's desirable to have tools to read or write this information. Indigo provides a command-line tool named Svcutil that imports and exports service metadata. Svcutil can:
Svcutil has quite a few command-line options. Run svcutil /? for a list of them. You can also find a Svcutil reference in Appendix D, which is downloadable from the Web, as described in the Introduction.
Svcutil can read metadata from a service's MEX endpoint and generate program code. To generate code from a service's metadata, specify the MEX endpoint for the service on the Svcutil command line. The MEX endpoint address for a service is usually the same as the services's HTTP base address or HTTP endpoint address.
svcutil http://localhost/MyService
The code generated contains the client's contracts and a proxy class for accessing the service. Code can be generated in a variety of programming languages, including C# and Microsoft Visual Basic .NET.
Svcutil can read metadata from WSDL and XSD files to generate program code. To generate code from a metadata file, specify the metadata file(s) for the service on the Svcutil command line. You can specify multiple files and use wildcards.
svcutil MyService.wsdl *.xsd
The code generated contains the client's contracts and a proxy class for accessing the service. Again, you have a choice of languages.
Once your service is compiled into a .NET assembly, Svcutil can generate metadata files from it. To generate WSDL and XSD files from a service assembly, specify the assembly file on the Svcutil command line:
svcutil MyService.dll
You can make a service self-describing by providing a MEX endpoint. This does not require the Svcutil tool. Metadata is provided live, on demand, by your running service.
Indigo is a managed code platform, so if you've ever programmed with .NET, you'll find yourself in familiar territory. Table 3-1 shows the primary Indigo namespaces and library assemblies. Your programs must reference these assemblies.
Table 3-1 Indigo Namespaces and References
Namespace
Assembly Reference
Description
System.ServiceModel
System.ServiceModel.dll
Core library
System.Runtime.Serialization
System.Runtime.Serialization.dll
Serialization library
System.Security.Authorization
System.Security.Authorization.dll
Security library
System.Transactions
System.Transactions.dll
Transaction library
Indigo also includes a number of tools for such tasks as metadata import and export and viewing traces. Table 3-2 lists the tools.
Table 3-2 Tool Programs
Tool
Description
Comsvcutil.exe
Configures COM+ integration with services
Dc.exe
Import/export tool for converting between data contract types and XML schema documents
Svcutil.exe
Import/export tool for converting between service metadata and code
TraceViewer.exe
Management tool for viewing end-to-end traces and logged messages
Indigo programming involves the use of an object model, declarative attributes, and configuration settings. Listings 3-1 and 3-2 show a small example of client code in which all three are in use. In this example, attributes are used to describe a service contract, objects are used to access the service, and the service endpoint and access method are specified in a configuration file rather than in the program itself.
Example of Attributes and Object Model
using System; using System.ServiceModel; [ServiceContract] interface ITaxService { [OperationContract] double ComputeTax(double amount, double taxRate); } public class Client { public static void Main() { using (TaxServiceProxy proxy = new TaxServiceProxy("DefaultEndpoint")) { double subtotal, tax, total; subtotal = 1500.00D; tax = proxy.ComputeTax(subtotal, 7.75D); total = subtotal + tax; proxy.Close(); } } }
Example of Configuration Settings
<?xml version="1.0" encoding="utf-8" ?> <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.serviceModel> <client> <endpoint configurationName="DefaultEndpoint" address="http://localhost/Tax/service.svc" bindingSectionName="wsProfileBinding" contractType="ITaxService" /> </client> </system.serviceModel> </configuration>
As for where to put your code and what it compiles to, you have some choices. You can host your service in Internet Information Services (IIS), or you can write a small amount of extra code to host a service yourself. You can self-host a service from just about any environment that supports managed code, including a WinForms application, console application, library assembly (DLL), or Microsoft Windows NT Service. We'll talk more about this shortly.
Connected applications can be more challenging to test and debug than standalone applications, due to their distributed nature. Indigo provides two features to help with these tasks: end-to-end tracing and message logging.
End-to-end tracing results in a log of a service's activities. With a trace log, you can follow the sequence of events as services interact. You enable and control end-to-end tracing through a program's configuration file.
Message logging involves storing copies of messages for your inspection as XML files on disk. You enable message logging in an application's configuration file. Listing 3-3 shows a logged message.
A Logged Message
<MessageTraceRecord Time="05/28/2004 22:05:11" ChannelType="System.ServiceModel.Channels.HttpChannelProviderBase+HttpRequestChannel" xmlns="http://schemas.microsoft.com/mb/2002/07/management/messagetrace"> <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"> <s:Header /> <s:Body xmlns:s="http://www.w3.org/2003/05/soap-envelope"> <amount xmlns="">752.00</amount> <accountNumber xmlns="">441234435312324</accountNumber> <expDate xmlns="">01/07</expDate> <nameOnAccount xmlns="">JOHN Q PUBLIC</nameOnAccount> </s:Body> </s:Envelope> </MessageTraceRecord>
You can relate events in the end-to-end trace log to logged messages. A message correlation ID allows you to follow a message's route from one service to another.
You can learn more about these management features in Chapter 11.
Indigo is hosting agnostic, which means you can write an Indigo program from any environment that supports managed code. The hosting environment you use generally has no effect on how you write code to create clients or services. As with other .NET code, you can put Indigo code into many kinds of programs and contexts, such as a console window program, a graphical WinForms application, a Windows NT service, or a Microsoft ASP.NET Web application. Both clients and services can be hosted this way. In each of these cases, you deploy your program in the usual manner. Configuration information is defined in an App.config or Web.config file. If you host in these environments, you must provide a few lines of code to create and start your service, for example:
ServiceHost<MyService> serviceHost = new ServiceHost<MyService>(); serviceHost.Open();
Another option is to host your services using IIS. In IIS hosting, your service doesn't have to be started up in advance; it is launched the first time an incoming message is received from it. In addition to automatic activation, hosting in IIS provides health monitoring and automatic process recycling in the event of unhandled exceptions. When your service is hosted in IIS, you don't have to write any hosting code.
Hosting in IIS is similar to working in ASP.NET. Applications are mapped to a virtual directory created in IIS Manager. An .svc file in the virtual directory describes the service. Your service code can either appear directly in the .svc file, where it is compiled on demand, or it can reside in an assembly. Hosting in IIS does not limit you to HTTP communication, but an Indigo listener service must be running for non-HTTP transports. You can find more information about IIS hosting in Chapter 10.
The Indigo programming model stresses unification, simplicity, and interoperability. The capabilities of predecessor communication technologies such as COM, DCOM, COM+, Enterprise Services, MSMQ, and .NET Remoting are united under a single programming model. The programming model is rich and capable yet simple to use. Because it is backed by a service-oriented runtime, what you develop with the programming model can interoperate with other platforms.
In Chapter 1 we went to great lengths to draw distinctions between service orientation (SO) and object orientation (OO). You should use OO to build programs but use SO to connect programs. This raises the question of how SO and OO meet up and what that means to developers.
In Indigo, objects are treated as first-class citizens. You define contracts and service implementations by writing interfaces and classes. Attributes are used to make the associations. Table 3-3 shows the relationship between SO and OO concepts in the programming model and the attributes that link them.
Table 3-3 Relationship of SO Entities to OO Entities
SO Entity
OO Entity
Attribute
Service contract
interface
Annotate interface with [ServiceContract]
Service operation
method
Annotate interface method with [OperationContract]
Implementation class
class
Annotate class with [ServiceBehavior] and derive from service contract interface
Implementation method
method
Annotate method with [OperationBehavior]
Data contract
class
Annotate class with [DataContract] and members with [DataMember]
Message contract
interface
Annotate service or data contracts with [MessageContract] and members with [MessageHeader] and [MessageBody]
To get a sense of this, study the following C# code, which defines a service contract by annotating an interface with attributes. The developer defines something familiar, an interface. Attributes relate the interface to a service contract and its methods to service operations.
[ServiceContract] public interface IStockTrading { [OperationContract] bool Buy(string symbol, int shares); [OperationContract] bool Sell(string symbol, int shares); }
Similarly, the implementation code for the service can be defined as a class that implements the interface, again using attributes. An attribute identifies the class as a service implementation class.
[ServiceBehavior] public class StockTrading : IStockTrading { [OperationBehavior] public bool Buy(string symbol, int shares) { ...implementation... } [OperationBehavior] public bool Sell(string symbol, int shares) { ...implementation... } }
This SO-to-OO mapping makes things convenient for the developer. Interfaces and classes are familiar entities from the object-oriented world. Annotating them with attributes defines corresponding entities in the service-oriented world. Using generated code from the Svcutil tool, a client can access this service's operations in an object-like manner:
StockTradingProxy proxy = new StockTradingProxy(); proxy.Buy("MSFT", 100);
This fusion of SO and OO means developers can use a paradigm they're already used to—object orientation—yet still enjoy all of the benefits of service orientation. Despite the familiar look of the code, what's sent over the wire is interoperable messages, not objects. The use of attributes in contracts gives developers explicit control over what is exposed to other programs.
The Indigo programming model supports three methods of programming:
This is not to say that everything in Indigo can be done in three ways, but there are quite a few things you can do in more than one way. To illustrate the versatility this gives you, consider the task of specifying an endpoint for a service, which can be done via configuration file settings or in code, as you prefer. Here's an endpoint defined in a configuration file:
<endpoint address="http://localhost:8000/MyService/" bindingSectionName="wsProfileBinding" contractType="IMyContract" />
The same endpoint definition in code looks like this:
serviceHost = new ServiceHost<MyService>(); serviceHost.AddEndpoint(typeof(IMyContract), new WSProfileBinding(), "http://localhost:8000/MyService/");
If it's important to provide deployment-time control of the endpoint to IT deployment personnel, specifying an address in a configuration file is the way to go.
These three programming approaches are not mutually exclusive. Developers can intermix attributes, the object model, and configuration files. In the appropriate chapters in this book, we'll identify the pertinent attributes, objects, and configuration settings.
Declarative attributes are used to define contracts and specify service behaviors. The following code shows a data contract in C#.
[DataContract] public class Contact { [DataMember] public string LastName; [DataMember] public string FirstName; [DataMember] public string Phone; [DataMember] public string Email; }
Note Declarative attributes in C# take the form [keyword] or [keyword(param=value, …)]. They can appear on the same line or precede the statements they apply to.
The equivalent code looks like this in Visual Basic .NET:
<DataContract> Public Class Contact <DataMember> Public LastName As String <DataMember> Public FirstName As String <DataMember> Public Phone As String <DataMember> Public Email As String End Class
Note Declarative attributes in Visual Basic .NET take the form <keyword> or <keyword(param:=value, …)>. If you want the attributes to appear on separate lines from the statements they apply to, you must indicate line continuation with the underscore character.
Attributes can specify parameters that modify details of their requirements or behavior. The following service contract specifies session requirements and service operation direction using parameters.
[ServiceContract(Session=true)] public interface IStockTrading { [OperationContract(IsOneWay=false)] bool Buy(string symbol, int shares); [OperationContract(IsOneWay=false)] bool Sell(string symbol, int shares); }
If you omit the parameters in an attribute, you must be aware of what they default to. By convention, Boolean parameters in attributes usually default to false.
You'll find a concise summary of attributes and parameters in Appendix B, which is downloadable from the Web, as described in the Introduction.
The object model, or application programming interface (API), is the collection of classes and interfaces that provide developers with the deepest level of access to Indigo. With the object model, developers can use object-oriented programming to create and access services. Of the three forms of programming supported by Indigo, the object model is the most extensive. When you need the most granular level of control over Indigo, you should use the object model.
The object model is managed code. If you've ever programmed with .NET, you'll find yourself in familiar territory. The following code, which creates and starts a service, shows the object model at work.
serviceHost = new ServiceHost<MyService>(); serviceHost.AddEndpoint(typeof(IMyContract), new WSProfileBinding(), "http://localhost:8000/MyService/"); serviceHost.Open();
Clients use the object model as well. Here a client creates a typed channel to a service using the object model and accesses the service.
IOrderProcessing orderProcessingService = ChannelFactory.CreateChannel <IOrderProcessing>(new EndpointAddress(uri), binding); string orderID = orderProcessingService.SubmitOrder(order);
Client code is simplified if proxy code is generated using the Svcutil tool. A client can create a proxy this easily using generated code:
OrderProcessingProxy proxy = new OrderProcessingProxy("DefaultEndpoint"); string orderID = proxy.SubmitOrder(order);
Indigo has hundreds of classes, but application developers typically need to access only a small subset on a regular basis. The object model is described in the remaining chapters of this book. You'll find a summary of the object model classes of interest to application developers in Appendix A, which is downloadable from the Web, as described in the Introduction.
Configuration-based development permits part of an application's behavior to be specified in configuration files. You can make such things as addresses, bindings, security details, service behavior, and tracing changeable without having to modify and recompile the program code. The following application configuration file defines a service endpoint.
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.serviceModel> <services> <service serviceType="SampleService"> <endpoint address=http://localhost:8000/SampleService/ bindingSectionName="basicProfileBinding" contractType="ISampleContract, service" /> </service> </services> </system.serviceModel> </configuration>
By specifying addresses, bindings, and behaviors in a configuration file, you can make deploy-time decisions about services that don't require any modification to the service programs themselves.
Configuration settings are described in the remaining chapters of this book. You'll find a concise list of configuration settings in Appendix C, which is downloadable fronm the Web, as described in the introduction.
You might be wondering who "wins" if declarative, imperative, and configuration-based programming are used in contradictory ways. For example, what happens if the same endpoint is defined in more than one place? Here's the order of precedence in Indigo:
Programming in Indigo is delightfully straightforward. With all of Indigo's many capabilities, you might expect the API to be enormous—and it's true that there are hundreds of classes and interfaces. Fortunately, you have to be familiar with only a small subset of them for typical application development tasks. What makes this possible is a carefully designed system of classes, declarative attributes, and configuration settings collectively known as the service model.
Most application developers will use the service model; there's no reason not to. Using the service model doesn't box you into a corner: if you occasionally want to do something at a lower level, you simply add some code. This is one of the nicest things about the Indigo API because you don't have to worry about going down a wrong path.
Tip The service model is your friend. Take advantage of it.
Service programs contain four elements:
Listing 3-4 shows a simple service hosted in a C# console program, and Listing 3-5 shows its accompanying configuration file. Take a moment to note the contract definitions, implementation code, hosting code, and endpoint definition.
Sample Service Program
using System; using System.ServiceModel; namespace Microsoft.Samples.Indigo.Samples { [ServiceContract] public interface ISampleContract { [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); } [ServiceBehavior] public class SampleService : ISampleContract { public double Add(double n1, double n2) { return n1 + n2; } public double Subtract(double n1, double n2) { return n1 - n2; } public double Multiply(double n1, double n2) { return n1 * n2; } public double Divide(double n1, double n2) { return n1 / n2; } public static void Main() { using (ServiceHost<SampleService> serviceHost = new ServiceHost<SampleService>(); { serviceHost.Open(); Console.WriteLine("Press ENTER to shut down service."); Console.ReadLine(); serviceHost.Close(); } } } }
Sample Service Configuration File
<?xml version="1.0" encoding="utf-8" ?> <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.serviceModel> <services> <service serviceType="Microsoft.Samples.Indigo.Samples.SampleService"> <endpoint address="http://localhost:8000/SampleService/" bindingSectionName="basicProfileBinding" contractType="Microsoft.Samples.Indigo.Samples.ISampleContract" /> </service> </services> </system.serviceModel> </configuration>
You define service contracts, data contracts, and message contracts by using declarative attributes.
You define service contracts by annotating an interface with [ServiceContract]. You identify service operations by annotating methods with [OperationContract]. The following service contract defines four service operations: Add, Subtract, Multiply, and Divide.
[ServiceContract] public interface ISampleContract { [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); }
You define data contracts by annotating a class with [DataContract] and its members with [DataMember]. The following data contract defines a structure for contact information.
[DataContract] public class Contact { [DataMember] public int ContactType; [DataMember] public string LastName; [DataMember] public string FirstName; [DataMember] public string Address; [DataMember] public string City; [DataMember] public string Region; [DataMember] public string Phone; [DataMember] public string EMail; }
You define message contracts by annotating a class or interface with [MessageContract] and its members with [MessageBody] and [MessageHeader]. The following class uses MessageContract attributes to specify which members of the class belong in the message body or its headers.
[MessageContract] class MyMessage { [MessageBody] public string MyData; [MessageHeader] public int MyHeader; }
The service model attributes are described in the remaining chapters of this book.
Implementation code is provided in a class that inherits from a service contract interface. The class is annotated with a [ServiceBehavior] attribute. The service operations in the contract are implemented in this class.
[ServiceBehavior] public class SampleService : ISampleContract { public double Add(double n1, double n2) { return n1 + n2; } ... }
Service behaviors are also controlled using the [ServiceBehavior] attribute. The following attribute specifies Singleton instancing so that there is just one instance of the service implementation class regardless of the number of clients accessing the service.
[ServiceBehavior(InstanceMode=InstanceMode.Singleton)] public class SampleService : ISampleContract { ... }
You can also specify behaviors for operations using the [OperationBehavior] attribute. [ServiceBehavior] and [OperationBehavior] are optional: you don't need to specify them unless you need to specify a behavior setting.
Endpoints can be defined in code or in a configuration file. The following code defines an endpoint.
serviceHost = new ServiceHost<MyService>(); serviceHost.AddEndpoint(typeof(IMyContract), new WSProfileBinding(), "http://localhost:8000/MyService/");
The same endpoint can be defined in a configuration file:
<endpoint address="http://localhost:8000/MyService/" bindingSectionName="wsProfileBinding" contractType="IMyContract, MyService" />
Client programs must contain contracts that match the service they are accessing. The recommended approach is to generate client code from a service using the Svcutil tool. Svcutil not only generates contract code, but it also provides a proxy class for accessing the service. Clients create a new instance of the proxy, and they can then access the service through the proxy.
Client programs contain three elements:
Listing 3-6 shows a simple client implemented as a console program, and Listing 3-7 shows its .config file. Take a moment to see if you can locate the proxy creation, service access code, and endpoint definition.
Sample Client Program
using System; using System.ServiceModel; namespace Microsoft.Samples.Indigo.Samples { class Client { static void Main() { using (SampleContractProxy proxy = new SampleContractProxy("SampleEndpoint") { double value1 = 100.00D; double value2 = 15.99D; double result = proxy.Add(value1, value2); value1 = 145.00D; value2 = 76.54D; result = proxy.Subtract(value1, value2); value1 = 9.00D; value2 = 81.25D; result = proxy.Multiply(value1, value2); value1 = 22.00D; value2 = 7.00D; result = proxy.Divide(value1, value2); proxy.Close(); } } } }
Sample Client Configuration File
<?xml version="1.0" encoding="utf-8" ?> <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.serviceModel> <client> <endpoint configurationName="SampleEndpoint" address="http://localhost:8000/SampleService/" bindingConfiguration="SampleBinding" bindingSectionName="basicProfileBinding" contractType="ISampleContract" /> </client> <bindings> <basicProfileBinding> <binding configurationName="SampleBinding" /> </basicProfileBinding> </bindings> </system.serviceModel> </configuration>
If the client makes use of code generated by Svcutil from the service's metadata, you can establish a connection to a service by simply creating a new instance of a proxy class. You can specify the name of a .config location where the service endpoint is defined.
using (SampleContractProxy proxy = new SampleContractProxy("SampleEndpoint") { ... }
Once a proxy is created, all it takes to interact with a service is to call its service operations via the proxy. Working with the proxy feels similar to .NET Remoting or other distributed object technologies.
double result = proxy.Add(value1, value2);
Clients can also define endpoints in code or in a configuration file.
Several levels of programming are available to developers:
Typed services are the highest level of service programming and are likely to be the choice of most application developers. Service operations resemble functions. Parameters and return types can be simple or complex data types. Data contracts define complex data types. The following code connects to an order entry service and submits a purchase order.
OrderEntryProxy orderEntry = new OrderEntryProxy("OrderEntryEndpoint"); orderID = orderEntry.SubmitOrder(purchaseOrder);
Working with typed services feels similar to RPC-style distributed object technologies such as .NET Remoting. Interoperable SOAP messages are sent over the wire, but the developer doesn't have to think in these terms. Serialization and deserialization of parameters and return values to and from messages is automatic. If you need to, you can customize where these items go in a message by using a message contract.
You can use ref and out parameters in typed services. The ComputeTax service operation shown here returns a tax amount and updates a ref parameter with an order total. Being able to use ref and out is convenient, but it's important to keep in mind that all information is passed by value.
public double ComputeTax(double subtotal, double taxRate, ref orderTotal) { double taxAmount = subtotal * (taxRate / 100.00D); orderTotal = subtotal + taxAmount; return taxAmount; }
Untyped services give developers direct access to messages. Like typed services, untyped services contain service operations that are like functions, but the parameters and return types are messages rather than data types. The following client code connects to an order entry service, creates a message from a purchase order, and submits it to the service.
OrderEntryProxy orderEntry = new OrderEntryProxy(); Message message = Message.CreateMessage("http://schemas.microsoft.com/OrderEntry", purchaseOrder); orderEntry.SubmitOrder(message);
You might use an untyped service instead of a typed service if you have to handle a lot of variation in messages. For example, you might want an order entry service to accept several purchase order formats. An untyped service can examine received messages in code to decide how to interpret them.
Programming against the messaging layer is the lowest level of all. Developers take charge of communication details and explicitly create and work with messages and channels. The following client code creates an HTTP channel factory, creates and opens a channel, creates a message from a purchase order, and sends the message over the channel.
HttpChannelFactory channelFactory = new HttpChannelFactory(); channelFactory.Open(); IOutputChannel channel = channelFactory.CreateChannel<IOutputChannel> ("http://AcctgServer/OrderEntryService"); channel.Open(); Message message = Message.CreateMessage("http://schemas.microsoft.com/OrderEntry", purchaseOrder); channel.Send(message);
To create intermediaries (such as routers or proxies) or extensions to Indigo (such as new transport channels or encoders), you must program at this level.
By longstanding tradition, the first program in a new language or platform is "Hello, world." Let's write "Hello, world" in Indigo. Our objective is simple: to give you experience creating, building, and running a service and a client. As discussed in the preceding section, the Indigo programming model provides more than one way to do this. To get a proper appreciation for the versatility of the programming model, we'll create two versions of "Hello, world." Table 3-4 describes how they differ. Hello World #1 is a bare-bones service that does everything in code. Hello World #2 takes advantage of contract-first programming, Svcutil-generated client proxy code, and .config files.
Table 3-4 "Hello, World" Programs
Program
Approach
Generated code
Configuration File
Hosting
Hello World #1
Code-first
No
No
Console application
Hello World #2
Contract-first
Yes
Yes
Internet Information Services (IIS)
Each program comprises a service that performs simple calculator functions and a client that accesses the service. As you're about to see, you can write this service in different ways in Indigo.
You can download all of the code samples in this book from the Web, as described in the Introduction, but you'll get more from the experience if you actually enter, build, and run these samples from scratch. Don't be concerned when you encounter new and unfamiliar terminology, classes, and attributes—we'll explain them throughout the rest of the book.
It's time to write some code. Let's get started!
In this programming exercise, you will create a service and a client. Both will be console applications. Imperative programming will be emphasized through heavy use of the object model—we'll use code wherever we can. The service performs Add, Subtract, Multiply, and Divide calculator functions.
This exercise has six development steps:
To create the service program, launch your development environment and create a new C# console application project named service. Enter the code in Listing 3-8.
To perform these tasks using Visual Studio:
Hello1 Service: Program.cs
using System; using System.ServiceModel; namespace ProgrammingIndigo { //Contract definition. [ServiceContract] public interface IHello { [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); } // Service implementation. [ServiceBehavior] public class HelloService : IHello { public double Add(double n1, double n2) { Console.WriteLine("Add called"); return n1 + n2; } public double Subtract(double n1, double n2) { Console.WriteLine("Subtract called"); return n1 - n2; } public double Multiply(double n1, double n2) { Console.WriteLine("Multiply called"); return n1 * n2; } public double Divide(double n1, double n2) { Console.WriteLine("Divide called"); return n1 / n2; } // Host the service. public static void Main() { // Create a ServiceHost. using (ServiceHost<HelloService> serviceHost = new ServiceHost<HelloService>()) { // Add an endpoint. WSProfileBinding binding = new WSProfileBinding(); Uri uri = new Uri("http://localhost:8000/hello1/"); serviceHost.AddEndpoint(typeof(IHello), binding, uri); // Open the service. serviceHost.Open(); // The service can now be accessed. // Hold it open until user presses ENTER. Console.WriteLine("The service is ready"); Console.WriteLine(); Console.WriteLine("Press ENTER to shut down service."); Console.WriteLine(); Console.ReadLine(); // Close the service. serviceHost.Close(); } } } }
We'll go through the code statement by statement later on.
Add a reference to the System.ServiceModel.dll.
To perform the task using Visual Studio:
To perform the task using Visual Studio, select Build Solution from the Build menu to generate Service.exe.
Create the client program. Add a second C# project named client to the solution. Enter the code in Listing 3-9.
To perform these tasks using Visual Studio:
Hello1 Client: Program.cs
using System; using System.ServiceModel; namespace ProgrammingIndigo { //Contract definition. [ServiceContract] public interface IHello { [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); } //Client implementation code. class Client { static void Main() { // Create a proxy. WSProfileBinding binding = new WSProfileBinding(); Uri uri = new Uri("http://localhost:8000/hello1/"); IHello proxy = ChannelFactory.CreateChannel<IHello>(uri, binding); try { // Call the Add service operation. double value1 = 100.00D; double value2 = 15.99D; Console.WriteLine("Calling Add({0},{1})", value1, value2); double result = proxy.Add(value1, value2); Console.WriteLine(" Result: {0}", result); // Call the Subtract service operation. value1 = 145.00D; value2 = 76.54D; Console.WriteLine("Calling Subtract({0},{1})", value1, value2); result = proxy.Subtract(value1, value2); Console.WriteLine(" Result: {0}", result); // Call the Multiply service operation. value1 = 9.00D; value2 = 81.25D; Console.WriteLine("Calling Multiply({0},{1})", value1, value2); result = proxy.Multiply(value1, value2); Console.WriteLine(" Result: {0}", result); // Call the Divide service operation. value1 = 22.00D; value2 = 7.00D; Console.WriteLine("Calling Divide({0},{1})", value1, value2); result = proxy.Divide(value1, value2); Console.WriteLine(" Result: {0}", result); } finally { ((IChannel)proxy).Close(); ((IChannel)proxy).Dispose(); } Console.WriteLine(); Console.WriteLine("Press ENTER to shut down client"); Console.ReadLine(); } } }
Add a reference to the System.ServiceModel.dll.
To perform the task using Visual Studio:
We're now ready to try things out. Launch the service and client as follows:
In the client window, you should see output like that shown in Figure 3-1. The service window will also display confirmation as client requests are serviced.
Figure 3-1. Hello World #1 client
Press ENTER on the client to shut it down. Press ENTER on the service to shut it down. Congratulations on successfully completing your first Indigo program!
The service program is shown in its entirety in Listing 3-8. The service contract for this service is named IHello. It is defined by creating an interface marked with the [ServiceContract] attribute. There are four service operations, defined by specifying four methods on the interface marked with [OperationContract] attributes.
[ServiceContract] public interface IHello { [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); }
The HelloService class implements the service contract. The class is marked with a [ServiceBehavior] attribute and derives from the IHello interface. Each service operation in the contract is implemented as a method.
[ServiceBehavior] public class HelloService : IHello { public double Add(double n1, double n2) { Console.WriteLine("Add called"); return n1 + n2; } ... }
The service in this example is self-hosted. That is, the program takes responsibility for creating the service and maintaining it over its lifetime. The code for this is in the static Main startup function. To host a service, the code in the Main function creates an instance of ServiceHost<T>, specifying the service's implementation class. The service host is discarded when the using block exits.
public static void Main() { // Create a ServiceHost. using (ServiceHost<SampleService> serviceHost = new ServiceHost<SampleService>()) { ... } }
An endpoint is added to the service host. An endpoint must specify an address, a binding, and a contract. Here, the address is http://localhost:8000/hello1/, the binding is a standard WSProfileBinding, and the contract is IHello.
WSProfileBinding binding = new WSProfileBinding(); Uri uri = new Uri("http://localhost:8000/hello1/"); serviceHost.AddEndpoint(typeof(IHello), binding, uri);
The service must be opened before it will accept requests. The code opens the service by calling the service host's Open method. The service is held open until the user presses ENTER. The program calls the service host's Close method to close the service.
// Open the service. serviceHost.Open(); // The service can now be accessed. Hold it open until user presses ENTER. Console.WriteLine("The service is ready"); Console.WriteLine(); Console.WriteLine("Press ENTER to shut down service."); Console.WriteLine(); Console.ReadLine(); // Close the service. serviceHost.Close();
Although we stressed the use of the object model in this example, we couldn't get away completely from service model declarative attributes. They were necessary to define the service contract and the service implementation class.
The client program is shown in its entirety in Listing 3-9. The client must agree with the service about address, binding, and contract, or communication is not possible. Because we are using code-first development and avoiding code generation tools in this example, the client's contract code is simply the result of a copy and paste from the service code.
[ServiceContract] public interface IHello { [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); }
The client creates a proxy channel to the service using the ChannelFactory class's static CreateChannel<T> method, specifying the contract name (IHello), address (http://localhost:8000/hello1), and the WSProfileBinding standard binding. Once the client is finished accessing the service, the proxy is closed and discarded. It's necessary to cast the proxy object—defined merely as something supporting the IHello interface in our program—to an IChannel in order to access the Close and Dispose functions.
static void Main() { // Create a proxy. WSProfileBinding binding = new WSProfileBinding(); Uri uri = new Uri("http://localhost:8000/hello1/"); IHello proxy = ChannelFactory.CreateChannel<IHello>(uri, binding); try { ... ((IChannel)proxy).Close(); } finally { ((IChannel)proxy).Dispose(); } }
The client's access to the service is straightforward. You can call the service's operations by using the proxy channel.
// Call the Add service operation. double value1 = 100.00D; double value2 = 15.99D; Console.WriteLine("Calling Add({0},{1})", value1, value2); double result = proxy.Add(value1, value2); Console.WriteLine(" Result: {0}", result); // Call the Subtract service operation. value1 = 145.00D; value2 = 76.54D; Console.WriteLine("Calling Subtract({0},{1})", value1, value2); result = proxy.Subtract(value1, value2); Console.WriteLine(" Result: {0}", result); ...
Our second "Hello, world" program will be functionally identical to the first one but will be written quite differently. The programming approach will be contract first. Client code will be generated from the service's metadata using the Svcutil tool. The service will be hosted by IIS. The client and the service will define endpoints and bindings in .config files rather than in code.
This exercise has 10 development steps:
Create a service program that compiles to a DLL library assembly. Launch your development environment and create a new C# console application project named service. Enter the code in Listing 3-10.
To perform these tasks using Visual Studio:
Your service project will need to reference System.ServiceModel.dll.
To perform the task using Visual Studio:
Hello2 Service: Program.cs
using System; using System.ServiceModel; using System.Diagnostics; namespace ProgrammingIndigo { // Contract definition. [ServiceContract] public interface IHello { [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); } // Service implementation. [ServiceBehavior] public class HelloService : IHello { public double Add(double n1, double n2) { return n1 + n2; } public double Subtract(double n1, double n2) { return n1 - n2; } public double Multiply(double n1, double n2) { return n1 * n2; } public double Divide(double n1, double n2) { return n1 / n2; } } }
Build the service program to make Service.dll. Resolve any typographical errors.
To perform the task using Visual Studio, choose Build Solution from the Build menu to generate Service.dll.
An .svc file is needed to identify the Indigo service to IIS. Using an editor or development environment, create a text file named Service.svc. Enter the code shown in Listing 3-11.
To perform these tasks using Visual Studio:
Hello2 Service: Service.svc
<%@Service language=c# Debug="true" class="ProgrammingIndigo.HelloService" %> <%@Assembly Name="service" %>
A Web.config file is needed to specify endpoints and bindings for the service. Using an editor or development environment, create a text file with the code shown in Listing 3-12. Save the code under the name Web.config in the same folder in which the service program and .svc file are located.
To perform these tasks using Visual Studio:
Hello2 Service: Web.config
<?xml version="1.0" encoding="utf-8" ?> <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.serviceModel> <services> <service serviceType="ProgrammingIndigo.HelloService"> <endpoint address="" bindingSectionName="wsProfileBinding" contractType="ProgrammingIndigo.IHello" /> </service> </services> </system.serviceModel> <system.web> <compilation debug="true" /> </system.web> </configuration>
Like ASP.NET applications, Web-hosted services reside in virtual directories. Follow this procedure to set up a virtual directory for the service.
\hello2 service.svc \bin service.dll
Before going on to the client, we must test to make sure the service can be accessed. Launch Internet Explorer. On the address bar, specify the URL http://localhost/hello2/service.svc and press ENTER. If the resulting page does not list any errors, proceed to step 7. If there are problems, check the following:
The service is now ready. Next we need a client program to access it.
Create the client program. Launch your development environment and create a new C# console application project named client. Enter the code in Listing 3-13.
To perform these tasks using Visual Studio:
Your client project will need to reference System.ServiceModel.dll.
To perform the task using Visual Studio:
Hello2 Client: Program.cs
using System; using System.ServiceModel; namespace ProgrammingIndigo { class Client { static void Main(string[] args) { // Create a proxy. Console.WriteLine("Creating proxy to service"); using (HelloProxy proxy = new HelloProxy("HelloEndpoint")) { // Call the Add service operation. double value1 = 100.00D; double value2 = 15.99D; Console.WriteLine("Calling Add({0},{1})", value1, value2); double result = proxy.Add(value1, value2); Console.WriteLine(" Result: {0}", result); // Call the Subtract service operation. value1 = 145.00D; value2 = 76.54D; Console.WriteLine("Calling Subtract({0},{1})", value1, value2); result = proxy.Subtract(value1, value2); Console.WriteLine(" Result: {0}", result); // Call the Multiply service operation. value1 = 9.00D; value2 = 81.25D; Console.WriteLine("Calling Multiply({0},{1})", value1, value2); result = proxy.Multiply(value1, value2); Console.WriteLine(" Result: {0}", result); // Call the Divide service operation. value1 = 22.00D; value2 = 7.00D; Console.WriteLine("Calling Divide({0},{1})", value1, value2); result = proxy.Divide(value1, value2); Console.WriteLine(" Result: {0}", result); proxy.Close(); } Console.WriteLine(); Console.WriteLine("Press ENTER to shut down client"); Console.ReadLine(); } } }
We will now generate client proxy code by accessing the service's MEX endpoint with the Svcutil tool:
svcutil http://localhost/hello2/service.svc
The file Out.cs will be generated, containing the service contract and a proxy class for accessing the service. It should match the code in Listing 3-14.
Hello2 Client: Out.cs
//--------------------------------------------------------------------- // <auto-generated> // This code was generated by a tool. // Runtime Version:2.0.50105.0 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //--------------------------------------------------------------------- [System.ServiceModel.ServiceContractAttribute()] public interface IHello { [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IHello/Add", ReplyAction = "http://tempuri.org/IHello/AddResponse")] [return: System.ServiceModel.MessageBodyAttribute(Name = "AddResult", Namespace = "http://tempuri.org/")] double Add([System.ServiceModel.MessageBodyAttribute(Namespace = "http://tempuri.org/")] double n1, [System.ServiceModel.MessageBodyAttribute(Namespace = "http://tempuri.org/")] double n2); [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IHello/Subtract", ReplyAction = "http://tempuri.org/IHello/SubtractResponse")] [return: System.ServiceModel.MessageBodyAttribute(Name = "SubtractResult", Namespace = "http://tempuri.org/")] double Subtract([System.ServiceModel.MessageBodyAttribute(Namespace = "http://tempuri.org/")] double n1, [System.ServiceModel.MessageBodyAttribute(Namespace = "http://tempuri.org/")] double n2); [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IHello/Multiply", ReplyAction = "http://tempuri.org/IHello/MultiplyResponse")] [return: System.ServiceModel.MessageBodyAttribute(Name = "MultiplyResult", Namespace = "http://tempuri.org/")] double Multiply([System.ServiceModel.MessageBodyAttribute(Namespace = "http://tempuri.org/")] double n1, [System.ServiceModel.MessageBodyAttribute(Namespace = "http://tempuri.org/")] double n2); [System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IHello/Divide", ReplyAction = "http://tempuri.org/IHello/DivideResponse")] [return: System.ServiceModel.MessageBodyAttribute(Name = "DivideResult", Namespace = "http://tempuri.org/")] double Divide([System.ServiceModel.MessageBodyAttribute(Namespace = "http://tempuri.org/")] double n1, [System.ServiceModel.MessageBodyAttribute(Namespace = "http://tempuri.org/")] double n2); } public interface IHelloChannel : IHello, System.ServiceModel.IProxyChannel { } public partial class HelloProxy : System.ServiceModel.ProxyBase<IHello>, IHello { public HelloProxy() { } public HelloProxy(string configurationName) : base(configurationName) { } public HelloProxy(System.ServiceModel.Binding binding) : base(binding) { } public HelloProxy(System.ServiceModel.EndpointAddress address, System.ServiceModel.Binding binding) : base(address, binding) { } public double Add(double n1, double n2) { return base.InnerProxy.Add(n1, n2); } public double Subtract(double n1, double n2) { return base.InnerProxy.Subtract(n1, n2); } public double Multiply(double n1, double n2) { return base.InnerProxy.Multiply(n1, n2); } public double Divide(double n1, double n2) { return base.InnerProxy.Divide(n1, n2); } } [System.ServiceModel.ServiceContractAttribute(Name = "WS-MetadataExchange", Namespace = "http://schemas.xmlsoap.org/ws/2004/08/mex")] public interface WSMetadataExchange { [System.ServiceModel.OperationContractAttribute(Action = "http://schemas.xmlsoap.org/ws/2004/08/mex/GetMetadata/Request", ReplyAction = "http://schemas.xmlsoap.org/ws/2004/08/mex/GetMetadata/Response")] [return: System.ServiceModel.MessageBodyAttribute(Name = "GetMetadataResult", Namespace = "")] System.ServiceModel.Message GetMetadata([System.ServiceModel.MessageBodyAttribute (Namespace = "")] System.ServiceModel.Message request); [System.ServiceModel.OperationContractAttribute(Action = "http://schemas.xmlsoap.org/ws/2004/08/mex/Get/Request", ReplyAction = "http://schemas.xmlsoap.org/ws/2004/08/mex/Get/Response")] [return: System.ServiceModel.MessageBodyAttribute(Name = "GetResult", Namespace = "")] System.ServiceModel.Message Get([System.ServiceModel.MessageBodyAttribute (Namespace = "")] System.ServiceModel.Message request); } public interface WSMetadataExchangeChannel : WSMetadataExchange, System.ServiceModel.IProxyChannel { } public partial class WSMetadataExchangeProxy : System.ServiceModel.ProxyBase <WSMetadataExchange>, WSMetadataExchange { public WSMetadataExchangeProxy() { } public WSMetadataExchangeProxy(string configurationName) : base(configurationName) { } public WSMetadataExchangeProxy(System.ServiceModel.Binding binding) : base(binding) { } public WSMetadataExchangeProxy(System.ServiceModel.EndpointAddress address, System.ServiceModel.Binding binding) : base(address, binding) { } public System.ServiceModel.Message GetMetadata(System.ServiceModel.Message request) { return base.InnerProxy.GetMetadata(request); } public System.ServiceModel.Message Get(System.ServiceModel.Message request) { return base.InnerProxy.Get(request); } }
A Client.exe.config file is needed to specify the service endpoint and binding to use. Using an editor or development environment, create a text file with the code shown in Listing 3-15. Save the code under the name Client.exe.config.
To perform these tasks using Visual Studio:
Hello2 Client: App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.serviceModel> <client> <endpoint configurationName="HelloEndpoint" address="http://localhost/hello2/service.svc" bindingConfiguration="helloBinding" bindingSectionName="wsProfileBinding" contractType="IHello" /> </client> <bindings> <wsProfileBinding> <binding configurationName="helloBinding" /> </wsProfileBinding> </bindings> </system.serviceModel> </configuration>
Build the client program to make Client.exe. Resolve any typographical errors.
To perform the task using Visual Studio, select Build Solution from the Build menu to generate Client.exe.
We're now ready to try things out. Run the client from your development environment or from a command line. You should see output like that shown in Figure 3-2. Notice that there's no need to launch the service; services hosted in IIS compile and activate when they are accessed. If the program fails, check that you've properly and fully carried out each of the preceding steps.
Figure 3-2. Hello World #2 client
Press ENTER on the client to shut it down. Congratulations on successfully completing your second Indigo program!
The service program code is shown in Listing 3-10.The service contract for this service is named IHello. This is the same contract used in the Hello World #1 example and is defined in the same way, by annotating an interface with attributes.
[ServiceContract] public interface IHello { [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); }
The HelloService class implements the service contract. The class is marked with a [ServiceBehavior] attribute and derives from the IHello interface. Each service operation in the contract is implemented as a method. The implementation classes for Hello World #1 and #2 implement service operations identically.
[ServiceBehavior] public class HelloService : IHello { public double Add(double n1, double n2) { return n1 + n2; } ... }
The service in this example is hosted by IIS, so there is no need for the hosting code required in Hello World #1. IIS launches the service when a message comes in. The Service.svc file, shown in Listing 3-11, defines the service for IIS. The Service directive identifies the service class, ProgrammingIndigo.HelloService. The Assembly directive identifies the assembly in which the service class resides, Service.dll.
<%@Service language=c# Debug="true" class="ProgrammingIndigo.HelloService" %> <%@Assembly Name="service" %>
The endpoint for the service and the binding it uses are defined in the Web.config file shown in Listing 3-12. This is different from Hello World #1, in which these elements were defined in code. The service endpoint has the address http://localhost/hello2/service.svc, a standard wsProfileBinding binding, and the contract IHello. The endpoint address for this service is quite different from Hello World #1 due to hosting by IIS. The virtual directory (hello2) and .svc filename (Service.svc) form part of the address.
<system.serviceModel> <client> <endpoint configurationName="HelloEndpoint" address="http://localhost/hello2/service.svc" bindingConfiguration="helloBinding" bindingSectionName="wsProfileBinding" contractType="IHello" /> </client> <bindings> <wsProfileBinding> <binding configurationName="helloBinding" /> </wsProfileBinding> </bindings> </system.serviceModel>
The client must agree with the service about address, binding, and contract, or communication will not be possible. Because we are using contract-first development for this client, client proxy code was generated from the service using the Svcutil tool. The generated program code is shown in Listing 3-14. The code generated includes both the service contract, IHello, and a class for accessing the service, HelloProxy.
The client program code is shown in Listing 3-13. The client creates a proxy to the service by creating a new instance of the generated HelloProxy class. The string specified in the constructor, HelloEndpoint, specifies a configuration section in which the endpoint for the service is defined. The client configuration file is shown in Listing 3-15. The proxy is created in a using statement; when the using statement block exits, the proxy is discarded.
using (HelloProxy proxy = new HelloProxy("HelloEndpoint")) { ... }
The client's access to the service is straightforward. The service's operations can be called using the proxy channel. When the client is finished communicating with the service, the proxy is closed.
// Call the Add service operation. double value1 = 100.00D; double value2 = 15.99D; Console.WriteLine("Calling Add({0},{1})", value1, value2); double result = proxy.Add(value1, value2); Console.WriteLine(" Result: {0}", result); // Call the Subtract service operation. value1 = 145.00D; value2 = 76.54D; Console.WriteLine("Calling Subtract({0},{1})", value1, value2); result = proxy.Subtract(value1, value2); Console.WriteLine(" Result: {0}", result); ... proxy.Close();
Hello World #1 was created with code-first programming, whereas the client for Hello World #2 was created with contract-first programming. In the code-first case, this meant copying the service's contract into the client code. In the contract-first case, the contract was generated by pointing the Svcutil tool to the service's MEX endpoint.
The service in Hello World #1 is self-hosted, requiring the program to create an instance of ServiceHost, open it, and maintain it while the service is accessed. The service in Hello World #2 requires no hosting code thanks to IIS. The Hello World #1 service must be launched before a client can access it; the Hello World #2 service is launched upon the first client request.
Creating the client was much simpler in Hello World #2 because there was a generated proxy class to use. In Hello World #1, we had to write code to create a channel.
Endpoint and binding definitions for Hello World #1's service and client are defined in code. In Hello World #2 they are defined in .config files.
This chapter introduced the Indigo programming model, starting with how to set up Indigo on your computer. To develop in Indigo, you need WinFX, a supported operating system, the Microsoft .NET Framework 2.0, and a development environment. Visual Studio 2005 is the recommended development environment because it integrates well with Indigo to provide a richer experience for developers.
Our overview of the development process described service design, development approaches, program implementation, application deployment, hosting, and debugging.
This chapter also introduced the programming model, describing how object orientation (OO) and service orientation (SO) are reconciled. Three programming approaches were identified: declarative programming through service model attributes, imperative programming through the object model, and configuration file–based programming through application .config files. Web hosting eliminates the need to write code to create and start up a service.
We created two "Hello, world" Indigo programs, each comprising a service and a client. Hello World #1's service is self-hosted and created with code-first programming, and it emphasizes doing everything in code. Hello World #2's service is hosted in Internet Information Services, its client was created with contract-first programming, and it emphasizes heavy use of .config file settings to define endpoints and bindings.
The chapters that follow explore Indigo programming by topic, starting with addresses and bindings in Chapter 4 and contracts in Chapter 5.
Excerpted from the upcoming book, Programming Indigo by David Pallmann.