Orcas Durable Services

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.

2 Comments

  • I really want to post something on my blog about this, but this comment will have to do in the meantime.

    Explicit state management is what scales and is robust in the face of failure. This solution seems, to me, to be lacking in those respects. It may also just be me, but the solution seems quite complex. What about something more similar to long-running workflows?

    Am I missing something here?

  • Hi,
    Your article is really useful for beginners who are exploring Durable services.
    Found one issue with the code though. In Fig. 2, you marked TextComposer service with a "DurableServiceBehavior" attribute instead of "DurableService". This is a typo, did trouble for a while though :). Hope you can correct it.
    Thanks for the post.

Comments have been disabled for this content.