Introducing Indigo: An Early Look

 

David Chappell
Chappell & Associates

February 2005

Summary: Provides an architectural overview of "Indigo," Microsoft's unified programming model for building service-oriented applications. The paper covers Indigo's relationship to existing distributed application technologies in the .NET Framework, the basics of creating and consuming Indigo services, and an overview of Indigo's capabilities, including security, reliable messaging, and transaction support. (24 printed pages)

Note   This article is based on a prerelease version of Indigo's first Community Technology Preview (CTP). Be aware that features and implementation details are subject to change as Indigo progresses through the product cycle.

Contents

What Is Indigo?
What Indigo Provides
Creating an Indigo Service
Creating an Indigo Client
Other Aspects of Indigo
Coexistence and Migration
Conclusions
About the Author

What Is Indigo?

Choosing the best abstractions for building software is an ongoing process. Objects are the dominant approach today for building an application's business logic, but modeling application-to-application communication using objects hasn't been as successful. A better approach is to explicitly model interactions between discrete chunks of software as services. Plenty of support already exists for building object-oriented applications, but thinking of services as a fundamental software building block is a more recent idea. Because of this, technologies explicitly designed to create service-oriented applications haven't been widely available.

Microsoft's framework for building service-oriented applications, code-named Indigo, changes this. Indigo allows developers who today create object-oriented applications using the .NET Framework to also build service-oriented applications in a familiar way. And to let those applications interact effectively with software running on Windows and on other platforms, Indigo implements SOAP and other Web services technologies, allowing developers to create reliable, secure, and transactional services that can interoperate with software running on any system.

Aa480188.introindigov1-001(en-us,MSDN.10).gif

The figure above shows a simple view of an Indigo client and service. Indigo provides a foundation, implemented primarily as a set of classes running on the Common Language Runtime (CLR), for creating services that are accessed by clients. A client and service interact via SOAP, Indigo's native protocol, and so even though the figure shows both parties built on Indigo, this certainly isn't required.

Indigo is built on and extends the .NET Framework 2.0, which is scheduled for release in 2005. Indigo itself will ship as part of the Windows release code-named Longhorn, scheduled for 2006, and it will also be made available on Windows XP and Windows Server 2003. This description is based on a pre-release version of Indigo's first Community Technology Preview. Be aware that some changes are likely (almost certain, in fact) before the final version ships.

What Indigo Provides

Many people at Microsoft have devoted years of their lives to creating Indigo. This level of effort wouldn't have been necessary if the problems it solved were simple or if their solutions were obvious. Accordingly, Indigo is a substantial piece of technology. Three things stand out, however, as Indigo's most important aspects: its unification of several existing Microsoft technologies, its support for cross-vendor interoperability, and its explicit service-orientation. This section looks at each of these.

Unification of Microsoft's Distributed Computing Technologies

The initial releases of the .NET Framework included several different technologies for creating distributed applications. The figure below lists each one, along with the primary reason why a developer would typically use that technology. To build basic interoperable Web services, for example, the best choice was ASP.NET Web services, more commonly referred to as ASMX. To connect two .NET Framework-based applications, .NET Remoting was sometimes the right approach. If an application required distributed transactions and other more advanced services, its creator was likely to use Enterprise Services, the .NET Framework's successor to COM+. To exploit the latest Web services specifications, such as WS-Addressing and WS-Security, a developer could build applications that used Web Services Enhancements (WSE), Microsoft's initial implementation of these emerging specifications. And to create queued, message-based applications, a Windows-based developer would use Microsoft Message Queuing (MSMQ).

  ASMX .NET Remoting Enterprise Services WSE MSMQ Indigo
Interoperable Web Services     X             X
.NET – .NET Communication       X           X
Distributed Transactions, etc.         X         X
Support for WS-* Specifications           X       X
Queued Messaging             X     X

All of these options had value, yet the diversity was certainly confusing to developers. Why have so many choices? A better solution would be to have one technology that addresses all of these problems. With the arrival of Indigo, that technology has appeared. Rather than forcing developers to choose one of several possibilities, Indigo lets them create distributed applications that address all of the problems solved by the technologies it subsumes. While Microsoft will still support these earlier technologies, most new applications that would previously have used any of them will instead be built on Indigo.

Interoperability with Non-Microsoft Applications

Making life easier for Windows developers by unifying disparate technologies is a good thing. But with the universal agreement among vendors on Web services, the long-standing problem of application interoperability can also be solved. Because Indigo's fundamental communication mechanism is SOAP, Indigo applications can communicate with other software running in a variety of contexts. As shown in the figure below, an application built on Indigo can interact with all of the following:

  • Indigo applications running in a different process on the same Windows machine.
  • Indigo applications running on another Windows machine.
  • Applications built on other technologies, such as application servers based on Java 2, Enterprise Edition (J2EE), that support standard Web services. These applications can be running on Windows machines or on machines with other operating systems, such as Sun Solaris, IBM's z/OS, or Linux.

Aa480188.introindigov1-003(en-us,MSDN.10).gif

