I just want the code man, give me the code, well OK chillax, it's right here:
Table of Contents
Introduction
This article is something I have been meaning to do for some time, not really
for anyone's benefit other than my own really. Before we get onto what the
article will cover, let me just outline why I wanted to write this article.
I have been working with both WCF/WPF a while now (I know some people
consider these old technologies now, but I can assure you they are both alive and
well, at least in my part of the world), and I have seen a variety of different
approaches to developing an n-tier application with these technologies.
I have seen good and bad things, some of the bad things being:
- Ridiculous amount of methods on a service, which become quite
unmaintainable quite quickly
- Everything is its own service, where you have about 30 WCF services all
trying to call each other; nightmare man
- Bad separation of concerns, where the dependency graph is all screwed up,
and your UI inadvertently ends up knowing way too much as it is forced to
reference things it has no business knowing about
- Use of Reference.cs (more on this later)
- Lack of support/thought for DD/IOC/Mocking or any of the cool stuff people take
for granted nowadays
I thought it would be nice to try and see if I could make a demo app which
kind of did things the way I had it mapped out in my mind. I just
wanted to be able to do a complete application from database to WPF client all
the way through, with only my own thoughts (and headaches/nightmares) to deal
with.
I freely admit I have used certain paradigms I have seen along the way, such
as issuing a Request
and getting a Response
. I actually like this pattern as it keeps your
WCF service contract very simple, as it is basically Response
ExecuteRequest(Request req)
, which means no matter how much stuff you add, your service contract stays up to date,
that is providing you keep your WCF
KnownType
(s)
up to date.
For the attached demo code I wanted to make sure I covered the
following aspects:
- Provide good level of separation of concerns, that is the UI should not
care about server side business logic, why should it?
- Be able to easily easily swap out/test certain areas of our application
(IOC allows this)
- Database access should be as lean as possible (NHibernate and
Repository pattern usage facilitate this)
- Should be able to test a small part of the overall system
I have called the project "WcfExemplar" which I hope people do not find
arrogant, it is certainly not meant to be. It is called that more for
me really, as I was doing this to prove something to myself, so it was an
"Exemplar" for me if you will. I hope you forgive me this one
indulgence.
So what does the demo app do?
It is a WPF (Prism/MVVM based) client which communicates with a Console hosted WCF service
application. The application allows users to search database stored zombie incidents. The
user may also add new zombie incidents. So it's actually not that complicated, it
is basic CRUD stuff.
I think the main salient points are as follows:
- WPF front end (that I will not dwell on too much, as that is a
mere vehicle to illustrate the server side concepts) which allows users to
- Search for items (I am storing zombie data where a
heading/description and GeoLocation data are stored for each zombie
incident)
- Add new items
- View items on a map (I have chosen to use Bing maps)
- Use Rx to show an RSS feed of global zombie incidents (as a bonus if
you will)
- It uses a shared DLL where only the relevant things are shared between
client and server, which ensures the dependency graph make sense. So things
like those shown below would typically be shared
- Business Objects
- Service contract
- Fault contracts
- There is no auto generated
Reference.xx
or client proxy, we
use a shared DLL and hand craft our own client proxy
- It uses Fluent NHibernate for its persistence, where
ISession
provides the Unit Of Work (UOW)
- It uses IOC such that any component that talks to an external part (i.e.,
database) can be easily substituted. This is done from the WCF service down
- It uses business logic to do any work on objects before any attempt to
persist an object is done.
- It keeps connections to the database at a minimum and only engages the
database for the minimum amount of time.
Please try and remember I am writing this using a Request/Response
mechanism, so even if you don't like the overall
structure of the Request
/Response
/Task
approach, I hope that there will be some
bits and pieces in here to keep you interested.
Pre-Requisites
In order to run the attached code you will need to have the following
components
- A reasonably good PC (One that is capable of running WPF and SQL Server)
- Your own SQL Server installation
- This demo uses Bing Maps, as such you will need to acquire a Bing Maps
developer API key (or just put up with the nag water mark message that the
demo will show if you do not have an API key, which to be honest is not that
big a deal, you call). You can obtain a Bing maps API key: http://msdn.microsoft.com/en-us/library/ff428642.aspx
- Steady connection to the internet is required, as the UI assumes it is
web connected in order to display maps and read RSS feeds
Getting Started
This mini step by step guide will tell you what you need to do in order to
get the demo
app up and running
- Create a new database in SQL server called "WcfExemplar"
- Run the following SQL scripts
- 02 Create ZombieIncidents table.sql
- 03 Create GeoLocations table.sql
- 04 Create some dummy data.sql
- Change the "ZombieDB" SQL connection string in the App.Config within the
"Hosting\ConsoleHost\WcfExamplar.Host" project to point to
your own SQL
Server installation
- Sign up for a Bing maps API key (if you want to get rid of the nag
message), and enter your details in the XAML of these files
-
WcfExemplar.WpfClient.Common\Controls\SlimLineZombieIncidentMapView.xaml,
look for the Bing map control and fill in the line
"CredentialsProvider="INSERT_YOUR_BING_MAPS_KEY""
- WcfExemplar.WpfClient.MapModule\ZombieIncidentMapView.xaml, look for
the Bing map control and fill in the line
"CredentialsProvider="INSERT_YOUR_BING_MAPS_KEY""
- Run the WCF console host by right clicking the
"Hosting\ConsoleHost\WcfExamplar.Host" project, and Debug->Start New
Instance
- Run the WPF client by right clicking the
"UI\PRISM\WcfExemplar.WpfClient" project, and Debug->Start New Instance
The Server Side: WCF
This section will talk about the WCF service layer. In a nutshell what the
WCF service does is provide a single method interface service contract. The
client will call the WCF service using
Request
s, which are available in a shared DLL.
Request
s immediately provide their properties to a Task
which in turn will call into the appropriate business logic. If the business
logic checks that a certain action is sound, and passes whatever specific
validation is needed, communication with the persistence
layer is permitted. Where I am using
Fluent NHibernate UOW and Repository patterns.
The Need For Layers - Why Bother
By now you should appreciate that there is a Service that accepts Request
(s)
which allow Task
(s) to be created, where the Task
(s) are the real guys doing the
work.
This article code had something like this
Request
(s) get exposed in shared DLL. The looking up of a
Task
(s) is done via an within the actual WCF service using a tiny bit of
reflection which is only ever done within the
WCF service, so the WCF clients no nothing about this or any
of
Task
(s) existing dependencies such as x/y and z. So you only
ever get the stuff that should be shared.
The reason we should care about this comes down to "separation
of concerns". The WCF client should not care about anything other than the
need to get/send business objects, and use Request/Response
objects
via a common service contract. Ok there will also be shared FaultContract
objects but these are also part of the shared DLL between the WCF service
and the WCF client.
To my mind this is a very important piece of the jigsaw solved, this provides
a good separation of concerns, and also allows reuse of critical pieces of shared
code.
The WCF Service
As previously stated the WCF is pretty simple, here is the shared service
contract interface, see how it literally has one method, which takes a Request
and returns a Response
.
[ServiceContract]
public interface IGateway
{
[OperationContract]
[FaultContract(typeof(GenericFault))]
[FaultContract(typeof(BusinessLogicFault))]
Response ExecuteRequest(Request request);
}
Where this service contract is shared with the client via a shared DLL. The full service implementation looks like this
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Gateway : IGateway
{
public Response ExecuteRequest(Request request)
{
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Gateway")
.InfoFormat("Executing request : {0}", request.GetType().Name);
object requestHandler = null;
try
{
Type requestType = request.GetType();
Type responseType = GetResponseType(requestType);
Type requestHandlerType = typeof(IRequestHandler<,>).MakeGenericType(requestType, responseType);
requestHandler = container.Resolve(requestHandlerType);
return (Response)requestHandlerType.GetMethod("Handle").Invoke(requestHandler, new[] { request });
}
catch (BusinessLogicException bex)
{
BusinessLogicFault bf = new BusinessLogicFault();
bf.Operation = requestHandler.GetType().FullName;
bf.Message = bex.Message;
throw new FaultException<BusinessLogicFault>(bf);
}
catch (Exception ex)
{
GenericFault gf = new GenericFault();
gf.Operation = requestHandler.GetType().FullName;
gf.Message = ex.Message;
throw new FaultException<GenericFault>(gf);
}
}
private static Type GetResponseType(Type requestType)
{
return GetRequestInterface(requestType).GetGenericArguments()[0];
}
private static Type GetRequestInterface(Type requestType)
{
return (
from @interface in requestType.GetInterfaces()
where @interface.IsGenericType
where typeof(IRequest<>).IsAssignableFrom(
@interface.GetGenericTypeDefinition())
select @interface)
.Single();
}
}
There are a couple of things to note here, which are as follows:
- We can see we are using
FaultException
s to send faults to
the WCF client (the ones we allow are attributed on the shared service
contract)
- We use a small bit of reflection to obtain a IOC container registered
instance of
IRequestHandler<,>
which just happens to be the
Task
that is able to take an instance of the current Request (which is how
we provide a nice separation of concerns, and are able to share only what we
should)
- We use the Castle Windsor IOC container (more on this later)
- We use
InstanceModeContext.PerCall
, as this is what seems to be best
practice when working with NHibernate and its ISession
UOW
object. ISession
should be short and snappy and should not be
long living at all
This service is hosted in a simple Console application host (see
WcfExamplar.Host
project if you are interested in that)
IOC
"Inversion Of Control" is
in my opinion, an absolute god send that aids the
development of enterprise level applications.
It basically facilitates a more decoupled
architecture, and also encourages the use of interfaces (strategy pattern if you ask
me) which in turn creates a better avenue for testing. With these couple of
points in mind it was my intention to provide IOC support from the WCF service
down. Such that it can be used at every level in the server side WCF code.
So what IOC library do I use? Well I have a couple of favourites, but
Castle
Windsor is right up there, so I have made use of that.
The WCF service itself does very little work, except to find a Task
and run it. So it is Tasks
where all the real work starts to get done, so
it should come as no surprise that the WCF service doesn't have any IOC
registered dependencies. It does however setup the
Castle
Windsor IOC Container
on aper call, yes I said PerCall
instance context.
Just to The reason I use PerCall
is that I am using NHibernate. The
ISession
UnitOfWork that NHibernate uses, is meant for short snappy
usage, and should not be kept alive for long periods of time. It is most commonly
used with web sites actually, which all operate using a per request arrangement,
which in my mind is most analogous to a PerCall
WCF instance
context service.
This is done as
follows:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Gateway : IGateway
{
}
Castle
Windsor allows us to configure the IOC container by providing one or more container installer files. I am using a single
Castle
Windsor container installer which looks like this:
public class StandardInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
string dbConnectionString = ConfigurationManager.ConnectionStrings["ZombieDB"].ConnectionString;
container.AddFacility<WcfFacility>()
.Register(
Component.For<NHibernateHelper>().DependsOn(new
{
connectionString = dbConnectionString
}).LifeStyle.Singleton,
Component.For<Func<ISessionFactory>>().Instance(() =>
container.Resolve<NHibernateHelper>().SessionFactory).LifeStyle.Singleton,
Component.For<Func<ISession>>().Instance(() =>
((INHibSessionProvider)container.Resolve<IUnitOfWork>()).Session).LifeStyle.PerWcfOperation(),
Component.For<IUnitOfWork>().ImplementedBy<NHibernateUnitOfWork>().LifeStyle.PerWcfOperation(),
Component.For<IRepository<ZombieIncident>>().ImplementedBy<NHibernateRepository<ZombieIncident>>().LifeStyle.PerWcfOperation(),
Component.For<IRepository<GeoLocation>>().ImplementedBy<NHibernateRepository<GeoLocation>>().LifeStyle.PerWcfOperation(),
Component.For<IZombieIncidentDomainLogic>().ImplementedBy<ZombieIncidentDomainLogic>().LifeStyle.Transient,
Component.For<IGateway>().ImplementedBy<Gateway>().LifeStyle.PerWcfOperation()
);
}
}
It can be seen above that this uses a couple of neat castle features such as anonymous objects and the use of Func<T>
delegates, which are
used as lightweight factories to provide Fluent NHibernate related classes with values. We will see more on this later.
Another point to note is that I am using the Castle WCF facility and also make use of the Castle Service Host (which the most important parts of are shown below)
IOCManager.Container.Install(new IWindsorInstaller[] { new StandardInstaller(), new TaskInstaller() });
simpleServiceHost = new DefaultServiceHostFactory(IOCManager.Container.Kernel)
.CreateServiceHost(typeof(IGateway).AssemblyQualifiedName
, new Uri[0]);
StartServiceHost(simpleServiceHost);
We also make use of another installer that will register all Tasks within the
Castle
Windsor IOC container. This is shown below
public class TaskInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(AllTypes
.FromAssemblyContaining(typeof(SaveZombieIncidentTask))
.BasedOn(typeof(IRequestHandler<,>))
.Unless(t => t.IsGenericTypeDefinition)
.WithService.Select((_, baseTypes) =>
{
return
from t in baseTypes
where t.IsGenericType
let td = t.GetGenericTypeDefinition()
where td == typeof(IRequestHandler<,>)
select t;
}).LifestyleTransient());
}
}
This installer basically examines all types in the WcfExamplar.Server.Tasks.Dll and looks for any type that implements
the IRequestHandler<,>
interface. This interface should only ever be implemented by Tasks
.
Requests
Request
s are what the WPF client (or any other client of the WCF service) would use to talk to the WCF service. As such a Request
based object is part of the shared contracts, that can be found in the shared DLL between the WPF client and the WCF service. Any specific Request
is nothing
more that a bunch of values that capture inputs that should be used to hand to a Task
.
So Request
(s) can be thought of as property bags that
immediately absolve themselves of any responsibility by finding an associated
Task
and telling the Task
, hey here is what the UI
asked you to do, go do it. The important part here is, that the WPF client knows
nothing about Tasks
(s), because that is bad, if it did it would be
forced to reference things like NHibernate.dll, does that sound
like a UI concern to you. No me neither.
Shown below is a typical Request
[DataContract]
public class SaveZombieIncidentRequest : Request, IRequest<SaveZombieIncidentResponse>
{
[DataMember]
public ZombieIncidentInfo ZombieIncidentInfo { get; set; }
public SaveZombieIncidentRequest(ZombieIncidentInfo zombieIncidentInfo)
{
ZombieIncidentInfo = zombieIncidentInfo;
}
}
There are only two considerations when using Request
(s), which are
- Making sure that the
Request
inherits from IRequest<T>
where T
is the expected Response
type. This is
used by the WCF service to do some metadata lookups.
- Making sure that we attribute up
Request
to ensure that it knows how to serialize its sub classes, this is
done as follows for the demo app.
[DataContract]
[KnownType(typeof(SaveZombieIncidentRequest))]
[KnownType(typeof(ZombieIncidentsRequest))]
[KnownType(typeof(SearchZombieIncidentsRequest))]
public abstract class Request
{
}
The same is true for Response
and its sub classes.
Request to Task Mapping
As a Request
ONLY really exists to be mapped to a Task
, we
are able to use a small but funky bit of reflection that essentially just tries
to find a IRequestHandler<TRequest,TResponse>
implementing class
within the IOC container. The ONLY classes that should implement this interface
are Task
(s). As such when the relevant Task
is found, we simply invoke its
Handle
method where the current Request
is provided as a method parameter.
public Response ExecuteRequest(Request request)
{
object requestHandler = null;
try
{
Type requestType = request.GetType();
Type responseType = GetResponseType(requestType);
Type requestHandlerType = typeof(IRequestHandler<,>).MakeGenericType(requestType, responseType);
requestHandler = IOCManager.Container.Resolve(requestHandlerType);
return (Response)requestHandlerType.GetMethod("Handle").Invoke(requestHandler, new[] { request });
}
catch (BusinessLogicException bex)
{
....
....
....
}
catch (Exception ex)
{
....
....
....
}
}
The small bit of reflection in the WCF service allows for a good separation of concerns between
Task
(s)/Request
(s).
Task(s)
know about Request(s)
, but Request(s)
does not know about
Task(s)
. Therefore
Request
s can be shared in a shared DLL no problem.
This is what a typical Request/Task
look like, I
hope this makes sense.
[DataContract]
public class SaveZombieIncidentRequest : Request, IRequest<SaveZombieIncidentResponse>
{
[DataMember]
public ZombieIncidentInfo ZombieIncidentInfo { get; set; }
public SaveZombieIncidentRequest(ZombieIncidentInfo zombieIncidentInfo)
{
ZombieIncidentInfo = zombieIncidentInfo;
}
}
public class SaveZombieIncidentTask : IRequestHandler<SaveZombieIncidentRequest, SaveZombieIncidentResponse>
{
private IZombieIncidentDomainLogic zombieIncidentLogic;
private IRepository<dtos.ZombieIncident> zombieRepository;
private IRepository<dtos.GeoLocation> geoLocationRepository;
private IUnitOfWork unitOfWork;
private ZombieIncidentInfo zombieIncidentInfo;
public SaveZombieIncidentTask(IZombieIncidentDomainLogic zombieIncidentLogic,
IRepository<dtos.ZombieIncident> zombieRepository,
IRepository<dtos.GeoLocation> geoLocationRepository,
IUnitOfWork unitOfWork)
{
this.zombieIncidentLogic = zombieIncidentLogic;
this.zombieRepository = zombieRepository;
this.geoLocationRepository = geoLocationRepository;
this.unitOfWork = unitOfWork;
}
public SaveZombieIncidentResponse Handle(SaveZombieIncidentRequest request)
{
try
{
this.zombieIncidentInfo = request.ZombieIncidentInfo;
....
....
....
....
return new SaveZombieIncidentResponse(false);
}
catch (BusinessLogicException bex)
{
throw;
}
catch (Exception ex)
{
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Tasks")
.ErrorFormat("{0}\r\n{1}", ex.Message, ex.StackTrace);
throw;
}
}
}
Tasks
Tasks
(s) are
kind of small units of work, but they should never call other tasks. What they
are meant for really is to take some Request
parameters, run the
data through some business logic, and possibly hit the persistence layer using
NHibernate (more on this later).
So if you like, Task
(s) are simply there to facilitate running
business logic and talking to the persistence layer.
Here is what a typical Task
may look like
public class SaveZombieIncidentTask : IRequestHandler<SaveZombieIncidentRequest, SaveZombieIncidentResponse>
{
private IZombieIncidentDomainLogic zombieIncidentLogic;
private IRepository<dtos.ZombieIncident> zombieRepository;
private IRepository<dtos.GeoLocation> geoLocationRepository;
private IUnitOfWork unitOfWork;
private ZombieIncidentInfo zombieIncidentInfo;
public SaveZombieIncidentTask(IZombieIncidentDomainLogic zombieIncidentLogic,
IRepository<dtos.ZombieIncident> zombieRepository,
IRepository<dtos.GeoLocation> geoLocationRepository,
IUnitOfWork unitOfWork)
{
this.zombieIncidentLogic = zombieIncidentLogic;
this.zombieRepository = zombieRepository;
this.geoLocationRepository = geoLocationRepository;
this.unitOfWork = unitOfWork;
}
public SaveZombieIncidentResponse Handle(SaveZombieIncidentRequest request)
{
try
{
this.zombieIncidentInfo = request.ZombieIncidentInfo;
bool result = zombieIncidentLogic.CanStoreZombieIncident(zombieIncidentInfo);
if (result)
{
ZombieIncident zombieIncident = ZombieIncidentDTOMapper.ToDTO(zombieIncidentInfo);
zombieRepository.InsertOnSubmit(zombieIncident);
zombieRepository.SubmitChanges();
geoLocationRepository.InsertOnSubmit(zombieIncident.GeoLocation);
geoLocationRepository.SubmitChanges();
unitOfWork.Commit();
unitOfWork.Dispose();
return new SaveZombieIncidentResponse(result);
}
return new SaveZombieIncidentResponse(false);
}
catch (BusinessLogicException bex)
{
throw;
}
catch (Exception ex)
{
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Tasks")
.ErrorFormat("{0}\r\n{1}", ex.Message, ex.StackTrace);
throw;
}
}
}
This simple Task
, actually demonstrates quite a few things
- All its dependencies are satisfied by the IOC container, where these are
fed in on the constructor
- The handling of a
Request
is done by implementing the
IRequestHandler<TRequest,TResponse>
interface, which allows the Task
to recieve the Request
on the Handle()
method,
where by it can grab the data from the Request
- It immediately delegates off to some business logic, which could be used
within a common DLL which is shared between not only the WCF service, but
could potentially be used by any code that needs to use common
business logic for its shared objects. Though I have not gone that far with
the attached demo code
- If the business logic says it is ok to save this entity, we ask the
persistence layer (NHibernate) to do that in a proper Unit Of Work
(Transactional) using NHibernate's
ISession
UOW object.
That is how I see a Task
working, lean and mean and delegating
to other single responsibility domain areas/services
Business Objects
The way I see things you may even be able to share your business objects if
you get things right. You know things like validation rules, they sound like
something that could be shared right?
If you allow the business objects to be shared along with things like
Requests/WCF contracts/Fault contracts/Business logic, you can do all sorts of
crazy stuff like including methods that might be useful to anyone in the
enterprise.
Here is a typical business object from the demo app:
[DataContract]
public class ZombieIncidentInfo
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Heading { get; set; }
[DataMember]
public string Text { get; set; }
[DataMember]
public GeoLocationInfo GeoLocation { get; set; }
public ZombieIncidentInfo(string heading, string text, GeoLocationInfo geoLocation)
{
this.Heading = heading;
this.Text = text;
this.GeoLocation = geoLocation;
}
public ZombieIncidentInfo(int id, string heading, string text, GeoLocationInfo geoLocation)
{
this.Id = id;
this.Heading = heading;
this.Text = text;
this.GeoLocation = geoLocation;
}
public bool IsValid()
{
return !string.IsNullOrEmpty(Heading) && Heading.Length <= 50
&& !string.IsNullOrEmpty(Text) && Text.Length <= 300;
}
}
Some folk may look at this and go but hey I use
WinForms/WPF/WinRT/Silverlight, why no INotifyPropertyChanged
. Well for me the
answer is that I would create a UI specific abstraction (ViewModel) for this.
Although very very simple I am sure you would all agree the IsValid()
method
does seem useful, and likely to be something more than 1 part of the enterprise
may be interested in, so share it I say.
Business Logic
As previously stated I think if you get this stuff right, there may even be
ways in which you can share business logic between many parts of the enterprise. Although this
obviously is just a demo app, the code shown below is generic enough to work in a
WPF/WinForms UI or Web UI, or another service, pretty much anywhere really. It's
about re-use in my opinion.
public class ZombieIncidentDomainLogic : IZombieIncidentDomainLogic
{
public bool CanStoreZombieIncident(ZombieIncidentInfo zombieIncident)
{
bool incidentValid = true;
bool geoLocationValid = true;
if (zombieIncident == null)
{
string error = ("ZombieIncidentDomainLogic.CanStoreZombieIncident : zombieIncident can not be null");
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Business Logic").Error(error);
throw new BusinessLogicException("Zombie Incident data provided is empty");
}
if (zombieIncident.GeoLocation == null)
{
string error = ("ZombieIncidentDomainLogic.CanStoreZombieIncident : zombieIncident.GeoLocation can not be null");
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Business Logic").Error(error);
throw new BusinessLogicException("GeoLocation data provided is empty");
}
if (!zombieIncident.IsValid())
{
incidentValid = false;
string error = ("ZombieIncidentDomainLogic.CanStoreZombieIncident : " +
'zombieIncident has invalid data, ensure Heading/Text are filled in correctly");
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Business Logic").Error(error);
}
if (!zombieIncident.GeoLocation.IsValid())
{
geoLocationValid = false;
string error = ("ZombieIncidentDomainLogic.CanStoreZombieIncident : " +
"geoLocation has invalid data, ensure Latitude/Longiture are filled in correctly");
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Business Logic").Error(error);
}
return incidentValid && geoLocationValid;
}
public bool IsValidSearch(string propertyName, SearchType searchType, string searchValue)
{
if (string.IsNullOrEmpty(propertyName))
{
string error = ("ZombieIncidentDomainLogic.IsValidSearch : propertyName can not be null");
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Business Logic").Error(error);
throw new BusinessLogicException("Search for Zombie Incident is invalid. 'PropertyName' is empty");
}
if (string.IsNullOrEmpty(searchValue))
{
string error = ("ZombieIncidentDomainLogic.IsValidSearch : searchValue can not be null");
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Business Logic").Error(error);
throw new BusinessLogicException("Search for Zombie Incident is invalid. 'SearchValue' is empty");
}
return true;
}
}
Unit Of Work / Generic Repositories / Persistence
This articles demo code uses Fluent NHibernate as its Object Relational
Mapper (ORM), which allows us to stay clear of SQL and just work with business
objects or Data Transfer Objects (DTO). NHibernate comes with its own UnitOfWork
which allows us to do things within transactional context. This is by
using ISession
. So we
will make use of that.
In order to use Fluent NHibernate within the demo code, I wanted it to all be
nice and swappable, so I do use elements of the IOC Container to ensure that the
relevant NHibernate helper classes/ISession
classes and Repositories are all
obtained from the IOC Container.
We will look at all of these parts separately, but for now here is the
relevant IOC Container setup that deals just with NHibernate:
public class StandardInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
string dbConnectionString = ConfigurationManager.ConnectionStrings["ZombieDB"].ConnectionString;
container.AddFacility<WcfFacility>()
.Register(
Component.For<NHibernateHelper>().DependsOn(new
{
connectionString = dbConnectionString
}).LifeStyle.Singleton,
Component.For<Func<ISessionFactory>>().Instance(() =>
container.Resolve<NHibernateHelper>().SessionFactory).LifeStyle.Singleton,
Component.For<Func<ISession>>().Instance(() =>
((INHibSessionProvider)container.Resolve<IUnitOfWork>()).Session).LifeStyle.Singleton,
Component.For<IUnitOfWork>().ImplementedBy<NHibernateUnitOfWork>().LifeStyle.Singleton,
Component.For<IRepository<ZombieIncident>>().ImplementedBy<NHibernateRepository<ZombieIncident>>().LifeStyle.Singleton,
Component.For<IRepository<GeoLocation>>().ImplementedBy<NHibernateRepository<GeoLocation>>().LifeStyle.Singleton,
....
....
....
);
}
}
This is all pretty standard IOC behavior, apart from the Func
delegates. These are simply factory delegates that are used to provide
one of the values for other IOC registered components.
Unit Of Work: ISession
The Unit Of Work is realized by using the attached demo code
NHibernateUnitOfWork
class which is as follows:
public interface INHibSessionProvider
{
ISession Session { get; }
}
public class NHibernateUnitOfWork : IUnitOfWork, INHibSessionProvider
{
private ITransaction transaction;
public ISession Session { get; private set; }
public NHibernateUnitOfWork(Func<ISessionFactory> sessionFactory)
{
Session = sessionFactory().OpenSession();
Session.FlushMode = FlushMode.Auto;
this.transaction = Session.BeginTransaction(IsolationLevel.ReadCommitted);
}
public void Dispose()
{
if(Session.IsOpen)
{
Session.Close();
}
}
public void Commit()
{
if(!transaction.IsActive)
{
throw new InvalidOperationException("No active transation");
}
transaction.Commit();
}
public void Rollback()
{
if(transaction.IsActive)
{
transaction.Rollback();
}
}
}
This class is pretty self explanatory, its simpy allows some work to be done
within a single Transaction
which is commited or rolled back.
It does however make use of another helper class, whch is provided by the IOC Container
by
using a Func
delegate which act as factory.
public class NHibernateHelper
{
private readonly string connectionString;
private ISessionFactory sessionFactory;
public ISessionFactory SessionFactory
{
get { return sessionFactory ?? (sessionFactory = CreateSessionFactory()); }
}
public NHibernateHelper(string connectionString)
{
this.connectionString = connectionString;
}
private ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(connectionString))
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
.BuildSessionFactory();
}
}
Generic Repositories
Another thing I tried to use was the Repository pattern, where I am using
castles ability to support open generics.
Here is an example of the Repositories you will find in the attached code,
where the NHibernate ISession
(Unit Of Work) is passed to the
Repository. This is done so that multiple operations across different Repositories,
can all participate in the same Transaction
. At least that is
the idea. There will be times when you MUST call Commit()
on the current Unit Of
Work, which is fine, you can then just use your Repositories again with a new
Transaction
via the NHibernate ISession
(Unit Of Work)
which is passed to the Repository.
public class NHibernateRepository<T> : IRepository<T> where T : class
{
private readonly ISession Session;
public NHibernateRepository(Func<ISession> session)
{
Session = session();
}
public T GetById(int id)
{
return Session.Load<T>(id);
}
public IQueryable<T> GetAll()
{
return Session.Query<T>();
}
public T FindBy(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
return FilterBy(expression).Single();
}
public IQueryable<T> FilterBy(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
return GetAll().Where(expression).AsQueryable();
}
public void InsertOnSubmit(T entity)
{
Session.Save(entity);
}
public void DeleteOnSubmit(T entity)
{
Session.Delete(entity);
}
public void SubmitChanges()
{
Session.Flush();
}
}
Here is an example showing you how the ISession
based Unit Of
Work and Repositories work together.
public SaveZombieIncidentTask(
IZombieIncidentDomainLogic zombieIncidentLogic,
IRepository<dtos.ZombieIncident> zombieRepository,
IRepository<dtos.GeoLocation> geoLocationRepository,
IUnitOfWork unitOfWork,
ZombieIncidentInfo zombieIncidentInfo)
{
this.zombieIncidentLogic = zombieIncidentLogic;
this.zombieRepository = zombieRepository;
this.geoLocationRepository = geoLocationRepository;
this.unitOfWork = unitOfWork;
this.zombieIncidentInfo = zombieIncidentInfo;
}
public override Response Execute()
{
try
{
bool result = zombieIncidentLogic.CanStoreZombieIncident(zombieIncidentInfo);
if (result)
{
ZombieIncident zombieIncident = ZombieIncidentDTOMapper.ToDTO(zombieIncidentInfo);
zombieRepository.InsertOnSubmit(zombieIncident);
zombieRepository.SubmitChanges();
geoLocationRepository.InsertOnSubmit(zombieIncident.GeoLocation);
geoLocationRepository.SubmitChanges();
unitOfWork.Commit();
unitOfWork.Dispose();
return new SaveZombieIncidentResponse(result);
}
return new SaveZombieIncidentResponse(false);
}
catch (BusinessLogicException bex)
{
throw;
}
catch (Exception ex)
{
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Tasks").ErrorFormat("{0}\r\n{1}",ex.Message, ex.StackTrace);
throw;
}
}
DTO Objects
Some of the eagle eyed amongst you, will notice that I do not use the shared
WCF DataContract based objects above. I actually use some more light weight Data
Transfer Objects (DTOs) which I use purely for talking with NHibernate and the
database.
Here are the two DTOs used in the demo app project, see how it is quite clean and simply allows
NHibernate to populate properties. One thing I decided to introduce was a
PersistableBase
class that dealt with common things that a peristable entity may
need such as
- Id
- Version (Optimistic concurrency)
public abstract class PersistableBase
{
public virtual int Id { get; set; }
public virtual byte[] Version { get; set; }
}
public class ZombieIncident : PersistableBase
{
public virtual string Heading { get; set; }
public virtual string Text { get; set; }
public virtual GeoLocation GeoLocation { get; set; }
}
public class GeoLocation
{
private int ZombieIncidentId { get; set; }
private ZombieIncident ZombieIncident { get; set; }
protected GeoLocation()
{
}
public GeoLocation(ZombieIncident zombieIncident)
{
ZombieIncident = zombieIncident;
}
public virtual double Latitude { get; set; }
public virtual double Longitude { get; set; }
}
You may be wondering how these guys get populated, well the answer to that
when using Fluent NHibernate, lies in the use of
Mapper files. Here are the two mapper files for the two DTOs you have just seen
(where I also use a base mapper class called
PersistanceMapping<TDomainEntity>
.
public abstract class PersistenceMapping<TDomainEntity> :
ClassMap<TDomainEntity> where TDomainEntity : PersistableBase
{
public PersistenceMapping()
{
Id(x => x.Id).GeneratedBy.Identity();
OptimisticLock.Version();
Version(x => x.Version).Column("Version").Generated.Always();
}
}
public class ZombieIncidentMap : PersistenceMapping<ZombieIncident>
{
public ZombieIncidentMap() : base()
{
Table("ZombieIncidents");
Map(x => x.Heading);
Map(x => x.Text);
HasOne(x => x.GeoLocation).Cascade.All();
}
}
public class GeoLocationMap : ClassMap<GeoLocation>
{
public GeoLocationMap()
{
Table("GeoLocations");
Id(Reveal.Property<GeoLocation>("ZombieIncidentId")).GeneratedBy.Foreign("ZombieIncident");
HasOne(Reveal.Property<GeoLocation, ZombieIncident>("ZombieIncident")).Constrained().ForeignKey();
Map(x => x.Latitude);
Map(x => x.Longitude);
}
}
Fluent HHibernate One To One Mapping
The two2 Fluent NHibernate files above are used to create a ZombieIncident
with a 1 to 1 relationship with a GeoLocation
object. This sounds trivial but it
turned out to be way harder than I thought, so I started googling, and found the following link which was brilliant, and it was I used to get this working.
So that about concludes the server side code, a lot of this may not sink in
until you examine the code for yourself.
Next we will talk about the UI. Before we do that, let me just say this
article is more about trying to come up with a good service side design, the UI is merely a vehicle for calling the service side code.
However I thought
it may be a good time to also give several people who use my Cinch
MVVM framework a richer example, and try creating a Metro'ish app, so I did just
that, and I think the demo UI code you see attached is not bad for showcasing my Cinch
MVVM framework and
PRISM
working together.
Although the UI
code is full featured and a rich example of using MVVM and
PRISM, I will not be
spending too much time going through its internals. I will ONLY talk about the
salient points and you can look at the codeor various other articles I have
written around Cinch, or check
the
PRISM
documentation should you want more.
The Client Side: WPF
As just stated the client is a WPF app which makes use of the following:
- Cinch: My own MVVM Framework, you can read more about that
cinch.codeplex.com
-
PRISM: Which is the modular framework which just happens to work quite
nicely with my Cinch library
If you are not familiar with either of these 2 things, I would suggest you
look at the cinch.codeplex.com
and follow the Version 2 article links there. As for
PRISM,
I would suggest you read the documentation (which is very good) that is avaiable from the
PRISM web site.
So What Does the UI Actually Do
Here is a recap of what the UI allows the user to do:
- Search for items (I am storing Zombie data where a
heading/description and GeoLocation data are stored for each Zombie
incident)
- Add new ZombieIncident
- View ZombieIncident items on a map (I have chosen to use Bing maps)
- Use Rx to show an RSS feed of global Zombie incidents (as a bonus if
you will)
The UI is obvisouly MVVM and makes heavy use of the following
PRISM features
The are four main regions/modules which do the following:
- ShellModule: Provides a module that deals with the
commands for the MainWindow
- MapModule: Provides a module that shows zombie
incidents on a 2D Bing map, and allows new zombie incidents to be created
- RSSFeedModule: Provides a module that will grab zombie
incidents of a zombie web site using Reactive Framework (Rx)
- SearchModule: Provides a module that allows users to
search zombie incidents, and will display the results in a Panorama
control and also allows users to view these incidents in more detail
Important note
The UI uses Bing maps, so make sure you obtain your own API key, and do what is mentioned in the pre-requisites section at the top of this article. Also as
this UI uses Bing Maps (and also RX to grab some zombie data from an RSS feed) you
must have a good internet connection to make the app work as expected.
Shared DLLs
I think one of the key things that I see people doing when they first started
working with WCF was to use a service reference.cs file. Which is either created
by using the Visual Studio "Add Service Reference" menu item, or is created
using SvcUtil.exe. The problem with using Reference.cs is that you will only get
to see any DataContract/DataMembers serialized but no constructors and methods
will get serialized, which is pretty crap really.
A better approach is to simply separate out things that are common between a
WCF service and clients of the WCF service. So typically this would be things
like
- DataContract business objects
- ServiceContract (the WCF service interface)
- FaultContracts, such that the WCF clients can handle faults raised by
the WCF service
For the demo app the following two DLLs are shared between the WCF service and the
WPF client. Hopefully it should be obvious what is being shared by the names of
the folders in the two screenshots shown below.
WcfExamplar.Contracts
WcfExemplar.Common
By sharing this DLL the WPF client is able to do completely insane things
like call actual methods in objects in the shared DLL. Another totally way out
there thing, is that the WPF client can even use constructors for the shared
business objects.Mental.
Now if you have of used Reference.cs (generated for you by either Visual
Studio or SvcUtil.exe) you would not have gotten any methods. The long and short
of it, never use Reference.cs, it is evil and gives you nothing. Just share the
common stuff in DLLs. It gives you everything Reference.cs gives you and more.
Such as the ability to know how to construct your objects in a valid state, instead
of guessing what properties you may need to set to successfully create a object
you may find in Reference.cs.
Common Proxy
To facilitate communications with the WCF service the WPF client uses a
simple proxy class which is shown below:
[PartCreationPolicy(CreationPolicy.Shared)]
[Export(typeof(IServiceInvoker))]
public class WCFServiceInvoker : IServiceInvoker
{
private static ChannelFactoryManager _factoryManager = new ChannelFactoryManager();
private static ClientSection _clientSection =
ConfigurationManager.GetSection("system.serviceModel/client") as ClientSection;
public R CallService<R>(Request request) where R : Response
{
var endpointNameAddressPair = GetEndpointNameAddressPair(typeof(IGateway));
IGateway proxy = _factoryManager.CreateChannel<IGateway>(endpointNameAddressPair.Key, endpointNameAddressPair.Value);
ICommunicationObject commObj = (ICommunicationObject)proxy;
try
{
return (R)proxy.ExecuteRequest(request);
}
catch (FaultException<GenericFault> gf)
{
WcfExemplar.Common.Logging.LogManager.Instance.Logger(
"WCFServiceInvoker").Error("A Gateway FaultException<GenericFault> occured", gf.InnerException);
throw new ApplicationException("A Gateway FaultException<GenericFault> occured", gf.InnerException);
}
catch (FaultException<BusinessLogicFault> bf)
{
WcfExemplar.Common.Logging.LogManager.Instance.Logger(
"WCFServiceInvoker").Error("A Gateway FaultException<BusinessLogicFault> occured", bf.InnerException);
throw new BusinessLogicException(bf.Message);
}
catch (Exception ex)
{
WcfExemplar.Common.Logging.LogManager.Instance.Logger(
"WCFServiceInvoker").Error("A Gateway Exception occured", ex);
throw new Exception("A Gateway Exception occured", ex);
}
finally
{
try
{
if (commObj.State != CommunicationState.Faulted)
{
commObj.Close();
}
}
catch
{
commObj.Abort();
}
}
}
private static KeyValuePair<string, string> GetEndpointNameAddressPair(Type serviceContractType)
{
var configException = new ConfigurationErrorsException(string.Format(
"No client endpoint found for type {0}. Please add the section <client>" +
"<endpoint name=\"myservice\" address=\"http://address/\" binding" +
"=\"basicHttpBinding\" contract=\"{0}\"/></client> in the config file.",
serviceContractType));
if (((_clientSection == null) || (_clientSection.Endpoints == null)) || (_clientSection.Endpoints.Count < 1))
{
throw configException;
}
foreach (ChannelEndpointElement element in _clientSection.Endpoints)
{
if (element.Contract == serviceContractType.ToString())
{
return new KeyValuePair<string, string>(element.Name, element.Address.AbsoluteUri);
}
}
throw configException;
}
}
Some of the salient points of this helper class are
- It catches the known
FaultContract
(s) and logs them
- It is also capable of escalating certain
Exception
(s) to the calling WPF
client code, such that it can be shown to users
- It will locate the address for the contract from the App.Config automatically
This helper proxy class also makes use of another helper class that deals with ChannelFactory
creation. This extra helper classis shown below:
public class ChannelFactoryManager : IDisposable
{
private static Dictionary<Type, ChannelFactory> factories = new Dictionary<Type, ChannelFactory>();
private static readonly object _syncRoot = new object();
public virtual T CreateChannel<T>() where T : class
{
return CreateChannel<T>("*", null);
}
public virtual T CreateChannel<T>(string endpointConfigurationName) where T : class
{
return CreateChannel<T>(endpointConfigurationName, null);
}
public virtual T CreateChannel<T>(
string endpointConfigurationName, string endpointAddress) where T : class
{
T local = GetFactory<T>(endpointConfigurationName, endpointAddress).CreateChannel();
((IClientChannel)local).Faulted += ChannelFaulted;
return local;
}
protected virtual ChannelFactory<T> GetFactory<T>(
string endpointConfigurationName, string endpointAddress) where T : class
{
lock (_syncRoot)
{
ChannelFactory factory;
if (!factories.TryGetValue(typeof(T), out factory))
{
factory = CreateFactoryInstance<T>(endpointConfigurationName, endpointAddress);
factories.Add(typeof(T), factory);
}
return (factory as ChannelFactory<T>);
}
}
private ChannelFactory CreateFactoryInstance<T>(
string endpointConfigurationName, string endpointAddress)
{
ChannelFactory factory = null;
if (!string.IsNullOrEmpty(endpointAddress))
{
factory = new ChannelFactory<T>(
endpointConfigurationName, new EndpointAddress(endpointAddress));
}
else
{
factory = new ChannelFactory<T>(endpointConfigurationName);
}
factory.Faulted += FactoryFaulted;
factory.Open();
return factory;
}
private void ChannelFaulted(object sender, EventArgs e)
{
IClientChannel channel = (IClientChannel)sender;
try
{
channel.Close();
}
catch
{
channel.Abort();
}
throw new ApplicationException("Exc_ChannelFailure");
}
private void FactoryFaulted(object sender, EventArgs args)
{
ChannelFactory factory = (ChannelFactory)sender;
try
{
factory.Close();
}
catch
{
factory.Abort();
}
Type[] genericArguments = factory.GetType().GetGenericArguments();
if ((genericArguments != null) && (genericArguments.Length == 1))
{
Type key = genericArguments[0];
if (factories.ContainsKey(key))
{
factories.Remove(key);
}
}
throw new ApplicationException("Exc_ChannelFactoryFailure");
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (_syncRoot)
{
foreach (Type type in factories.Keys)
{
ChannelFactory factory = factories[type];
try
{
factory.Close();
continue;
}
catch
{
factory.Abort();
continue;
}
}
factories.Clear();
}
}
}
}
Some of the salient points of this helper class are
- It holds a
Dictionary
of ChannelFactory
instances that are not Faulted. Though in this demo codes case this is only
going to contain one key, as there is only one service
- It will automatically remove any Faulted
ChannelFactory
I actually got the basics of both of these classes from a
StackOverflow forum entry
which you can read more abut here :
http://stackoverflow.com/questions/3200197/creating-wcf-channelfactoryt
Services to Talk To The WCF Service
In order to talk to the WCF service the WPF client makes use of services.
These services are typically abstracted away behind an interface. This is done in order to
facilitate better testing to
some extent. As if you your WCF service is not avaiable, we could feed in some
mock services that could talk to some in memory store rather than directly to
the WCF service.
Here is a typical service:
namespace WcfExemplar.WpfClient.RSSFeedModule.Services
{
public interface IRssFeedProvider
{
void LoadFeed (Action<IEnumerable<Item>> subscribeCallback, Action<Exception> errorCallback);
}
}
namespace WcfExemplar.WpfClient.MapModule.Services
{
public interface IZombieIncidentProvider
{
bool Save(ZombieIncidentViewModel newIncident);
void LoadIncidents(Action<IEnumerable<ZombieIncidentViewModel>> successCallback, Action<Exception> errorCallback);
}
}
namespace WcfExemplar.WpfClient.SearchModule.Services
{
public enum SearchType
{
Contains = 1,
StartsWith = 2,
EndsWith = 3,
ShowAll = 4
};
public interface ISearchProvider
{
void SearchIncidents(
string propertyName,
SearchType searchType,
string searchValue,
Action<IEnumerable<PanoramaZombieIncidentViewModel>> successCallback, Action<Exception> errorCallback);
}
}
Some of you may notice that these service methods don't return any results directly. The reason for this is
that we want to keep the UI responsive.
All such UI services are
asynchronous (either using Reactive Extensions (RX) or use Task Parallel
Library (TPL)) to ensure the UI is kept responsive such that it may do things
like o animate a busy indicator while some background work is
happening. The Action<T>
delegates, serve as callbacks when either
the work is done or an error has occurred.
Another thing you may notice (if you examine the code base), is that these
services are made available to the ViewModels in the demo app by using MEF. That
is what you get for free if you use Cinch V2 and
PRISM 4 together. They work
very well together indeed.
We will be talking more about the services used in the 3 sub-sections shown
below.
RSS Feed Module
Important note:
You can click on the images below to view a bigger copy of the image
The RSS feed module reads data from the following URL:
http://www.zombiereportingcenter.com/feed/ using the Reactive
Extensions (RX).
Where you can click on the "View full RSS feed" button to view the full RSS Feed, which is as shown below.
This is a friction enabled ScrollViewer
which I have talked about
before, so click and release to have some friction fun.
The main work to produce the data that drives these screens is done by the
RssFeedProvider, which is as follows:
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportService(ServiceType.Runtime, typeof(IRssFeedProvider))]
public class RssFeedProvider : IRssFeedProvider
{
private string feedUri = "http://www.zombiereportingcenter.com/feed/";
public void LoadFeed(Action<IEnumerable<Item>> subscribeCallback, Action<Exception> errorCallback)
{
Func<IObservable<string>> readZombieFeed = () =>
{
var request = (HttpWebRequest)HttpWebRequest.Create(new Uri(feedUri));
var zombieFeedAsyncObs = Observable.FromAsyncPattern<WebResponse>(request.BeginGetResponse, request.EndGetResponse);
return zombieFeedAsyncObs().Select(res => WebResponseToString(res));
};
var sub = Observable.Interval(TimeSpan.FromSeconds(5))
.SelectMany(txt => readZombieFeed())
.Select(response => ParseZombieFeedData(response))
.Subscribe(zombieResults => subscribeCallback(FilterResults(zombieResults)), ex => errorCallback(ex));
}
private IEnumerable<Item> FilterResults(IEnumerable<Channel> zombieResults)
{
List<Item> items = new List<Item>();
foreach (Channel channel in zombieResults)
{
items.AddRange(channel.Items);
}
return items;
}
private string WebResponseToString(WebResponse webResponse)
{
HttpWebResponse response = (HttpWebResponse)webResponse;
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
return reader.ReadToEnd();
}
}
private IEnumerable<Channel> ParseZombieFeedData(string response)
{
var xdoc = XDocument.Parse(response);
return from channels in xdoc.Descendants("channel")
select new Channel
{
Title = channels.Element("title") != null ? channels.Element("title").Value : "",
Link = channels.Element("link") != null ? channels.Element("link").Value : "",
Description = channels.Element("description") != null ? channels.Element("description").Value : "",
Items = from items in channels.Descendants("item")
select new Item
{
Title = items.Element("title") != null ? items.Element("title").Value : "",
Link = items.Element("link") != null ? items.Element("link").Value : "",
Description = items.Element("description") != null ? items.Element("description").Value : "",
Guid = (items.Element("guid") != null ? items.Element("guid").Value : "")
}
};
}
}
It can be seen that this does several things in order to get results
- It creates an
Observable
from an asynchronous web request, which it
refreshes using the Rx Interval
method
- The data is then parsed using standard XLINQ operations, into
Channel/Item objects which you can see in the code
- As all this module does it parse some results from a web site, there is
no need for WCF in this module, it is all nice and self contained
MapModule
This module shows all existing zombie incidents on a Bing map. Just for
the record if you followed the "Getting Started" section you should have some
existing zombie incidents as I gave you some dummy data to seed the database
with.
Here is what it possible with this module
- You can zoom the map using the controls on the right
- You can change the type of map using the controls on the right
- View tooltips for existing zombie incidents by hovering over the zombie
icons shown
- When you double click on a location on the map, a new zombie panel will slide in from the right which will allow you to create new zombie incidents
(this is shown below)
Let's break down the things this module does and examine the code in more
detail:
Working With Bing Maps
The Bing map is available as a standard WPF control, which means you
literally just have to put something like this in your XAML.
<!---->
<bing:Map x:Name="map"
Grid.Row="1"
Grid.Column="0"
PreviewMouseDoubleClick="Map_PreviewMouseDoubleClick"
CredentialsProvider="INSERT_YOUR_BING_MAPS_KEY"
ZoomLevel="4.0"
AnimationLevel="Full"
Mode="AerialWithLabels"
Center="37.806029,-122.407007" />
So with the map in place, adding data, and controlling the map is easily
achieved using some simple code behind (Yes even I a MVVM pusher, use code behind
occasionally where I see it makes sense). Here is the entire code behind for
the ZombieIncidentMapView
.
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class ZombieIncidentMapView : UserControl, IZombieIncidentMapView
{
private double minZoom = 0;
private double maxZoom = 20;
LocationConverter locConverter = new LocationConverter();
public ZombieIncidentMapView()
{
InitializeComponent();
map.Focus();
Cinch.Mediator.Instance.Register(this);
}
public void CreatePinsForZombieIncidents(IEnumerable<ZombieIncidentViewModel> zombieIncidents)
{
map.Children.Clear();
foreach (var zombieIncident in zombieIncidents)
{
MapPin pin = new MapPin() { ZombieIncident = zombieIncident };
pin.SetValue(MapLayer.PositionProperty, new Location(zombieIncident.Latitude, zombieIncident.Longitude, 0));
pin.SetValue(MapLayer.PositionOriginProperty, PositionOrigin.Center);
map.Children.Add(pin);
}
}
public void AddNewlyCreatedIncident(ZombieIncidentViewModel zombieIncident)
{
VisualStateManager.GoToState(this, "HideAddState", true);
MapPin pin = new MapPin() { ZombieIncident = zombieIncident };
pin.SetValue(MapLayer.PositionProperty, new Location(zombieIncident.Latitude, zombieIncident.Longitude, 0));
pin.SetValue(MapLayer.PositionOriginProperty, PositionOrigin.Center);
map.Children.Add(pin);
}
[Cinch.MediatorMessageSink("HideAddIncidentPaneMessage")]
public void OnHideAddIncidentPaneMessage(bool dummy)
{
VisualStateManager.GoToState(this, "HideAddState", true);
}
private void Map_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
Point mousePosition = e.GetPosition(this);
Location pinLocation = map.ViewportPointToLocation(mousePosition);
addNewGrid.Content = new AddNewIncidentView()
{
GeoLocation = new Tuple<double, double>(pinLocation.Latitude, pinLocation.Longitude)
};
VisualStateManager.GoToState(this, "ShowAddState", true);
}
private void btnZoomIn_Click(object sender, RoutedEventArgs e)
{
try
{
var zoom = map.ZoomLevel + 2;
map.ZoomLevel = zoom > maxZoom ? maxZoom : zoom;
}
catch { }
}
private void btnZoomOut_Click(object sender, RoutedEventArgs e)
{
try
{
var zoom = map.ZoomLevel - 2;
map.ZoomLevel = zoom < minZoom ? minZoom : zoom;
}
catch { }
}
private void ContextMenu_Click(object sender, RoutedEventArgs e)
{
try
{
MenuItem menuItem = (MenuItem)e.OriginalSource;
switch (menuItem.Header.ToString())
{
case "Aerial":
map.Mode = new AerialMode(true);
break;
case "Road":
map.Mode = new RoadMode();
break;
}
}
catch { }
}
private void btnMapType_Click(object sender, RoutedEventArgs e)
{
(sender as Button).ContextMenu.IsOpen = true;
}
}
It is pretty straight forward, and I hope that you can see this code simply
manipulates the Bing map control, and allows the displaying of existing zombie incidents/ allows the adding of a new zombie incident via a new view which is
simply shown/hidden from this view.
Obtaining All Existing Zombie Incidents
In order to fetch ALL existing zombie incidents from the database the
following WPF UI service is used
public interface IZombieIncidentProvider
{
bool Save(ZombieIncidentViewModel newIncident);
void LoadIncidents(Action<IEnumerable<ZombieIncidentViewModel>> successCallback, Action<Exception> errorCallback);
}
Where obviously we need to focus on the LoadIncidents(..)
method. Which is as follows:
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportService(ServiceType.Runtime, typeof(IZombieIncidentProvider))]
public class ZombieIncidentProvider : IZombieIncidentProvider
{
private IServiceInvoker serviceInvoker;
[ImportingConstructor]
public ZombieIncidentProvider(IServiceInvoker serviceInvoker)
{
this.serviceInvoker = serviceInvoker;
}
public void LoadIncidents(Action<IEnumerable<ZombieIncidentViewModel>>
successCallback, Action<Exception> errorCallback)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task<bool> cancellationDelayTask = TaskHelper.CreateDelayTask(20000);
cancellationDelayTask.ContinueWith(dt =>
{
cancellationTokenSource.Cancel();
}, TaskContinuationOptions.OnlyOnRanToCompletion);
try
{
Task<IEnumerable<ZombieIncidentViewModel>> searchTask =
Task.Factory.StartNew<IEnumerable<ZombieIncidentViewModel>>(() =>
{
var incidents = this.serviceInvoker.CallService<ZombieIncidentsResponse>(
new ZombieIncidentsRequest()).ZombieIncidents.ToList();
List<ZombieIncidentViewModel> zombieIncidentViewModels = new List<ZombieIncidentViewModel>();
foreach (var incident in incidents)
{
zombieIncidentViewModels.Add(new ZombieIncidentViewModel(
incident.GeoLocation.Latitude, incident.GeoLocation.Longitude, incident.Heading, incident.Text));
}
return zombieIncidentViewModels;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
searchTask.ContinueWith(ant =>
{
successCallback(ant.Result);
}, cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext());
searchTask.ContinueWith(ant =>
{
LogManager.Instance.Logger("WpfClient.MapModule.ZombieIncidentProvider").Error(
"A timeout occurred whilst attempting to fetch the zombie incidents");
errorCallback(new TimeoutException("A timeout occurred whilst attempting to fetch the zombie incidents"));
}, cancellationToken, TaskContinuationOptions.NotOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext());
}
catch(AggregateException ex)
{
LogManager.Instance.Logger("WpfClient.MapModule.ZombieIncidentProvider").Error(ex.Flatten().Message);
errorCallback(new ApplicationException("A generic error occurred whilst trying to fetch the zombie incidents"));
}
}
}
The code above demonstrates several things that we will see again
- We use TPL to ensure this work is done using the
ThreadPool
,
to ensure the UI stay responsive whilst this work is happening
- We make use of the
Service
proxy class that we talked about
earlier
- We make use of callback
Action<T>
to call on success and on
failure
- We create a new delay
Task
that will cancel the actual
Task
if it has not completed within x amount of time
So that is how the UI works. Let's now move our attention onto the WCF
Task
that actual provides the existing zombie incidents shall we.
The WCF Task ZombieIncidentsTask
is where this happens. Here is the entire
class, which I think its very self explanatory, now that we know that we are
using Unit Of Work / Repository pattern and make use of Fluent NHibernate.
public class ZombieIncidentsTask : Task
{
private IRepository<dtos.ZombieIncident> zombieRepository;
private IUnitOfWork unitOfWork;
public ZombieIncidentsTask(IRepository<dtos.ZombieIncident> customerRepository, IUnitOfWork unitOfWork)
{
this.zombieRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public override Response Execute()
{
try
{
List<ZombieIncidentInfo> zombies = new List<ZombieIncidentInfo>();
foreach (dtos.ZombieIncident zombie in zombieRepository.GetAll().ToList())
{
zombies.Add(ZombieIncidentDTOMapper.FromDTO(zombie));
}
return new ZombieIncidentsResponse(zombies);
}
catch (Exception ex)
{
return new ZombieIncidentsResponse(new List<ZombieIncidentInfo>());
}
}
}
Saving a New Zombie Incident
In order to create a new zombie incident within the database the
following WPF UI service is used
public interface IZombieIncidentProvider
{
bool Save(ZombieIncidentViewModel newIncident);
void LoadIncidents(Action<IEnumerable<ZombieIncidentViewModel>> successCallback, Action<Exception> errorCallback);
}
Where obviously we need to focus on the Save(..)
method. Which is as follows:
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportService(ServiceType.Runtime, typeof(IZombieIncidentProvider))]
public class ZombieIncidentProvider : IZombieIncidentProvider
{
private IServiceInvoker serviceInvoker;
[ImportingConstructor]
public ZombieIncidentProvider(IServiceInvoker serviceInvoker)
{
this.serviceInvoker = serviceInvoker;
}
public bool Save(ZombieIncidentViewModel newIncident)
{
ZombieIncidentInfo zombieIncidentInfo= new ZombieIncidentInfo(newIncident.Heading, newIncident.Text,
new GeoLocationInfo(newIncident.Latitude,newIncident.Longitude));
return this.serviceInvoker.CallService<SaveZombieIncidentResponse>(
new SaveZombieIncidentRequest(zombieIncidentInfo)).Success;
}
}
As before we make use of the Service
proxy class that we talked about
earlier.
So that is how the UI works. Let's now move our attention onto the WCF task
that actual provides the existing zombie incidents, shall we?
The WCF Task SaveZombieIncidentTask
is where this
happens.
public class SaveZombieIncidentTask : Task
{
private IZombieIncidentDomainLogic zombieIncidentLogic;
private IRepository<dtos.ZombieIncident> zombieRepository;
private IRepository<dtos.GeoLocation> geoLocationRepository;
private IUnitOfWork unitOfWork;
private ZombieIncidentInfo zombieIncidentInfo;
public SaveZombieIncidentTask(IZombieIncidentDomainLogic zombieIncidentLogic,
IRepository<dtos.ZombieIncident> zombieRepository, IRepository<dtos.GeoLocation> geoLocationRepository,
IUnitOfWork unitOfWork, ZombieIncidentInfo zombieIncidentInfo)
{
this.zombieIncidentLogic = zombieIncidentLogic;
this.zombieRepository = zombieRepository;
this.geoLocationRepository = geoLocationRepository;
this.unitOfWork = unitOfWork;
this.zombieIncidentInfo = zombieIncidentInfo;
}
public override Response Execute()
{
try
{
bool result = zombieIncidentLogic.CanStoreZombieIncident(zombieIncidentInfo);
if (result)
{
ZombieIncident zombieIncident = ZombieIncidentDTOMapper.ToDTO(zombieIncidentInfo);
zombieRepository.InsertOnSubmit(zombieIncident);
zombieRepository.SubmitChanges();
geoLocationRepository.InsertOnSubmit(zombieIncident.GeoLocation);
geoLocationRepository.SubmitChanges();
unitOfWork.Commit();
unitOfWork.Dispose();
return new SaveZombieIncidentResponse(result);
}
return new SaveZombieIncidentResponse(false);
}
catch (BusinessLogicException bex)
{
throw;
}
catch (Exception ex)
{
WcfExemplar.Common.Logging.LogManager.Instance.Logger(
"Tasks").ErrorFormat("{0}\r\n{1}",ex.Message, ex.StackTrace);
throw;
}
}
}
As before we simply make use of the things we have already seen:
- Business logic: To ensure the new zombie business object is valid for
saving
- DTO: Transfer objects to write to the database
- Unit of work: Allows work to be done one transaction
- Repository: Allows us to abstract away the operations with the
database, we simply add to a repository
Search Module
This module allows the user to craft a search, which is used to view
matching zombie incidents in a tile Panorama control, which is a control I
constructed in a previous article.
As can be seen from the above diagram you have several options to allow you
to craft your search
- You may pick search property
- You may pick search type (where some of these will also need a search
value)
- Contains
- StartsWith
- EndsWith
- ShowAll (default)
Whilst some background operation is happening in the background the UI will show this animated spinner
Here is what the results look like when they arrive
When you click on one of the tiles an information panel will animate in from
the RHS of the screen, which shows you some text and also a mini map.
Here is the panel showing the data as text
Here is the panel showing the data on a mini map
In order to search for zombie incident within the database the
following WPF UI service is used
public enum SearchType
{
Contains = 1,
StartsWith = 2,
EndsWith = 3,
ShowAll = 4
};
public interface ISearchProvider
{
void SearchIncidents(
string propertyName,
SearchType searchType,
string searchValue,
Action<IEnumerable<PanoramaZombieIncidentViewModel>> successCallback, Action<Exception> errorCallback);
}
Where the actual code service code is as follows:
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportService(ServiceType.Runtime, typeof(ISearchProvider))]
public class SearchProvider : ISearchProvider
{
private IServiceInvoker serviceInvoker;
[ImportingConstructor]
public SearchProvider(IServiceInvoker serviceInvoker)
{
this.serviceInvoker = serviceInvoker;
}
public void SearchIncidents(string propertyName, SearchType searchType, string searchValue,
Action<IEnumerable<PanoramaZombieIncidentViewModel>> successCallback, Action<Exception> errorCallback)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task<bool> cancellationDelayTask = TaskHelper.CreateDelayTask(20000);
cancellationDelayTask.ContinueWith(dt =>
{
cancellationTokenSource.Cancel();
}, TaskContinuationOptions.OnlyOnRanToCompletion);
try
{
Task<IEnumerable<PanoramaZombieIncidentViewModel>> searchTask =
Task.Factory.StartNew<IEnumerable<PanoramaZombieIncidentViewModel>>(() =>
{
SearchZombieIncidentsRequest searchZombieIncidentsRequest =
new SearchZombieIncidentsRequest(propertyName,
(WcfExamplar.Contracts.SearchType)searchType, searchValue);
var incidents = this.serviceInvoker.CallService<SearchZombieIncidentsResponse>(
searchZombieIncidentsRequest).ZombieIncidents.ToList();
List<PanoramaZombieIncidentViewModel> zombieIncidentViewModels = new List<PanoramaZombieIncidentViewModel>();
foreach (var incident in incidents)
{
zombieIncidentViewModels.Add(new PanoramaZombieIncidentViewModel(
incident.GeoLocation.Latitude, incident.GeoLocation.Longitude, incident.Heading, incident.Text));
}
return zombieIncidentViewModels;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
searchTask.ContinueWith(ant =>
{
successCallback(ant.Result);
}, cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext());
searchTask.ContinueWith(ant =>
{
LogManager.Instance.Logger("WpfClient.SearchModule.SearchProvider").Error(
"A timeout occurred whilst attempting to search for zombie incidents");
errorCallback(new TimeoutException("A timeout occurred whilst attempting to search for zombie incidents"));
}, cancellationToken, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (AggregateException ex)
{
LogManager.Instance.Logger("WpfClient.SearchModule.SearchProvider").Error(ex.Flatten().Message);
errorCallback(new ApplicationException("A generic error occurred whilst trying to search for zombie incidents"));
}
}
}
As before we make use of the Service
proxy class that we talked about
earlier.
So that is how the UI works. Let's now move our attention onto the WCF task
that actual provides the existing zombie incidents, shall we?
The WCF Task "SearchZombieIncidentsTask
" is where this
happens.
public class SearchZombieIncidentsTask : Task
{
private IZombieIncidentDomainLogic zombieIncidentLogic;
private IRepository<dtos.ZombieIncident> zombieRepository;
private IUnitOfWork unitOfWork;
private string propertyName;
private SearchType searchType;
private string searchValue;
public SearchZombieIncidentsTask(IZombieIncidentDomainLogic zombieIncidentLogic,
IRepository<dtos.ZombieIncident> zombieRepository,
IUnitOfWork unitOfWork, string propertyName, SearchType searchType, string searchValue)
{
this.zombieIncidentLogic = zombieIncidentLogic;
this.zombieRepository = zombieRepository;
this.unitOfWork = unitOfWork;
this.propertyName = propertyName;
this.searchType = searchType;
this.searchValue = searchValue;
}
public override Response Execute()
{
try
{
List<ZombieIncidentInfo> zombies = new List<ZombieIncidentInfo>();
if (searchType == SearchType.ShowAll)
{
foreach (dtos.ZombieIncident zombie in zombieRepository.GetAll().ToList())
{
zombies.Add(ZombieIncidentDTOMapper.FromDTO(zombie));
}
}
else
{
bool result = zombieIncidentLogic.IsValidSearch(propertyName, searchType, searchValue);
if (result)
{
var paramStart = Expression.Parameter(typeof(dtos.ZombieIncident), "x");
Expression<Func<dtos.ZombieIncident, bool>> searchExp = Expression.Lambda<Func<dtos.ZombieIncident, bool>>(
Expression.Call(Expression.Property(paramStart,
typeof(dtos.ZombieIncident).GetProperty(propertyName).GetGetMethod()),
typeof(String).GetMethod(searchType.ToString(), new Type[] { typeof(String) }),
new Expression[] { Expression.Constant(searchValue, typeof(string)) }),
new ParameterExpression[] { paramStart });
foreach (dtos.ZombieIncident zombie in zombieRepository.FilterBy(searchExp).ToList())
{
zombies.Add(ZombieIncidentDTOMapper.FromDTO(zombie));
}
}
}
return new SearchZombieIncidentsResponse(zombies);
}
catch (BusinessLogicException bex)
{
throw;
}
catch (Exception ex)
{
WcfExemplar.Common.Logging.LogManager.Instance.Logger("Tasks").ErrorFormat("{0}\r\n{1}",ex.Message, ex.StackTrace);
throw;
}
}
}
As before we simply make use of the things we have already seen
- Business logic: To ensure the new zombie search is valid (i.e., has all required values)
- DTO: Transfer objects to write to the database
- Unit of work: Allows work to be done one transaction
- Repository: Allows us to abstract away the operations with the
database, we simply use the repository
One thing to note is that if the search type is "ShowAll" we simply return
all existing incidents, otherwise we construct a dynamic LambdaExpression
from
the Task
s input values, which is used against the Repository, which will query
the database correctly.
That's It
That is all I wanted to say this time. I also think this may be my last WPF
article for a while as I want to spend some time getting my JavaScript skills up
to date, so I will be spending some time on things like
- Node.js
- Backbone.js
- Underscore.js
- D3.js
- Raphael.js
- Easle.js
To name but a few, so you can hope to see some articles around those once I
begin/become terrified/beg for help or possibly even master them.
Special Thanks
I would like to thank the following people
History
- 10/10/12: Initial article
- 17/10/12:
- Added optimistic concurrency Fluent Nhibernate mapping stuff.
- Added better Request/Task mapping concept. Thanks to The .NET Junkie reader, for his excellent suggestions