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)

Contents

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:

  • Know how to set up Indigo on your computers.
  • Be familiar with the development process.
  • Understand the Indigo programming model.
  • Have created your first "Hello, world" Indigo programs.

Setting Up for Indigo

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:

  • An operating system that supports Indigo
  • Microsoft .NET Framework 2.0
  • Windows Application Pack (WAP)
  • A development environment

If you have WAP and Microsoft Visual Studio 2005 installed, you already have all of the preceding items.

Supported Operating Systems

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:

  • Windows XP SP2
  • Microsoft Windows Server 2003

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.

.NET Framework 2.0

Indigo requires the .NET Framework 2.0. If the framework is not present on your operating system, you can download it from Microsoft at https://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.

Windows Application Pack

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 https://msdn.microsoft.com/webservices.

Visual Studio .NET 2005

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.

The Development Process

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).

Service Design

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:

  • Does your service have a well-defined role as part of a distributed solution?
  • What are the target audiences for your service? Who will be consuming it?
  • What dependencies does your service have? What other services will your service access?
  • What boundaries will be crossed to access your service, and what are the implications?
  • What contracts and schemas should your service implement?
  • What message exchange patterns should your service support?
  • What are your service's security requirements?
  • What are your service's interoperability requirements?

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.

Development Approach

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.

Code-First 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.

Contract-First Development

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.

Contractless or Late-Bound Development

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.

Code Generation: The Svcutil Tool

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:

  • Generate program code from service metadata retrieved from a running service's MEX endpoint.
  • Generate program code from service metadata read from WSDL and XSD files.
  • Generate service metadata WSDL and XSD files from a service assembly.

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.

Generating Program Code from a MEX Endpoint

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 Svcutilcommand line. The MEX endpoint address for a service is usually the same as the services's HTTP base address or HTTP endpoint address.

svcutil https://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.

Generating Program Code from WSDL and XSD Files

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 Svcutilcommand 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.

Generating WSDL and XSD Files from a Service Assembly

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

Self-Describing Services

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.

Program Implementation

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.

Listing 3-1.

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();
        }
    }
}

Listing 3-2.

Example of Configuration Settings

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0">
    <system.serviceModel>
        <client>
            <endpoint
                configurationName="DefaultEndpoint"
                address="https://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.

Debugging Applications

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.

Listing 3-3.

A Logged Message

<MessageTraceRecord Time="05/28/2004 22:05:11" 
ChannelType="System.ServiceModel.Channels.HttpChannelProviderBase+HttpRequestChannel" 
xmlns="https://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 >752.00</amount> 
      <accountNumber >441234435312324</accountNumber> 
      <expDate >01/07</expDate> 
      <nameOnAccount >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.

Deploying Applications

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 Programming Model

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.

Reconciling Object Orientation and Service Orientation

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.

Programming Approaches

The Indigo programming model supports three methods of programming:

  • Declarative programming, in which you specify attributes
  • Imperative programming, in which you work with the Indigo object model in code
  • Configuration-based programming, in which you specify behavior in application configuration files

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="https://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(), 
       "https://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 Programming: Attributes

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.

Imperative Programming: The Object Model

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(), 
   "https://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 Programming

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="https://schemas.microsoft.com/.NetConfiguration/v2.0">
    <system.serviceModel>
        <services>
            <service serviceType="SampleService">
                <endpoint
                    address=https://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.

Order of Precedence in Indigo

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:

  1. Attributes are applied.
  2. Configuration settings are applied (possibly overriding some definitions).
  3. Code executes (possibly overriding some definitions).

The Service Model

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.

What Service Programs Look Like

Service programs contain four elements:

  • Contract definitions A service must have at least one service contract, and it might contain multiple service contracts, data contracts, or message contracts.
  • Implementation code The service contracts in a service need code to implement their service operations.
  • Hosting code Some code is needed to create and start the service.
  • Endpoint definitions One or more address–binding–contract endpoints must be declared.

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.

Listing 3-4.

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();
            }
        }
    }
}

Listing 3-5.

Sample Service Configuration File

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0">
    <system.serviceModel>
        <services>
            <service serviceType="Microsoft.Samples.Indigo.Samples.SampleService">
                <endpoint
                    address="https://localhost:8000/SampleService/" 
                    bindingSectionName="basicProfileBinding" 
                    contractType="Microsoft.Samples.Indigo.Samples.ISampleContract" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

Contracts

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.

Service Implementation Code

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

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(), 
       "https://localhost:8000/MyService/");

The same endpoint can be defined in a configuration file:

<endpoint
    address="https://localhost:8000/MyService/" 
    bindingSectionName="wsProfileBinding" 
    contractType="IMyContract, MyService" />

What Client Programs Look Like

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:

  • Proxy creation A client must create a proxy, which establishes a channel to the service.
  • Service access code Through the proxy, the client interacts with the service.
  • Endpoint definitions The client declares an address-binding-contract endpoint for the service.

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.

Listing 3-6.

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();
            }
        }
    }
}

Listing 3-7.

