Click here to Skip to main content
Click here to Skip to main content
Go to top

WCF by Example - Chapter VII - WPF Client - Contract Locator

, 5 Nov 2010
Rate this:
Please Sign up or sign in to vote.
Baseline WCF Distribution Layer - Decoupling of WCF Services and ViewModels.
This is an old version of the currently published article.

Previous

Next

Chapter VI Chapter VIII

The Series

WCF by example is a series of articles that describe how to design and develop a WPF client using WCF for communication and NHibernate for persistence purposes. The series introduction describes the scope of the articles, and discusses the architect solution at a high level.

Chapter Overview

In the previous chapter, we introduced a basic WPF client. Currently, the View and the Model are almost ready, but the ViewModel does not provide any mechanism for calling service methods. We previously discussed the need for decoupling the client from the WCF distribution layer. The goal is to provide a development environment for RAD that leverages business exploration without the overhead of deploying a full production infrastructure. We would like to provide a development environment where changes are inexpensive and deployments are easy. We don't want to install services in IIS, or maintain a SQL Server database; we are looking for an installation that comprises a bunch of files that can be deployed in a memory stick, if necessary. BAs, product owners, testers should be able to execute this application from any folder.

Therefore, we need to abstract the distribution layer so different implementations can be used in a transparent manner. In this chapter, we will describe the contract locator and how the client uses this mechanism to put in place a sort of pipeline pattern that provides the required functionality we are looking for.

The source code for this chapter can be found at CodePlex change set 76739. The latest code for the eDirectory solution can be found at CodePlex.

Base Components

We need to define a couple new interfaces in the Common assembly.

The IContractLocator interface provides a list of all the services required for the client. At this time, our project contains a single service: CustomerServices; therefore, the new interface is quite straightforward. Both the server and the client will implement this interface.

public interface IContractLocator
{
    ICustomerService CustomerServices { get; }
}

The IContract interface is used by any service used in the client side; the interface does not contain any implementation, so it is what we call a behavioral interface. A generic class will use this interface in its declaration; we will cover this aspect later in this chapter. We need to do a little change so the ICustomerService interface inherits from this new interface.

public interface IContract
{
}

[ServiceContract(Namespace = "http://wcfbyexample/customerservices/")]
public interface ICustomerService
    :IContract
{
    ...
}

Server-side Implementation

In the server side, the implementation of IContractLocator is very straightforward; it just delegates to the customer service instance. It is worth noting that only the in-memory client will use this implementation. This is the implementation used when we want to execute client and server components in a single process; normally, this means that WCF is not used.

public class ServerContractLocator
        :IContractLocator
{
    ICustomerService CustomerServiceInstance;

    #region IContractLocator Members

    public ICustomerService CustomerServices
    {
        get 
        {
            if (CustomerServiceInstance != null) return CustomerServiceInstance;
            CustomerServiceInstance = new CustomerService();
            return CustomerServiceInstance;
        }
    }

    #endregion
}

Client-side Implementation

The implementation of IContractLocator in the client is slightly different. In the client, we need to separate two concerns in our design. We need to provide a layer for dealing with the response instance if there are business warnings or exceptions. In the second place, we need to deal with the invocation of the service itself. The first layer uses a template pattern for the service invocation; as a result of the abstraction used in the design, it is relatively easy to "plug" different implementations if needed.

We need to explain in some detail the IContractLocator in the client:

public class ClientContractLocator
    :IContractLocator
{
01  private ICustomerService CustomerServiceInstance;

02  public IContractLocator NextAdapterLocator { get; set; }

    #region IContractLocator Members

    public ICustomerService CustomerServices
    {
        get 
        {
            if (CustomerServiceInstance != null) return CustomerServiceInstance;
03              CustomerServiceInstance = 
                      new CustomerServiceAdapter(NextAdapterLocator.CustomerServices);
            return CustomerServiceInstance;
        }
    }

    #endregion
}

In this implementation, when CustomerServices is called, an instance of CustomerServiceAdapter is returned. An IContractLocator is used the first time this property is used to determine whom the CustomerServiceAdapter delegates to invoke the service call (line 03). The NextAdapterLocator property is used for that purpose (line 02). It is worth noting that the NextAdapterLocator needs to be instanced before CustomerServices can be called. With respect to the ViewModel classes, they can interact with the ClientContractLocator as they were dealing with the service itself; this is due to the CustomerServices read-only property that implements ICustomerService. Effectively, this is where the abstraction that we mentioned before takes place.

We haven't properly introduced the CustomerServiceAdapter; this class inherits from a base class named BaseServiceAdapter - this is a generic class for services that implement the mentioned IContract interface:

The purpose of CustomerServiceAdapter is to execute the CustomerService instance that is provided by the ClientContractLocator with the NextAdapterLocator instance. The CustomerServiceAdapter invokes the service and then deals with the warnings and exceptions from the response:

abstract class ServiceAdapterBase<TService> 
01    where TService: IContract
{
    protected TService Service;

    protected ServiceAdapterBase(TService service)
    {
02      Service = service;
    }              
    
03  public TResult ExecuteCommand<TResult>(Func<TResult> command) 
           where TResult : IDtoResponseEnvelop
    {
04      TResult result = command.Invoke();
            
05      if (result.Response.HasWarning)
        {
            // TODO: Implement Business Warning processing
        }
06      if (result.Response.HasException)
        {
            // TODO: Implement Business Exception processing
        }
        return result;
    }
}

