Ryan Dawson on Longhorn

The software we think, but do not write

Indigo brightens Wallstreet

*Indigo brightens Wallstreet

 

More like:

*Indigo build 4051 = Indiguano

 

 

Source Code can be found here

 

 

This article focuses on Indigo holistically, such that by the time beta rolls around, everyone will be ready for the next generation of communication.  That is not to say that it is not built around today’s communication architecture and the WS specs.  We will cover everything from the simple set-up of a service and a client to different types of messaging, and then dive into features like security and reliability.

 

 

Let me be honest: If you would like to learn Indigo, I will teach you in this article, but do not rely on the code.  I am only providing the code for educational benefit, and the actual program was not finished because of technical difficulties (more on this later).  As above, indiguano is not ready for human or machine consumption.  If you would like to start developing for Indigo, make sure you read the bottom of this article and the difficulties I faced so you don’t have to rip your hair out, too.  I will explain how the program works, in case anyone would like to attempt a similar effort.  But, for now, let us study Indigo.  If you feel compelled, go ahead and skip the basics and jump right into the more advanced topics near the middle.

 

 

 

Indigo Basics

 

What do I need to get a service running?

 

1)      Add a file named App.config (non-case sensitive) to your project

 

2)      This file, upon compilation will be turned into <Your Applications name>.exe.config, so in case you are not using VS, make sure it has this name and is in the same folder as the executable.  The name of the file will change automatically if you are using VS (I am not familiar with the internals of how this happens, so if you are using only MSBuild, make sure you check the specification and see if the name is automatically changed for you).  Important note: if your service is a dll, it does not matter; the config file must be in the same folder as the executable (more on this later).

 

3)      Inside the App.config, we need to add some configuration detail.  Don’t be fooled with this new programming model, *most* things for Indigo are configured in the App.config, so it is important that you understand it.  That is not to say that the same things can be written in code.  In case you were wondering, there are a couple benefits of using a configuration file: the most important, the application does not need to be recompiled upon configuration changes.  That means that you can update the config file as many times as you want and the application will reflect these changes (given that the app is restarted).  In the area of communications and the internet, it always seems like services are being deployed, moved, and re-deployed, so it is essential to be able to change things like the address.  Let’s look at the bare minimum:

 

<configuration>

    <system.messagebus>

        <serviceEnvironments>

            <serviceEnvironment name="main">

        </serviceEnvironments>

    </system.messagebus>

</configuration>

 

 

To be honest, I don’t even know if this will work, it may throw an exception when loaded, but that is beside the point.  The point is that each configuration file will look exactly like this.  The only difference is that you are able to have multiple serviceEnvironments.  The default service environment for Indigo is “main”.  Further, the idea of the service environment is key to Indigo.  It is the starting point for everything.  You need a service environment to host a service.

 

From my experience, I was not able to load a service environment if it was not named “main”, and this very well could be a bug.  So, be wary in following the samples, as some of them have service environments listed with different names.  By the very nature of this bug, you are also not able to have multiple service environments within one file.  Like I said, these are bugs and will be fixed.  But, the point of this article is to introduce Indigo to the degree in which you are ready to code when beta drops.

 

More…

 

<configuration>

    <system.messagebus>

        <serviceEnvironments>

            <serviceEnvironment name="main">

                <port>

                  <identityRole>soap.tcp://localhost:46001/NYSE/</identityRole>

                </port>

            </serviceEnvironment>

        </serviceEnvironments>

    </system.messagebus>

</configuration>

 

The property that should be completed for every service is a URI to its location.  The same if true for a client.  The WS specs refer to these URIs as EndpointReferences, so keep in mind that identityRole is basically the same.  You must have noticed that identityRole is the child of port.  A port is key to Indigo, it is the main orifice to the world; it is the dot on the map that people see when they want to communicate.  The semantics are not exactly the same as when we talk about traditional ports, as in port 80 for HTTP (this should be evident since the identityRole is a complete URI and only a small portion is the location port 46001).  The parts of the URI are as follows.

 

soap.tcp – communication protocol.  Other examples:

a)      ms.soap.xproc

b)      http

c)      soap

 

localhost:46001 – physical address; could be Microsoft.com:3201

 

NYSE – the name of the service.  This is a unique identifier for the service environment, or anything running at the same address.

 

<configuration>

    <system.messagebus>

        <serviceEnvironments>

            <serviceEnvironment name="main">

                <port>

                    <identityRole>soap.tcp://localhost:46001/NYSE/</identityRole>

                </port>

                <remove name="securityManager" />

                <policyManager>

                    <areUntrustedPolicyAttachmentsAccepted>true</areUntrustedPolicyAttachmentsAccepted>

                    <isPolicyReturned>true</isPolicyReturned>

                </policyManager>

                <serviceManager>

                    <activatableServices>

<add type="Conviat.NYSE.NYSEDatabase, NYSE" />

                    </activatableServices>

                </serviceManager>

            </serviceEnvironment>

        </serviceEnvironments>

    </system.messagebus>

</configuration>

 

Adding on quite a bit, I am going to get us to the point of what I would consider a full config file for a basic service.  If this were only a client, and did not have any services, then the whole serviceManager section would be removed.

 

