Previous Next
Chapter XII Chapter XIV

The series

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

Chapter overview

At this point in the series we have covered the most important infrastructure components, however our business domain consists of a single entity which doesn't help to explain how to resolve some common scenarios when designing parent-child across the different application layers. In this chapter, we are introducing a new entity to the model so we can describe how the above mentioned cases might be resolved.

At the end of this chapter, at the appendix section, we also discussed the following topics:

  • How to execute the application in Visual Studio
  • Couple WPF aspects added in this chapter: Custom WPF Converter and Explicit Application ShutDown
  • AutoMapper

Model Entity Overview

Up to this point our model consisted of a single class: Customer. We are adding a new entity named: Address, this is a simple entity that contains customer address details:

    
    public class Address
        :EntityBase
    {
01      protected Address(){}

02      public virtual Customer Customer { get; private set; }
        public virtual string Street { get; private set; }
        public virtual string City { get; private set; }
        public virtual string PostCode { get; private set; }
        public virtual string Country { get; private set; }

03      public static Address Create(IRepositoryLocator locator, AddressDto operation)
        {
            var customer = locator.GetById<Customer>(operation.CustomerId);
            var instance = new Address
            {
                ...
            };

            locator.Save(instance);
            return instance;
        }

        public virtual void Update(IRepositoryLocator locator, AddressDto operation)
        {
            UpdateValidate(locator, operation);
            ...
            locator.Update(this);
        }

        private void UpdateValidate(IRepositoryLocator locator, AddressDto operation)
        {
            return;
        }
    }

The entity has a reference to the Customer reference (line 02) so we will have a one-to-many relationship. As we did with the Customer class, we hide the constructor (line 01) so the Create static method (line 03) needs to be invoked when a new instance is required.

The Customer class needs some re-factor to accommodate for the new Address class; couple important points are that the Customer class will be responsible for the creation and deletion of Address instances and that the collection of addresses is not exposed directly to ensure the collection is well managed, see also how the ISet needs to be used because NHibernate:

01      public virtual ReadOnlyCollection<Address> Addresses()
        {
            if (AddressSet == null) return null;
            return new ReadOnlyCollection<Address>(AddressSet.ToArray());
        }

02      public virtual Address AddAddress(IRepositoryLocator locator, AddressDto operation)
        {
            AddAddressValidate(locator, operation);
            var address = Address.Create(locator, operation);
            AddressSet.Add(address);
            return address;
        }

03      public virtual void DeleteAddress(IRepositoryLocator locator, long addressId)
        {
            DeleteAddressValidate(locator, addressId);
            var address = AddressSet.Single(a => a.Id.Equals(addressId));
            AddressSet.Remove(address);
            locator.Remove(address);
        }

Instead, the collection is exposed by cloning the collection into a ReadOnlyCollection (line 01). If a new address needs to be added to the customer the AddAddress method must be used (line 02), the same applies when an address is to be removed (line 03)

As a result of above changes the domain model is as follows:

The following changes need to be added to the NHibernate mapping file:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
                   assembly="eDirectory.Domain" 
                   namespace="eDirectory.Domain.Entities">
  
  <class name="Customer" table="Customer">
    ...

01  <set name ="AddressSet" fetch="subselect">
      <key column="Customer_ID" foreign-key="FK_Customer_Address" not-null="true" />
      <one-to-many class="Address"/>
    </set>
  </class>

  <class name="Address" table="Address">
    <id name="Id" type="Int64" unsaved-value="0">
      <generator class="native" />
    </id>

02  <many-to-one name="Customer" class="Customer" column="Customer_ID" not-null="true"/>
    <property name="Street" length="50" not-null="true" />
    <property name="City" length="50" not-null="true" />
    <property name="PostCode" length="10" not-null="true" />
    <property name="Country" length="50" not-null="true" />
  </class>
</hibernate-mapping>

In the Customer mapping the private AddressSet collection is declared as an one-to-many collection of Address instances, we indicate that the Customer_ID field in the Address table is used as the link (line 01). At the Address mapping section, we also declare the Customer reference to use the same column name (line 02). This approach permits to navigate from the child back to the parent.

Lets demonstrate how easy is to propagate the changes to our database, if we create a new test:

And the configuration is set so the test is run using the NHibernate mode, then the test will generate the new schema for us, isn't it nice?. Just remember to change the test App.config file:

You may want to open a connection to the database to see the new schema:

New Address Service

We are planning to modify the user interface so the following screens will be available:

We need to provide a new service so we can create, retrieve and update an address instance:

