| |
|
Chapter VII | | Chapter IX |
The series
WCF by example is a series of articles that describes 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 kept working on the client side, and we articulated
a mechanism that permits invoking server services from the ViewModel class in a decoupled fashion. In this chapter, we will define the pattern for the View (XAML)
to invoke the mentioned services from the ViewModel. We will introduce the RelayCommand pattern which leverages the use of XAML binding capabilities, easing the execution
of methods declared in the ViewModel.
This pattern is based on an article by Josh Smith about the MVVM pattern; he covers a lot of MVVM topics
in his blog. The source code for this chapter can be found
at CodePlex change set 93453. The latest code for the eDirectory solution can
be found at CodePlex.
ICommand
A comprehensive explanation of the pattern can be found in Josh's articles. As a result, we are going to focus on the implementation in our solution rather than explaining the pattern
in detail. You may find it beneficial reading the article before continuing with this chapter.
In our client, we created two command buttons in Chapter VI when the XAML was defined;
the Save and the Refresh buttons. It might not have been obvious at the time, but we declared bindings to actions in the ViewModel. These methods were not defined
in the CustomerViewModel
on Chapter VI, but did not stop us from executing the application. It is worth noting this aspect of the XAML Views, faulty bindings don't throw exceptions;
however, well documented warnings can be found in VS when debugging the application. As an example, the Save button XAML declaration in
Chapter VI was:
<Button Grid.Column="1" Grid.Row="2"
Padding="5" Margin="5"
Command="{Binding SaveCommand}">Save</Button>
The command action will invoke a read-only property called SaveCommand
in the ViewModel; the property in the ViewModel must implement the ICommand
interface to work.
Relay Command
The RelayCommand
class provides a simple mechanism for declaring properties in the ViewModel so buttons can invoke actions using binding definitions. In the
example of the Save button, the binding indicates that the button will invoke a property called SaveCommand
; the CustomerViewModel
class
declares the property as follows:
01 private RelayCommand SaveCommandInstance;
public RelayCommand SaveCommand
{
get
{
if (SaveCommandInstance != null) return SaveCommandInstance;
02 SaveCommandInstance = new RelayCommand(a => Save());
return SaveCommandInstance;
}
}
private void Save()
{
03 var result = CustomerServiceAdapter.Execute(
s => s.CreateNewCustomer(Model.NewCustomerOperation));
Refresh();
}
We are using a lazy approach (line 02) to instantiate the RelayCommand
private instance (line 01); this approach permits to use a lambda expression that
indicates the internal method that will be called when the View invokes the Execute
method of the RelayCommand
. It is worth noting that two constructors
are available for the RelayCommand
; in this example, we are using the one that only requires to pass the action to invoke. There is an overloaded constructor
that provides a mechanism for validation, which in the case of command buttons ensures that the button is only enabled if the validation is successful. We may see examples
in later chapters of how to use this feature.
Let's see what other changes are required to get our front-end invoking service methods.
Service Adapter - Usage in the ViewModel
In line 03, we can see that the Save
method is invoking the CreateNewCustomer
service method. Couple aspects to explain at this point are how
the CustomerServiceInstance
is declared and also the mechanism used to pass customer details from the UI to the service using the NewCustomerOperation
DTO.
CustomerServiceAdapter
is instantiated on the ViewModel constructor:
01 private readonly ServiceAdapter<ICustomerService> CustomerServiceAdapter;
public CustomerViewModel()
{
02 CustomerServiceAdapter = new ServiceAdapter<ICustomerService>();
Refresh();
View = new CustomerView { DataContext = this };
View.ShowDialog();
}
In line 01, we instantiate the ServiceAdapter
indicating that we want to deal with the Customer services for this ViewModel. This is a very nice way to leverage
WCF services in the client side; the ViewModel is not coupled to the WCF service implementation, which reduces complexity when developing and testing. It also provides an easy way to
avoid using MS WCF proxies if we are developing both the server and client side. We discuss in Chapter XII - WCF Implementation how easy it is to implement the WCF services in the client side using this design.
The Refresh
method is called before we display the view; we do so to initialise the Model:
private void Refresh()
{
01 var result = CustomerServiceAdapter.Execute(s => s.FindAll());
02 Model = new CustomerModel { NewCustomerOperation = new CustomerDto(),
CustomerList = result.Customers};
}
The purpose of the Refresh
method is to retrieve all the Customer
instances invoking the FindAll
service method and then create an instance
of the CustomerModel
class which is the model for the CustomerView
. In line 02, a CustomerDto
instance is assigned to the NewCustomerOperation
property. This is bound to the customer details section in the front-end using a TwoWay
mode. This means that the XAML is capable of updating the
CustomerDto
instance without any other additional code; not too bad. This is a nice pattern for populating service method parameters as we saw in the
Save
method.
BootStrapper Changes
| At this stage in the project, we just need the WPF client to invoke the service methods within a single domain process, without the overhead of dealing with WCF services.
In a later chapter, we will cover DI and how we can deploy the server assemblies without having to create references in our client to the server components. For
the time being, for the sake of keeping things simple, we will just add those references to the client project.
|
Then, we just need to enhance the eDirectoryBootStrapper
, adding the following code to the InitialiseDependencies
method:
private void InitialiseDependencies()
{
01 GlobalContext.Instance().TransFactory = new TransManagerEntityStoreFactory();
02 Container.RequestContext = new RequestContextNaive();
03 ClientServiceLocator.Instance().CommandDispatcher = new DirectCommandDispatcher();
}
So we indicate to the client that we want to use the in-memory implementations for the TransFactory
in the GlobalContext
service (line 01), and
the RequestContext
is set to the naive implementation (line 02): the RequestContextNaive
class.
We also set the CommandDispatcher
so we use the direct service implementation (line 03) that we discussed
in the previous article.
Almost There
If we run the application and we enter some customer details and then press the Save button, we notice that nothing happens. But if we debug our code, we can confirm
that our Save
method is executed:
It seems that everything is working properly; the Refresh
method seems to work fine after the CreateNewCustomer
service method is executed.
We can even inspect the results and see that the returned collection contains a customer and all the properties are correctly populated. So then why is the front-end
not being refreshed?
This is another characteristic of WPF client applications; we need to indicate to the View that the model has been refreshed. In order to do so, we need to introduce
the INotifyPropertyChanged
interface, we cover this aspect in the next article.
Chapter Summary
In this chapter, we introduced the RelayCommand
class, an implementation of the ICommand
by Josh Smith. We discussed how the View and ViewModel
are designed so the front-end can invoke service methods using XAML bindings. At this point, we almost have the application working, using the in-memory mode and invoking the server methods
within the client domain process; we are currently avoiding the WCF and NHibernate components for convenience purposes.
At this point, we just need the ViewModel to notify the View when the Model has been updated.
The next chapter resolves the notification issue, and provides a comprehensive infrastructure to start demonstrating our solution features to the client.