Indigo applications can also interoperate with applications built on some of the .NET Framework technologies that preceded Indigo, such as ASMX, as described later.

To allow more than just basic communication, Indigo implements a group of newer Web services technologies collectively referred to as the WS-* specifications. These documents define multi-vendor ways to add reliable messaging, security, transactions, and more to SOAP-based Web services. All of these specs were originally defined by Microsoft, IBM, and other vendors working together. As they become stable, ownership often passes to standards bodies such as the Organization for the Advancement of Structured Information Standards (OASIS). The Web services specs supported in Indigo's first release include WS-Addressing, WS-Policy, WS-MetadataExchange, WS-ReliableMessaging, WS-Security, WS-Trust, WS-SecureConversation, WS-Coordination, WS-AtomicTransaction, and the SOAP Message Transmission Optimization Mechanism (MTOM).

When an Indigo application communicates with an application running on a non-Windows system, the protocol used is standard SOAP (perhaps with some WS-* extensions), represented on the wire in its usual text-based XML encoding. When one Indigo-based application communicates with another Indigo-based app, however, it makes sense to optimize this communication. All of the same features are provided, including reliable messaging, security, and transactions, but the wire encoding used is an optimized binary version of SOAP. Messages still conform to the data structure of a SOAP message, referred to as its Infoset, but their encoding uses a binary representation of that Infoset rather than the standard angle-brackets-and-text format of XML.

Explicit Support for Service-Oriented Development

Thinking of an application as providing and consuming services is hardly a new idea. What is new is a clear focus on services as distinct from objects. Toward this end, Indigo's creators kept four tenets in mind during the design of this technology:

  • Share schema, not class: unlike older distributed object technologies, services interact with their clients only through a well-defined XML interface. Behaviors such as passing complete classes, methods and all, across service boundaries aren't allowed.
  • Services are autonomous: a service and its clients agree on the interface between them, but are otherwise independent. They may be written in different languages, use different runtime environments, such as the CLR and the Java Virtual Machine, execute on different operating systems, and differ in other ways.
  • Boundaries are explicit: a goal of distributed object technologies such as Distributed COM (DCOM) was to make remote objects look as much as possible like local objects. While this approach simplified development in some ways by providing a common programming model, it also hid the inescapable differences between local objects and remote objects. Services avoid this problem by making interactions between services and their clients more explicit. Hiding distribution is not a goal.
  • Use policy-based compatibility: when possible, determining which options to use between systems should rely on WS-Policy-based mechanisms.

Service-orientation is a broad area, encompassing service-oriented applications and the more general concepts of service-oriented architecture (SOA). Indigo will be the foundation for service-oriented applications built on Windows, and so it will be fundamental to the SOA efforts of many organizations.

Creating an Indigo Service

As the figure below shows, every Indigo service is constructed from three things:

  • A service class, implemented in C# or VB.NET or another CLR-based language, that implements one or more methods;
  • A host environment—an application domain and process—in which the service runs;
  • One or more endpoints that allow clients to access the service.

Aa480188.introindigov1-004(en-us,MSDN.10).gif

All communication with an Indigo service happens via the service's endpoints. Each endpoint specifies a contract that identifies which methods are accessible via this endpoint, a binding that determines how a client can communicate with this endpoint, and an address that indicates where this endpoint can be found.

Understanding Indigo requires grasping all of these concepts. This section describes each one, beginning with service classes.

Creating a Service Class

An Indigo service class is a class like any other, but it has a few additions. These additions allow the class's creator to define one or more contracts that this class implements. Each Indigo service class implements at least one service contract, which defines the operations this service exposes. A service class might also explicitly implement a data contract, which defines the data those operations convey. This section looks at both, beginning with service contracts.

Defining Service Contracts

Every Indigo service class implements methods for its clients to use. The creator of a service class determines which of its methods are exposed as client-callable operations by including them in a service contract. Defining service contracts—in fact, working explicitly with services in general—is largely a new idea for the .NET world. Indigo's creators needed to find a way to graft this idea on top of the CLR and the programming languages built on it. Fortunately, the CLR's creators anticipated the need for extensions like this, and so they provided support for attributes. As seen by a developer, attributes are character strings, perhaps with associated properties, that can appear before a class definition, a method definition, and in other places. Wherever an attribute appears, it changes some aspect of the behavior of the thing it's associated with.

The .NET Framework has used attributes for various things since its initial release. For example, to mark a method as a SOAP-callable web service in the Framework's ASMX technology, that method is preceded by the WebMethod attribute. Similarly, Enterprise Services uses the Transaction attribute to indicate that a method requires a transaction. Indigo applies this idea to services, defining a range of attributes to define and control services.

The most fundamental attribute in Indigo is ServiceContract. In fact, an Indigo service class is just a class that is either itself marked with the ServiceContract attribute or that implements an interface marked with this attribute. Here's a simple C# example that uses the first approach:

using System.ServiceModel;

[ServiceContract]
class Calculator
{
 [OperationContract]
 private int Add(int a, int b)
 {
   return a + b; 
 }
   
 [OperationContract]
 public int Subtract(int a, int b)
 {
   return a - b;
 }
   