Adding a new service requires the following:

  • Add the new interface to the IContractLocator
  • There are three implementations of the interface that need to be updated
  • Add three new AddressServiceProxy, AddressServiceAdapter and AddressWcfService classes

The implementation of the above classes is straight forward as they are in fact very similar to the implementations for the Customer service; you may want to get the source code for further details.

In the server side, we need to amend the eDirectory.WcfService to add the new Address service to the list of endpoints:

<configuration>
  ...
  <system.serviceModel>
    <services>
      ...
      <service name="eDirectory.WcfService.AddressWcfService" behaviorConfiguration="eDirectory.WcfServiceBehaviour">
        <endpoint address="AddressServices" binding="basicHttpBinding" bindingConfiguration="eDirectoryBasicHttpEndpointBinding" contract="eDirectory.Common.ServiceContract.IAddressService" />
      </service>
    </services>
    ...
  </system.serviceModel>
  ...
</configuration>

Client Side

Besides implementing the new classes: AddressServiceAdapter and AddressServiceProxy

We have added a new bunch of views with their respective models and viewmodels:

Among the model classes the one that needs to be mention is AgendaModel:

    class AgendaModel
    {
        public IList<CustomerDto> CustomerList { get; set; }
        public CustomerDto SelectedCustomer { get; set; }
        public AddressDto SelectedAddress { get; set; }
    }

Notice that the model provides class holders for the selected grid rows, this works in both ways which is very nice. The only thing to do in the view is to set the binding correctly:

It may not be obvious but when the list of clients is retrieved from the server, eash customer dto contains a collection of addresses. You may implement a more chaty design where the address collection is only retrieved when the customer is selected. As well, the Customer reference in the Address class translates into the Dto implementation in storing the CustomerId instead, if you don't take this approach the serialization of your Dtos would be a nightmare to say the least:

There is another interesting aspect on the AgendaViewModel, that is the way we manage the action buttons using the RelayCommand class. In this case, if a customer instance contains any address, the user needs to delete all addresses before the delete button for the customer is enabled. This is achieved easily implementing a predicate in the RelayCommand constructor using the above mentioned selected holder:

private RelayCommand DeleteCustomerCommandInstance;
        public RelayCommand DeleteCustomerCommand
        {
            get
            {
                if (DeleteCustomerCommandInstance != null) return DeleteCustomerCommandInstance;
                DeleteCustomerCommandInstance = new RelayCommand(a => DeleteCustomer(Model.SelectedCustomer.Id), 
                                                                 p => Model.SelectedCustomer != null && Model.SelectedCustomer.Addresses.Count == 0);

                return DeleteCustomerCommandInstance;
            }
        }

The XAML declaration is piece of cake:

Another aspect implemented is something that we have not had a chance to see before; this is how the ViewModel and Services use the selected customer dto to enhance the user experience, for example, when a new customer instance is created we need to ensure that the new customer instance is the one selected in the grid once the user is back to the main screen. We resolve this requirement as follows:

        public RelayCommand CreateCustomerCommand
        {
            get
            {
                if (CreateCustomerCmdInstance != null) return CreateCustomerCmdInstance;
01              CreateCustomerCmdInstance = new RelayCommand(a => OpenCustomerDetail(null));
                return CreateCustomerCmdInstance;
            }
        }

        private void OpenCustomerDetail(CustomerDto customerDto)
        {
            var customerDetailViewModel = new CustomerDetailViewModel(customerDto);
02          var result = customerDetailViewModel.ShowDialog();
03          if (result.HasValue && result.Value) Model.SelectedCustomer = customerDetailViewModel.Model.Customer;
04          Refresh();
        }

        private void Refresh()
        {
            long? customerId = Model !=null && Model.SelectedCustomer != null ? Model.SelectedCustomer.Id : (long?) null;
            long? addressId = Model != null && Model.SelectedAddress != null ? Model.SelectedAddress.Id : (long?)null;
            var result = CustomerServiceInstance.FindAll();
            Model = new AgendaModel { CustomerList = result.Customers };
            if(customerId.HasValue)
            {
05              Model.SelectedCustomer = Model.CustomerList.FirstOrDefault(c => c.Id.Equals(customerId));
                ...
            }
            RaisePropertyChanged(() => Model);
        }

There is a little of code above but bear with us for a second; the CreateCustomerCommand delegates to the OpenCustomerDetail method (line 01), this method calls the customer detail screen and if a new customer instance is created it sets the SelectedCustomer property in the Model (lines 02 & 03). Then the Refresh method is called which invokes the CustomerServiceInstance.FindAll() and sets the Model.SelectedCustomer (line 05) to the value it had before the service was called.

