Click here to Skip to main content
15,892,298 members
Articles / Programming Languages / C#

Creating a Generic (Static and Unchanging) WCF Interface

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
11 Dec 2012CPOL10 min read 33.3K   430   33  
Learn how to create a static and unchanging WCF interface that still supports most WCF capabilities.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;

namespace Contracts
{
	public delegate void OnSendOneWay<T>(T message);
	public delegate V OnSendRequestResponse<T, V>(T message);
	public delegate V OnSendNullRequestResponse<V>();

	/// <summary>
	/// Abstract base class for a WCF service implementing IWcfContract.
	/// </summary>
	/// <typeparam name="TTopic">TopicID enumeration type with an underlying type of int</typeparam>
	/// <typeparam name="TMessage">MessageID enumeration type with an underlying type of int</typeparam>
	public abstract class ServiceWrapperBase<TTopic, TMessage> : IWcfContractWithCallback, IWcfContract
		where TTopic : struct, IConvertible
		where TMessage : struct, IConvertible
	{
		// Dictionaries of serializers and deserializers.  
		// Note that we have to have two dictionaries here, because there might exist a serializer and a deserializer for a single topic/message pair.
		Dictionary<Tuple<int, int>, Delegate> deserializers = new Dictionary<Tuple<int, int>, Delegate>();
		Dictionary<Tuple<int, int>, Delegate> serializers = new Dictionary<Tuple<int, int>, Delegate>();

		// Dictionary of topic/message > method delegate mapping
		Dictionary<Tuple<int, int>, Delegate> handlers = new Dictionary<Tuple<int, int>, Delegate>();

		bool supportsDuplex;

		/// <summary>
		/// Doing some validation of the types passed in to the class
		/// </summary>
		public ServiceWrapperBase(bool supportsDuplex)
		{
			this.supportsDuplex = supportsDuplex;

			if (!typeof(TTopic).IsEnum || !typeof(TMessage).IsEnum)
			{
				throw new ArgumentException("TTopic and TMessage must be an enum type");
			}

			if (typeof(TTopic).GetEnumUnderlyingType() != typeof(int) || typeof(TMessage).GetEnumUnderlyingType() != typeof(int))
			{
				throw new ArgumentException("TTopic and TMessage must have an underlying type of 'int'");
			}
		}

		/// <summary>
		/// Register a handler for a one-way WCF call by passing in the topic id, message id, and delegate pointing to your handler method.
		/// </summary>
		/// <typeparam name="TSendMessage">Type of the message that is going to be handled</typeparam>
		/// <param name="topicId">The topic ID of the message</param>
		/// <param name="messageId">the message ID of the message</param>
		/// <param name="handler">The OnSendOneWay delegate that will handle any incoming messages for this topic and message ID.</param>
		public void RegisterHandler<TSendMessage>(TTopic topicId, TMessage messageId, OnSendOneWay<TSendMessage> handler)			
			where TSendMessage : class
		{
			RegisterDeserializer<TSendMessage>(topicId, messageId);

			lock (handlers)
				handlers.Add(new Tuple<int, int>(topicId.ToInt32(CultureInfo.CurrentCulture), messageId.ToInt32(CultureInfo.CurrentCulture)), handler);
		}

		/// <summary>
		/// Register a handler for a two-way WCF call that returns TReceiveMessage by passing in the topic id, message id, and delegate pointing to your handler method.
		/// </summary>
		/// <typeparam name="TSendMessage">Type of the message that is going to be handled</typeparam>
		/// <typeparam name="TReceiveMessage">Type of the return value that this handler will output</typeparam>
		/// <param name="topicId">The topic ID of the message</param>
		/// <param name="messageId">the message ID of the message</param>
		/// <param name="handler">The OnSendRequestResponse delegate that will handle any incoming messages for this topic and message ID.</param>
		public void RegisterHandler<TSendMessage, TReceiveMessage>(TTopic topicId, TMessage messageId, OnSendRequestResponse<TSendMessage, TReceiveMessage> handler) 
			where TSendMessage : class
			where TReceiveMessage : class			
		{
			RegisterDeserializer<TSendMessage>(topicId, messageId);
			RegisterSerializer<TReceiveMessage>(topicId, messageId);

			lock (handlers)
				handlers.Add(new Tuple<int, int>(topicId.ToInt32(CultureInfo.CurrentCulture), messageId.ToInt32(CultureInfo.CurrentCulture)), handler);
		}

		/// <summary>
		/// Register a handler for a two-way WCF call that returns null by passing in the topic id, message id, and delegate pointing to your handler method.
		/// </summary>		
		/// <typeparam name="TReceiveMessage">Type of the return value that this handler will output</typeparam>
		/// <param name="topicId">The topic ID of the message</param>
		/// <param name="messageId">the message ID of the message</param>
		/// <param name="handler">The OnSendRequestResponse delegate that will handle any incoming messages for this topic and message ID.</param>
		public void RegisterHandler<TReceiveMessage>(TTopic topicId, TMessage messageId, OnSendNullRequestResponse<TReceiveMessage> handler)			
			where TReceiveMessage : class
		{			
			RegisterSerializer<TReceiveMessage>(topicId, messageId);

			lock (handlers)
				handlers.Add(new Tuple<int, int>(topicId.ToInt32(CultureInfo.CurrentCulture), messageId.ToInt32(CultureInfo.CurrentCulture)), handler);
		}

		/// <summary>
		/// Unregister a handler
		/// </summary>
		/// <param name="topicId">topic ID of the handler to unregister</param>
		/// <param name="messageId">message ID of the handler to unregister</param>
		public void UnregisterHandler(TTopic topicId, TMessage messageId)
		{
			Tuple<int, int> key = new Tuple<int, int>(topicId.ToInt32(CultureInfo.CurrentCulture), messageId.ToInt32(CultureInfo.CurrentCulture));

			lock (handlers)
				if (handlers.ContainsKey(key))
					handlers.Remove(key);

			if (serializers.ContainsKey(key))
				serializers.Remove(key);

			if (deserializers.ContainsKey(key))
				deserializers.Remove(key);
		}
		
		/// <summary>
		/// Register a deserializer associated with this topic and message ID
		/// </summary>
		private void RegisterDeserializer<T>(TTopic topicId, TMessage messageId)
		{
			Func<byte[], T> deserialize = SerializationHelper.Deserialize<T>;

			deserializers.Add(new Tuple<int, int>(topicId.ToInt32(CultureInfo.CurrentCulture), messageId.ToInt32(CultureInfo.CurrentCulture)), deserialize);
		}

		/// <summary>
		/// Register a serializer associated with this topic and message ID
		/// </summary>
		private void RegisterSerializer<T>(TTopic topicId, TMessage messageId) where T : class
		{
			Func<T, byte[]> serialize = SerializationHelper.Serialize;

			serializers.Add(new Tuple<int, int>(topicId.ToInt32(CultureInfo.CurrentCulture), messageId.ToInt32(CultureInfo.CurrentCulture)), serialize);
		}

		/// <summary>
		/// Call the handler registered for this topic and message id
		/// </summary>		
		private byte[] CallHandler(int topicId, int messageId, byte[] message, bool isOneWay)
		{
			Tuple<int, int> key = new Tuple<int, int>(topicId, messageId);

			lock (handlers)
				if (!handlers.ContainsKey(key))
					throw new FaultException("Method not supported");

			if (isOneWay)
			{
				lock (handlers)
					handlers[key].DynamicInvoke(deserializers[key].DynamicInvoke(message));

				return null;
			}
			else
			{
				object serializedObject;

				if (message == null)
				{
					lock (handlers)
						serializedObject = serializers[key].DynamicInvoke(handlers[key].DynamicInvoke());
				}
				else
				{
					lock (handlers)
						serializedObject = serializers[key].DynamicInvoke(handlers[key].DynamicInvoke(deserializers[key].DynamicInvoke(message))); ;
				}				

				return serializedObject as byte[];
			}
		}

		#region Wcf Methods

		/// <summary>
		/// One-way message interface exposed to WCF
		/// </summary>
		public void SendMessageOneWay(int topicId, int messageId, byte[] message)
		{
			CallHandler(topicId, messageId, message, true);
		}

		/// <summary>
		/// Two-way message interface exposed to WCF
		/// </summary>
		public byte[] SendMessageRequestResponse(int topicId, int messageId, byte[] message)
		{
			return CallHandler(topicId, messageId, message, false);
		}

        public void Callback<TSendMessage>(TTopic topicId, TMessage messageId, TSendMessage message, OperationContext currentContext)
        {
			if(!supportsDuplex)
				throw new InvalidOperationException("The service does not support Duplex actions!");

            IWcfCallbackContract callback = currentContext.GetCallbackChannel<IWcfCallbackContract>();
			callback.Callback(topicId.ToInt32(CultureInfo.CurrentCulture), messageId.ToInt32(CultureInfo.CurrentCulture), SerializationHelper.Serialize(message));
        }

		#endregion Wcf Methods		
	}
	
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Check out my technical blog here: The Fyslexic Duck. You can find most of what I've put on CodeProject there, plus some additional technical articles.

Comments and Discussions