Introduction
The internet, with its vast array of information, along with the constant stream of media being produced
by
publishers and similar organizations, offers the contemporary software developer an overwhelming set of
resources to choose
from. When a new technology arrives, the market is quickly flooded with media surrounding the essential
"How To's" of that
new thing. However, it often takes very long for best practices to emerge if they do at all. As a result
an interested and
motivated developer has to constantly search, often unable to find any real world examples. Though the
sample discussed in this
article is very simple, it is designed to show a set of best practices that can be used in a full
enterprise scenario. These best
practices are not specific to WPF and did in fact not originate in this area. I have
attempted to apply what I have learned in
other types of application development to a WPF scenario and have included all the necessary framework
pieces to help a developer be
successful with WPF.
Figure 1: Solution
Functionality of the Sample
The sample application is about as simple in nature as one can get and still include all of the pieces
needed to demonstrate
an enterprise example. Its purpose is to allow a user to track books that they have in their home
library. The user
interface allows the addition of new items to the library and a basic search feature.
When viewing items in your personal library, Amazon.com is queried for additional information about the
book (only a
picture at present). The user can also track whether or not they have loaned the book out to an
acquaintance.
Architectural Overview
The HomeLibrary solution consists of three assemblies: HomeLibrary.Client,
HomeLibrary.Core and HomeLibrary.Data.
Each of these assemblies represents a layer or an important aspect of a layer of the application.
The HomeLibrary.Core is what
would typically be thought of as the middle layer of the application. It contains the Domain
Model, the fundamental representation
of our "problem" in code; in this case: LibraryItems and LoanInfo. The model
is built entirely of POCOs (Plain Old CLR Objects),
having no knowledge of data access code. The POCO nature of these classes is important because it allows
us to have a clean
Separation Of Concerns. Having the domain model loosely coupled to data access allows us to
focus on individual aspects of
our application without muddying the waters and facilitates the creation of clean, maintainable code. The
best example of this
loosely coupled nature can be seen in the ILibraryItemRepository interface. This is an
abstraction of the LibraryItem data
store and is used by the application to work against the database rather than any concrete
implementation.
The UI/Application layer is represented by the HomeLibrary.Client assembly. This layer is
organized into two main sets of objects:
Presenters and Views. The presenters hold the application logic and interact
with the domain model and the abstract repositories. They
have a series of methods and properties which are bound at runtime to a set of views. The views are
represented primarily by WPF User
Controls and are organized in namespaces that relate to the presenters they work for.
At runtime the repository interfaces are instantiated with concrete classes from the
HomeLibrary.Data assembly. This assembly forms the bridge
between the domain model/repositories and the data access layer. Keeping the concrete repositories in
their own assembly allows the model and application
layers to be "unconcerned" about these details by always interacting through an interface. This "loose
coupling" allows for greater testability and
an overall increase in maintainability.
Looking At The Infrastructure
There are several frameworks and tools included with the sample that help to bring all of the pieces
together in a manageable way. They are:
NHibernate, NHibernate Query Generator, Rhino.Commons, Castle.Windsor, log4net and MVPforWPF.
NHibernate is an O/RM (Object Relational Mapper) that uses metadata to understand how the
classes in the domain model are represented within the
relational data store. Using this metadata (which can be found in the Mappings folder of
HomeLibrary.Data), NHibernate can save, retrieve and
delete domain objects from the underlying data store without the need
to write custom SQL. It does this intelligently and flexibly, allowing the developer access to low level
functionality if they need to optimize code.
NHibernate provides several different ways to query against objects. This sample application uses a tool
called NHibernate Query Generator to generate
strongly typed query objects for use with NHibernate. (The output of this tool can be found in
HomeLibrary.Data.Querying.)
These query classes allow the code to have a very readable LINQ type syntax. The utility
assembly Rhino.Commons sits on top
of NHibernate and manages many of the low level details as well as adding some nice additional
functionality. Mainly, it provides a concrete
repository implementation (NHRepository<T>) that can be used for all data access. You
can see a good example of all of these pieces coming together
by examining the RepositoryImplementations folder in the HomeLibrary.Data assembly and by looking at the
Update method of the LibraryItemPresenter in
the HomeLibrary.Client assembly. NHibernate's configuration is in its own file found at
bin/ebug/config.
Castle.Windsor is a Dependency Injection framework (also known as Inversion Of Control).
Let me explain. If we look at the constructor of the ShellPresenter
class in the HomeLibrary.Client we see that this class depends on an implementation of
ILibraryItemRepository and AmazonSearchService. An instance of this
class cannot be constructed without these two pieces. Windsor is essentially a fancy object factory that
knows how to instantiate classes that
depend on others. It also knows the relationships between abstract classes/interfaces and their concrete
implementations. This framework enables the developer
to simply register types with it and later call on them to be instantiated; Windsor will determine all of
the dependencies and hand you a fully
configured object ready for work. You can view the configuration of Windsor by looking at the
Windsor.config file in the bin/Debug/config folder of HomeLibrary.Client.
The information in the file is used by Rhino.Commons to provide strongly typed repositories and by
MVPforWPF to instantiate presenters like the one mentioned above.
Many applications require some form of logging. log4net provides a very robust solution. It is used
internally by several of the frameworks listed above and has been
configured for use in the sample application. Take a look at the app.config for a basic
logging configuration. The app.xaml.cs demonstrates a simple usage of the API.
MVPforWPF
I have included full source for a WPF Model View Presenter framework. The framework is
the beginnings of something that I have been experimenting with in my
spare time. It is highly influenced by the MonoRail web framework and attempts to intuitively add some
similar functionality to WPF. If you spend any amount of
time researching MVC or MVP patterns with WPF, you will not find
very many resources. It is fairly widely agreed upon that these patterns or their variants should be
considered when developing a presentation tier.
There are several pieces of the framework that you should know about if you would like to use or extend
it. The two most important pieces are ViewSite and PresenterBase.
ViewSites are locations in the UI where you wish a "view" to appear. Presenters contain the application
logic and should inherit from PresenterBase. When you declare a
ViewSite, you specify a Presenter, either by name (if it is configured for
Windsor) or by type if you are not using Windsor.
Listing 1: ViewSite Declaration
When the UI is instantiated the
ViewSite will
obtain the presenter from the
IPresenterFactory. Any view displayed in the
ViewSite
will be bound to the
Presenter. The
Presenter is a
WPF
DependencyProperty, which means
that which presenter is used can be databound.
Listing 2: ViewSite With Databinding
ViewSite also has a View property which you can set to the name of a view or you can use the
Presenter attribute on your presenter
to declare a default view.
Listing 3: Presenter Attribute
Binding to a Presenter allows you to use a special AttachedProperty to bind Presenter
methods to UI elements.
Listing 4: Presenter Actions
When the above button is clicked, the "Search" method on the bound presenter will be called and if that method
has a parameter (which it does), whatever the DataContext is
will be passed in. In the case that the control is an ItemsControl, then the selected item will be
passed in. Additionally, you can place an Action attribute or an AsynAction
attribute on any presenter method. Using this you can specify custom code that automatically disables parts of
the UI, run code asynchronously and specify callbacks. Here is an example of
what that would look like:
Listing 5: Action Attribute
There is some nice functionality provided by the
Action,
AsyncAction and
Presenter attributes that is worth looking into. Additionally,
PresenterBase has a
method on it
that allows for changing the view and
ViewSite will use a built in transition mechanism to switch
from view to view. (You can also add your own transitions.)
Listing 5: Render View
Note: Views should be arranged hierarchically by Presenter within the solution. A
default namespace should be configured with the framework.
Spend some time studying the code and I am sure you will find some useful tools to simplify WPF
development. Hopefully this will give you some ideas to chew on and
motivate the creation of a more maintainable presentation layer for your apps.
Possible Improvements
There are several possible changes that could be made to the sample that would be a great study in
application development. One of the first things I might do
would be to convert this to either a distributed or a service oriented architecture. This is a common
scenario. To make these changes, one would remove dependencies
on repositories (HomeLibrary.Data) and Rhino.Commons. If you are going the distributed
route, you can simply move the repositories and their associated code into the service and then
inject these services into the presenters using Castle.Windsor. If you are taking the SOA
route, you may need to move more code from your presenters into your services
and additionally, remove the reference to HomeLibrary.Core. In a SOA you don't
want to talk in terms of domain objects, but rather in terms of messages. The domain objects
should be hidden below the service later.
One big set of improvements surround the MVP framework. The code provided with the sample is only the
beginning of what would be needed on a typical project. There
are many improvements to the existing functionality that could be made as well as plenty of other useful
features that could be added. A few examples are:
Remove ViewSite's hard coded uses of the CrossFade transition and add a transition
property to allow different transitions to be selected.
Improve databinding to action parameters. Automatically bind the values of controls to the input
parameters of actions based on name.
Allow the return value of actions to be bound back to the UI.
Add a system for loosely coupled events.
Abstract a set of interfaces that cover common UI related tasks, such as Dialog and
MessageBox functionality. Concrete implementations should be automatically injected at
runtime.
I will be continually
improving this framework over the next several months and may even begin an official open source project.
If you are interested, please watch my blog for details
in the future.
The final piece that is missing from the sample are Unit Tests. In any real world project I
would employ a test first methodology. The architecutre that I have
recommended here should make it easier to test various aspects of the application. I have included the
excellent NSpecify framework with the download. This is my
favorite tool for doing TDD/BDD at present.
Summary
I hope that you will take time to look closely at the sample code. Though its purpose is simple in
nature, it shows all the necessary pieces needed to
build an enterprise WPF application. It demonstrates such useful strategies as application
layering, loose coupling, dependency injection, domain modeling and MVP.
If anything, I hope that you can use the sample as a startup project for future WPF work.
Note: Before running the solution you must create the database, set the connection
string (NHibernate.config) and declare your amazon.com key (LibraryItemPresenter.cs).
References
Top Articles in this category
Introduction to WPF Animations
WPF is a new framework that has many advanced capabilities. Animations are one of those capabilities, where an object can be animated via rotating, stretching, scaling, moving it across the screen, changing its color, etc. This article will show some of the basic animations.
Styles, Resources, and Control Templates in WPF
This article will show how to use Styles, Resources, and Control Templates in WPF.
WPF Data Binding, With LINQ
Brian Mains explains how to perform data binding in WPF with LINQ.
More WPF Animations
WPF (Windows Presentation Foundation) has many ways to manipulate objects. Some of these ways involve transforming an object, or using animation to animate the various values of an object. These can all work together to provide a very interactive interface. This article will show you how.
WPF Flow Documents and Images
Brian mains shows how to use the flow documents and images.
|
|
Please login to rate or to leave a comment.