Chapter Summary

Parent-Child relationships are common in all applications, in this chapter we have discussed how relatively easy is to implement those across all our application layers. We have discussed how to model our entities so collections are well managed, in summary the parent is fully responsible for the creation and deletion of child instances. It is a good example how our entities are moving away from just being simple CRUD data classes to more complex entities that implement business behavior.

We also discussed the NHibernate implementation and how easy is at this point of the project creating new tests that automatically manage the new database schema, an aspect that proves be invaluable. We also covered some MVVM technics to leverage some common scenarios on the client side like enable/disable action buttons using the predicates on the RelayCommand; once more it was demonstrated how much value can be achieved providing a rich model implementation to the XAML views reducing the amount of code behind as result of the XAML binding capabilities.

In the next chapter we will discuss how easy is to deploy our application to Microsoft Azure.

Appendix

Get the Application Running

For those that are new to the series or those ones that are not sure yet how to get the eDirectory application running, the following section describes the steps of how to quickly get the application running. eDirectory is an application that can be run in-memory or against a SQL Server database using a NHibernate repository implementation. Here we discuss how to get the client running in a very easy manner: in-process in-memory mode.

In first place you need to verify that the client App.Config is set properly so the SpringConfigFile is set to use the InMemoryConfiguration.xml file:

Ensure that the eDirectory.WPF application is set to be the start up one:

Change the configuration to the InMemory instance in Visual Studio:

Now the only thing you need is to start the application: F5 or CTRL+F5:

Couple WCF Beauties

There are couple things done in this chapter on the WPF side that are worth of a brief discussion. WCF by default terminates the client application when the first view that was created is closed. In this version of eDirectory we required to ask the user which view must be open, once the user presses the Ok button the original screen must be closed; if nothing is done, the application terminates at that point. An easy way of stopping this behavior is to indicate WPF that the application itself will look after its shutdown:

    public partial class App : Application
    {
        
        public App()
        {
01          ShutdownMode = ShutdownMode.OnExplicitShutdown;
        }

        private void BootStrapper(object sender, StartupEventArgs e)
        {
            var boot = new eDirectoryBootStrapper();
            boot.Run();            
02          Shutdown();
        }
    }

When the App instance is created, it is indicated that the application will shutdown manually (line 01), which it takes place after the Run method returns (line 02).

The second beauty is a customized enum converter that is used by the Selector view that permits matching a radio-button to a specific enum value. The converter is:

    public class EnumMatchToBooleanConverter : IValueConverter
    {
01      public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null || parameter == null) return false;

            string checkValue = value.ToString();
            string targetValue = parameter.ToString();
            return checkValue.Equals(targetValue, StringComparison.InvariantCultureIgnoreCase);
        }

02      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null || parameter == null) return null;

            bool useValue = (bool)value;
            string targetValue = parameter.ToString();
            return useValue ? Enum.Parse(targetType, targetValue) : null;
        }
    }

The Convert method is used to see if the radio-button must be set given an enumeration value, the method assumes that the radio-button is to be set if the parameter matches the passed value. The ConvertBack returns null if the radio-button is not set, if it is set it returns the enum value set in the XAML.

The XAML is as follows:

The converter is declared as a resource named enumConverter and then used in the radio-button declaration, a enum value is assigned to each; the CurrentOption is a ViewTypeEnum property declared on the ViewModel that is correctly set without any other additional code. Nice !.

AutoMapper

In this chapter we decided to introduce AutoMapper, this is an object-to-object mapper and it is ideal to use when dealing with domain entities and Dtos. You may want to have a look at the CodePlex project for further details.

It is quite easy to use AutoMapper, in first place we create the mappings, then we install them and then the mappings can be used. In the eDirectory.Domain project a new class was added that declares the mappings:

Two mappings are defined, the mapping from Customer to CustomerDto is the interesting one. This one maps the Dto Addresses collection to a function that delegates into the other AutoMapper mapping to map the Addresses collection in the entity to a collection of AddressDto instances.

Then when the WCF service is started the static Install method is invoked:

You can also levarage the Spring.Net capabilities to initialise the static method just declaring the class in the configuration file, this is the approach used when we execute the application in-memory mode, this is another nice example of the Spring.Net capabilities:

An example of how the eDirectory solution uses AutoMapper mapping is found at the Customer service implementation:

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架