June 2007 - Posts

I am at New York this week. I will be speaking tomorrow at SOAWorld about Web Services interoperability; we have some cool demos showing interoperability between WCF and different J2EE-J2SE technologies. If you are in the area or attending to the conference just swing by and we can have a chat.
Posted by gsusx | 1 comment(s)

Long running WCF Services is another great feature of the new Orcas Beta 1. Implementing durable service is always a combination of persisting the message itself as well as the state of the service. The current version of WCF provides stateful WCF services using sessions. However this feature does not address a lot of the most common enterprise stateful services scenarios on which state of an service instance needs to be persisted to a more robust durable store in order to handle unexpected events like host recycling, server shutdown, etc. Other Microsoft technologies such as BizTalk Server and Windows Workflow Foundation (WF) do provide support for long running services. Specifically, WF provides an extensible model based on persistence services that allow developers to create their own mechanisms for persisting the state of a WF instance.

The upcoming version of the .NET Framework (3.5) implements a similar mechanism for persisting the state of WCF Services. The following sections will describe the steps required to implement truly WCF stateful services using Orcas Beta 1. Let’s take the following service contract that describes a stateful interaction:

[ServiceContract]

public interface ITextComposer

{

 

    [OperationContract]

    string PowerOn(string text);

 

    [OperationContract]

    string InsertText(string text);

 

    [OperationContract]

    string DeleteText(string text);

 

   [OperationContract]

    void PowerOff();

}

Figure 1: Service Contract

To customize the contract implementation as a durable service we use the DurableService and DurableOperation behaviors. Similarly to the current support for session management in .NET 3.0, we can use behavior parameters to configure the lifetime of an instance. On our example the PowerOn and PowerOff operations set the instantiation and completion of a service instance.

[Serializable]

[DurableServiceBehavior]

public class TextComposer : ITextComposer

{

    private string CurrentText ;

 

    [DurableOperationBehavior(CanCreateInstance = true)]

    public string PowerOn(string text)

    {

        CurrentText = text;

        return CurrentText;

    }

 

    [DurableOperationBehavior()]

    public string InsertText(string text)

    {

        CurrentText += " " + text;

        return CurrentText;

    }

 

    [DurableOperationBehavior()]

    public string DeleteText(string text)

    {

        CurrentText = CurrentText.Replace(text, "");

        return CurrentText;

    }

 

   [DurableOperationBehavior(CompletesInstance=true)]

    public void PowerOff()

    {

    }

}

Figure 2: WCF Service implementation

Now it is time to configure the persistent service to be used to serialize the state of the service instance. For that we use the new wsHttpContextBinding which allows passing the state information in the form of Http headers. The SDK includes the scripts to create the database used by the default persistent service. As I mentioned before this process is very similar to configuring the WF Persistent Service.

<?xml version="1.0"?>

<configuration>

          <system.serviceModel>

                   <services>

                             <service name="TextComposer" behaviorConfiguration="ServiceBehavior">

                                      <endpoint address="ContextOverHttp" binding="wsHttpContextBinding" bindingConfiguration="DemoBinding" contract="ITextComposer" />

                             </service>

                   </services>

                   <behaviors>

                             <serviceBehaviors>

                                      <behavior name="ServiceBehavior">

                                                <serviceMetadata httpGetEnabled="true"/>

                                                <serviceDebug includeExceptionDetailInFaults="true"/>

                                                <serviceCredentials>

                                                          <windowsAuthentication allowAnonymousLogons="false" includeWindowsGroups="true"/>

                                                </serviceCredentials>

                                                <persistenceProvider type="System.ServiceModel.Persistence.SqlPersistenceProvider, System.WorkflowServices, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DurableServiceStore" persistenceOperationTimeout="00:00:10" lockTimeout="00:01:00" serializeAsText="true"/>

                                      </behavior>

                             </serviceBehaviors>

                   </behaviors>

 

    <bindings>

      <wsHttpContextBinding>

        <binding name="DemoBinding">

          <security mode="None" />

        </binding>

      </wsHttpContextBinding>

    </bindings>

   

          </system.serviceModel>

          <connectionStrings>

                   <add name="DurableServiceStore" connectionString="Data Source=localhost\SQLEXPRESS;Initial Catalog=ServiceState;Integrated Security=SSPI"/>

          </connectionStrings>

          <system.web>

                   <compilation debug="true">

                             <assemblies>

                                      <add assembly="System.WorkflowServices, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

      </assemblies>

    </compilation>

  </system.web>

</configuration>

Figure 3: WCF Service Host configuration

Now we are ready to create our WCF client. Although this is very similar to the traditional process we follow with NET 3.0 the implementation code presents some interesting differences.

ServiceReference.TextComposerClient proxy = new ClientApp.ServiceReference.TextComposerClient();

using(new OperationContextScope((IContextChannel)proxy.InnerChannel))

  {

            string text= proxy.PowerOn("Text...");

            context = ContextManager.ExtractContextFromChannel(proxy.InnerChannel);

            ContextManager.ApplyContextToChannel(Context, proxy.InnerChannel);

            text = proxy.InsertText("First line ");

            text = proxy.InsertText("Second line");

      }

Figure 4: Client code

