Enabling WCF Duplex Channel in DSA solution
One of the features included in Smart Client Software Factory (May 2007) is the Disconnected Service Agent (DSA).
A service agent provides an abstraction of a service (typically a web service) and implements a set of features and capabilities that make it convenient and effective to use the service. A Disconnected Service Agent is a service agent that provides offline capabilities; this is useful for occasionally connected applications.
The Smart Client Software Factory Source Code includes the Disconnected Service Agent (with CAB) QuickStart. This QuickStart uses a simple Windows Communication Foundation (WCF) service to retrieve a list of restaurants and menu items. When you click a restaurant on the Shell, the application uses a Disconnected Service Agent (DSA) to send requests to the WCF service, and displays the results (if the application is online).
Note: I recommend you to review the original QuickStart before going on reading. You can find information about the original QuickStart in the topic Inspecting the Software Factory Assets | QuickStars | Offline Application Blocks QuickStarts | Disconnected Service Agent (with CAB) QuickStart from the SCSF Help.
In this post I will show you how to update the DSA QuickStart to add support for a WCF Duplex Service to the Disconnected Service Agent.
A duplex service contract is a message exchange pattern in which both endpoints can send messages to the other independently. A duplex service, therefore, can send messages back to the client endpoint, providing event-like behavior. Duplex communication occurs when a client connects to a service and provides the service with a channel on which the service can send messages back to the client.
Restaurant Service
First, you must update the IMenuService service contract in order to specify in the ServiceContractAttribute the callback contract (using the CallbackContract property) that the client must implement in order to participate in a duplex conversation.
Also, in the OperationContractAttribute of the interface methods, we must specify IsOneWay = true property because the operations do not return reply messages.
[ServiceContract(CallbackContract = typeof(IMenuServiceCallback), SessionMode = SessionMode.Required)]
public interface IMenuService
{
[OperationContract(IsOneWay = true)]
void RequestMenuItems(string restaurantId);
[OperationContract(IsOneWay = true)]
void RequestRestaurants();
}Then, we define the callback contract. The remote service will use this contract to send us the operation results.
public interface IMenuServiceCallback
{
[OperationContract(IsOneWay = true)]
void MenuItemsCallback(MenuItem[] items);
[OperationContract(IsOneWay = true)]
void RestaurantsCallback(Restaurant[] restaurants);
}
DuplexChannelFactory
The WCFProxyFactory included in the Microsoft.Practices.SmartClient.DisconnectedAgent assembly it doesn’t supports Duplex Channel. Because of that, we need to implement a new factory. Our factory will extend the existing WCFProxyFactory class and its name will be DuplexChannelFactory.
This is the signature for the DuplexChannelFactory class:
public class DuplexChannelFactory<TChannel> : WCFProxyFactory<TChannel>
where TChannel : class
In the factory, override the GetOnlineProxy method in order to provide the required functionality for Duplex Channel support. To make the factory generic, also add a new property that users will use to set the Callback type.
private static DuplexClientBase<TChannel> _onlineProxy;
private static Type _onlineProxyType;
// We are caching the proxy because it has to be alive
// to receive the callback
private static DuplexClientBase<TChannel> OnlineProxy
{
get
{
if (_onlineProxy == null)
{
// To receive the callback, we have to specify the callback handler
InstanceContext instanceContext = new InstanceContext(GetCallbackHandler());
_onlineProxy = (DuplexClientBase<TChannel>)Activator.CreateInstance(_onlineProxyType, instanceContext);
}
return _onlineProxy;
}
}
public override object GetOnlineProxy(Request request, string networkName)
{
if (request == null)
new ArgumentNullException("request");
_onlineProxyType = request.OnlineProxyType;
// Set the credentials
ClientCredentials clientCredentials = OnlineProxy.ClientCredentials;
if ((EndpointCatalog != null) && (EndpointCatalog.Count > 0) && (EndpointCatalog.EndpointExists(request.Endpoint)))
{
NetworkCredential networkCredential = EndpointCatalog.GetCredentialForEndpoint(request.Endpoint, networkName);
clientCredentials.UserName.UserName = networkCredential.UserName;
clientCredentials.UserName.Password = networkCredential.Password;
EndpointAddress address = new EndpointAddress(EndpointCatalog.GetAddressForEndpoint(request.Endpoint, networkName));
OnlineProxy.Endpoint.Address = address;
}
return OnlineProxy;
}
private static Type _callback;
public static void SetCallbackHandler(Type callback)
{
_callback = callback;
}
private static object GetCallbackHandler()
{
if (_callbackInstance == null)
_callbackInstance = Activator.CreateInstance(_callback);
return _callbackInstance;
}Quickstart Changes
The DSA Recipes generates a Callback class. In our custom implementation, this class implements the IMenuServiceCallback interface. This means that the remote service will invoke callbacks in this class.
public class Callback : CallbackBase, IMenuServiceCallback
{
#region IMenuServiceCallback Members
public void MenuItemsCallback(MenuItem[] items)
{
if (GetMenuItemsReturn != null)
{
GetMenuItemsReturn(this, new EventArgs<MenuItem[]>(items));
}
}
public void RestaurantsCallback(Restaurant[] restaurants)
{
if (GetRestaurantsReturn != null)
{
GetRestaurantsReturn(this, new EventArgs<Restaurant[]>(restaurants));
}
}
#endregion
//...
}To have the DSA block use our custom factory, you must modify the Agent class created by the Add Disconnected Service Agent as shown in the following code:
public static OfflineBehavior GetAgentDefaultBehavior()
{
OfflineBehavior behavior = new OfflineBehavior();
behavior.MaxRetries = 3;
behavior.Stamps = 1;
behavior.Expiration = DateTime.Now + new TimeSpan(1, 0, 0, 0);
behavior.ProxyFactoryType = typeof(DuplexChannelFactory<QuickStart.RestaurantModule.MenuService.IMenuService>);
return behavior;
}Finally, in the RestaurantModule class, set the callback type for the factory. You set the callback type by invoking the SetCallbackHandler method on the factory as shown below.
private void AddServices()
{
DuplexChannelFactory<QuickStart.RestaurantModule.MenuService.IMenuService>.SetCallbackHandler(typeof(Callback));
WorkItem.Services.AddNew<Agent>();
}
You are ready to run the QuickStart. With the changes made, the client sends requests to the WCF service but it does not wait for the service responses; instead, the remote service invokes callbacks in an instance of the Callback class (which implements the callback contract, IMenuServiceCallback) when the operations are completed.
Important: The code available for download is provided "as is" with no warranties of any kind.
Attachment(s): MyCustomSessionState.zip