The way that I think the config file loads internally goes like this: for sections that you do not have within your service environment, the Indigo runtime will add those settings from the Machine.config file located in the <.Net Runtime version folder>\Config\Machine.config.  For sections that you have explicitly removed like <remove name="securityManager" /> it will not add in at runtime.  Since we don’t care much for security at the moment, we will completely remove it.

 

As you may recall, one of the core tenants to Indigo is that policy determines version and type.  But, the default of Indigo is to not accept policy statements that are not signed.  This presents a problem, since I really don’t want to worry about signing for a demo.  In production, I would imagine the policyManager to have some interesting properties.

 

The serviceManager is core to hosting a service.  It enumerates the services that the environment will listen on for requests when the environment starts up.  The tag is pretty simple to code, especially if you are familiar with Assemblies: <add type="Conviat.NYSE.NYSEDatabase, NYSE" />.  The string before the comma is the fully qualified name of the class that is configured to be the service, in assembly terms.  After the comma is the Executable or Dll name that the class resides in.

 

 

4)      The next step would be to instantiate the service environment from code.  You must keep in mind that in order for this to work, we must have a real service class registered in the config file, but we will get to that later.

 

            [STAThread]

            static void Main(string[] args)

            {

                  try

                  {

                        // Load from the config file

                        ServiceEnvironment environment = ServiceEnvironment.Load();

                        // Do the actual instantiation of the environment

                        //    and start listening for requests.

                        environment.Open();

                        Console.WriteLine("Started NYSE server...");

                        // Keep service open until we type a key

                        Console.ReadLine();

                  }

                  catch (AggregateException ex)

                  {

                        // Do something with exception

                        Exception[] e = ex.GetExceptions();

                  }

                  catch (Exception ex)

                  {

                        // Do something with exception

                  }

                  finally

                  {

                        environment.Close();

                  }

}

 

It is pretty straight forward, get an instance of a ServiceEnvironment with the static Load method and then Open(…) it.  When we do not pass any parameters to the Load method, it with automatically load the service environment with the name=”main”.  I already mentioned that I was unable to get a service environment loaded that was not named “main”.  The last thing to do is Close(…) the ServiceEnvironment.  You may notice a peculiar Exception of type AggregateException.  The Indigo team decided that since they were going to throw so many exceptions they wanted to create a new type of Exception to hold an array of exceptions.  No, I am just kidding, but I wouldn’t doubt it.  The point is this: the config file and serviceEnvironment are nothing to be messed with.  If you don’t talk the lingo of the serviceEnvironment he will shoot your foot off, seriously, I had it happen about 50 times before I got the message.

 

 

 

5)      Creating a service can be a little more complicated, but you do have the knowledge necessary to get any service running.

 

a.       Create a skeleton class

 

      [DialogPortType(Name = "NYSEDatabase", Namespace = "http://tempuri.org/Conviat/NYSE/")]

      public class NYSEDatabase

      {

}

 

There are two types of services: Dialog and Datagram.  You would decorate your class definition with either of the two.  If your scenario requires you have a stateful conversation with your client, i.e. you need request/reply style messaging, you want a Dialog.  On the other hand, if you are only interested in stateless messaging, like ASMX, you want a Datagram, a fire-and-forget or one-way style.  Stateful and Stateless mean much more than this, but I think these definitions will suffice for now.  I am not exactly sure if the Name property of the PortType (DialogPortType) needs to have a specific name; nevertheless, I have been using the same name as the class.  Namespace is important, as always, no need to run an XML tutorial here.  But, I must mention.  If you are going to have transparent proxies lying around on the client, you are going to want to pay particular attention to the namespace since it will mean a little more with the client programming against it.

 

b.      Add some methods

 

      [DialogPortType(Name = "NYSEDatabase", Namespace = "http://tempuri.org/Conviat/NYSE/")]

      public class NYSEDatabase

      {

            private INYSEDatabaseClient client = null;

 

            public NYSEDatabase()

            {

 

 

            }

 

            [ServiceMethod]

            public bool Buy(SharesInfo shares)

            {

                  return true;

            }

 

            [ServiceMethod]

            public bool Sell(SharesInfo shares)

            {

                 

                  return true;

            }

 

            [ServiceMessage]

            public void RegisterForStockUpdates(INYSEDatabaseClient client)

            {

                  this.client = client;

            }

}

 

We have added three methods.  Two if which are decorated with ServiceMethod, which means that they will act as request/reply.  In this case, we are able to return a boolean to let the client do some extra processing on the basis of the outcome.  Don’t be fooled, though, this is not the same as .Net v1.0 Remoting, where you wire up an object and make calls on it.  RPC is out the window.  Indigo is message-based, even though the semantics speak otherwise.  Under the covers, Indigo actually creates 2 actions for this method.  A request message and a reply message, so it is truly message-based.  If that confused you, hold in there and we will look at the details later.

 

Forget about SharesInfo, it has no meaning, it is just an object that both the client and the service know about, in which they are putting data into and passing between each other.

 

The third method is annotated with ServiceMessage, which will let the client one-way a message, like ASMX.   That does not mean that there is no state; the Dialog still keeps track of it.  You may be asking: What is this code about INYSEDatabaseClient?

 

c.       Now, we need to create an interface so we can make callbacks on the client.

 

      public interface INYSEDatabaseClient : IDialogPortTypeChannel

      {

            [ServiceMessage]

            void StockPriceUpdate(StockInfo stock);

}

 