Sample Client Configuration File

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0">
     <system.serviceModel>
          <client>
               <endpoint
                     configurationName="SampleEndpoint"
                     address="https://localhost:8000/SampleService/" 
                     bindingConfiguration="SampleBinding" 
                     bindingSectionName="basicProfileBinding" 
                     contractType="ISampleContract" />
          </client>
          <bindings>
               <basicProfileBinding>
                    <binding configurationName="SampleBinding" />
               </basicProfileBinding>
          </bindings>
     </system.serviceModel>
</configuration>

Proxy Creation

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")
{
    ...
}

Service Access

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);

Endpoints

Clients can also define endpoints in code or in a configuration file.

Levels of Programming

Several levels of programming are available to developers:

  • Typed service Contains service operations that are like functions, where parameters and return values can be simple or complex data types
  • Untyped service Similar to a typed service, but the parameters and return values are messages
  • Messaging-layer programming Gives the developer precise control over communication

Typed Services

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

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("https://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.

Messaging-Layer Programming

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("https://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.

Programming Exercise: "Hello, World"

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!

Hello World #1

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:

  1. Create the service.
  2. Add a reference to System.ServiceModel.dll.
  3. Build the service.
  4. Create the client.
  5. Add a reference to System.ServiceModel.dll.
  6. Build the client.

Step 1: Create the Service

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:

  1. Choose File, New, Project.
  2. Under Project Type, select Windows under Visual C#. Under Templates, select Console Application.
  3. In the Name box, type service, in the Location box, type any path you want, and in the Solution Name box, type hello1. Click OK to generate and open the new project.
  4. Replace the generated code in Program.cs with the code shown in Listing 3-8.

Listing 3-8.

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("https://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.

Step 2: Add a Reference to System.ServiceModel.dll

Add a reference to the System.ServiceModel.dll.

To perform the task using Visual Studio:

  1. Right-click References in the Solution Explorer window and select Add Reference.
  2. In the Add Reference dialog box, on the .NET tab, select System.ServiceModel.dll and click OK.

Step 3: Build the Service

  1. Build the service program to make Service.exe. Resolve any typographical errors.

To perform the task using Visual Studio, select Build Solution from the Build menu to generate Service.exe.

Step 4: Create the Client

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:

  1. Choose File, New, Project.
  2. Under Project Type, select Windows under Visual C#. Under Templates, select Console Application. In the Name box, type client, in the Location box, type any path you want, and in the SolutionName box, type Add To Solution. Click OK to generate and open the new project.
  3. Replace the generated code in Program.cs with the code shown in Listing 3-9.

Listing 3-9.

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("https://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();
        }
    }
}

Step 5: Add a Reference to System.ServiceModel.dll

Add a reference to the System.ServiceModel.dll.

To perform the task using Visual Studio:

  1. Right-click References (under the client project) in the Solution Explorer window and select Add Reference.
  2. In the Add Reference dialog box, on the .NET tab, select System.ServiceModel.dll and click OK.

Step 6: Build the Client

  1. Build the client program to make Client.exe. Resolve any typographical errors.
  2. To perform the task using Visual Studio, select Build Solution from the Build menu to generate Client.exe.

Deployment

We're now ready to try things out. Launch the service and client as follows:

  1. Run Service.exe from a command window.
  2. Wait for the service to initialize and display "Press ENTER to shut down service."
  3. Run Client.exe from another command window.

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.

Aa480201.progindigoch3_01(en-us,MSDN.10).gif

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!

Understanding the Service Code

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 https://localhost:8000/hello1/, the binding is a standard WSProfileBinding, and the contract is IHello.

WSProfileBinding binding = new WSProfileBinding();
Uri uri = new Uri("https://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.

Understanding the Client Code

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 (https://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("https://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);

...

Hello World #2

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:

  1. Create the service.
  2. Build the service.
  3. Create the .svc file.
  4. Create a configuration file for the service.
  5. Create a virtual directory for the service.
  6. Test the service with a browser.
  7. Create the client.
  8. Generate proxy code for the client.
  9. Create a configuration file for the client.
  10. Build the client.

Step 1: Create the Service

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:

  1. Choose File, New, Project.
  2. Under Project Type, select Windows under Visual C#. Under Templates, select Console Application. In the Name box, type service, in the Location box, type any path you want, and in the Solution Name box, type hello2. Click OK to generate and open the new project.
  3. Replace the generated code in Program.cs with the code shown in Listing 3-10.

Your service project will need to reference System.ServiceModel.dll.

To perform the task using Visual Studio:

  1. Right-click References in the Solution Explorer window and select Add Reference.
  2. In the Add Reference dialog box, on the .NET tab, select System.ServiceModel.dll and click OK.

Listing 3-10.

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;
        }

    }
}

Step 2: Build the Service

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.

Step 3: Create the .svc File

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:

  1. Right-click the service project and select Add, New Item.
  2. Select Text File, and click Add. Rename the text file Service.svc.
  3. Type the code in Listing 3-11 into Service.svc.

Listing 3-11.

Hello2 Service: Service.svc

<%@Service language=c# Debug="true" class="ProgrammingIndigo.HelloService" %>
<%@Assembly Name="service" %>

