Welcome to MSDN Blogs Sign in | Join | Help

Automatic port number generation

It has been an age since I updated this, the reason is quite simple - My team has been working hard to get Peer Channel ready to ship.

One of my favourite of the features we have added recently is now available in the latest CTP (download here) is the ability to tell the framework to automagically assign a random port to a PeerNode.

There are several benefits with automatically generating the port number:

  1. It is easy to run multiple instances of the application on a single machine
  2. It is harder to attack an application if it is not on a fixed port, because an application can't tell just by observing what port's are open on a machine what applications are running on it.

To automatically assign is really quite easy the NetPeerTcpBinding instance has a Port property if you set that to zero before opening the channel then the system will automatically generate a port randomly and set the peernode to listen on that port.

This can also be easily done in a configuration file for example to modify the sample in the post Is there anybody out there? (Online /Offline and the Peer channel)

The configuration file simply changes to this:

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <system.serviceModel>

    <client>

      <endpoint name="HelloEndpoint"

                address="net.p2p://hellomesh/hellomesh"

                binding="netPeerTcpBinding"

                bindingConfiguration="Binding1"

                contract="HelloMesh.IHelloMesh">

      </endpoint>

    </client>

 

    <bindings>

      <netPeerTcpBinding>

        <binding name="Binding1" port="0">

          <security mode="None"/>

        </binding>

      </netPeerTcpBinding>

    </bindings>

  </system.serviceModel>

</configuration>


Now you can run as many instances as you want on each computer without recieving the Address in use exception that indicates that you are trying to use the same port in two applications.

 

Until next time

Posted by Kevin Ransom | 0 Comments

Is there anybody out there? (Online /Offline and the Peer Channel)

*********************************************************

Updated to reflect changes in the February CTP

*********************************************************

One of the most frequently asked questions is this: How can I tell if a node is the only member of the mesh?  We addedevents and a property that can be queried to get this information

The definition of online status is straightforward:

  • A node that has no immediate neighbors is Offline.
  • A node with at least one neighbor is Online.

 

PeerNode and Online Status

To set the online and offline events to handlers specified in your own code use:

IOnlineStatus status = channel.GetProperty<IOnlineStatus>();

status.Online += online;

status.Offline += offline;

The handler is a method of type EventHandler very simple handlers might look like:

static void Online(object o, EventArgs args)

{

  Console.WriteLine("Online");

}

 

static void Offline(object o, EventArgs args)

{

  Console.WriteLine("Offline");

}

It should be noted that in Beta 1 and the forthcoming Beta 2 release of Peer Channel the handlers are not called on the UI thread so you must take care to ensure that you write thread safe code for these handlers.  If you try not to change global or static state you should be all right, the 2 most common uses of these events are to 1. Send a message to the mesh or 2 to toggle an indicator on the UI.

If instead of responding to an event you prefer to query the status then use the IsOnline property true means online false means offline:

IOnlineStatus status = channel.GetProperty<IOnlineStatus>();

if (status.IsOnline)

  Console.WriteLine("Online");

 

 

Non-Guarantees

It should be noted that the online status is subject to change at any time; it is eminently possible for you to receive an Online event during which you send a message to the mesh and then before the message has been transmitted to any neighbors for the node to go offline again.

Online is fired when a node becomes Online, Offline is fired when a node loses it’s last neighbor.  Offline is not fired during Open if the node is opened and does not have any neighbors.

The following example shows how to create a proxy and specify handlers for the online and offline events, this is not the only way to do it but it is the way I like and use.

 

The contract

The contract is the same as before

[ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

interface IHelloMesh

{

  [OperationContract(IsOneWay = true)]

  void Say(string message);

}

 

The proxy

Add an overloaded Constructor to HelloProxy that sets the events to handlers passed into the constructor:

public HelloProxy(InstanceContext inputInstance, string name, EventHandler online, EventHandler offline)

       : base(inputInstance, name)

{

  IOnlineStatus status = base.InnerChannel.GetProperty<IOnlineStatus>();

  status.Online += online;

  status.Offline += offline;

  base.InnerDuplexChannel.Open();

}

 

Add the message handler

class HelloMesh : IHelloMesh

{

  public void Say(string message)

  {

    Console.WriteLine("Say : {0}", message);

  }

}

 

Add handlers to the main program:

 

static void Online(object o, EventArgs args)

{

  Console.WriteLine("Online");

}

 

static void Offline(object o, EventArgs args)

{

  Console.WriteLine("Offline");

}

And that is all there is to it.  Now when your application starts it will display Online in the output  as soon as it is connected to a neighbor; when it is completely alone it will produce Offline in the output.

 

The complete program

using System;

using System.Collections.Generic;

using System.ServiceModel;

using System.ServiceModel.Channels;

using System.Text;

 

namespace HelloMesh

{

  [ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

  interface IHelloMesh

  {

    [OperationContract(IsOneWay = true)]

    void Say(string message);

  }

 

  partial class HelloProxy : System.ServiceModel.DuplexClientBase<IHelloMesh>, IHelloMesh

  {

    public HelloProxy(InstanceContext inputInstance, string name, EventHandler online, EventHandler offline)

           : base(inputInstance, name)

    {

      IOnlineStatus status = base.InnerChannel.GetProperty<IOnlineStatus>();

      status.Online += online;

      status.Offline += offline;

      base.InnerDuplexChannel.Open();

    }

 

    public HelloProxy(InstanceContext inputInstance, string name)

           : base(inputInstance, name)

    {

      base.InnerDuplexChannel.Open();

    }

 

    public void Say(string message)

    {

      base.InnerProxy.Say(message);

    }

  }

 

  class HelloMesh : IHelloMesh

  {

    public void Say(string message)

    {

      Console.WriteLine("Say : {0}", message);

    }

  }

 

  class Program

  {

    static void Online(object o, EventArgs args)

    {

      Console.WriteLine("Online");

    }

 

    static void Offline(object o, EventArgs args)

    {

      Console.WriteLine("Offline");

    }

 

    static void Main(string[] args)

    {

      InstanceContext instanceContext = new InstanceContext(new HelloMesh());

      using (HelloProxy hello = new HelloProxy(instanceContext, "HelloEndpoint", Online, Offline))

      {

        string msg = "";

        while (msg != "q")

        {

          msg = Console.ReadLine();

          hello.Say(msg);

        }

        hello.Close();

      }

    }

  }

}

The config is unchanged from before and looks like:

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <system.serviceModel>

    <client>

      <endpoint name="HelloEndpoint"

                address="net.p2p://hellomesh/hellomesh"

                binding="netPeerTcpBinding"

                bindingConfiguration="Binding1"

                contract="HelloMesh.IHelloMesh">

      </endpoint>

    </client>

 

    <bindings>

      <netPeerTcpBinding>

        <binding name="Binding1"

              port="5182">

          <security mode="None"/>

        </binding>

      </netPeerTcpBinding>

    </bindings>

  </system.serviceModel>

</configuration>

 

Next:

Possibly:

   Structured data (DataContracts and Peer Channel)

   Sending a message only to your nearest neighbor (Message Filters) !!!!!

   Channels Input/Output/Duplex  and Peer Channel

           

Posted by Kevin Ransom | 1 Comments

Handling Messages directly

*********************************************************

Updated to reflect changes in the February CTP

*********************************************************

My previous samples demonstrated using ServiceModel to dispatch messages to a strongly typed message handlers.  There are times when instead of having a seperate implementation for every message you will want to have a centralised message handler (often this handler is called ProcessMessage).  An early version of the Peer channel system code had a message handler called ProcessMessage where the Peer channel messages were dispatched.

A Strongly Typed contract

[ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

interface IHelloMesh {

    [OperationContract(IsOneWay = true)]

    void Say(string message);

}

This is considered strongly typed because the method Say takes a string as an argument.

The Contract

This contract defines a a handler that recieves raw messages, it will recieve messages for any action.

public static class ProcessMessageActions

{

  public const string SayAction = "Say";

}

 

[ServiceContract(Namespace = "urn:30E9233A-6C5C-47a2-8E90-93D592DA0A00/", CallbackContract = typeof(IProcessMessage))]

interface IProcessMessage

{

  [OperationContract(Action="*", IsOneWay = true)]

  void ProcessMessage(Message message);

}

The  Action="*" option on the OperationContract attribute is called the wildcard action it says that any value in the Headers.Action property of the message will be handled by the message handler specified by this contract, and the argument type is Message;  Message is a new abstraction in the System.ServiceModel namespace that allows a developer to operate on raw messages when necessary, such as adding or retrieving Message Headers, generating or retrieving the raw Xml.

The ProcessMessageActions class defines the actions to be used in a convenient manner.

The Proxy

class HelloMesh : IProcessMessage

{

  public void ProcessMessage(Message message)

  {

    if (message.Headers.Action == ProcessMessageActions.SayAction)

    {

      string data = message.GetBody<string>();

      Console.WriteLine(data);

    }

  }

}

Again the proxy does little more than delegate to the base implementation.  This time the Method is called ProcessMessage and it takes a a Message as its argument. It can be used to create and read the elements of the message including headers.

The InstanceContext

This is the class that is used to handle inbound messages.  It has an Implementation IProcessMessage in this case it verifies that the action matches a specific value, if so it does the work associated with that action.

class HelloMesh : IProcessMessage

{

  public void ProcessMessage(Message message)

  {

    if (message.Headers.Action == ProcessMessageActions.SayAction)

    {

      string data = message.GetBody<string>();

      Console.WriteLine(data);

    }

  }

}

The Application

The application is still trivial to write, as before you create an InstanceContext for incoming messages, a proxy for outgoing messages, and then in a loop read what to send, and then use the proxy to send it. In this case it is necessary to create a message object with the data.

class Program

{

  static void Main(string[] args)

  {

    InstanceContext instanceContext = new InstanceContext(new HelloMesh());

    using (HelloProxy proxy = new HelloProxy(instanceContext, "HelloEndpoint"))

    {

      string msg = "";

      while (msg != "q")

      {

        msg = Console.ReadLine();

        Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, ProcessMessageActions.SayAction, msg);

        proxy.ProcessMessage(message);

      }

      proxy.Close();

    }

  }

}

The Configuration

The only changes necessary to the config are because we renamed the service contract and I also changed the mesh address:

To enable certain changes to be made after the application has been deployed for instance port, the endpoint and binding are usually specified in the app.config file.  This is the config file for our application above.

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <system.serviceModel>

    <client>

      <endpoint name="HelloEndpoint"

         address="net.p2p://hellomesh/processmessage"

         binding="netPeerTcpBinding"

         bindingConfiguration="Binding1"

         contract="HelloMesh.IProcessMessage">

      </endpoint>

    </client>

    <bindings>

      <netPeerTcpBinding>

        <binding name="Binding1"

              port="5182">

          <security mode="None"/>

        </binding>

      </netPeerTcpBinding>

    </bindings>

  </system.serviceModel>

</configuration>

Putting the code together in one piece leaves us witt:

The Complete Program:

using System;

using System.Collections.Generic;

using System.ServiceModel;

using System.ServiceModel.Channels;

using System.Text;

 

namespace HelloMesh

{

  public static class ProcessMessageActions

  {

    public const string SayAction = "Say";

  }

 

  [ServiceContract(Namespace = "urn:30E9233A-6C5C-47a2-8E90-93D592DA0A00/", CallbackContract = typeof(IProcessMessage))]

  interface IProcessMessage

  {

      [OperationContract(Action="*", IsOneWay = true)]

      void ProcessMessage(Message message);

  }

 

    partial class HelloProxy : System.ServiceModel.DuplexClientBase<IProcessMessage>, IProcessMessage

  {

      public HelloProxy(InstanceContext inputInstance, string configurationName) : base(inputInstance, configurationName)

      {

          base.InnerDuplexChannel.Open();

      }

 

      public void ProcessMessage(Message message)

      {

          base.InnerProxy.ProcessMessage(message);

      }

   }

 

   class HelloMesh : IProcessMessage

   {

      public void ProcessMessage(Message message)

      {

         if (message.Headers.Action == ProcessMessageActions.SayAction)

         {

            string data = message.GetBody<string>();

            Console.WriteLine(data);

         }

      }

   }

 

   class Program

   {

      static void Main(string[] args)

      {

         InstanceContext instanceContext = new InstanceContext(new HelloMesh());

         using (HelloProxy proxy = new HelloProxy(instanceContext, "HelloEndpoint"))

         {

             string msg = "";

             while (msg != "q")

             {

                 msg = Console.ReadLine();

                 Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, ProcessMessageActions.SayAction, msg);

                 proxy.ProcessMessage(message);

             }

             proxy.Close();

         }

      }

    }

 }

Posted by Kevin Ransom | 0 Comments

Multiple channels

**** One of my colleagues has pointed out a bug in this example ****

I have edited this so that it now shows the correct code.

The bug is that for Peer channel the configuration for each endpoint needs a distinct address to see what has changed please view the section labeled The Configuration

the update items are italicized.

*********************************************************

Updated to reflect changes in the February CTP

*********************************************************

In my previous post (can be found: here) I showed how to write a very simple Peer channel app with a single Message type "Say".  In this post I will show you how to extend it so that it contains multiple channels, each of which have different messages.

The application consists of 2 ServiceContracts, IHelloMessage for talking and IStatus for propogating information about how busy a member is.

The Contracts

  [ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

  interface IHelloMesh

  {

    [OperationContract(IsOneWay = true)]

    void Say(string message);

  }

 

  [ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IStatus))]

  interface IStatus

  {

    [OperationContract(IsOneWay = true)]

    void Busy(string userId);

 

    [OperationContract(IsOneWay = true)]

    void NotBusy(string userId);

  }