 public int Multiply(int a, int b)
 {
    return a * b;
 }
} 

The ServiceContract attribute and all of the other attributes that Indigo uses are defined in the System.ServiceModel namespace, and so this example begins with a using statement that references this namespace. Each method in a service class that can be invoked by a client must be marked with another attribute named OperationContract. All of the methods in a service class that are preceded by the OperationContract attribute are automatically exposed by Indigo as SOAP-callable operations. In this example, Add and Subtract are both marked with this attribute, and so both are exposed to clients of this service. Any methods in a service class that aren't marked with OperationContract, such as Multiply in the example above, aren't included in the service contract, and so can't be called by clients of this Indigo service.

Two quite separate abstractions, services and objects, come together in Indigo. It's important to understand that both rely on contracts, either explicitly or implicitly, to define what they expose to the outside world. An object, specified by some class, effectively defines a contract that determines which of its methods can be invoked by another object in the same application. Access to these methods is controlled by language keywords such as public and private. In the class Calculator shown above, for example, other objects in the same application can call Subtract and Multiply, this class's two public methods. The object ** contract this class exposes contains only these two methods.

Using Indigo's attributes, Calculator also defines a service contract, as just described. This contract also has two methods, but they aren't the same as those in its object contract. Whether a method can be invoked by a client of this Indigo service is controlled by the OperationContract attribute, not the public and private keywords. Because this attribute appears only on Add and Subtract, only these two methods can be called by clients. The object contract and service contract are completely distinct from one another, which is why a method such as Add can be private while still carrying the OperationContract attribute.

The example just shown illustrates the simplest way to create an Indigo service class: marking a class directly with ServiceContract. When this is done, the class's service contract is implicitly defined to consist of all methods in that class that are marked with OperationContract. It's also possible (and probably better in most cases) to specify service contracts explicitly using a language's interface type. With this approach, the Calculator class might look like this:

using System.ServiceModel;

[ServiceContract]
interface ICalculator
{
 [OperationContract]
 int Add(int a, int b);

 [OperationContract]
 int Subtract(int a, int b);
}

class Calculator : ICalculator
{
 public int Add(int a, int b) // private methods aren't 
 {                            // allowed in interfaces
   return a + b; 
 }
      
 public int Subtract(int a, int b)
 {
   return a - b;
 }
      
 public int Multiply(int a, int b)
 {
   return a * b;
 }
}

In this example, the ServiceContract and OperationContract attributes are assigned to the ICalculator interface and the methods it contains rather than to the Calculator class itself. The result is the same, however, and so this version of the service exposes the same service contract as the previous one. Using explicit interfaces like this is slightly more complicated, but it also allows more flexibility. For example, a class can implement more than one interface, which means that it can also implement more than one service contract. By exposing multiple endpoints, each with a different service contract, a class can present different groups of services to different clients.

One final point: marking a service class with ServiceContract and OperationContract also allows automatically generating service contract definitions in the Web Services Description Language (WSDL). Accordingly, the externally visible definition of every Indigo service contract can be accessed as a standard WSDL document specifying the operations in that contract. And although it's not described here, it's also possible to create an Indigo service class directly from a WSDL document, an approach that's especially useful for implementing externally defined WSDL interfaces.

Defining Data Contracts

An Indigo service class specifies a service contract that defines which of its methods are exposed to service clients. Each of those operations will typically convey some data, which means that a service contract also implies some kind of data contract describing the information that will be exchanged. In some cases, this data contract is defined implicitly as part of the service contract. For example, in the Calculator classes shown above, each method takes two input parameters, both integers, and returns a single integer. These parameters define all of the data exchanged by this service, and so they comprise the service's data contract. For services like this one, where every operation uses only simple types, it makes sense to define the data aspects of its contract implicitly within the service contract. There's no need for anything else.

But services can also have parameters of more complex types, such as structures. In cases like this, an explicit data contract is required. Data contracts define how in-memory types are converted to a form suitable for transmission across the wire, a process known as serialization. In effect, data contracts are a mechanism for controlling how data is serialized.

In an Indigo service class, a data contract is defined using the DataContract attribute. A class, structure, or other type marked with DataContract can have one or more of its members preceded by the DataMember attribute, indicating that this member should be included in a serialized value of this type. Here's a simple example:

[DataContract]
struct Customer {
 [DataMember] public string Name; 
 int public age;
 [DataMember] private int CreditRating;
}

When an instance of this Customer type is passed as a parameter in a method marked with OperationContract, only the fields marked with the DataMember attribute—Name and CreditRating—will be passed.

Whether a field is labeled as public or private has no effect on whether that field is serialized. Just as with methods, the public and private keywords are part of the contract defining how this type can be accessed by other objects in the same application. DataMember, like OperationContract, defines how the type can be accessed by clients of the service this class implements. Once again, the two are completely distinct.

One final point worth emphasizing about Indigo contracts is that nothing becomes part of either a service contract or a data contract by default. Instead, a developer must explicitly use the ServiceContract and DataContract attributes to indicate which types have Indigo-defined contracts, then explicitly specify which parts of those types are exposed to clients of this service using the OperationContract and DataMember attributes. One of the tenets of its designers was that services should have explicit boundaries, and so Indigo is an opt-in technology. Everything a service makes available to its clients is expressly specified in the code.