The call to PowerOn on this code produces the following HTTP-SOAP request and response message.

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
    <s:Header>
        <a:Action s:mustUnderstand="1">http://tempuri.org/ITextComposer/PowerOn</a:Action>
        <a:MessageID>urn:uuid:4bbd8b6e-8a27-4515-8856-f4e3aa684983</a:MessageID>
        <a:ReplyTo>
            <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
        </a:ReplyTo>
        <a:To s:mustUnderstand="1">http://localhost:1242/TextComposer/Service1.svc/ContextOverHttp</a:To>
    </s:Header>
    <s:Body>
        <PowerOn xmlns="http://tempuri.org/">
            <text>Text...</text>
        </PowerOn>
    </s:Body>
</s:Envelope>

Figure 5: HTTP-SOAP request

HTTP/1.1 200 OK

Server: ASP.NET Development Server/9.0.0.0

Date: Tue, 12 Jun 2007 05:08:27 GMT

X-AspNet-Version: 2.0.50727

Set-Cookie: WscContext=PEFycmF5T2ZLZXlWYWx1ZU9mUU5hbWVzdHJpbmcgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzL…context state

Cache-Control: private

Content-Type: application/soap+xml; charset=utf-8

Content-Length: 559

Connection: Close

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
    <s:Header>
        <a:Action s:mustUnderstand="1">http://tempuri.org/ITextComposer/PowerOnResponse</a:Action>
        <a:RelatesTo>urn:uuid:4bbd8b6e-8a27-4515-8856-f4e3aa684983</a:RelatesTo>
        <Context xmlns="http://schemas.microsoft.com/ws/2006/05/context">
            <InstanceId>e32c3fd9-8f56-4024-b818-4f920813d313</InstanceId>
        </Context>
    </s:Header>
    <s:Body>
        <PowerOnResponse xmlns="http://tempuri.org/">
            <PowerOnResult>Text...</PowerOnResult>
        </PowerOnResponse>
    </s:Body>
</s:Envelope>

Figure 6: HTTP-SOAP response

The highlighted lines on figure 4 leverage a helper class for manipulating the service state headers provided as part of the .NET Framework 3.5 SDK. Specifically, the ExtractContectFromChannel operation retrieves the instance key from the SOAP headers .

static public IDictionary<XmlQualifiedName, string> ExtractContextFromChannel(IClientChannel channel)

        {   // extract context from channel

            IContextManager cm = channel.GetProperty<IContextManager>();

            if (cm != null)

            {   // attempt to extract context from channel

                return cm.GetContext();

            }

            else if (OperationContext.Current != null)

            {   // attempt to extract context from HttpCookie

                CookieContainer cookies = new CookieContainer();

                if (OperationContext.Current.IncomingMessageProperties.ContainsKey(HttpResponseMessageProperty.Name))

                {

                    HttpResponseMessageProperty httpResponse = (HttpResponseMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name];

                    cookies.SetCookies(ccUri, httpResponse.Headers[HttpResponseHeader.SetCookie]);

                }

                if (cookies.Count > 0)

                {   // put WscContext cookie into dictionary

                    Dictionary<XmlQualifiedName, string> newContext = new Dictionary<XmlQualifiedName, string>();

                    foreach (Cookie cookie in cookies.GetCookies(ccUri))

                    {

                        if (cookie.Name.Equals(WscContextKey))

                        {

                            newContext.Add(new XmlQualifiedName(WscContextKey), cookie.Value);

                            break;

                        }

                    }

                    return newContext;

                }

            }

            return null;

        }

  Figure 7: ExtractContectFromChannel helper

Similarly, the ApplyContextToChannel operation adds the specific state to the operation context.

 static public bool ApplyContextToChannel(IDictionary<XmlQualifiedName, string> context, IClientChannel channel)

        {

            if (context != null)

            {

                IContextManager cm = channel.GetProperty<IContextManager>();

                if (cm != null && cm.GetContext() == null)

                {   // apply context to ContextChannel

                    cm.SetContext(context);

                    return true;

                }

                else if (OperationContext.Current != null)

                {   // apply context as HttpCookie

                    CookieContainer cookies = new CookieContainer();

                    foreach (KeyValuePair<XmlQualifiedName, string> item in context)

                    {

                        cookies.Add(ccUri, new Cookie(item.Key.ToString(), item.Value));

                    }

                    if (cookies.Count > 0)

                    {

                        HttpRequestMessageProperty httpRequest = new HttpRequestMessageProperty();

                        OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest);

                        httpRequest.Headers.Add(HttpRequestHeader.Cookie, cookies.GetCookieHeader(ccUri));

                        return true;

                    }

                }

            }

            return false;

        }

 

  Figure 8: ApplyContextToChannel helper

The SQL Server persistent service allows the WCF Service instance state to be persisted even if the host gets recycled. This is a great feature that can be combined with the use of some channels such as MSMQ or Service Broker in order to complement the stateful WCF services with durable messaging.

Posted by gsusx | 10 comment(s)
Thanks to all the people who attended to my session at Teched. The sample code and presentation can be downloaded from here. The Before folder contains the template code we used to create the adapter. Similarly, the After folder contains the final code of the adapter.
Posted by gsusx | 3 comment(s)
More Posts