The client interface must implement IDialogPortTypeChannel, as far as I know.  I have not tested it otherwise, so I cannot be sure.

 

This interface defines a method that the client must implement, in which case, is the way we are going to pass stock updates to the client.

 

Now, if you look at the NYSEDatabase.RegisterForStockUpdates(…), you will understand why we pass an instance of the client; it allows us to pass messages to the client in an event-centric fashion without the client initiating the request.

 

                        We now have a full-functioning service.

 

 

6)      Now, you can use the ServiceEnvironment to listen for requests on this service.  Once the first request is made, the ServiceEnvironment will instantiate a new instance of the class.  The only part we are missing is a way for the client to interact with service.  This is accomplished by exporting an interface of the service and the methods it supports, along with the interface of the client.  A new tool provided with the SDK is wsdlgen.exe.  This tool allows you to export WSDL and XSD files.  Also, this tool allows you to compile the WSDL and XSD files back into a meaningful interface in code format (C#/VB.NET, among others).  So, the client would perform the latter, while the service does the former.  For our class, the command line would look like this:

 

"%SDKTOOLPATH%\wsdlgen.exe" /nologo NYSE.exe

 

Considering your executable where the class is located is named NYSE.exe.  On the same token, if the class is inside a Dll, it would be NYSE.dll.  The nologo switch suppresses a splash screen that obviously pops up when the utility is run, I have never tried it without the nologo switch, but I would imagine it is annoying and useless.  To expedite the process of thinking about the WSDL and XSD, you can set that exact command line as the Post-Build Event in VS (or create a batch file and run it manually when you update the class).

 

The resultant files:

 

tempuri_org.Conviat.NYSE.wsdl

tempuri_org.Conviat.NYSE.xsd

tempuri_org.Conviat.xsd

 

The last line deals with the SharesInfo class that we are calling from another namespace in another Dll.  You will not need this line if the object you pass in Buy/Sell is one within the namespace.

 

In case you are interested we will have a look at the WSDL:

 

<definitions xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:gxa="http://schemas.xmlsoap.org/wsdl/gxa/2003/01/extensions" xmlns:i0="http://tempuri.org/Conviat/NYSE/" xmlns:tns="http://tempuri.org/Conviat/NYSE/" targetNamespace="http://tempuri.org/Conviat/NYSE/" xmlns="http://schemas.xmlsoap.org/wsdl/">

  <import namespace="http://tempuri.org/Conviat/NYSE/" />

  <types />

  <message name="BuyRequest">

    <part element="tns:BuyRequest" name="parameters" />

  </message>

  <message name="BuyResponse">

    <part element="tns:BuyResponse" name="parameters" />

  </message>

  <message name="SellRequest">

    <part element="tns:SellRequest" name="parameters" />

  </message>

  <message name="SellResponse">

    <part element="tns:SellResponse" name="parameters" />

  </message>

  <message name="RegisterForStockUpdates">

    <part element="tns:RegisterForStockUpdates" name="parameters" />

  </message>

  <message name="StockPriceUpdate">

    <part element="tns:StockPriceUpdate" name="parameters" />

  </message>

  <portType gxa:correlation="session" name="NYSEDatabase" gxa:usingName="NYSEDatabaseClient">

    <operation name="Buy" gxa:parameterOrder="tns:shares" gxa:transaction="mandatory">

      <input message="tns:BuyRequest" name="BuyRequest" />

      <output message="tns:BuyResponse" name="BuyResponse" />

    </operation>

    <operation name="Sell" gxa:parameterOrder="tns:shares" gxa:transaction="mandatory">

      <input message="tns:SellRequest" name="SellRequest" />

      <output message="tns:SellResponse" name="SellResponse" />

    </operation>

    <operation name="RegisterForStockUpdates" gxa:parameterOrder="" gxa:transaction="reject">

      <input message="tns:RegisterForStockUpdates" name="RegisterForStockUpdates" />

    </operation>

    <operation name="StockPriceUpdate" gxa:parameterOrder="tns:stock" gxa:transaction="reject">

      <output message="tns:StockPriceUpdate" name="StockPriceUpdate" />

    </operation>

  </portType>

</definitions>

 

                       

You can now see that it is evident that a method is just a request and a response message.

 

 

Similarly, if you are programming on the client side, the command line that you would use to get a code file would be as follows:

 

"%SDKTOOLPATH%\wsdlgen.exe"  /nologo ..\..\..\NYSE\bin\Debug\tempuri_org.Conviat.NYSE.wsdl ..\..\..\NYSE\bin\Debug\tempuri_org.Conviat.NYSE.xsd

..\..\..\NYSE\bin\Debug\tempuri_org.Conviat.xsd

 

The last one is actually accounting for the class SharesInfo that was passed to the Buy/Sell methods above.  If the object you are passing is within the same namespace, you would not need that line.

 

The result of wsdlgen is the creation of a file named:

 

tempuri_org.Conviat.NYSE.cs

 

Let’s take a look (I have removed the SharesInfo gunk for clarity):

 

namespace Conviat.NYSE {

    using System;

    using System.Serialization;

    using System.Xml.Schema;

 

    [System.MessageBus.Services.DialogPortTypeAttribute(Namespace="http://tempuri.org/Conviat/NYSE/", ChannelType=typeof(INYSEDatabaseClientChannel))]

    public interface INYSEDatabase {

       

        [System.MessageBus.Services.ServiceMethodAttribute()]

        [System.MessageBus.Services.TransactedServiceContractAttribute(System.MessageBus.Services.TransactionContract.Mandatory)]

        bool Buy(Conviat.NYSE.NYSEBankAccount buyer, Conviat.SharesInfo shares);

       

        [System.MessageBus.Services.ServiceMethodAttribute()]

        [System.MessageBus.Services.TransactedServiceContractAttribute(System.MessageBus.Services.TransactionContract.Mandatory)]

        bool Sell(Conviat.NYSE.NYSEBankAccount seller, Conviat.SharesInfo shares);

       

        [System.MessageBus.Services.ServiceMessageAttribute()]

        void RegisterForStockUpdates(INYSEDatabaseClientChannel sender);

    }

   

    public interface INYSEDatabaseClient {

       

        [System.MessageBus.Services.ServiceMessageAttribute()]

        void StockPriceUpdate(INYSEDatabaseChannel sender, Conviat.StockInfo stock);

    }

   

    [System.MessageBus.Services.PortTypeChannelAttribute(Name="NYSEDatabase", Namespace="http://tempuri.org/Conviat/NYSE/", DispatchType=typeof(INYSEDatabaseClient))]

    public interface INYSEDatabaseChannel : System.MessageBus.Services.IDialogPortTypeChannel {

       

        [System.MessageBus.Services.ServiceMessageAttribute()]

        event Conviat.NYSE.NYSEDatabase_StockPriceUpdateEventHandler StockPriceUpdate;

       

        [System.MessageBus.Services.WrappedMessageAttribute(Namespace="http://tempuri.org/Conviat/NYSE/")]

        [System.MessageBus.Services.ServiceMethodAttribute()]

        [System.MessageBus.Services.TransactedServiceContractAttribute(System.MessageBus.Services.TransactionContract.Mandatory)]

        [return: System.MessageBus.Services.WrappedMessageAttribute(Namespace="http://tempuri.org/Conviat/NYSE/")]

        bool Buy(Conviat.NYSE.NYSEBankAccount buyer, Conviat.SharesInfo shares);

       

        [System.MessageBus.Services.WrappedMessageAttribute(Namespace="http://tempuri.org/Conviat/NYSE/")]

        [System.MessageBus.Services.ServiceMethodAttribute()]

        [System.MessageBus.Services.TransactedServiceContractAttribute(System.MessageBus.Services.TransactionContract.Mandatory)]

        [return: System.MessageBus.Services.WrappedMessageAttribute(Namespace="http://tempuri.org/Conviat/NYSE/")]

        bool Sell(Conviat.NYSE.NYSEBankAccount seller, Conviat.SharesInfo shares);

       

        [System.MessageBus.Services.WrappedMessageAttribute(Namespace="http://tempuri.org/Conviat/NYSE/")]

        [System.MessageBus.Services.ServiceMessageAttribute()]

        void RegisterForStockUpdates();

    }

   

    public interface INYSEDatabaseClientChannel : System.MessageBus.Services.IDialogPortTypeChannel {

       

        [System.MessageBus.Services.ServiceMessageAttribute()]

        event Conviat.NYSE.NYSEDatabase_RegisterForStockUpdatesEventHandler RegisterForStockUpdates;

       

        [System.MessageBus.Services.WrappedMessageAttribute(Namespace="http://tempuri.org/Conviat/NYSE/")]

        [System.MessageBus.Services.ServiceMessageAttribute()]

        void StockPriceUpdate(Conviat.StockInfo stock);

    }

   

    [System.MessageBus.Services.WrappedMessageAttribute(Namespace="http://tempuri.org/Conviat/NYSE/")]

    public delegate void NYSEDatabase_RegisterForStockUpdatesEventHandler(INYSEDatabaseClientChannel sender);

   

    [System.MessageBus.Services.WrappedMessageAttribute(Namespace="http://tempuri.org/Conviat/NYSE/")]

    public delegate void NYSEDatabase_StockPriceUpdateEventHandler(INYSEDatabaseChannel sender, Conviat.StockInfo stock);

}

 

On the client side, you can include this file in your project and you are ready to start programming against it.

 

You will notice that for each interface there is also another interface with the word Channel added to the end.  You will be using the Channel interface, and not the other.  So, you will instantiate the INYSEDatabaseChannel and make calls on it like Buy/Sell.  Or, you will pass the INYSEDatabaseClientChannel to the service in case you are talking about client.

 

 

 

What do I need to get a client to call a service?

 

1)      Well, we already went over how you would import the interface to program against, so we are pretty much home free.

 