Contracts and the attributes that define them are a major aspect of Indigo, and this short description covers only the highlights. The OperationContract attribute can be used to define one-way operations, for example, where a call to a service has no reply. It's also possible to define interactions where both sides can act as client and service, each invoking operations and exposing operations that the other invokes, by creating what are called duplex contracts. The DataContract attribute has several more options as well, and it's even possible to work directly with SOAP messages natively using an attribute called MessageContract. Contracts are used to express much of what Indigo provides, and so they're one of its most fundamental concepts.

Selecting a Host

A class implementing an Indigo service is typically compiled into a library. By definition, all libraries need a host application domain and Windows process to run in. Indigo provides two options for hosting libraries that implement services. One is to use a host app domain and process provided by the Windows Activation Service (WAS), while the other allows a service to be hosted in any app domain running in an arbitrary process. This section describes both, beginning with WAS.

Hosting a Service Using the Windows Activation Service

The simplest way to host an Indigo service is to rely on WAS. (Note that WAS isn't supported in Indigo's first Community Technology Preview. Instead, Indigo services can be hosted in Internet Information Server on Windows Server 2003 and Windows XP, although only SOAP over HTTP is supported in this configuration.) Using WAS is much like using the hosting mechanism provided by IIS for ASMX. Among other things, both rely on the notion of a virtual directory, which is just a shorter alias for an actual directory path in the Windows file system.

To see how WAS hosting works, suppose either of the Calculator classes shown earlier was compiled into a library called calc.dll, then placed in the virtual directory calculator on a system running Windows Server 2003. To indicate that the Indigo service implemented in calc.dll should be hosted by WAS, a developer creates a file in the calculator virtual directory with the extension .svc (which stands, of course, for "service"). For our simple example, this file might be called calc.svc, and its entire contents could be:

%@Service language=c# class="Calculator" %

Once this has been done and an endpoint has been defined as shown in the next section, a request from a client to one of the Calculator service's methods will automatically create an instance of this class to execute the specified operation. That instance will run in an application domain created within the standard process that WAS provides.

Hosting a Service in an Arbitrary Process

Relying on WAS to provide a process for hosting an Indigo service is certainly the simplest choice. Yet applications often need to expose services from their own process rather than relying on one provided by Windows. Fortunately, this isn't hard to do. The following example shows how to create a process that hosts either of the Calculator classes defined earlier:

using System.ServiceModel;

public class CalculatorHost
{
  public static void Main()
  {
    ServiceHost<Calculator> s1 = 
      new ServiceHost<Calculator>();
    s1.Open();
    Console.Writeline("Press ENTER to end service");
    Console.Readline();    
  }
}

