Introduction
This article could be considered as a continuation of the WS-Transfer Service for Workflow article written by Roman Kiss. I've implemented the WS-Enumeration spec based on the approach suggested by Roman Kiss. Utilizing the sequence workflows for service or protocols development attracts me since it is a very natural way to do it. In many cases, you need to just look at the scheme in order to understand how the service or protocol operates.
The purpose of WS-Enumeration is to enumerate the elements in different structures (it could be a list, tree etc.). So one may suppose that LINQ could be helpful for it. Thinking of it, I recall the LINQ to Tree - A Generic Technique for Querying Tree-like Structures article written by Colin Eberhardt. Colin's main idea was to apply the "LINQ to XML" technique to non-XML tree structures. Colin developed the ILinqToTree<T>
adapter interface and a set of extension methods which allows to execute queries in a unified way for different tree structures whether it is a file system or a database or something else. Since it is right what I need for WS-Enumeration, I utilized Colin's approach here.
Background
In contrast to WS-Transfer, WS-Enumeration is a stateful protocol developed to enumerate elements based on filter. Once the enumeration is done, the client pulls the instances page-by-page.
The protocols works as follows:
- Client sends the enumerate request.
- Based on the enumeration request parameters, the service constructs the enumeration, the context (identifier of enumeration), and returns it to the client.
- Client sends a pull request, which contains the "enumeration context" and the number of instances to be returned. Because the enumeration context is required, a pull request is only valid after a successful enumerate request and before the enumeration context expires. This ordering is why we call WS-Enumeration stateful.
- The server returns data to the client in the pull response. In order to represent instances in XML, we utilize the
IXmlSerializable
interface. - The client keeps pulling data until it receives an end of sequence notice.
Enumerate
When processing the enumerate request, the service should prepare the enumeration based on the supplied filter. An enumerate request may have Filter and Dialect attributes. The Filter expression could be XPath, regex, or something else. This Filter behavior should be reflected in the Dialect attribute. In case the service receives an enumerate request with an unsupported dialect, a fault should be send to the client. Once the enumeration and the context are prepared, the service returns the "context" to the client.
Pull
The Pull request should contain the obligatory "context" parameter. The context is like a unique identifier of the enumeration stored in the service. The pull operation requests the next N object instances for a given enumeration context, where N is provided in the request. The Service returns at most N XML representations of these object instances in the response. Each of the returned objects satisfies the filter in the enumerate request.
GetStatus
The "enumeration context" has a limited life time. The GetStatus request is designed for getting the expiration time of the specified context.
Renew
This request asks the service to prolong the context life time. This operation could be helpful during long-time pull operations.
Release
The Release request initiates the deletion of the context.
Concept and design implementation
Virtually, the project could be divided in to two loosely coupled parts: communication layer and business logic.
- Communication layer - WCF-Service.
- Business logic - (Adapter -> Workflow -> LinqToTree) the layer which processes the client's requests.
The adapter is a connector between the communication layer and the business logic. The service behavior extensions allow to configure the service behavior through a configuration file. The value of the "type" attribute determines the type of the adapter we want to utilize.
<configuration>
<system.serviceModel>
<diagnostics wmiProviderEnabled="true"/>
<services>
<service name="WSEnumeration.WSEnumerationService"
behaviorConfiguration="WxfServiceExtension">
<endpoint address="net.tcp://localhost:11111/PublicStorage"
binding="netTcpBinding"
bindingConfiguration="Binding1"
contract="WSEnumeration.IWSEnumeration"/>
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="Binding1" transactionFlow="true"/>
</netTcpBinding>
</bindings>
<extensions>
<behaviorExtensions>
<add name="WSEnumerationAdapter"
type="WSEnumeration.ServiceAdapterBehaviorElement,
WSEnumeration, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null"/>
<add name="logger"
type="RKiss.Logger.LoggerBehaviorElement, Logger,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior name="WxfServiceExtension">
<WSEnumerationAdapter StoragePath="c:\"
ExpirationType="T|D|H|M"
Filtering="true"
FilterDialect="XPath"
type="WSEnumeration.Adapters.WFAdapter, WSEnumeration"
WorkflowTypeEnumerate=
"WSEnumerationWorkflow.FileSystem.WorkflowEnumerate,
WSEnumerationWorkflow"
WorkflowTypePull=
"WSEnumerationWorkflow.FileSystem.WorkflowPull,
WSEnumerationWorkflow"
WorkflowTypeRenew=
"WSEnumerationWorkflow.FileSystem.WorkflowRenew,
WSEnumerationWorkflow"
WorkflowTypeGetStatus=
"WSEnumerationWorkflow.FileSystem.WorkflowGetStatus,
WSEnumerationWorkflow"
WorkflowTypeRelease=
"WSEnumerationWorkflow.FileSystem.WorkflowRelease,
WSEnumerationWorkflow"/>
<logger enable="true" logAfterReceiveRequest="true"
logBeforeSendReply="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Here are the meanings of the WSEnumerationAdapter
element attributes:
Attribute | Description |
StoragePath | Path to the resource storage. It could be a file system path or a connection string in the case of a database or something else. |
ExpirationType | The expiration time may be either an absolute time or a duration. This attribute determines which expiration type the service supports. "T|D|H|M" means: T-absolute time, D - duration in days, H - duration in hours, M duration in minutes. |
Filtering | Value of this attribute reflects the filtering support. |
FilterDialect | Value of this attribute reflects the dialects which the service is able to understand. For example, if the value is "XPath|Regex", it means that the service can understand XPath and Regex filter expressions. |
Type | Adapter type |
WorkflowTypeEnumerate | Type of the workflow class for the Enumerate request. |
WorkflowTypePull | Type of the workflow class for the Pull request. |
WorkflowTypeRenew | Type of the workflow class for the Renew request. |
WorkflowTypeGetStatus | Type of the workflow class for the GetStatus request. |
WorkflowTypeRelease | Type of the workflow class for the Release request. |
So through a configuration file, we are potentially able to configure our service to work with a file system or a database or something else. Of course, just in cases where we've already developed the corresponding business logic layer. In this project, I've developed a business logic intended to work with a file system.
Test
In order to demonstrate how the WS-enumeration works, I have created a demo application which allows the user to navigate the file system on a service host.
In order to set the Service host resource directory, you have to set the "StoragePath" attribute in the configuration file. Now you are ready to start the service. The service host conversation with the client is displayed by the logger written by Roman Kiss. The client application represents a single form with a DataGridView
which displays the content of some directory located at the service host.
Conclusion
Like language patterns (Singleton, etc.) or architectural patterns (MVC, MVVM etc.), WS-* spec provides us a way to standardize the development of service oriented applications (SOA).
At this moment, the project is in developing stage, and does not have production quality. Nevertheless, it can be used for research or test purposes.
History
- 04/02/2011 - Initial release.