Step 4: Create a Configuration File for the 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:

  1. Right-click the service project and select Add, New Item.
  2. Select Application Configuration File, and click Add.
  3. Name the file Web.config.
  4. Type the code in Listing 3-12 into Web.config.

Listing 3-12.

Hello2 Service: Web.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="https://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>

Step 5: Create a Virtual Directory for the Service

Like ASP.NET applications, Web-hosted services reside in virtual directories. Follow this procedure to set up a virtual directory for the service.

  1. Create a directory. Create a folder on your computer named hello2.

  2. Create a virtual directory. From the Start menu, launch Control Panel. Select Administrative Tools, Internet Information Services. Navigate to Web Sites, Default Web Sites. Right-click and select New, Virtual Directory. Specify the name hello2 and associate it with the hello2 folder created in step 1. Before exiting IIS Manager, check step 3.

  3. Enable anonymous access. While in IIS, navigate toWeb Sites, Default Web Sites. The hello2 folder should be listed. Right-click hello2 and select Properties. On the Directory Security tab, click Edit. An Authentication Methods dialog box appears. Anonymous Access should be selected; if it is not, select it. Close all dialog boxes by clicking OK, and close IIS.

  4. Deploy the service. Copy the Service.svc file to the hello2 folder. Underneath hello2, create a bin subdirectory and copy Service.dll into it. You should have the following directory-file structure.

    \hello2
      service.svc
         \bin
           service.dll
    

Step 6: Test the Service with a Browser

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 https://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:

  • If the page returned by the browser describes compilation errors, check the code entered in the preceding steps. The compilation error display should highlight the offending code.
  • If the resulting page describes HTTP errors, make sure your system has IIS enabled. You should be able to access https://localhost in a browser without receiving an error. If IIS seems to be properly enabled, review the virtual directory setup in step 5. If this also fails, look for clues in Control Panel, Administrative Tools, Event Log or try an IISRESET.

The service is now ready. Next we need a client program to access it.

Step 7: Create the Client

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:

  1. Select New, Project from the File menu.
  2. Under Project Type, select Windows under Visual C#. Under Templates, select Console Application. In the Name box, type client, in the Location box, type any path you want, and in the Solution Name box, type hello2. Click OK to generate and open the new project.
  3. Replace the generated code in Program.cs with the code shown in Listing 3-13.

Your client project will need to reference System.ServiceModel.dll.

To perform the task using Visual Studio:

  1. Right-click References in the Solution Explorer window and select Add Reference.
  2. In the Add Reference dialog box, on the .NET tab, select System.ServiceModel.dll and click OK.
  3. Build the client, which will create Client.exe.

Listing 3-13.

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();
        }
    }
}

Step 8: Generate Proxy Code for the Client

We will now generate client proxy code by accessing the service's MEX endpoint with the Svcutil tool:

  1. In a command window, change the directoryto the location where your client project and source files reside. Run the following Svcutilcommand:

    svcutil https://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.

  1. Add Out.cs to your client project. In Visual Studio, right-click the project in the Solution Explorer window and select Add, Existing Item.
  2. In the File Open dialog box, select Out.cs and click OK. Out.cs is added to the client project.

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 = "https://schemas.xmlsoap.org/ws/2004/08/mex")]
public interface WSMetadataExchange
{

    [System.ServiceModel.OperationContractAttribute(Action = 
       "https://schemas.xmlsoap.org/ws/2004/08/mex/GetMetadata/Request", ReplyAction = 
       "https://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 = 
       "https://schemas.xmlsoap.org/ws/2004/08/mex/Get/Request", ReplyAction = 
       "https://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);
    }
}

Step 9: Create a Configuration File for the Client

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:

  1. Right-click the service project and select Add, New Item.
  2. Select Application Configuration File, and click Add.
  3. Name the file App.config. (It will be copied to Client.exe.config at build time.)
  4. Enter the code in Listing 3-15 into App.config.

Listing 3-15.

Hello2 Client: App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0">
    <system.serviceModel>
        <client>
            <endpoint
               configurationName="HelloEndpoint"
               address="https://localhost/hello2/service.svc" 
               bindingConfiguration="helloBinding" 
               bindingSectionName="wsProfileBinding" 
               contractType="IHello" />
        </client>
        <bindings>
            <wsProfileBinding>
                <binding configurationName="helloBinding" />
            </wsProfileBinding>
        </bindings>
    </system.serviceModel>
 </configuration>

Step 10: Build the Client

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.

Deployment

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.

Aa480201.progindigoch3_02(en-us,MSDN.10).gif

Figure 3-2. Hello World #2 client

Press ENTER on the client to shut it down. Congratulations on successfully completing your second Indigo program!

Understanding the Service Code

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 https://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="https://localhost/hello2/service.svc" 
            bindingConfiguration="helloBinding"  
            bindingSectionName="wsProfileBinding" 
            contractType="IHello" />
    </client>
    <bindings>
        <wsProfileBinding>
            <binding configurationName="helloBinding" />
        </wsProfileBinding>
    </bindings>
</system.serviceModel>

Understanding the Client Code

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();

A Tale of Two Services: Comparing Hello World #1 and Hello World #2

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.

Summary

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.