Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

How to abstract a WCF Service out of business logic

4.47/5 (11 votes)
18 May 2010CPOL3 min read 49.5K   858  
Service Bus Architecture based on WCF (2): Abstract WCF.

Introduction

People always discuss how to abstract a WCF Service out of the business logic? How could we make minimize changes to expand our existing system to a WCF Service? Put it in another way, we always wish we could develop our applications without worrying about those services we could possibly need to open in the future. When we really need those services, we simply load a service component and make a very small refactoring to make it work. My design here is to encapsulate the subscription and the abstracted function service into a Service Engine. And on the client side, I design an endpoint to communicate with the Service Engine.

abstractservice.png

Background

I abstract the service out of the business logic based on the last part: how to subscribe to WCF.

Using the code

Service Engine

In my last article, in order to open all the functions to the client, I added NewInventory, UpateInventory, and so on into InventoryService. This will prevent us from abstracting the service out of the business. What I'm going to do is write a single method named Invoke to provide the service to all the client calls.

C#
public class SAOTService<T> : ISAOTService
{
    public object Inovke(string methodname, params object[] args)
    {
        try
        {
            object obj = typeof(T).InvokeMember(methodname, BindingFlags.Static |
                BindingFlags.Public | BindingFlags.IgnoreCase | 
                BindingFlags.InvokeMethod, null, null, args);
            return obj;
        }
        catch (Exception ex)
        {
            return ex.Message;
        }
    }
}

In order to completely abstract the service out of the business logic. I designed a generic <T> in the service implementation class. <T> will accept a class type providing the real static functions. On the server application, if you want to open some of your functions to the WCF Service, create an adapter class:

C#
public class ServiceAdapter
{
    public static Inventory GetInventory(int productid)
    {
        return Inventory.GetInventory(productid);
    }
    public static Inventory[] GetInventoryList()
    {
        return Inventory.GetInventoryList();
    }
    public static bool ChangeInventory(string user, int productid, int changednumber)
    {
        Inventory inv = Inventory.GetInventory(productid);
        return inv.ChangeInventory(user, changednumber);
    }
    public static int CreateNewProduct(Inventory inventory, string user)
    {
        return new Inventory(user,inventory.ProductName,inventory.CurrentInventory).ProductID;
    }
}

Then, when you refer to the Service Engine, send the adapter into the abstracted service.

C#
ServiceController.OpenFunctionService<ServiceAdapter>(8886,"Inventory");

On the client side, open the connection to the service:

C#
FunctionServiceConnector.CreateFunctionService<T>("Inventory",serviceaddress);

After that, you use the Invoke method the get the ServiceAdapter functions.

C#
FunctionServiceConnector.GetService("Inventory").Invoke("GetInventory", 1)
//get product inventory with id=1

For the subscription part, in order to avoid the known type issue, I design the subscription message to be a fixed class Message:

C#
[DataContract]
public class Message: EventArgs
{
    #region Properties
    [DataMember]
    public string Title { get; set; }
    [DataMember]
    public string Content { get; set; }
    [DataMember]
    public string Context { get; set; }
    [DataMember]
    public DateTime Createtime { get; set; }
    [DataMember]
    public string PrimaryID { get; set; }
    [DataMember]
    public string Sender { get; set; }
    [DataMember]
    public string To { get; set; }
    [DataMember]
    public string CC { get; set; }
    [DataMember]
    public DateTime ExpiredTime { get; set; }
    [DataMember]
    public string Attachment { get; set; }
    [DataMember]
    public string FunctionCode { get; set; }
    [DataMember]
    public string ServiceName { get; set; }
    #endregion
}

Content and Context will store complicated information needed to be sent. There are two standard methods in IMessageSenderCallBack to provide all the subscription information to the clients: void SendMessage(Message message); void SendHeartBeat(string address);

C#
[ServiceContract(SessionMode = SessionMode.Required, 
        CallbackContract = typeof(IMessageSenderCallBack))]
public interface IMessageSender
{
  [OperationContract(IsOneWay = false, IsInitiating = true, 
   IsTerminating = false)]
  void Init(string address);
}
[ServiceContract]
public interface IMessageSenderCallBack
{
  [OperationContract(IsOneWay = true)]
  void SendMessage(Message message);
  [OperationContract(IsOneWay = true)]
  void SendHeartBeat(string address);
}

SendHeartBeat keeps sending a heart beat information to the client to indicate the server is still running. SendMessage encapsulates a message to the client to indicate the client to do the next logic in terms of the message.

The finished Service Engine has two important methods that will be invoked by the host application:

C#
ServiceController.OpenFunctionService<T>(in portnumber,string servicename);
ServiceController.OpenMessageService(in portnumber, string servicename);

Note: If you debug the function, you will encounter an exception that Inventory is unknown in SAOTService. Since we didn't put the Inventory into SAOTService, the service is not able to recognize this class and serialize it properly. In order to let the serializer know this type, we add the KnownType attribute on the service interface.

C#
[ServiceKnownType("GetKnownTypes", typeof(KnowTypes))]

This statement tells the serializer that the static method GetKnownTypes in the class KnowTypes will provide a type list to let the serializer recognize all the types in the list.

C#
public static class KnowTypes
{
    private static List<type> MyKnownTypes = new List<type>();
    public static void AddKnownType(Type type)
    {
        MyKnownTypes.Add(type);
    }
    public static IEnumerable<type> GetKnownTypes(ICustomAttributeProvider provider)
    {
        return MyKnownTypes.ToArray();
    }
}

Then, before we open the service, add these statements:

C#
ServiceController.AddKnowType(typeof(Inventory));
ServiceController.AddKnowType(typeof(Inventory[]));

Remember, the same exception will happen in the client code as well.

C#
FunctionServiceConnector.AddKnownType(typeof(SAOT.Inventory));
FunctionServiceConnector.AddKnownType(typeof(SAOT.Inventory[]));

Service EndPoint

I create two classes to receive the function service and the subscription service from the Service EndPoint.

C#
//MessageDistributor 
//get subscription service
class MessageDistributor : Trilobita.SAOT.Object.IMessageSenderCallBack

In MessageDistributor, four events will tell the client application that subscription messages are coming:

C#
//a subscription message arrives
public event EventHandler<Message> MessageDelivered;
// server connected
public event EventHandler
ServerConnectedEvent;
// server disconnected
public event EventHandler
ServerDisconnectedEvent;
// heart beat message from server every 2 seconds
public event EventHandler HeartBeatFromServer;

Open and close Subscription Service:

C#
//open the subscription connection between client and server
MessageDistributor.CreateMessageService(servicename,address);
// close the subscription connection between client and server
MessageDistributor.CloseMessageService(servicename)

Open and close Function Service:

C#
FunctionServiceConnector.CreateFunctionService<T>(servicename, address);
FunctionServiceConnector.CloseFunctionService(servicename);

// get a function service proxy
FunctionServiceConnector.GetFunctionService<T>( servicename));

License

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