2)      We need an App.config file like the service, except in this instance, we want a different identityRole and we also do not want the serviceManager section, since we do not have any services we want to start (It is possible for the client to act as another service, as you will see in the demo, but elided here for clarity).

 

3)      We need a program main that will start our ServiceEnvironment like in the service.  The code is the exact same.

 

4)      Now, we can create the Channel to the service and start using it:

 

Uri serviceUri = new Uri("soap.tcp://localhost:46001/NYSE/");

ServiceManager manager = environment[typeof(ServiceManager)] as ServiceManager;

INYSEDatabaseChannel nyse = (INYSEDatabaseChannel)manager.CreateChannel(typeof(INYSEDatabaseChannel), serviceUri);

 

a.       Create a Uri to the location of the service

b.      Grab a reference to the ServiceManager

c.       Create a INYSEDatabaseChannel by calling the CreateChannel(…) method on the ServiceManager, passing the type of the Channel to create and the URI to the service.

 

5)      Call some methods:

 

nyse.RegisterForStockUpdates();

nyse.StockPriceUpdate +=new NYSEDatabase_StockPriceUpdateEventHandler(nyse_StockPriceUpdate);

nyse.Buy(null);

 

a.       The interesting thing to note is that we do not have to pass the interface of our client in RegisterForStockUpdates(…); I am not exactly sure how this works, but it is able to recognize that the calling instance is of the Client interface type (I guess).  Even stranger, you do not have to implement the Client interface on the class that is going to be the client.  At first, I was doing this, but then I noticed that the method in the interface was never called, and I looked at the WSDL and noticed that it marshals it into the form of an event (delegate).

