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.