M-V-VM Building an ERP-system – part 5 of N – CustomersViewModel
We introduced commanding in part 4 and so far we have mostly been trying to get to the point where we can actually see something on the screen – and now we will look at the data behind the view. Enter the ViewModel. The responsebility of the ViewModel (in my version, at least) is to act as a mediator between the (dumb) View and the Model. For the ICustomersViewModel we have three things in play:
- DataAccess (we need some data to show)
- Filtering and paging (we need to be able to find the relevant data)
- Commands (we wish to interact with the data)
DataAccess is a whole chapter in itself. To keep things simple here – I use an in-memory database to serve this functionality – it is hidden behind the ICustomerRepository interface:
public interface ICustomerRepository:IPersistRepository<ICustomer>
{
IEnumerable<ICustomer> FindByFilter(ICustomerFilter customerFilter, int currentPage, int pageSize);
int GetCountByFilter(ICustomerFilter customerFilter);}
public interface IPersistRepository<TEntity> where TEntity:IAggregateRoot
{
bool TrySave(TEntity entity);
bool TryDelete(TEntity entity);
event Action RepositoryUpdated;}
Why the IPersistRepository interface?
Basically it will help me when I build a base-ViewModel when we go to edit entities – remember this is the refactored interface, so some responsebilities have been isolated into separate interfaces.
Why two functions for the data?
The GetCount function is there to initialize the Paging for the ICustomersViewModel – basically we need to know how many customers fulfill the ICustomerFilter to show an accurate number of pages. This function will only be called initially and when conditions change (the ICustomerFilter is updated or an entity changes or added). This is also the reason for the RepositoryUpdated event on the IPersistRepository interface.
Why a return type of bool to TrySave and TryDelete?
Well, you are allowed to yell YAGNI at this point – because currently I don’t use the return value for anything. From experience it will be needed later when users start crying for reasons why it didn’t save their changes. Right now, we don’t care why it failed (when it fails), so we don’t know if we will throw an exception or return an array of strings (which is the reason, why I didn’t at an out string[] argument to the TrySave and TryDelete).
Now, let’s look at the CustomersViewModel implementation – I won’t go into details about the base-class, as it is mostly plumbing code to make paging work – it is not really relevant for the points, I’m stressing. You are welcome to leave a comment if you need an explanation for it, but take a look at in the solution accompanying this series of posts:
public class CustomersViewModel : PagedViewModel
, ICustomersViewModel
{
private readonly ICustomerRepository _customerRepository;
public CustomersViewModel(ICustomerFilter customerFilter, ICustomerCommands customerCommands, ICustomerRepository customerRepository)
{
CustomerFilter = customerFilter;
CustomerCommands = customerCommands;
_customerRepository = customerRepository;}
public ICustomerCommands CustomerCommands { get; private set; }
public ICustomerFilter CustomerFilter { get; private set; }
protected override int GetNumberOfEntities()
{
return _customerRepository.GetCountByFilter(CustomerFilter);}
protected override IEnumerableGetPagedData(int currentPage, int pageSize)
{
return _customerRepository.FindByFilter(CustomerFilter, currentPage, pageSize);}
protected override void SetUpdateListeners()
{
_customerRepository.RepositoryUpdated += ResetPagedData;
CustomerFilter.FilterUpdated += ResetPagedData;}
}
Okay – not much going on here – as I stated in the beginning – the CustomersViewModel is only a mediator. It doesn’t do a lot of logic – basically it just makes sure that calls from the UI is handed down to whatever Model is behind it (in this case basically just read-only data from database). All logic concerning editing and how it is done is handed off to the ICustomerCommands interface which I’ll touch when I have implemented how to actually edit a customer. A few points of interest:
What’s up with the SetUpdateListeners?
The base-class (PagedViewModel) needs to know when to reset paging. Basically we are telling it to reset whenever the Filter is updated or the repository is updated (a save or delete happened).
In part 6, I’ll look into the interactions with the customer.
About the series:
The solutions introduced here are not entry-level material. You should understand generics and basic patterns to fully utilize the material presented. The solution represents about 3 years of experience working with one ERP-solution. Some of the choices taken are heavily influenced from the large-scale solution and may seem overkill for the simple examples here, but the idea is to show the end-result of my experience and hopefully you as the reader will at least see a new way of doing things. The solution is built in .Net 4.0 with heavy use of WPF’s capabilities. Here you can find the source-code for the series. You are free to use this code for any purpose, but it is presented as-is with no guarantees. Enjoy!
Goblincave » M-V-VM Building an ERP-system – part 6 of N – customer interaction said,
June 6, 2010 at 22:29
[...] – so in post 5 we have managed to get and find data – now we need to be able to perform interactions with [...]