b.      Register for the event and you will receive the callbacks from the service.

c.       Just to demonstrate that we can call the Buy(…) method.

 

You are done!

 

 

Advanced Basics

 

I thought about actually calling this section Advanced Features, but I didn’t feel that it would relay the message very well, and I picked up the monomer from MSDN Magazine.  The reason I did this was because services are more than what we did above.  No one, I mean no one implements a service like that.  Services have danglers that make them interact in a certain ways; such as confidentially, authorization, authentication, and reliability.

 

Transactions

 

            For the Service:

 

            [TransactedService]

[TransactedServiceContract(TransactionContract.Mandatory)]

[ServiceMethod]

public bool Buy(SharesInfo shares)

{

            return true;

}

 

Add 2 attributes; one saying that it is a transacted service, and the other specifying the type of transaction to take place: mandatory, optional, and reject (Optional would mean that the client can choose to call the method using a transaction, etc., I think).

 

Actually, a transaction is not very helpful unless you have true transaction support all the way through the API stack that you will be using.  So, if you are building a custom object that will be called from within the method, make sure it has full transaction support.  Most of the time, people are just going to turn around and hook the transaction up to a SQL transaction, and let it do the operation within a Stored Procedure (actually, this is straight from the SDK, but does not use a SP):

 

            [TransactedService]

            [TransactedServiceContract(TransactionContract.Mandatory)]

            [ServiceMethod]

            public bool Buy(SharesInfo shares)

            {

                  Transaction tx = Transaction.Current;

                  System.EnterpriseServices.ITransaction estx = null;

                  SqlConnection connection = null;

 

                  try

                  {

                        connection = new SqlConnection();

                        connection.ConnectionString = @"Server=(local);Trusted_Connection=SSPI;Database=pubs;Enlist=false;";

                        connection.Open();

 

                        // Marshal the transaction object to make it compatible

                        // with EnterpriseService transactions (required for ADO.NET)

                        estx = (System.EnterpriseServices.ITransaction)tx.Marshal(MarshaledTransactionTypeNamespaceUri.ITransaction);

 

                        // Associate the marshalled EnterpriseServices transaction with the DB connection

                        connection.EnlistDistributedTransaction(estx);

 

                        // Do work on the database

                        SqlCommand command = new SqlCommand("update sales set qty = qty + 1", connection);

                        command.ExecuteNonQuery();

                  }

                  catch (System.Data.SqlClient.SqlException ex)

                  {

                        if (tx != null)

                        {

                              tx.Rollback();

                        }

                  }

                  catch (System.Transactions.TransactionException ex)

                  {

                        if (tx != null)

                        {

                              tx.Rollback();

                        }

                  }

                  catch (System.Exception ex)

                  {

                        if (tx != null)

                        {

                              tx.Rollback();

                        }

                  }

                  finally

                  {

                        connection.Close();

                        connection = null;

                  }

 

                  return true;

      }

 

 

 

            For the Client:

 

                  Transaction tx = null;

 

                  try

                  {

                        tx = Transaction.Create();

                        Transaction.Current = tx;

                        if (nyse.Sell(null, null))

                        {

                              // Maybe call someone else,

                              //    like the bank.

 

                              // ...

 

                              // If all is well, Commit

                              tx.Commit();

                        }

                  }

                  catch (Exception Ex)

                  {

                        if (tx != null)

                        {

                              tx.Rollback();

                        }

                  }

 

Notice that the client creates the Transaction, but everyone called subsequently can just grab the transaction from the context: Transaction tx = Transaction.Current;

           

 

Reliability

 

For the most part, we have already been using a flavor of reliability by decorating our services with DialogPortType.  The Dialog is the type of communication that supports this type of stateful messaging.

 

1)      First Flavor of Reliability

 

            Port port = environment[typeof(Port)] as Port;

            DialogManager dialogManager = new DialogManager(port);

            dialogManager.DialogRequested += new DialogRequestedEventHandler(dialogManager_DialogRequested);

            dialogManager.DialogLoadError +=new DialogLoadErrorEventHandler(dialogManager_DialogLoadError);

dialogManager.DialogLoaded +=new DialogLoadedEventHandler(dialogManager_DialogLoaded);

 

 

 

                        // ...

 

 

 