IHelloMesh is as described in my previous post.  IStatus defines a new ServiceContract with 2 operations Busy() and NotBusy()

The Proxies

  partial class HelloProxy : System.ServiceModel.DuplexClientBase<IHelloMesh>, IHelloMesh

  {

    public HelloProxy(InstanceContext inputInstance, string name)

        : base(inputInstance, name)

    {

        base.InnerDuplexChannel.Open();

    }

 

    public void Say(string message)

    {

      base.InnerProxy.Say(message);

    }

  }

 

  partial class StatusProxy : System.ServiceModel.DuplexClientBase<IStatus>, IStatus

  {

    public StatusProxy(InstanceContext inputInstance, string name)

         : base(inputInstance, name)

    {

      base.InnerDuplexChannel.Open();

    }

 

    public void Busy(string userId)

    {

      base.InnerProxy.Busy(userId);

    }

 

    public void NotBusy(string userId)

    {

        base.InnerProxy.NotBusy(userId);

     }

  }

Again HelloProxy is unchanged, I have Added a StatusProxy so that the application can be used to send status messages to the mesh.

The Main is somewhat different, instead of a using {} I have a try {} finally {} mainly to show a different way of achieving the same result.  To send a NotBusy message just enter a line that begins with "notbusy".  To send a Busy type a line that begins with "busy".  To quit enter "q".

  class Program

  {

    static void Main(string[] args)

    {

      Console.WriteLine("Please enter your id");

      string id = Console.ReadLine();

      InstanceContext instanceContext = new InstanceContext(new HelloMesh());

      using (HelloProxy hello = new HelloProxy(instanceContext, "HelloEndpoint"))

      {

        using (StatusProxy status = new StatusProxy(instanceContext, "StatusEndpoint"))

        {

          string msg = "";

          while (msg != "q")

          {

            msg = Console.ReadLine();

            if (msg.StartsWith("busy", StringComparison.CurrentCultureIgnoreCase))

            {

              status.Busy(id);

            }

            else if (msg.StartsWith("notbusy", StringComparison.CurrentCultureIgnoreCase))

            {

              status.NotBusy(id);

            }

            else

            {

              hello.Say(msg);

            }

          }

        }

      }

    }

  }

 

The Complete Program

using System;

using System.Collections.Generic;

using System.ServiceModel;

using System.Text;

 

namespace HelloMesh

{

  [ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

  interface IHelloMesh

  {

    [OperationContract(IsOneWay = true)]

    void Say(string message);

  }

 

  [ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IStatus))]

  interface IStatus

  {

    [OperationContract(IsOneWay = true)]

    void Busy(string userId);

 

    [OperationContract(IsOneWay = true)]

    void NotBusy(string userId);

  }

 

  partial class HelloProxy : System.ServiceModel.DuplexClientBase<IHelloMesh>, IHelloMesh

  {

    public HelloProxy(InstanceContext inputInstance, string name)

        : base(inputInstance, name)

    {

        base.InnerDuplexChannel.Open();

    }

 

    public void Say(string message)

    {

      base.InnerProxy.Say(message);

    }

  }

 

  partial class StatusProxy : System.ServiceModel.DuplexClientBase<IStatus>, IStatus

  {

    public StatusProxy(InstanceContext inputInstance, string name)

         : base(inputInstance, name)

    {

      base.InnerDuplexChannel.Open();

    }

 

    public void Busy(string userId)

    {

      base.InnerProxy.Busy(userId);

    }

 

    public void NotBusy(string userId)

    {

        base.InnerProxy.NotBusy(userId);

     }

  }

 

  class HelloMesh : IHelloMesh, IStatus

  {

    public void Say(string message)

    {

      Console.WriteLine("Say : {0}", message);

    }

 

    public void Busy(string userId)

    {

      Console.WriteLine("User {0} is busy", userId);

    }

 

    public void NotBusy(string userId)

    {

      Console.WriteLine("User {0} is not busy", userId);

    }

  }

 

  class Program

  {

    static void Main(string[] args)

    {

      Console.WriteLine("Please enter your id");

      string id = Console.ReadLine();

      InstanceContext instanceContext = new InstanceContext(new HelloMesh());

      using (HelloProxy hello = new HelloProxy(instanceContext, "HelloEndpoint"))

      {

        using (StatusProxy status = new StatusProxy(instanceContext, "StatusEndpoint"))

        {

          string msg = "";

          while (msg != "q")

          {

            msg = Console.ReadLine();

            if (msg.StartsWith("busy", StringComparison.CurrentCultureIgnoreCase))

            {

              status.Busy(id);

            }

            else if (msg.StartsWith("notbusy", StringComparison.CurrentCultureIgnoreCase))

            {

              status.NotBusy(id);

            }

            else

            {

              hello.Say(msg);

            }

          }

        }

      }

    }

  }

}

 

