| |
|
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 needed. BAs, product owners, testers should be able to execute this application in an easy manner.
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 original implementation, although it resolved the coupling issue between the client and services, it imposed the overhead of maintaining a set of client classes
for each service implementation. A ServiceAdapter
based on generic classes with generic functions and anonymous methods provide a much better implementation where
the client does not need to implement any additional classes to decouple the service calls. We also introduce in this article the CommandDispatcher
which is required
at a later stage when WCF services are introduced on the client side, see Chapter WCF by Example - Chapter XII - WCF Implementation for more details.
The source code for this chapter can be found at CodePlex
change set 93405. The latest code for the eDirectory solution can be found
at CodePlex.
Client Re-factor
For those that are familiar with the series and may be interested to know what the scope of the re-factor was. The following screen indicates which files were added,
removed, and modified:
The following actions were taken for the re-factor:
- Get rid of the
IClientServices
in the WPF project - Get rid of the
IContractLocator
:
- ServerContractLocator is gone -- Need to re-fator tests: CustomerServiceTests
- Remove it from the
ClientServiceLocator
-- A ref to ICommandDispatcher
is required - Remove
ClientContractLocator
- Remove
IContractLocator
- Remove
CustomerServiceAdapter
in the WPF project - Remove the
ServiceAdapterBase
class in the WPF project
- Add
eDirectory.Common.Message
, ICommandDispatcher
- Add
CommandDispatcher
to ClientServiceLocator
- Create Server
DirectCommandDispatcher
class - Create generic
ServiceAdapter
class -- this version does not include the async internal dispatcher - Refactor tests
Command Dispatcher
The ICommandDispatcher
interface provides a mechanism for clients to indicate which method and parameters are used when invoking a given service.
The only requirement for the client is to know the contract interface, it does not require to know about the implementation. On the other hand, the responsibility
of the ICommandDispatcher
is to deal with the service implementation and pass the method results to the client. The interface is:
public interface ICommandDispatcher
{
TResult ExecuteCommand<TService, TResult>(Func<;TService, TResult> command)
where TResult : IDtoResponseEnvelop
where TService : class, IContract;
}
In this chapter, we will only discuss the DirectCommandDispather
implementation that we use to connect the client directly to the service back-end without the need
of WCF and deploying the application on IIS. In this mode, the application just runs on a single application domain. This is useful for testing and RAD purposes.
The implementation is very simple:
public class DirectCommandDispatcher
: ICommandDispatcher
{
#region Implementation of ICommandDispatcher<TService>
public TResult ExecuteCommand<TService, TResult>(Func<TService, TResult> command)
where TResult : IDtoResponseEnvelop
where TService : class, IContract
{
01 var service = GetService<TService>();
return command.Invoke(service);
}
#endregion
02 private readonly IDictionary<Type, Type> _serviceMap = new Dictionary<Type, Type>
{
{typeof(ICustomerService), typeof(CustomerService)}
};
private TService GetService<TService>() where TService : class, IContract
{
var type = typeof(TService);
if (!_serviceMap.ContainsKey(type))
{
var msg = "Implementation for contract: {0} was " +
"not defined in the dispatcher service map";
msg = string.Format(msg, type.Name);
throw new NotImplementedException(msg);
}
03 return (TService)Activator.CreateInstance(_serviceMap[type]);
}
}
The implementation is just a factory class (Line 01) that creates the service instance (Line 03) based on the TService
that the client passes. If a new contract
is added in the server side, the only aspect to remember is to add the service to the map in line 02.
In Chapter XII - WCF Implementation, we will discuss the WCF implementation
of the CommandDispatcher
. This is the production implementation and in a way simpler than the one we discussed above as it does not require any amendments when a contract
is added to the solution.
Client Service Locator
For some client services, a single instance for the whole life of the client application is sufficient and recommended. I normally like to create a sort of
service container where the code can easily fetch for this sort of resources without exposing the DI container. There are several ways to provide this
functionality and people have different opinions. In this case, we use the locator pattern in conjunction with DI
for dynamically resolving service implementations at run-time. For further information regarding DI, see Chapter X - Dependency Injection with Spring.Net.
The ClientServiceLocator
implementation is straightforward; for the moment, we only expose the ICommandDispacher
service but when we get
to the end of the series, this locator will expose two other services.
public class ClientServiceLocator
{
static readonly Object LocatorLock = new object();
private static ClientServiceLocator InternalInstance;
private ClientServiceLocator() { }
public static ClientServiceLocator Instance()
{
if (InternalInstance == null)
{
lock (LocatorLock)
{
if (InternalInstance == null)
{
InternalInstance = new ClientServiceLocator();
}
}
}
return InternalInstance;
}
public ICommandDispatcher CommandDispatcher { get; set; }
}
Service Adapter
The ServiceAdapter
is being used by the ViewModels when they want to invoke service methods. This a generic class so the ViewModel when creating an instance also
determines which service contract the ServiceAdapter
will deal with, for example:
01 var customerServiceAdapter = new ServiceAdapter<ICustomerService>();
var dto = new CustomerDto
{
FirstName = "Joe",
LastName = "Bloggs",
Telephone = "9999-8888"
};
02 var customerInstance =
customerServiceAdapter.Execute(s => s.CreateNewCustomer(dto));
In line 01, a new instance is created for the ServiceAdapter
passing the ICustomerService
contract interface. This mechanism will help to invoke the service
method in line 02. To execute the method, we just use the Execute
method, this requires us to pass a function expression that takes an instance
of the service (this is the responsibility of the CommandDispatcher
) and executes one of its methods. The return value, always an instance from
IDtoResponseEnvelop
, is the result of the service method.
The way we have structured the ServiceAdapter
and CommandDispatcher
provides the developer a strong type mechanism for the ViewModels to invoke methods and
pass input parameters. It is similar to the RepositoryLocator
that we will see at a later stage within the series but the generic ServiceAdapter
provides
the additional functionality to deal with different service contracts. It is worth noting that the ServiceAdapter
does not require a service implementation, and it is not
its responsibility to create/maintain a service instance, the CommandDispatcher
looks after this aspect. The main reason for
this class is to manage the business warnings/messages in a generic manner. The implementation is as follows:
public class ServiceAdapter<TService>
where TService:class, IContract
{
public TResponse Execute<TResponse>(Func<TService, TResponse> command)
where TResponse : IDtoResponseEnvelop
{
01 var dispatcher = ClientServiceLocator.Instance().CommandDispatcher;
02 var result = dispatcher.ExecuteCommand(command);
if (result.Response.HasWarning)
{
03
}
if (result.Response.HasException)
{
04
}
return result;
}
}
In line 01, we use the new ClientServiceLocator
to fetch for the CommandDispatcher
,
then in line 02, the command is passed to the dispatcher; see how the ServiceAdapter
does not have a clue which service implementation is being used.
This is not the definitive version of this class; in Chapter XIV - Validation and Exception
Management, we will discuss the implementation of the business warning and exceptions within this class, lines 03 and 04. Also, in Chapter XII - WCF Implementation, a better mechanism for calling services in an async manner is covered.
Test Re-Factor
We already have a set of tests that execute our services from the server side. These tests have been re-factored to use the ServiceAdapter
instead. In this manner,
we can test the new infrastructure. I leave for the reader to inspect the code and see how these tests were implemented, they provide a comprehensive set of examples
on how to call the ServiceAdapter
.
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
invoke 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 for leveraging the deployment process at this stage.
We have not discussed 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. See Chapter XII - WCF Implementation.
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 ServiceAdapter
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.