private void dialogManager_DialogRequested(object sender, DialogRequestedEventArgs e)

            {

                  DialogChannel dialogChannel = null;

 

                  try

                  {

                        // Throw the DialogRequest out if it was not of the expected action

                        if (e.InitialMessage.Action != new Uri("test://good_action"))

                        {

                              return;

                        }

 

                        dialogChannel = dialogManager.AcceptDialog(e.InitialMessage);

                        dialogChannel.Done += new EventHandler(dialogChannel_Done);

                        dialogChannel.DoneReceiving += new EventHandler(dialogChannel_DoneReceiving);

                        dialogChannel.Error += new DialogErrorEventHandler(dialogChannel_Error);

                        dialogChannel.MessageAvailable += new EventHandler(dialogChannel_MessageAvailable);

 

                        // ...

 

                        dialogChannel.DeleteDialog();

                  }

                  catch (Exception)

                  {

 

                  }

            }

 

In this flavor, you can create a DialogManager and then a DialogChannel and do different things depending on different actions.

 

 

2)      The second type is self explanatory and verbose.  Basically, it is how NTFS would write something to the hard disk.  It would keep a log, and before it did anything it would write “writing to file,” then it would do something and then write “done writing.”  SQL Server does the same thing.  It is not a hard concept to grasp.  If the process crashes, the first thing it will do when it wakes up is see that it was in the middle of something, and in that case, either rollback, or recreate it (considering you are using a vector to maintain state).  NTFS is actually a bad example because they use all sorts of caching and lazy writes for performance, but you get the idea.  They call this type of Reliability durable messaging, because everything is backed up on durable storage (hard disk).  You could use a text file to recreate what I am talking about above, but in the sample in the SDK and in personal opinion, it is best to use SQL Server.  Here is some code:

 

            DialogStore myStore = new SqlDialogStore(SqlStoreName, myUri, dbConfig.DatabaseConnectString);

            dialogManager.Stores.Add(myStore);

            dialogManager.Profiles["default"].StoreName = SqlStoreName;

dialogManager.Profiles["default"].MaximumMessageTimeToLive = new TimeSpan(TimeSpan.TicksPerMinute * 5);

 

                       

Then, right out of the SDK I pulled a method to show you how it may work (Note: I have not run this code, I am just letting you catch a glimpse):

 

 

            private void SendMessages()

            {

                  SqlConnection connection = null;

                  Transaction wsTransaction = null;

 

                  try

                  {

                        connection = new SqlConnection(dbConfig.DatabaseConnectString);

                        connection.Open();

                        wsTransaction = TransactionManager.Create().CreateTransaction("transactionDescription", System.Transactions.IsolationLevel.ReadCommitted, TimeSpan.FromSeconds(60));

 

                        System.EnterpriseServices.ITransaction itransaction = (System.EnterpriseServices.ITransaction)wsTransaction.Marshal(MarshaledTransactionTypeNamespaceUri.ITransaction);

 

                        connection.EnlistDistributedTransaction(itransaction);

                        dialogChannel.Transaction = wsTransaction;

                       

                        // Consider this an instance variable of the number of messages sent

                        sentMessages++;

                        String cmdString = "update SessionState set Sent= " + sentMessages + " where Dlgid = '" + dialogChannel.ID.ToString() + "'";

                        SqlCommand sqlCommand = new SqlCommand(cmdString, connection);

 

                        sqlCommand.ExecuteNonQuery();

 

                        // Send a message

                        dialogChannel.Send(...);

 

                        // Initiate done sending

                        dialogChannel.DoneSending();

 

                        wsTransaction.Commit();

                        connection.Close();

                        dialogChannel.Transaction = null;

                  }

                  catch (TimeoutException)

                  {

                        if (wsTransaction != null)

                        {

                              wsTransaction.Rollback();

                        }

 

                        dialogChannel.Transaction = null;

                        // Try again in 30 seconds

                        Thread.Sleep(30000);

                  }

                  catch (Exception ex)

                  {

                        throw ex;

                  }

                  finally

                  {

                        if (connection != null)

                        {

                              connection.Close();

                        }

                  }

}

 

 

                       

Security

 

            Authorization

 

The process of validating a user against a set of known credentials is authorization.  The most basic authorization that we use everyday is signing in to a website by providing a user name and password.  We will add similar ability to our NYSE service on the Buy method.

 

For the service:

 

The first thing we need to modify is the App.config file.  In place of the <remove name="securityManager" /> we need to add this code:

 

<securityManager>

          <applicationSecurity>

           <binding scope="StandardScope" profile="userNamePassword" />

          </applicationSecurity>

</securityManager>

 

First of all, you can add as many binding elements as you wish, considering that a real application will have many different security scopes.  The scope property can be set to any string that will help you identity the scope that the binding should be used for.  The scope will be used later to help us identify this binding in code.  The profile is the type of authorization we are going to use.  In this case, we will use basic user name and password authorization.  Other profiles that exist are “windows” and “x509”.  The scope can also be a qualified Assembly full name.  For example, “Conviat.NYSE.NYSEDatabase.Buy”.

 

The next step is creating a new file in our project called App.security (non case-sensitive).  This file will need to get into the executable directory under the name <application name>.exe.security.  I was unable to find a way for VS to do this automatically, so I just renamed it and placed it in the executable directory.  Inside App.security you should write this code:

 

<securityData>

    <credentials>

        <username id="mainUsers" nonceLength="24">

            <memoryPasswordResolver>

                <add user="user1" password="password1" />

                <add user="user2" password="password2" />

            </memoryPasswordResolver>

        </username>

    </credentials>

     <authorizationData>

        <memoryMapping id="mainAuthorizationData">

            <globalEntries>

                <userNameRoleAssignment user = "user1" roles = "StandardUserRole"/>

                <userNameRoleAssignment user = "user2" roles = "StandardUserRole"/>

            </globalEntries>

        </memoryMapping>

    </authorizationData>

</securityData>

 

With this code, we have 2 users registered with our service: {user1, user2}, with {password1, password2}, respectively.  For a user to access a method that is under the scope specified above, they must provide these credentials.  The second section is a way to map a specific user to a specific scope.  Obviously, a website admin should have more access to a website than an anonymous user.  This way, you can easily decorate methods with a specific scope and nothing else is needed to throttle access control.

 

[ServiceSecurity(Name = "StandardScope", Role = "StandardUserRole")]

[ServiceMethod]

public bool Buy(SharesInfo shares)

{

            return true;

}

 

 

For the client:

 

There are 2 ways that might come in handy in different situations, so I will show both of them:

 

1)      By way of config and security file

 

First, remove the <remove name="securityManager" /> line from the config file, and put nothing in its place.  By doing this, we are enabling security, but loading all default values from the machine.config.  Now, create a security file following the same convention as we did for the service.  Inside, place this code:

 

<securityData>

            <tokens>

                  <tokenCache id="mainTokenCache">

                        <userNameToken user="user1" password="password1"/>

                  </tokenCache>

             </tokens>

</securityData>

 

2)      By way of code

 

First, remove the <remove name="securityManager" /> line from the config file, and put nothing in its place.  Now place the following code in between where you Load the ServiceEnvironment and the place where you Open(…) it.

 

SecurityManager securityManager = environment[typeof(SecurityManager)] as SecurityManager;

            UserNameToken token = new UserNameToken("user1", "password1", 24);

securityManager.EndpointSettings.TokenCache.AddToken(token);

 

           

            Both of these methods will now enable the user call Buy(…) on the service.

 

           

            Authentication

 

            For the service:

 

On the service side, we will change the binding in the App.config file so that the profile property is set to “x509”.  Then, change the whole security file to read as follows:

 

<securityData>

    <credentials>

        <trustTestRoot id="mainX509TrustTestRoot">true</trustTestRoot>

    </credentials>

    <tokens>

        <tokenCache id="mainTokenCache">

            <x509Token>

                <certificate>

                    <store location="currentUser" name="certificate" />

                    <criteria type="subjectString">NYSE</criteria>

                </certificate>

            </x509Token>

        </tokenCache>

    </tokens>

    <authorizationData>

        <memoryMapping id="mainAuthorizationData">

            <globalEntries>

                <x509RoleAssignment subject="CN=NYSE" roles="StandardUserRole" />

            </globalEntries>

        </memoryMapping>

    </authorizationData>

</securityData>

 

This will work guaranteed that you have certificate named certificate.cert with a subject of NYSE.  If you need help in creating a certificate, you should consult the SDK.

 

 

For the client:

 

Change the security file to read as follows:

 

<securityData>

    <tokens>

        <tokenCache id="mainTokenCache">

            <x509Token>

                <certificate>

                   <store location="currentUser" name="certificate" />

                   <criteria type="subjectString">NYSE</criteria>

                </certificate>

            </x509Token>

        </tokenCache>

    </tokens>

</securityData>

 

            That is it!

It is highly recommended that you get in the habit of using certificates.  The goal for the future, and the only way to enable the future that I see, secure, is to start using digital signatures that are guaranteed by a signing authority.  That is the only way to communicate on a global scale and verify content and messages.

 

 

 

Confidentiality

 

Say for example that you want to enable confidentiality.  If you take the example above that uses the x509 certificate, then you are one line away from a confidential dialog session:

 

[ServiceSecurity(Name = "StandardScope", Role = "StandardUserRole", Confidentiality = true)]

[ServiceMethod]

public bool Buy(SharesInfo shares)

{

            return true;

}

 

            Notice that all we have added is the property Confidentiality = true.

 

 

 

I know that these security elements overlap, and it is hard to discern between their uses, but just keep in mind these facts:

 

·         Authorization verifies that you have access to a certain resource.

·         Authentication verifies that who you say you are is really you.

·         Confidentiality means that the message is encrypted before is travels over the wire.

 

With an X.509 certificate, you are able to knock out all items at once.

 

 

 

Messages

           

If it is the case that you do not want to work on an ASMX style level, using methods, that is perfectly okay.  As everyone should know, Indigo is built on messaging, so why not use it like it was demoed at the PDC.

 

The Message object is the actual envelope that is sent over the wire; in other words, it is the only object you can send.  Although, within a Message you can add any object that has a [Serializable] attribute set to true.  Also, you must specify an action URI, which should not be unfamiliar if you have worked with the WS specs.  An action URI can be anything, such as “MyAction://EatCake/” to something a little more realistic.  The action URI can be consumed at a number of levels along the stack for customized processing.  You could route messages depending on the action URI, or you can decide to consume them depending on it.  If you take a look at the Reliable messaging section above, you will notice that in one method it checks to make sure the action URI is not a specific URI, otherwise, it processes the message.

 