There are a few aspects in the above code that needs to be mentioned. In line 03, we can see that the generic ExecuteCommand method in conjunction with the Func parameter provides a neat mechanism for executing service methods. The method executes the delegate instance and stores the result, then it checks for warnings and exceptions. What is not obvious is how the Service is used (line 02); to understand this, we need to take a look at the CustomerServiceAdapter implementation:

class CustomerServiceAdapter
01      :ServiceAdapterBase<ICustomerService>, ICustomerService
{

    #region Constructor

    public CustomerServiceAdapter(ICustomerService service)
        : base(service) { }

    #endregion
    #region ICustomerService Members

    public CustomerDto CreateNewCustomer(CustomerDto customer)
    {
02      return ExecuteCommand(() => Service.CreateNewCustomer(customer));
    }

    ...

    #endregion
}

As mentioned, the CustomerServiceAdapter implements ICustomerService. Using Generics (line 01) in the base class definition simplifies the design as it makes it easy to use lambda expressions when implementing the service interface. In line 02, we can see an example of this type of implementation; the lambda expression works really well as it permits to call service methods with different parameters using a delegate that does not require input parameters in its definition (great!!!); this is possible as the lambda expression can use outer variables of the anonymous method. This feature simplifies the design a lot, it is a real beauty.

Client Service Locator

The ViewModels need access to the ClientContractLocator; in our client application a single instance is sufficient. DI could be used for this purpose, but again, the Locator pattern that was used in the server side comes quite handy in this case.

public class ClientServiceLocator
        : IClientServices
{
    static readonly Object LocatorLock = new object();
    private static ClientServiceLocator InternalInstance;

    private ClientServiceLocator() { }

    public static ClientServiceLocator Instance()
    {
        if (InternalInstance == null)
        {
            lock (LocatorLock)
            {
                // in case of a race scenario ... check again
                if (InternalInstance == null)
                {
                    InternalInstance = new ClientServiceLocator();
                }
            }
        }
        return InternalInstance;
    }

    #region IClientServices Members

    public IContractLocator ContractLocator { get; set; }

    #endregion
}

The IClientServices interface declares which global services must be available.

Test Re-Factor

We already have a set of tests that execute our services from the server side. Let's re-factor those tests so the services are executed from the client side using the ClientContractLocator. As we will see, this is relatively easy. On the left side, we have the test code before the re-factor, on the right side is the new code:

As we can see above, after the re-factor, the Service property is an instance of ClientContractLocator.

[TestClass]
public class CustomerServiceTests
{
    public ICustomerService Service { get; set; }
    public CustomerDto CustomerInstance { get; set; }

    [TestInitialize]
    public void CustomerServiceTestsInitialize() 
    {
        GlobalContext.Instance().TransFactory = 
                   new TransManagerEntityStoreFactory();
        Container.RequestContext = new RequestContextNaive();
01      Service = new ClientContractLocator { NextAdapterLocator = 
                            new ServerContractLocator() }.CustomerServices;
    }

    ...
}

There are a few things here that are taking place when the ClientContractLocator is defined in line 01. Let's see if we can explain in more detail how the tests are setting the Service property used in our tests. The following figure demonstrates how when calling the CustomerServices in the ClientContractLocator, an instance of CustomerServiceAdapter is returned which was initialised by passing a CustomerService instance.

So when we call the service to create a new customer in our tests, the following takes place:

It is recommendable to debug these tests to get familiar with the design that we have introduced in this chapter.

Chapter Summary

In this charter, we have set up the baseline for our distribution layer in the client side. Our design provides the decoupling required between the ViewModel classes and the services that we mentioned at the start of the chapter. With a little change in our service tests, we have also demonstrated how easy it is to have the client invoking the services in the server side. This model is critical to provide a plug-gable architecture so we can deploy our application during business exploration without having to use WCF leveraging the deployment process at this stage.

We have not covered the WCF implementation; this aspect is covered in a later chapter in the series. However, as we will see, the WCF implementation is relatively straightforward once we have in place the pattern introduced in this chapter.

In the next chapter, we will see how commands are implemented in the front-end and how the ViewModel classes invoke the services using the ClientServiceLocator mentioned in this chapter. We are very close to having a comprehensive infrastructure in both the client and server side to start deploying versions of our solution to our client.

License

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

Share

About the Author

Enrique Albert
Software Developer (Senior)
Ireland Ireland
No Biography provided
Follow on   Twitter

You may also be interested in...

Comments and Discussions


Discussions posted for the Published version of this article. Posting a message here will take you to the publicly available article in order to continue your conversation in public.
 
QuestionSuitable for silverlight ? Pinmembercaosnight6-Nov-11 9:24 
AnswerRe: Suitable for silverlight ? PinmemberEnrique Albert7-Nov-11 0:43 
GeneralMy vote of 5 Pinmemberlinhjob21-Oct-10 4:32 
GeneralRe: My vote of 5 PinmemberEnrique Albert31-Oct-10 21:00 
GeneralMy vote of 5 Pinmembersaurabh bansal2513-Sep-10 23:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 5 Nov 2010
Article Copyright 2010 by Enrique Albert
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid