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"> |
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
|
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;
} |
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;
}
|
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.