Since the class CalculatorHost includes a Main method, it will run as a distinct process. To host the example Calculator service, this method must create a new instance of the class ServiceHost<T>, passing in the Calculator class. (Note that this standard Indigo class is a generic, indicated by the < and > that enclose its parameter. Generics are a new language feature in version 2.0 of C#, Visual Basic .NET, and other languages based on version 2.0 of the .NET Framework.) Once an instance of this class is created, the only thing required to make the service available is to call the Open method on that instance. Indigo will now automatically direct requests from clients to the appropriate methods in the Calculator class.

To allow an Indigo service to process requests from its clients, the process that hosts it must remain running. This isn't an issue with WAS-hosted services, since the standard process WAS provides ensures this. A hosting application must solve this problem on its own, however. In this simple example, the process is kept running through the straightforward mechanism of waiting for input from a console user.

Defining Endpoints

Along with defining operations in an Indigo service class and specifying a host process to run those operations, an Indigo service must also expose one or more endpoints. Every endpoint specifies the following three things:

  • A contract name indicating which service contract this Indigo service class exposes via this endpoint. A class marked with ServiceContract that implements no explicit interfaces, such as Calculator in the first example shown earlier, can expose only one service contract. In this case, all of its endpoints will expose the same contract. If a class explicitly implements two or more interfaces marked with ServiceContract, however, different endpoints can expose different contracts.
  • An address indicating where this endpoint can be found. Addresses are URLs that identify a machine and a particular endpoint on that machine.
  • A binding determining how this endpoint can be accessed. The binding determines what protocol combination can be used to access this endpoint along with other things, such as whether the communication is reliable and what security mechanisms can be used. Suppose, for instance, that a service's creator wishes to allow clients to access that service using either SOAP over HTTP or SOAP over TCP. Each of these is a distinct binding, and so the service would need to expose two endpoints, one with a SOAP-over-HTTP binding and the other with a SOAP-over-TCP binding.

Bindings are a critical part of how communication is accomplished. To make them easier to use, Indigo includes a set of predefined bindings, each of which specifies a particular group of options. This set includes:

  • BasicProfileHttpBinding: conforms to the Web services Interoperability Organization (WS-I) Basic Profile 1.0, which specifies SOAP over HTTP. This is an endpoint's default binding if none is explicitly specified.
  • BasicProfileHttpsBinding: conforms to the WS-I Basic Security Profile 1.0, which specifies SOAP over HTTPS.
  • WsHttpBinding: supports reliable message transfer with WS-ReliableMessaging, security with WS-Security, and transactions with WS-AtomicTransaction. This binding allows interoperability with other Web services implementations that also support these specifications.
  • WsDualHttpBinding: like WsHttpBinding, but also supports interaction using duplex contracts. Using this binding, both services and clients can receive and send messages.
  • NetTcpBinding: sends binary-encoded SOAP, including support for reliable message transfer, security, and transactions, directly over TCP. This binding can only be used for Indigo-to-Indigo communication.
  • NetNamedPipeBinding: sends binary-encoded SOAP over named pipes. This binding is only usable for Indigo-to-Indigo communication between processes on the same Windows machine.
  • NetMsmqBinding: sends binary-encoded SOAP over MSMQ, as described later. This binding can only be used for Indigo-to-Indigo communication.

Aa480188.introindigov1-005(en-us,MSDN.10).gif

The figure above shows example values for each of the three elements in an endpoint for the first Calculator service shown earlier. The name of the service's contract is Calculator, which is the name of the class that implements this service, and the binding is BasicProfileHttpBinding. Assuming this service is hosted using WAS, installed in the virtual directory calculator as described earlier, and running on a machine named qwickbank.com, its address might be http://www.qwickbank.com/calculator/calc.svc.

Unlike contracts, endpoints aren't defined using attributes. While it is possible to create endpoints programmatically, the most common approach will probably be to use a configuration file associated with the service. WAS-hosted services use the web.config file, while those hosted independently using the configuration file associated with the application they're running in (commonly referred to as app.config, although the actual filename varies). If used solely for the first Calculator service class shown earlier, this configuration file might look like this:

<configuration>
  <system.serviceModel>
    <services>
      <service serviceType="Calculator">
           <endpoint 
          contractType="Calculator"
          bindingType="basicProfileHttpBinding />
      </service>
    </services>
  </system.serviceModel>
</configuration>

The configuration information for all services an Indigo application implements is contained within the system.serviceModel element. This element contains a services element that can contain one or more service elements. This simple example has only a single service, so there's just one occurrence of service. The serviceType attribute of the service element identifies the service class that implements the service this configuration applies to, which in this case is Calculator. Each service element can contain one or more endpoint elements, each of which specifies a particular endpoint through which this Indigo service can be accessed. In this example, the service exposes only a single endpoint, and so only one endpoint element appears. The name of the endpoint's contract is Calculator, which is the name of the class that implements it. If this configuration file were for the second Calculator service class shown earlier, which defined its service contract using an explicit interface, the value of the serviceType attribute would remain the same, but the value of contractType would instead be ICalculator, the name of this explicit interface. The binding specified here is basicProfileHttpBinding, although since this is the default, it could have been omitted. And assuming Calculator is a WAS-hosted service, an address is created automatically, so there's no need to specify one in this config file.

Creating an Indigo Client

Creating a basic Indigo service isn't especially complicated. Creating an Indigo client is even simpler. All that's required is to create a local stand-in for the service, called a proxy, that's connected to a particular endpoint on the target service, then invoke the service's operations via this proxy. The figure below shows how this looks.

Aa480188.introindigov1-006(en-us,MSDN.10).gif

Creating a proxy requires knowing exactly what contract is exposed by the target endpoint, then using this contract's definition to generate the proxy. In Indigo, this process is performed by a tool called svcutil. If the service is implemented using Indigo, svcutil can access the service's DLL to learn about the contract and generate a proxy. If only the service's WSDL definition is available, svcutil can read this to produce a proxy. If only the service itself is available, svcutil can access it directly using either WS-MetadataExchange or a simple HTTP GET to acquire the service's WSDL interface definition, then generate the proxy.

However it's generated, the client can create a new instance of the proxy, then invoke the service's methods using it. Here's a simple example of a client for the Calculator class:

using System.ServiceModel;
using Indigo.Example; // namespace for generated proxy class

public class CalculatorClient
{
  public static void Main()
  {
    CalculatorProxy p = new CalculatorProxy();
    Console.WriteLine("7 + 2 = {0}", p.Add(7, 2));
    Console.WriteLine("7 - 2 = {0}", p.Subtract(7, 2));
    p.Close();
  }
} 

One more thing remains to be specified by the client: the exact endpoint it wishes to invoke operations on. Like a service, the client must specify the endpoint's contract, its binding, and its address, and this is typically done in a config file. In fact, if enough information is available, svcutil will automatically generate an appropriate client configuration file for the target service.

Other Aspects of Indigo

The basics of services and clients are fundamental to every Indigo application. Yet most of those applications will also use other aspects of this technology. This section takes a look at some of the additional features that Indigo provides for applications built on it.

Controlling Local Behavior

Many aspects of Indigo, such as contracts, bindings, and more, are related to communication between a service and its clients. Yet there are also parts of a service's behavior that are essentially local. How is a service instance's lifetime controlled, for example, and how is concurrent access to that instance managed? To allow developers to control behaviors like these, Indigo defines two primary attributes, both of which have a number of properties. One of these attributes, ServiceBehavior, can be applied to classes that are also marked with the ServiceContract attribute. The other, OperationBehavior, can be applied to methods in a service class that are also marked with the OperationContract attribute.

The ServiceBehavior attribute has various properties that affect the behavior of the service as a whole. For example, a property called ConcurrencyMode can be used to control concurrent access to the service. If set to Single, Indigo will deliver only one client request at a time to this service, i.e., the service will be single-threaded. If this property is set to Multiple, Indigo will deliver more than one client request at a time to the service, each running on a different thread. Similarly, ServiceBehavior's InstanceMode property can be used to control how instances of a service are created and destroyed. If InstanceMode is set to PerCall, a new instance of the service will be created to handle each client request, then destroyed when the request is completed. If it's set to PrivateSession, however, the same instance of the service will be used to handle all requests from a particular client.

Suppose, for example, that its creator decided that the Calculator class should be multi-threaded and use the same instance for each call from a particular client. The class's definition would then look like this:

using System.ServiceModel;

[ServiceContract] 
[ServiceBehavior(
  ConcurrencyMode=Multiple, 
  InstanceMode=PrivateSession)]
class Calculator { ... } 

Similarly, properties on the OperationBehavior attribute allow controlling the impersonation behavior of the method that implements this operation, its transactional requirements (described later), and other things.

Messaging Options

The simple examples shown in this article assume a synchronous remote procedure call (RPC) approach to client/service interaction. Indigo supports this option, but it's not the only choice. SOAP is a message-oriented protocol, which means that it can support a variety of programming models. In fact, Indigo supports several possibilities, including the following:

  • Traditional RPC, with blocking calls carrying lists of typed parameters;
  • Asynchronous RPC, with non-blocking calls carrying lists of typed parameters;
  • Traditional messaging, with non-blocking calls carrying a single message parameter;
  • Message-based RPC, with blocking calls carrying a single message parameter.

And even though the great majority of distributed applications require it, the SOAP specification says nothing about reliability. One common way to ensure reliability is to use SOAP only in point-to-point scenarios, relying on TCP to guarantee delivery of requests and responses. This is sufficient in some cases, and it's what's done when the BasicProfileHttpBinding is used.

Yet there are plenty of cases for which this isn't enough. What if a service is accessed through multiple SOAP intermediaries, for example? The reliability guarantees provided by TCP aren't enough in this case to ensure end-to-end reliability. To address this problem, Indigo implements the WS-ReliableMessaging specification. By choosing a binding such as WsHttpBinding, which uses WS-ReliableMessaging, a service and its client can guarantee reliable end-to-end communication even through multiple SOAP intermediaries.

Security

Exposing services on a network, even an internal network, usually requires some kind of security. How can the service be certain of its client's identity? How can messages sent to and from a service be kept safe from malicious changes and prying eyes? And how can access to a service be limited to only those authorized to use it? Without some solution to these problems, it's too dangerous to expose many kinds of services. Yet building secure applications can get complicated. Ideally, there should be straightforward ways to address common security scenarios, along with more fine-grained control for applications that need it.

To achieve this, Indigo provides the core security functions of authentication, message integrity, message confidentiality, and authorization. Indigo's approach to the first three of these relies primarily on bindings, and a developer's choices include the following:

  • Choose a standard binding that supports security. Applications that need only transport-based security, for example, can use a binding such as BasicProfileHttpsBinding. This approach is sufficient for requests that go directly from a client to a service without traversing any intermediaries, such as HTTP proxies or other SOAP nodes. Applications that require end-to-end security for messages that go through multiple SOAP intermediaries can instead use a binding that supports WS-Security, such as WsHttpBinding.
  • Choose a standard binding that supports security, then customize it by changing one or more of its default values. For example, the authentication mechanism used by a binding such as WsHttpBinding can be changed if desired.
  • Create a custom binding that provides exactly the security features a developer needs. Doing this isn't for the faint of heart, but it can be the right solution for some advanced scenarios.
  • Choose a standard binding that provides no support for security, such as BasicProfileHttpBinding. While using no security is generally a risky thing to do, it can still be the best option in some situations.

An Indigo service can also control which clients are authorized to use its services. For the most part, Indigo just supports existing authorization mechanisms in the .NET Framework. A service can use the standard PrincipalPermission attribute, for example, to define who is allowed to access it.

Letting developers build secure applications without exposing them to overwhelming complexity has proven to be challenging in the past. By providing both a straightforward approach for the most common cases and fine-grained control for more complex situations, Indigo aims at hitting this target in a usable and effective way.

Transactions

Handling transactions is an important aspect of building many kinds of business logic. Yet using transactions in a service-oriented world can be problematic. Distributed transactions assume a high level of trust among the participants, and so it often isn't appropriate for a transaction to span a service boundary. Still, there are situations where combining transactions and services makes good sense, and so Indigo includes support for this important aspect of application design.

Transactions in the .NET Framework 2.0

The transaction support in Indigo builds on enhancements provided in version 2.0 of the .NET Framework. This forthcoming release includes System.Transactions, a new namespace focused solely on controlling transactional behaviors. Developers will most often use the services of System.Transactions in concert with an execution context, a construct that's new in version 2.0 of the .NET Framework. An execution context allows specifying common information, such as a transaction, that applies to all code contained within a defined scope. Here's an example of how an application can use this approach to group a set of operations into a single transaction:

using System.Transactions;

using (TransactionScope ts 
   = new TransactionScope(TransactionScopeOption.Required)) {
   // Do work, e.g., update different DBMSs
   ts.Complete();
}

All of the operations within the using block will become part of a single transaction, since they share the transactional execution context it defines. The last line in this example, calling the TransactionScope's Complete method, will result in a request to commit the transaction when the block is exited. This approach also provides built-in error handling, aborting the transaction if an exception is raised.

Specifying Required for the new TransactionScope, as this example does, means that this code will always run as part of a transaction: joining its caller's transaction if one exists, creating a new one if it doesn't. As in Enterprise Services, other options can also be specified, including RequiresNew, Supported, and NotSupported.

Unlike Enterprise Services and its predecessors MTS and COM+, Systems.Transactions is focused entirely on controlling transactional behavior. There's no required connection between a transaction and the internal state of an object, for example. While Enterprise Services requires an object to be deactivated when it ends a transaction, Systems.Transactions makes no such demand. Since Indigo builds on Systems.Transaction, Indigo applications are also free to manage transactions and object state independently.

Transactions in Indigo

Indigo applications can use System.Transactions explicitly, or they can control transactions using attributes that rely on System.Transactions under the covers. One option is for a method inside a class marked with the ServiceContract attribute to wrap its work in a transaction using TransactionScope, as just described. For example, this method might include a using statement that establishes a transaction scope, then update two independent databases within that transaction.

A service's methods can also control transactional behavior using an attribute. Rather than explicitly using System.Transactions, a service can use the OperationBehavior attribute described earlier. Here's an example:

using System.ServiceModel;

[ServiceContract]
class XactOperations
{
 [OperationContract]
    public int Add(int value1, int value2)
    {
       return value1 + value2;
    }

 [OperationContract] 
 [OperationBehavior(RequireTransaction=true,
                    AutoCompleteTransaction=true)]
 int Update(int value1, int value2)
 {
       // Insert value1 and value2 into
    // two different databases
 }
}

The first method in this example, Add, doesn't use a transaction, and so its simple operation will happen just as before. But the second method, Update, is preceded by the OperationBehavior attribute with the RequireTransaction property set to true. Because of this, all work done within this method will happen inside a transaction, just as if it were inside the transaction scope of the using block shown earlier. And because the AutoCompleteTransaction property is also specified, the transaction will automatically vote to commit if no exception is raised.

If the client invoking this method is not running inside a transaction, the Update method will run in its own transaction—there's no other choice. But suppose the client is already part of an existing transaction when it calls Update. Will the work done by the Update method join the client's transaction, or will it still run inside its own independent transaction? The answer depends on whether this service can accept a transaction context passed by the client, an option controlled using the TransactionFlowAllowed property of the OperationContract attribute. If TransactionFlowAllowed is not attached to a method in the service, as in the example shown above, work done within this method can never join an existing transaction. If this attribute is present, however, a method is able to join the transaction of its client.

It's also worth emphasizing that applications built on Indigo can participate in transactions that include applications running on non-Indigo platforms. For example, an Indigo application might start a transaction, update a record in a local SQL Server database, then invoke a web service implemented on a J2EE application server that updates a record in another database. If this service is transactional and the platform it's running on supports the WS-AtomicTransaction specification, both database updates can be part of the same transaction. Like security and reliable messaging, Indigo transactions work in the heterogeneous world that Web services make possible.

Queuing

Using a binding such as WsHttpBinding, an Indigo application can communicate reliably with another application built on Indigo or any other Web services platform that implements WS-ReliableMessaging. But while the technology this specification defines guarantees reliable end-to-end delivery of a SOAP message, it doesn't address message queuing. With queuing, an application sends a message to a queue rather than directly to another application. When the receiving application is ready, it can read the message from the queue and process it. Allowing this kind of interaction is useful, for example, when the sender of a message and its receiver might not be running at the same time.

Accordingly, Indigo provides support for message queuing. This support is built on top of MSMQ, which means that unlike most other aspects of Indigo, such as reliable messaging, security, and transactions, Indigo queuing doesn't interoperate directly across vendor boundaries (although an MSMQ-MQSeries bridge is available).

To use Indigo queuing, a developer creates a standard Indigo service class, marked as usual with ServiceContract. The operations in this class's service contract have some limitations, however. In particular, they must all be marked as one-way, which means that no response is returned. This isn't surprising, since invoking a queued operation sends a message into a queue rather than to its ultimate receiver, and so waiting for an immediate response wouldn't make much sense. And like any other service class, queued Indigo applications expose endpoints. These endpoints use bindings such as NetMsmqBinding, which allows communication with other queued Indigo applications, or MsmqIntegrationBinding, which allows a queued Indigo application to interoperate with a standard MSMQ application that doesn't use Indigo. Indigo queuing also supports other traditional features of queued environments, such as dead letter queues and handling of poison messages.

Queuing is the right approach for a significant set of distributed applications. Indigo's support for this communication style allows developers to build queued applications without learning an entirely separate queuing technology.

Coexistence and Migration

Indigo represents a modern approach to creating distributed applications in the era of reliable, secure, and transactional services. A key point to understand, however, is that installing Indigo will not break any existing applications. Current code running on ASMX, .NET Remoting, and the other technologies whose functionality is subsumed by Indigo will continue to run, and so there's no requirement to move to Indigo. But for organizations with investments in current Microsoft technologies, an obvious question remains: what happens to existing code written using the technologies that preceded Indigo?

For each of the current technologies whose future is deeply affected by the advent of Indigo, developers need to understand two things: whether applications built on this technology will interoperate with applications built on Indigo, and how much work it will be to port applications from this technology to the Indigo environment. Here's a short description of how each technology addresses these issues:

  • ASP.NET Web services (ASMX): Web services built with ASMX will interoperate with Indigo applications. Since ASP.NET Web services and Indigo both support standard SOAP, this shouldn't be surprising. Moving existing ASP.NET Web services code to Indigo will require some mechanical work, but should still be straightforward. The basic structure of the two technologies is quite similar, so for the most part only attributes and configuration files will need to be changed. More advanced features, however, such as SOAP Extensions, will not be directly portable to Indigo. Instead, they'll need to be re-written using the extensibility options that Indigo provides.
  • .NET Remoting: applications built on .NET Remoting will not interoperate with applications built on Indigo—their wire protocols aren't compatible. Moving existing .NET Remoting code to Indigo will require some effort, but it will be possible. Anyone who's built custom .NET Remoting extensions, however, such as channels and sinks, will find that this code won't port to the new world. Similar extensions are possible in Indigo, but the interfaces for doing it don't match those in .NET Remoting.
  • Enterprise Services: to allow an existing Enterprise Services application to interoperate with Indigo clients (or other web services-based software), a developer can identify exactly which interfaces in that application should be exposed. Using an Indigo-supplied tool, service contracts can then be automatically created for those interfaces and exposed via Indigo. For existing clients of Enterprise Services applications that aren't built on the .NET Framework (and for other purely COM-based clients), an Indigo moniker is provided to allow straightforward access to web services. The effort required to port existing Enterprise Services applications to run directly on Indigo will be similar to what's required to port ASMX applications. Much of the work, although not all of it, will be straightforward mechanical changes to attributes and namespaces.
  • Web services Enhancements (WSE): WSE is Microsoft's tactical solution for implementing Web services applications that require some or all of the functions provided by the WS-* specs. Applications built using WSE 1.0 and WSE 2.0 won't interoperate with applications built on Indigo. Applications built on WSE 3.0, which will be shipped before Indigo is released, will interoperate with Indigo applications, however. For portability, the story is similar to the technologies already described: some amount of effort will be required to move existing code from WSE to Indigo, although this effort will be minimized for applications that use the final WSE version.
  • MSMQ: because Indigo's queuing functions are built on MSMQ, queued applications built on Indigo can interoperate with queued applications built directly on MSMQ. Porting applications from the System.Messaging namespace provided with the original .NET Framework will require some work, since this earlier interface is different from what Indigo provides. Once Indigo ships, developers should use it rather than System.Messaging to create most MSMQ-based queuing applications.

Before Indigo is available, the best technology choice for distributed .NET applications that don't need queuing is probably ASMX. It's simple to use, and it also provides the smoothest migration path to Indigo. Enterprise Services can also make sense for applications that need the things it provides, such as distributed transactions, while MSMQ remains the right choice for queued applications. .NET Remoting, however, should be used primarily for communication between two application domains in the same process. ASMX is a better choice for direct application-to-application communication in most other cases.

Introducing new software always has an impact on what already exists. By providing a common foundation for building service-oriented applications, Indigo offers a simpler, more consistent platform for developers. While this change incurs some pain, the goal of Indigo's creators is to make the transition as smooth and as simple as possible.

Conclusions

Indigo represents an important evolution in how developers create software. As service-oriented applications become the norm, Indigo will be a mainstream technology for Windows software developers. Other Microsoft products will also change to take advantage of what Indigo offers. BizTalk Server, for example, will add support for Indigo as a communication option sometime following the release of BizTalk Server 2006. Because Indigo provides a standard foundation for service-oriented software, it will be the basis for a large fraction of Windows communication.

The impact of this technology will not be small. Anyone who builds distributed applications on Windows, especially applications that must interoperate with those on other platforms, should pay close attention. Indigo will significantly change their world.

 

About the Author

David Chappell is Principal of Chappell & Associates (www.davidchappell.com) in San Francisco, California. This article is drawn from his forthcoming book on Indigo, to be published by Addison-Wesley.