The Configuration

The configuration is extended with the specification of a new endpoint for the IStatus contract:

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <system.serviceModel>

    <client>

      <endpoint name="HelloEndpoint"

                address="net.p2p://hellomesh/helloendpoint"

                binding="netPeerTcpBinding"

                bindingConfiguration="Binding1"

                contract="HelloMesh.IHelloMesh">

      </endpoint>

      <endpoint name="StatusEndpoint"

                address="net.p2p://hellomesh/statusendpoint"

                binding="netPeerTcpBinding"

                bindingConfiguration="Binding1"

                contract="HelloMesh.IStatus">

      </endpoint>

    </client>

    <bindings>

      <netPeerTcpBinding>

        <binding name="Binding1"

                 port="5182">

          <security mode="None"/>

        </binding>

      </netPeerTcpBinding>

    </bindings>

  </system.serviceModel>

</configuration>

Run

Compile and build copy the exe and config to 2 windows XP machines, start up each instance.  Type hello, world in one watch the message appear ion both.  Then type busy and observe the Busy message appear on both machines, enter notbusy and observe the not busy message.

Posted by Kevin Ransom | 1 Comments

Peer Channel

Updated to reflect Feb CTP changes in the config model.

In Longhorn we are implementing many enhancements to the Peer to Peer programming platform one of the most exciting to me is the Peer channel.   The discussion that follows describes pre-release software, specifically it describes the implementation of Peer Channel that ships on the CD for delegates to the 2005 PDC. 

You can also download the September CTP WinFX runtime from here:

What is the Peer Channel ?

The Peer Channel is an Indigo channel that supports multi-party communication.  A typical Indigo channel has 2 participants, a Peer Channel can have any number of participants.  A message sent by one particpant will be recieved by all participants.

Node   - A single instance of an application that operates in a Peer to Peer application
Mesh   - A group of nodes that are connected together and form a Peer to Peer application.
ServiceContract   - The definition for a group of related messages.  This definition is usually realized as a ManagedCode interface, the signature of the methods and attributes control the actual message format.  This definition may also be realized as a WSDL contract.
OperationContract   - The definition for a single operation may refer to the incoming and outgoing messages for a single operation.
InstanceContext    - The managed code class that handles incoming messages.
Proxy   - The managed code class that is used to generate outgoing messages.

A simple Peer Channel application

Lets look at a simple Peer Channel App that allows a message to be broadcast and recieved by all members of a mesh .  Lets call the program HelloMesh.

The Contract

[ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

interface IHelloMesh {

   [OperationContract(IsOneWay = true)]

   void Say(string message);

}

This contract describes a single operation called Say.  The operation Say has one argument which is a string. 

The ServiceContract attribute specifies the XmlNamespace for the message which in this case is a guid that I used Guidgen to generate, guids are not very user friendly as names since they are hard to type in this case though the guid makes a good namespace since the managed programming model never surfaces it, so a developer doesn’t need to type it.  The ServiceContract also specifies that the Contract has a CallbackContract which is of the same type.  Therefore this is a symmetrical contract meaning the outgoing and incoming messages have the same format, because the CallbackContract is specified it is only usable by Duplex (2 way) channels.

The Proxy

partial class HelloProxy : System.ServiceModel.DuplexClientBase<IHelloMesh>, IHelloMesh {

   public HelloProxy(InstanceContext inputInstance, string name) : base(inputInstance, name) {

      base.InnerDuplexChannel.Open();

   }

   public void Say(string message) {

      base.InnerProxy.Say(message);

   }

}

The proxy is used by client code to send messages to the mesh, the majority of work is done by the base classes, the proxy class must, however, provide an implementation of the IHelloMesh contract which delegates the actual sending of the message to the InnerProxy.  A constructor is also provided, in it we Open the DuplexChannel.  If the channel is not opened here then it will be opened when the first message is sent accross the channel - in our case that is not what we want because a client would not be able to recieve a message until after it had sent on, so we Open the channel when we construct and everything works as you would expect.  The constructor takes 2 arguments inputInstance which is an instance of the class which handles inbound messages, and name which is the name of the endpoint description to read from the app.config file.

The InstanceContext

This is the class that is used to handle inbound messages.  It has an Implementation of the ServiceContract.

class HelloMesh : IHelloMesh {

   public void Say(string message) {

   Console.WriteLine("Say : {0}", message);

   }

}

The Application

The application is trivial to write, you just create an InstanceContext for incoming messages, a proxy for outgoing messages, and then in a loop read what to send, and then use the proxy to send it. 

class Program {

   static void Main(string[] args) {

      InstanceContext instanceContext = new InstanceContext(new HelloMesh());

      using (HelloProxy proxy = new HelloProxy(instanceContext, "HelloEndpoint")) {

      string msg = "";

      while (msg != "q") {

         msg = Console.ReadLine();

         proxy.Say(msg);

      }

      proxy.Close();

     }

   }

}

The Complete Program:

using System;

using System.Collections.Generic;

using System.ServiceModel;

using System.Text;

namespace HelloMesh {

   [ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

   interface IHelloMesh {

      [OperationContract(IsOneWay = true)]

      void Say(string message);

   }

 

   partial class HelloProxy : System.ServiceModel.DuplexClientBase<IHelloMesh>, IHelloMesh {

      public HelloProxy(InstanceContext inputInstance, string name) : base(inputInstance, name) {

         base.InnerDuplexChannel.Open();

      }

      public void Say(string message) {

         base.InnerProxy.Say(message);

      }

   }

   class HelloMesh : IHelloMesh {

      public void Say(string message) {

         Console.WriteLine("Say : {0}", message);

      }

   }

   class Program {

      static void Main(string[] args) {

         InstanceContext instanceContext = new InstanceContext(new HelloMesh());

         using (HelloProxy proxy = new HelloProxy(instanceContext, "HelloEndpoint")){

            string msg = "";

            while (msg != "q") {

               msg = Console.ReadLine();

               proxy.Say(msg);

            }

            proxy.Close();

         }

      }

   }

}

The Configuration

The last part of the application's source code is the configuration:

To enable certain changes to be made after the application has been deployed for instance port, the endpoint and binding are usually specified in the app.config file.  This is the config file for our application above.

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <system.serviceModel>

    <client>

      <endpoint name="HelloEndpoint"

         address="net.p2p://hellomesh/hellomesh"

         binding="netPeerTcpBinding"

         bindingConfiguration="Binding1"

         contract="HelloMesh.IHelloMesh">

      </endpoint>

    </client>

    <bindings>

      <netPeerTcpBinding>

        <binding name="Binding1" port="5182">

          <security mode="None" />

        </binding>

      </netPeerTcpBinding>

    </bindings>

  </system.serviceModel>

</configuration>

This file defines one endpoint called HelloEndpoint and one binding called Binding1.  The binding specifies the port, the endpoint specifies the uri for the mesh.

To compile this you will need to reference System.ServiceModel.dll.

Longhorn when the application is run on Windows XP or Longhorn the Firewall software will pop a dialog asking if you want to unblock the application, this occurs because it is a communications application and is trying to access a port.  It is okay to unblock this application, select Unblock.

If this app is run on a machine that doesn't have PNRP enabled an InvalidOperationException will be thrown with the message:

System.InvalidOperationException: Resolver must be specified. The default resolver (PNRP) is not available.

If your system is running Longhorn or Windows XP with SP2 then enable Pnrp: Details

I think you can see that creating a multiparty app is very easy using Indigo and the PeerChannel.  And also that your apps can be written in a modular way with clearly defined roles for the various classes in your application.

Next time I will show you how to write the same application with 2 one way channels rather than a DuplexChannel.

Posted by Kevin Ransom | 1 Comments

Peer to Peer SDK

At the beginning of this year I joined the Collaboration Technology group.  Our group is charted with creating a platform for developing Peer to Peer applications and for creating experiences on top of that platform.  Windows XP already contains the elements of a Peer to Peer Development platform.  The Peer to Peer SDK contains samples and tools that can be used to develop peer to peer applications. The APIs are contained in the Advanced Networking Pack for Windows XP:  http://msdn.microsoft.com/library/default.asp?url=/downloads/list/winxppeer.asp

If you have not yet done so and you are interested in Peer to Peer programming you should enable PNRP this is a very cool technology that allows you to register a name that can be discovered by applications running on other computers without using a server.  The SDK contains a sample application GraphChat which is a simple chat application that uses PNRP. 

To enable PNRP you will need a computer running Windows XP Service Pack 2. PNRP is currently not available for Windows 2003 server.

  • Select Add / Remove programs
  • Select Add / Remove Windows components
  • Select Networking Services and click on details
  • ensure that the Peer to peer check box is ticked and then select Okay followed by Next
  • The Windows installer will do its thing then you should select Next  followed by Finish

To check that everything went well run a netsh command shell by:

  • Select Start followed by Run
  • enter netsh followed by the return key

In the command shell that appears enter the command:

  • netsh p2p pnrp cloud

followed by the command

  • show list

You should see output similar to:

Scope Id    Addr  State            Name

----- ----- ----- ---------------- -----

3     4     1     Virtual          LinkLocal_{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}

1     0     1     Virtual          Global_

netsh p2p pnrp cloud>

Note: {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} will be a guid and will differ from machine to machine. 

One LinkLocal_{} entry will appear for each network adapter you have installed and there may be a SiteLocal_ entry too.

The peer to peer extensions for netshell are excellent diagnostic tool.  I expect you will be come very familiar with it over time.

Posted by Kevin Ransom | 1 Comments

Algorithms and .Net collections

Algorithms and .Net collections

Recently I was asked by a colleague about an InvalidOperation exception with the message:

Collection was modified; enumeration operation may not execute.


It would seem that my colleague like many of us expected code similar to this to work

foreach (Item s in collection)
{
    if (s.expired == true)
    {
        collection.Remove(s);
    }
}

And yet it gave an Invalid operation exception with the text: "Collection was modified; enumeration operation may not execute."

Updating collections of one sort or another is probably the most common thing that I do in my code, I seem to be always adding to and removing from collections in .NetFramework 1.0 and 1.1 System.Collections.ArrayList and System.Collections.HashTable were my closest allies in keeping my application state under control.  And yet I still ran into the dreaded InvalidOperationException all of the time.

It turns out that there are plenty of ways to fix code with this exception probably the one involving the least typing is ToArray()

foreach (Item s in collection.ToArray())
{
    if (s.expired == true)
    {
        collection.Remove(s);
    }
}

If the collection is a reasonable size then this is probably as good a solution as you can get, however, if the collection is large relative to the size of edits performed then ToArray() is not the way to go, the reason for this is that a reasonable implementation of ToArray() would look like this::

Item[] array = new Item[collection.Count];
int i=0;
foreach (Item item in collection)

    array[i++] = item;
}
return array;

Clearly this would mean that the fix I proposed above would entail two complete enumerations through our data which would certainly make our processors cache put in some effort, and it also bloats our applications working set by having two copies of the collection in memory (on the other hand the second copy will be an array which mitigates it's presence somewhat).

When the collection is large compared to the number of updates being implemented it is probably best to create a seperate list to hold the edits and then enumerate them like so:

List<Item> updates = new List<Item>();
foreach (Item s in collection)
{
    if (s.expired == true)
    {
        updates .Add(s);
    }
}

foreach (Item s in updates)
{
    collection.Remove(s);
}


Recently I have been thinking about algorithms and .Net collections.  In this case I am using algorithm in the C++ STL (Standard Template Library) sense to mean some code that applies to all members of a collection, for instance a RemoveItems algorithm could be used for the example above to encapsulate the code that updates the collection away from my app code to a utility class somewhere.  The only problem is that I may want to remove items from a collection for lots of different reasons not just because the expired field is set to true, so how then can a developer parametize the decision for an algorithm, it turns out that a delegate is ideal for this type of customization.

My requirements for a RemoveItems algorithm are as follows:
  1. Work with all .Net collections
  2. Parameterisable decision point
  3. Not use a two pass enumeration scheme
The code I finally came up with looked like:

public static class RemoveItems<T>
{
    public delegate bool Decide(T item);
    public static void Execute(ICollection<T> collection, Decide decide)
    {
        List<T> removed= new List<T> ();
        foreach (T item in collection)
        {
            if(decide(item))
               removed.Add(item);
        }
        foreach(T item in removed)
        {
            collection.Remove(item);
        }
        removed.Clear();
    }
}

I can now rewrite my function that needs to eliminate expired items to call my algorithm like so:

RemoveItems<Item>.Execute(collection, delegate(Item item) { return item.expired == true; });

Note that I am using the new anonymous method feature of C# to allow me to express the decide method inline in my caller code.

The complete example program looks like this:

#region Using directives

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

#endregion

namespace UpdateIteration

{

    public static class RemoveItems<T>

    {

        public delegate bool Decide(T item);

        public static void Execute(ICollection<T> collection, Decide decide)

        {

            List<T> removed = new List<T>();

            foreach (T item in collection)

            {

                if(decide(item))

                    removed.Add(item);

            }

            foreach (T item in removed)

            {

                collection.Remove(item);

            }

            removed.Clear();

        }

    }

    class Program

    {

        class Item

        {

            public Item(string value, bool expired) { this.value = value; this.expired = expired; }

            public string value;

            public bool expired = false;

        }

        static void Main(string[] args)

        {

            List<Item> collection = new List<Item>();

            collection.Add(new Item("one", true));

            collection.Add(new Item("two", false));

            collection.Add(new Item("three", true));

            collection.Add(new Item("four", false));

            collection.Add(new Item("five", true));

            collection.Add(new Item("six", false));

            collection.Add(new Item("seven", true));

            collection.Add(new Item("eight", false));

            collection.Add(new Item("nine", true));

 

            RemoveItems<Item>.Execute(collection, delegate(Item item) { return item.expired == true; });

        }

    }

}

I have noticed that using delegates to parametize these types of algorithms is allowing me to write much more reusable and readable code.  You should take a look at the anonymous methods features and generic collections in the Beta 1 and soon the Beta 2 whidbey compilers and see how they affect your own programming style.

Posted by Kevin Ransom | 1 Comments

Some thoughts on the Global Assembly Cache and the compatibility of assemblies

Enormous Caveat  ---  what follows is my opinion and my interpretation of the opinions of people around me, it does not represent the advice of Microsoft or the .Net Framework team.

Over the last month I have been embroiled in a series of long and interesting email threads about whether assemblies should be added to the GAC or not.  This has highlighted that the advice that we have given up to now may be incomplete.  We haven't yet concluded the discussion and so the ultimate advice may well differ from the conclusions I reach in this article.

The thrust of the question is under what conditions should an application's assemblies be placed in the GAC?; additionally what are the compatibility burdens placed on the author of an assembly who includes public types and members in an assembly.  

The current advice of record is that the Global Assembly Cache should be used for assemblies that are shared between applications,  I.e. used by more than one app.  This yields the benefit that less disk space is used since each app references the same assembly, and also the installer can manage the number of applications that add the assembly and ensure that it is only removed when the final application that depends on it is uninstalled, and the ability to centrally administer assemblies is gained, the GAC is only be modifiable by an administrator by default and so assemblies are safe from tampering by normal users.   Another benefit is that the CLR can load assemblies from the GAC a bit faster than other locations.

You should note that assembly load is a one time event and so any performance benefit is probably tiny and not affected by how many time an assembly is loaded because the CLR satisfies subsequent load requests from the already loaded instance of the requested assembly.  AppDomain unloads when the assembly is not shared between app domains will cause the assembly to be unloaded.

  1. The first optimization is the for the CLR to look into the GAC first when resolving an Assembly reference.  If it doesn't find the requested assembly in the GAC then it looks in the application directory and follows the rest of the probing logic algorithm.  Clearly there is a performance gain by putting the assembly in the first place to be searched.
  2. When the CLR loads a strongly signed assembly it computes a hash for that assembly and compares it with the hash that was computed at compile time, or when the strong signature was applied.  If the hashes match then the CLR continues the load because it is sure the assembly was not tampered with.  When loading from the GAC the CLR doesn't need to do that, since the hashes were compared when the assembly was added to the GAC, and the assembly couldn't have been tampered with, except by an administrator who could have done much worse things to your PC just by virtue of being an admin.  Not recomposing the hash is beneficial because doing so touches the whole file which causes it to be loaded into memory potentially causing the virtual memory manger to page out information that may be accessed again shortly.  When loading an
  3. We can cache the resolutions to native images for assemblies that are loaded from the GAC and compiled using ngen.

These optimizations are sufficiently compelling to some people that the decision to store an assembly to GAC is not as simple as it used to be.  In the past it would be based solely on the intent to share the assembly between apps, or to satisfy a more complicated binding logic, for instance to make use of multiple versions of an assembly in a single application, or the type needed to be loaded in different AppDomains with separate AppBase directories.  Now the performance consideration is brought up more and more frequently.


Above it can be seen that GAC has the emergent property of being a performance accelerator - large or small performance enhancements are always welcome and therefore will likely be taken advantage of.  Which brings us to the issue that has been consuming our email bandwidth namely what are the compatibility issues that arise from extending the use of the GAC beyond shared assemblies.  By compatibility we are not talking about the policy that causes different versions of the assembly to be loaded, in this case it is more like source code compatibility, if an application is recompiled with the new assembly will it require any modifications to run correctly.  Clearly if compatibility is high then policy could be used to bind applications to the newer type but that is not the issue here.

A common understanding of developers I have discussed this with is as follows:

  • If my intent is for the assembly to be shared then any public or protected APIs that are exposed form a contract; it is my responsibility to ensure that the contract is honored in future versions.  When it was pointed out that any application that bound to the earlier version would still use the earlier version even in the presence of the later versionof the assembly it was still agreed that the original assembly created an expectation in the minds of customers that the contract should be kept.  This would mean that an API in an early version of an assembly must be present in subsequent assemblies with the same semantics unless decorated with the deprecated attribute.
  • If my intent is for the assembly to be an implementation detail then any public or protected APIs that are exposed do not form a contract and that no expectation is created in the minds of anyone about future versions of that assembly.  This would mean that an API in an early version of an assembly need not be present in subsequent assemblies.

We also considered the possibility that any public or protected API in an assembly created an expectation that it would exist in future versions of that assembly regardless of whether it was an implementation detail or part of a framework.  Everyone agreed  that this places a high burden on the developer of an assembly.  Some argued that even though the burden was high, it was still right because the developer of the assembly had exposed those APIs as public or protected.  In released versions of the .Net Framework it is pretty hard to minimize the public contract of an assembly but Whidbey has friend assemblies and so the public contract of assemblies which are part of a larger application can be minimized or even eliminated

One thing is certain, none of the current machinery of the CLR has identified a foolproof mechanism where a developer can identify an assembly as an implementation detail or a framework assembly.

It was suggested that because the GAC is for shared assemblies and that multiple versions could exist in it then a strong argument exists that by placing an assembly in the GAC then a developer is making a strong statement about future compatibility of the assemblies.  Assemblies not installed in the GAC could be considered implementation details whose future compatibility was not guaranteed or even likely.


Because of the changing nature of GAC it is probably no longer safe to assume that assemblies in the GAC may be reused at will.  Perhaps it never was safe.  It is also not safe to assume that an assemblies API set will remain constant unless the developer has specifically declared that to be the case.  A developer of any assembly should minimize the public API set to reduce the compatibility burden on hime for later versions of shipped assemblies.

The following recommendations are those that I think are prudent, I expect that by the time Whidbey is released the CLR will have published a comprehensive set of guidelines, they may well differ from these.

Recommendations:

  1. When consuming a third party assembly, examine the license agreement or any documentation provided by the author describing the conditions that apply to the reuse of that assembly.  If they haven't explicitly said that it is reusable, then you should avoid reusing it.
  2. If it is a reusable assembly, see what statements about future compatibility plans for that assembly the developer is prepared to make.  If the developer has not stated that future versions will maintain the current contracts to the best of their ability you should consider whether you wish to take that dependency.
  3. When developing an assembly regardless of your compatibility intent:
    • Keep public API's to the bare minimum because:
      • It will be the easier to produce future versions that are compatible
      • There will be fewer unintended dependencies taken on your assembly
      • There will be fewer potential security holes
    • Always set the ComVisible attribute to false if the assembly is not intended to be used from COM.
    • Never add the AllowPartiallyTrustedCallers attribute, unless it is required and you have performed the necessary security checks on your code
    • Consider adding a LinkDemand for StrongNameIdentityPermission to limit the callers to those assemblies signed with a specific key when the intent is not to share the assembly.
  4. When developing an application that consists of multiple assemblies make all of the types internal and use the new FriendAssemblies feature in Whidbey to restrict the assemblies that can consume them.

 

Until next time

 

Kevin

Posted by Kevin Ransom | 0 Comments

To foreach or not to foreach that is the question.

Recently email was forwarded to me with a link to a page with some performance tips for developers.  The second performance tip on the page was:

foreach

foreach through an array is incredibly slow compared to for (int i = 0; i < array.Length; i)

This one leapt out at me because it is well ... not to put too fine a point on it ... wrong!  (at least that is what I thought).  C# and Visual Basic 7 both contain specific optimizations to ensure that  foreach on arrays perform equivalently to for(int i=0; i < array.Length; i); I have written plenty of code assuming foreach and for(int i; ...) were equivalent.  To verify my assumption and to ensure that the code I wrote was as efficient as possible in the future I did a little experiment: I wrote a small C# application with 2 functions the first of which enumerated an array of integers using foreach, the second enumerated an array of integers using for(int ...).  I then used ildasm to disassemble the compiled binary to IL and then examined the resulting code.

Test code:

using System;

class MyClass
{
    static void One()
    {
        int[] a = new int[10000];
        foreach(int i in a)
            Console.WriteLine(i);
    }
    static void Two()
    {
        int[] a = new int[10000];
        for(int j=0; j<a.Length; j++)
       {
            int i = a[j];
            Console.WriteLine(i);
        }
    }
}

I saved this little program in to a c# source file called fe.cs and compiled it using the command line:

csc /t:library fe.cs

The .Net Framework sdk is installed with Visual Studio 7.0 and 7.1 or it can be downloaded from microsoft at: http://msdn.microsoft.com/netframework/downloads/updates/default.aspx .   The SDK contains a nifty utility called ildasm.exe.  If you have Visual Studio 7 or 7.1 installed then you already have the sdk installed.

Ildasm rather cleverly operates as either a command line tool or as a GUI depending.  For my purposes the command line was all that I required.  I created an IL file using the command (you may need to ensure that the path is set to the "Program Files\Microsoft.NET\SDK\v1.1\Bin" directory:

ildasm fe.dll /out:fe.il /text

then in an editor I examined this file's details, the interesting part for me is the method bodies for One (foreach use) and Two(for(int; ..) use.

    static void One()
    {
        int[] a = new int[10000];
        foreach(int i in a)
            Console.WriteLine(i);
    }
  static void Two()
    {
        int[] a = new int[10000];
        for(int j=0; j<a.Length; j++)
       {
            int i = a[j];
            Console.WriteLine(i);
        }
    }
.method private hidebysig static void One() cil managed
{
// Code size 38 (0x26)
.maxstack 2
.locals init (int32[] V_0,
int32 V_1,
int32[] V_2,
int32 V_3)
.method private hidebysig static void Two() cil managed
{
// Code size 36 (0x24)
.maxstack 2
.locals init (int32[] V_0,
int32 V_1,
int32 V_2)
IL_0000: ldc.i4 0x2710
IL_0005: newarr [mscorlib]System.Int32
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: stloc.2
IL_000d: ldc.i4.0
IL_000e: stloc.3
IL_000f: br.s IL_001f
IL_0000: ldc.i4 0x2710
IL_0005: newarr [mscorlib]System.Int32
IL_000a: stloc.0
IL_000b: ldc.i4.0
IL_000c: stloc.1
IL_000d: br.s IL_001d

IL_0011: ldloc.2
IL_0012: ldloc.3
IL_0013: ldelem.i4
IL_0014: stloc.1
IL_0015: ldloc.1
IL_0016: call void [mscorlib]System.Console::WriteLine(int32)
IL_001b: ldloc.3
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.3
IL_001f: ldloc.3
IL_0020: ldloc.2
IL_0021: ldlen
IL_0022: conv.i4
IL_0023: blt.s IL_0011
IL_000f: ldloc.0
IL_0010: ldloc.1
IL_0011: ldelem.i4
IL_0012: stloc.2
IL_0013: ldloc.2
IL_0014: call void [mscorlib]System.Console::WriteLine(int32)
IL_0019: ldloc.1
IL_001a: ldc.i4.1
IL_001b: add
IL_001c: stloc.1
IL_001d: ldloc.1
IL_001e: ldloc.0
IL_001f: ldlen
IL_0020: conv.i4
IL_0021: blt.s IL_000f
IL_0025: ret
} // end of method MyClass::One

IL_0023: ret
} // end of method MyClass::Two

If you examine the IL for each version of the function you will see that the red is basic variable initialization, creating the array setting loop counters to zero, that kind of thing.  The bold blue text is the body of the loop, close examination shows that they are effectively identical.  Which confirms my original belief that there is no performance benefit to be gained by replacing foreach(...) with for(int i; ...).

There are circumstances where foreach() introduces a performance penalty; but when the compiler can statically determine that the collection is infact an array, then foreach performs exactly the same as the equivalent hand coded loop.

I hope to visit managed collections again in the future.

 

 

Early Technology Adoption Programs

Microsoft Business Framework team has begun an early Technology Adoption Program that is currently engaging with ISV’s focusing on business applications development. Although the Technology Adoption Program is currently near full there will be other opportunities to engage with the MBF tools.

The plan includes the following:

  1. Technology Adoption Program for 50-100 ISV’s that have strategic market share in select geographic areas developing or maintaining a business application.
  2. An Alpha release of less than or equal to 500 developers leveraging Microsoft technology currently experienced in their own business application maintenance or development.
  3. Beta release of >2000 developers focusing on business application development.

If you feel you would be interested in being added to a list to be contacted when a beta program is ready to accept applications please email your contact information with a Subject: Add me to your MBF Beta nominations list to: contact.

Please include:

  • Passport Login Email name:
  • First Name: Last Name:
  • Company: Address:
  • City: State: Postal Code:
  • County:
  • Email:
  • Phone:

Thank you for your interest in being apart of the exciting work we are doing here at Microsoft.

Posted by Kevin Ransom | 6 Comments

Developing the Microsoft Business Framework

 

Hello, World!

I am a Development Lead on the Microsoft Business Framework, with responsibilities for XmlSerialization, Web Services and the Web Service Designer.  I have opinions on almost every topic going (I even have opinions on “not having an opinion”).  I have been at Microsoft for a little over 4 years and intend to never leave.  As a Developer I tend to think and talk about features in the small;  I will leave it to my program management and architecture colleagues to paint the larger picture for you.

We are currently working on version 1.0 of the Business Framework.  It will offer a collection of class libraries and a set of Visual Studio hosted development tools.  The class libraries support the creation and deployment of an object layer around a set of database tables that we call Business Entities and a means of assigning Business Logic to the appropriate tier in your architecture Business Operations and Collaborations.  Obviously MBF does much more than that but in the main the extra features support and extend the scenarios enabled by Entities, Operations and Collaborations.

The following list of features supported by our runtime is far from exhaustive but may give you a flavour of what is coming:

Operations

Entities

Collaborations

Runtime Metadata service

Object - Data Mapping

Data Location policy

Role based security

Service Binding

Service Activation and Remoting

Validation

Caching

Configuration

Serialization/Deserialization of Entities

Web Service integration

XSD Schema generation from a graph of Entities

Brokered Events

Administration / Configuration tools

Reliable Operations

Business Intelligence

Business Intelligence Programing model

 

 

The following lists the tools and features which we will provide as an add-in to Visual Studio again it's not exhaustive and the names will probably change closer to release.

Business Entity Designer

Web Service Designer

Component editor

Visual Studio Project Integration

Code generation

Metadata explorer

Metadata editor

Business Intelligence Wizard

Map designer

The Microsoft Business Framework is 100% managed code, written from the ground up in C# using the Whidbey toolset.  We sit on top of a host of new technologies the database persistence is ObjectSpace, the Visual Studio modelling tools are written on top of Whitehorse, the Business Intelligence technology works against Yukon, the Mbf Serializer integrates seemlessly with the .Net Framework Xml Serialization stack and Business Operations are exposed as ASP.Net Web Services.

One of the more interesting and challenging aspects of our team is that it is geographically diverse.  This causes us interesting challenges that are new to most of us.  I will undoubtedly mention this fact again in the future since it has an important fact on how we develop.  There are four locations where MBF is developed - by developed I mean features are designed and coded.  The four locations are Redmond, Fargo - North Dakota, Findlay - Ohio and Copenhagen - Denmark (although it seems silly to qualify Wonderfull ... Wonderfull ... Copenhagen).  What this means is that scheduling tele-conferences between locations is pretty tricky, Copenhagen goes home before Redmond gets to work; when it is required to tele-conference the Redmond members get in early and the Copenhagen attendees stay late.

I am most closely involved in the Web Services integration, the Web Services Designer, Xml Serialization and Web Services and tangentially involved with the Entity Designer.  I will mainly use this column to discuss these features as well as general MBF programming model issues and what life is like at Microsoft.

If there is any topic you would like to discuss feel free to ask.  If I don't have an opinion - and you already know that I will - I will try to corral someone who really understands and get them to help out.

My next post will describe how we integrate with the .Net Framework serializer to enable Entity serialization.  Hopefully it will include some code if I can remember how to program.

 

Kevin

SDE Lead - MBF Web Services, Redmond

This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at: http://www.microsoft.com/info/cpyright.htm"

 
Page view tracker