Further, you can create a class that inherits TypedMessage which will allow you to send the object over the wire instead of wrapping it with Message, but, obviously, both extend the same base class.  This can allow your service and client to analyze custom headers of the raw message before it is processed.  Here is a simple TypedMessage:

 

      [Message(Namespace = "http://www.tempuri.org/Conviat")]

      public class StockMessage: TypedMessage

      {

            private string companyName;

 

            public StockMessage()

            {

 

            }

 

            [MessageBody]

            public string CompanyName

            {

                  get

                  {

                        return this.companyName;

                  }

                  set

                  {

                        this.companyName = value;

                  }

            }

}

 

            You *must* have a default constructor and a MessageBody.

 

 

 

Wallstreet Indigo Demo

 

            The original idea went like this:

 

           

 

 

            Client:

·         There are different users that log onto a stock trading program

·         The users can Buy/Sell stock

·         The users can check the current amount of stock they own

·         The users can check the amount of funds in their bank account

·         The users will receive notifications when the stock price changes

 

 

 

 

ETrade:

·         Users were verified using authorization and the orders were encrypted

·         Buy/Sell order would be received from Client and it would create a Transaction to process the order.  The method would then call Wells Fargo and try to transfer funds from the client to its bank account.  Transaction would rollback if this failed.  If succeeded, the transaction would move forward and it would pass the order on to the NYSE Service.  The NYSE service would then call up Wells Fargo and try to transfer the funds from ETrade to NYSE.  Upon approval from the bank, the stocks would then be bought for Ebay.  If any step in the transaction failed, the whole process would RollBack, otherwise, it would Commit.

·         StockUpdate events would be received from NYSE which would be passed on to the Client.

 

NYSE:

·         At a specified interval, a Random stock would be chosen and the price would change.  The new stock data would be sent down to ETrade.

·         Every order would increase or decrease the price of a stock.

 

 

Every service was actually going to run as its own executable, and the reason was to define one of the main tenants to Indigo: Service Boundaries are Explicit.  Nothing does this as well as three Console Windows, in addition to the Client.

 

 

Did I really mean Indiguano?

 

Well, let’s put it this way, I don’t quit any project for any reason, especially a demo.  If that doesn’t explain things, let’s step through it one by one, so that you don’t fall in the same trap.

 

·         First: Go Read this.  If you don’t read the release notes, you are going to fall into the same traps that I did.  It will not just help you for Indigo; it will help you for all of Longhorn.  The known errors are in it.  The ones that take days to figure out.

 

·         I mentioned this above, but I am serious, if you make the serviceEnvironment mad; he will literally ruin your day, no joke.  First of all, I couldn’t get multiple serviceEnvironment objects because it would throw an exception if I didn’t have the name set to “main”, so that took me a long time to figure out.

 

·         If you cannot even get the serviceEnvironment loaded, and you are on a Windows workgroup, and you are using the securityManager, there are things you need to edit in the machine.config file to fix this.  Refer to step one, as it is documented in the release notes.

 

·         Even then, with the securityManager functional, I was getting AsyncCallback exceptions all day, ones that don’t have any code.  The ones where the MessageBox pops up and says “AsyncCallback exception;” these are so frustrating.  I finally figured that it had to do with a Policy Exception because I noticed something on the Output window of VS.  This may be solved according to the release note if I log in on a domain, which is totally unacceptable.  I don’t have a handy Domain Controller that I can just hook up, not that I want to, anyway.

 

·         This is mentioned above.  But, if you have never seen AggregateException, as I had not.  Then remember that there is a method on this instance to get an array of exceptions.

 

·         Oh yeah, I can’t forget this one.  If you have an outbound message on a service, you cannot receive a callback from it before the first message has received a response.  This is ridiculous.  Services are autonomous.  It was like race conditions all over again.

 

·         The documentation is terrible and the API is not consistent.  It is easy to program against an API in which there is only ONE way to do something.  When there are 35 ways, I am lost.  Yeah, I will learn quickly.  But, this advice is not for me.  It is for Jack, who picks up Indigo and can’t figure it out so he resorts to .Net v1.0 Remoting or maybe DCOM (L).

 

You could say I was a little harsh.  Or, you could say that I received something like 20 Exceptions per hour.  I think I may know every exception that Indigo throws, now.

 

If you do download the source code, be wary.  I tore it up so much trying to find the right answer that I never felt like putting it back together.

 

If you wish, there may be some Avalon stuff in Client that you may want to look at.  I wrote that code in no time, and it uses a lot of vector graphics.

 

Where are we?

 

We should be ready to jump in a lake.  No, seriously, this article should have given you a surface area that you can take and build on.  My advice: do not use Indigo until beta.  If you do, it will frustrate you, and you may lose some hair (From pulling or stress, but probably both).  I am actually looking forward to Indigo, yet, they need a little more time.

 

If you think I may have morphed into Chris Brumme, you are probably wrong, I just decided to take my anger out by writing a book.

 

 

PostTypeIcon
9,210 Views