Min hustru har en større interesse i gamle billeder og har i en periode spurgt om jeg ikke kunne lave et lille simpelt galleri til at holde styr på data på disse billeder. Jeg lider af NIH-syndromet (Not Invented Here), så jeg satte mig ned og kastede lidt kode efter det – og her er resultatet – SimpleGallery.
Den største udfordring var faktisk at undgå at lave koden for kompliceret. Når man er vant til at lave store komplekse løsninger, er det lidt den omvendte verden at lave små applikationer, hvor alt ikke behøver et interface fordi det alene tilføjer kompleksitet til noget, der ikke bør være komplekst (og ja, det kløede flere gange i fingrene, for at implementere interfaces over det hele). NHibernate sneg sig ind mens jeg kiggede væk – jeg gad ikke sætte mig ind i DataSets igen (er nogle år siden jeg sidst brugte dem). Funktionaliteten er til at overse:
- Tilføj billeder via en ‘Åbn fil’-dialog
- Tilføj meta-data – fotograf, årstal, by, album osv.
- Filtrere billederne efter album, by og land
- Tilføje/rette albums direkte via en kontekst-menu i valgboksene
- Tilføje/rette byer direkte via en kontekst-menu i valgboksene
- Tilføje/rette lande albums direkte via en kontekst-menu i valgboksene
- Vise billedet i fuld format, når musen holdes over den lille version
- Rette billeder via dobbelt-klik (og lukke (fortryd) via dobbeltklik)
- Ingen installation og der skal kunne tages sikkerhedskopi alene af mappen inkl. billeder.
Så, efter et par timer eller tre, har jeg nu en prototype som er bygget på .Net framework 4.0 (jeg kunne godt have brugt 3.5, men så skulle jeg sende wpftoolkit.dll’en med). Hvis der er nogen der er interesseret i kildekoden, så kontakt mig – den er naturligvis PD (public domain). Og til sidst – en lille teaser:

These past few days, I’ve held a company-specific course on NHibernate. It went really well, and I believe the participants also got those few golden eggs that makes the course worthwhile for them. They had been using NHibernate for some time and have specialists abroad – so, it was with some trepidation I approached the course material.
I focused on relaying the design decisions in NHibernate and then discussed a lot of do’s and dont’s. I think the key thing to learn when using NHibernate is the power of the ISession. Typically, people are locked into a world where they need to express things as you do with SQL – ie. you query the same way that you would in SQL. Behold the famous Customer vs. Order scenario:
Each Order has a Customer and a Customer can have any number of Orders making the association a one-to-many in the database. But how about querying the database efficiently? Most people will first try this given lazy-loading:
using(var session = GetSession())
using(var tx = session.BeginTransaction())
{
var query = session.CreateQuery(“from Order”);
var orders = query.List<Order>();
foreach(var order in orders)
{
Console.WriteLine(“OrderDate {0} with Customer {1}”, order.Id,order.Customer.AccountNumber);
}
}
This will result in the dreaded Select N+1 where N is the number of Customers that have Orders. Worse, it opens equally many database-connections. (To explain – everytime we hit an Order with a Customer that has not yet been loaded, an SQL query is sent for the Customer – if we hit a Customer that has already been fetched NHibernate will take it from the 1st level cache. Okay, most will then try this:
…
var query = session.CreateQuery(“select o from Order o join fetch o.Customer”);
…
We have now reached our goal, right? While it is true that we have reduced the number of queries to one and thus avoided Select N+1, we have a potential problem left. The problem with joins is that we will return a potentially much larger result-set (called the Cartesian product). Suppose our Customers are really heavy on data – and each Customer has many Orders – we will effectively be returning the same heavy customer data object many times over. Let’s say each Customer is 10 KB and each order is 1 KB and each Customer has on average 1.000 orders and there are 10.000 Orders in total. We will now be transferring 1 KB * 10.000 (orders) and 10 KB * 10.000 (customers each returned on average 1000 times) making for 110.000 kb or ~ 107 MB…
How to solve this conundrum – SQL cannot help us here, since it will have exactly the same problem? Well, there is one thing left to exploit. The ISession’s IdentityMap:
…
session.CreateQuery(“from Customer”).List();
var query = session.CreateQuery(“from Order”);
…
Why does this work? Every time anything is loaded in an ISession, it is stored in the 1st level cache. This when we iterate over Orders and query for the Customer, it will result in a cache-hit every time. Given our previous example, we have reduced this to two queries, resulting in 1 KB * 10.000 (orders) and 10 KB * 10 (customers) = 10.100 KB ~ 10 MB. Only one thing is still bugging us – we open two connections to the database – but we can get rid of this using MultiQuery:
using(var session = GetSession())
using(var tx = session.BeginTransaction())
{
var multiQuery = session.CreateMultiQuery();
multiQuery.Add(“from Customer”);
multiQuery.Add(“from Order”);
var list = multiQuery.List();
foreach(var order in ((IEnumerable)list[1]).OfType<Order>())
{
Console.WriteLine(“OrderDate {0} with Customer {1}”, order.Id,order.Customer.AccountNumber);
}
}
As with all performance optimizations – this is a contrived example – you will rarely run into such improvements – and you need to know your scenarios to decide which is the better thing to do – basically what are we trying to reduce? The amount of data – the amount of connections and what possibilities do we have. Also, in a real world scenario, you would use NH Profiler or SQL Profiler to determine that it is actually a problem before deciding to performance optimize in the first place. Optimizations come at a price… reduced readability of the code and thus increased maintenance.
I am reading up on usability at the moment and I passed a usability guru’s website – Jacob Jensen. He has strong opinions – and I like strong opinions and tend to agree with him mostly. Well, that’s up until now – I read an article by him entitled “Google makes you dumber” (link). That one irked me enough to be writing this blog post.
His basic premise is that by making it too easy to obtain information – people will stop being critical about the information and just take the first search result as truth and skip over the in-depth discussion – making us dumber. I read through the comments – and one struck me in particular: “This seems to me a continuation of the eternal “calculators are evil” debate” (Leonid S. Knyshov). I agree whole-heartedly with this one. If you need to calculate the interest of your loan – would you do it by hand? Of course not, you would ask your calculator – but wait, does that make you dumber? No, it makes you more efficient (and less errorprone – remembering long-division when it is 10 years since you were taught it…). For me it’s a matter of perception and reason for doing the calculus. If you are trying to learn calculus – yes, you should spend some time understanding why it is calculated this way – study the proofs – trying it out by hand to see if you understand it fully. If you are ‘just’ a user of math… don’t bother. You should only know enough math to use the calculator correctly.
If I didn’t have Google (or StackOverflow for that matter), I would be a much less efficient programmer. I work in such small teams that we will eventually reach a consensus on what is best practise. We will search for information using our best practise as the basis. Which will in turn lead us to reading books that agree with our best practise. Also, if I had to understand everything about the compiler, performance of List<T> when doing swapping and the internals of representing colors on a TFT screen with backlighting – I would be in an never-stopping loop of trying to stay up-to-date. And if I had to do it by reading books using library indexes to find relevant information… well, you get the picture.
I rely on frameworks, 3rd party condensed API information, blogs, forums, google… and books to give me just enough information to be able to do my job right – which entails doing things efficiently and good enough for the customer to be happy. Will I be rewarded by increasing the performance of my IO-operations by 10% by spending half a year understanding how SSD-disks perform? Probably not. Basically, throw your energy at your core competency – for me this means: “Read everything you can find on design patterns, commanding, templating, styling and nHibernate. The rest… I’ll rely on peers.
So, Google isn’t making me dumber – quite the contrary. Everyone using Google intensively for just one project will know that Google isn’t the answer to everything – and you have to very carefull about who you trust. I once heard a comment about people using Google as a spelling-checker – that you would only find people that were spelling as badly as yourself… You need to know how to search and how to figure out if that site is reliable. So, source criticism is vital – just as it is with books. As for reading in-depth about a particular subject – yes, the author of a book has probably done more research compared to a blog-writer – but a blog-writer will have used the time spent researching doing practical work.
So, trying to conclude… it’s not Google vs. books – it’s a stupid battle – each works differently, but used in unison – they are close to unbeatable… (I use Google to find the right books…) Treat Google as what it is – a great (and fast) indexer. Google will not answer questions you didn’t know you had, books, blog-content etc. might. But Google will let you find that enthusiast that just never got around to writing that book his knowledge entitled him to.
Sometimes, I’m wondering whether anyone is actually reading my posts here at GoblinCave. I realize that many will stop by only once or twice when a google search has turned this way – but I have no idea if there is an audience for the posts I’m writing.
So, a quick raise of hands (read: post a comment) – anyone think this site provides actual value, or am I yelling into an empty void?
I’ve saved the goodies for the last post. We are now ready to edit an order after post 9 – and we want to do it in style. We want to present the user with a really nice interface for fast input. We could have done it the old-fashioned way with an ‘Add’ button for adding an orderline in a new window, but hey, we got a shiny ‘new’ DataGrid in 4.0 – so let’s put that for a spin:
public interface IOrderDetailViewModel : IEntityViewModel<IOrder>
{
DateTime OrderDate { get; set; }
ICustomer Customer { get; set; }
ObservableCollection OrderLines { get; }
IAvailableCustomerSelection AvailableCustomers { get; }
}
public interface IOrderLineViewModel : INotifyPropertyChanged
{
IOrderLine CreateUpdatedOrderLine();
decimal Quantity { get; set; }
string Description { get; set; }
decimal PriceEach { get; set; }
decimal TotalPrice { get; }
void SetInvoiceLine(IOrderLine orderLine);
}
Okay – that should provoke a few comments…
You made a typo – shouldn’t the ObservableCollection<OrderLineViewModel> be of <IOrderLineViewModel>?
Actually no. The problem lies with the DataGrid – when there are no items in the collection, it will not show the ‘new item placeholder’ since it does not know whether it is possible to do so. It needs to know which class is to be instantiated and that class must have a default no-argument constructor. And since the ViewModel exists to please the View – there’s nothing for us to do, but satisfy the whims of the View.
But you still made the IOrderLineViewModel interface?
A valid point. It is mostly for consistency – I don’t like it when 99% of a the same type of class has interface and the last 1% doesn’t – it makes me think too much. “Why was it that class didn’t have an interface…” – “Argh, why is there two more classes than interfaces for ViewModels…” – those kinds of questions pop up every once in a while, and they are noise. The interface doesn’t cost me a whole lot of time, but it saves time when I don’t have to wonder why it isn’t there 6 months from now.
Why are there setters on the properties in the IOrderLineViewModel – IOrderLine doesn’t have them?
Remember that the ViewModel isn’t the Model – it’s a View of it. Here the OrderLineViewModel acts as an input-helper for the user. It allows the user to input orderlines without respecting the constructor of OrderLine. Basically, the user can come back to an orderline and change it before committing it to the order. And it gives the semblance to the user that the order line is editable, when it actually is not.
I will not go into further detail of the implementations – I’ve already made 5 more posts than originally planned and 10 is a nice round number. So, what have I tried to show you, the reader? First off, I’ve tried to give you a glimpse of what I do, when I program ERP-systems – second, I’ve shown you how I handle (some of) domain driven design in the UI and finally third – I’ve given a few pointers and ways to handle collections, null and properties that does not exist in the Model. I hope you enjoyed the series – and hope, I’ve shown you a few things that might help you get past a few hurdles using M-V-VM.
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!
We are now turning towards a somewhat more involved example after a little break in part 8 – the orders. We’ll make a bit more complex filter for the list of orders while keeping it paged – the IOrdersViewModel is so akin to ICustomersViewModel that I’ll jump to the differences in the IOrderFilter:
public interface IOrderFilter : IBaseFilter
{
DateTime? OrderDateFrom { get; set; }
DateTime? OrderDateTo { get; set; }
ICustomer Customer { get; set; }
IAvailableCustomerSelection AvailableCustomers { get; }
}
public class OrderFilter : BaseFilter, IOrderFilter
{
private readonly IDefaultEntities _defaultEntities;
private ICustomer _customer;
private DateTime? _orderDateFrom;
private DateTime? _orderDateTo;
public OrderFilter(IAvailableCustomerSelection availableCustomers, IDefaultEntities defaultEntities)
{
AvailableCustomers = availableCustomers;
_defaultEntities = defaultEntities;
}
…
public ICustomer Customer
{
get { return _customer ?? _defaultEntities.DefaultCustomer; }
set
{
_customer = value;
OnPropertyChanged(“Customer”);
OnFilterUpdated();
}
}
}
public class DefaultEntities : IDefaultEntities
{
private static readonly ICustomer _defaultCustomer = new NullCustomer();
public ICustomer DefaultCustomer
{
get { return _defaultCustomer; }
}
private class NullCustomer : ICustomer
{
public long Id
{
get { return -1; }
}
…
public string CompanyName
{
get { return “No customer”; }
set { }
}
}
}
So, a few notes:
What is the deal with the default customer?
The DefaultCustomer is a dummy-implementation of ICustomer that is guarantied to exist only once in memory as it is statically new’ed up and the class is private to the DefaultEntities-class. Basically, it represents null everywhere a Customer is allowed to be null at some point. This means that in the IOrderFilter implementation, the Customer is allowed to be null – representing ‘not searching by customer’. So, we get the ability to show null in a combobox’ed list.
Why -1 in the Id property
To make absolutely certain that Default customer is never saved, I’ve arranged for it to behave ‘badly’ in the persistence layer. Id -1 is an obviously wrong value – meaning that it does not belong in a persistent state.
Why isn’t available customers a part of a ViewModel – and what are they doing in a filter?
Since I’ve chosen to implement the filter with a combobox containing all customers, I need to gain access to these – and I’ll need it again when we turn to Order Detail, so I decided to put it in it’s own class. I didn’t make it a part of the ICustomersViewModel since it isn’t really a list, I want to interact with – it’s readonly by nature – so it received the name IActiveCustomers. I don’t want to read the database for customers everytime, I new up an order – so, it is basically a form of caching.
Other than that, Orders behave much like Customers when we look at the list – so, next and last chapter will focus on the detail editing of Orders.
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!
Okay, we have successfully created a small application in part 7 where the user can add, edit and remove customers. Not bad considering the small amount of code in the solution. Still we have a lot of classes involved and this alone creates a lot of complexity. We are paying the price for a very stable and easy-to-maintain solution down the road. I hope this will be easy to see when we start implementing entity number two – the order.
The first 7 posts in this series have been focused on taking baby-steps in order to get to the point where we have something to show. I’ve intentionally split this series into two – to show how I work when I develop ERP-solutions. I call it vertical slizing – ie. taking one core functionality and making that ‘finished’ before turning to the next functionality. Many of the ideas behind the design of the model is taken from Domain Driven Developement while a lot of patterns shine through in the class-design.
Room for improvement
Some things in the solution are intentionally kept simple to focus on the M-V-VM pattern. For one thing, the repositories are _very_ simple, but since most ERP solutions will be designed around an ORM, this is actually a non-issue. The repositories in the solution are fine for testing and not having to depend on schemas and other expensive (especially to change) friction makes you productive. If it costs you next to nothing to change into a better design, you will be easier to persuade to do it. The longer you can postpone the data-access, the clearer your approach will be when you decide to tackle it.
Next the CustomerCommands class is not exactly how I would do it in production code. I would put the wiring into a mediator class instead of a command – much of it is plumbing, and could easily be contained in a separate class. Some call it a Controller (MVC) – some call it a Presenter (MVP). I usually end up with a constellation of ViewModel, Model, View, Controller and perhaps a helper or two for specialized views (usually drag and drop functionality or a secondary ViewModel). Usually my Entity commands are one-liners using the IoC container and calling a method on the resulting class.
Third, we have the views – these are obviously not receiving the loving they are entitled to. WPF is a rich language and you can make absolutely stunning things here, but maybe I’ll look into that in another series. Again, the focus here is the ViewModels – not the Views.
Last, but not least – the order in which I showed the classes are not necessarily the order in which I design production code. The order has been slightly modified to better fit the logic flow of the series. I usually start out with the Model – when I have that working, I turn to UI basically specifying the bindings I’ll need from the ViewModel and then designing that last. So far things have been relatively straight-forward, but when we turn to Orders, things will become somewhat more complex.
After this short stop – I’ll turn to orders in part 9.
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!
Okay – part 6 was ViewModels – now it is time for the eye-candy and unleashing the power of the ViewModels. All views (save one) will implement the IView<TViewModel> interface which has a single method: void SetViewModel(TViewModel viewmodel). I’ll explain later why there are no more methods…
Now for the Views – I’ve implemented two relatively simple views – CustomerDetailView and CustomersView which will handle editing a single customer and showing customers respectively. I’ll not show the code here as that is not the intention of this series, but only give a few small notes:
Why is there so many small sections – shouldn’t it all be in one long XAML?
It’s a matter of preference mostly. I like to declaratively show what a view is comprised of. Some use comments inside the xaml, but I prefer having small sections that can be tied together into a larger picture. When I come back to the view 6 months later – I can quickly see where to go to change things. Some might think that it is for reusability, but that is only true for a very few controls – namely the DeicdeSaveSection and the PagingSection which will be used in the OrderDetailView and OrdersView later. When it comes to templating this is a much more valid point.
The Shell
Every application needs somewhere the user can select what he wants to look at. I call it a shell – a term stolen from Prism (the Composite Application Guidance from Microsoft). Basically, the Shell keeps track of what is possible in any given context and handles where the Views should be placed and how. The Shell does not know how many Views there are, nor what they show – it is a ‘dumb’ container responsible for layout’ing the views in relation to each other. One other responsebility is showing the ‘switchboard’ – here the ShellCommands.
There is a bit of styling in the Shell to allow it to show web 2.0-style popups. This will effectively enforce the problems around multiple transient Customers/Orders – making sure that only one new entity can be edited at the same time. Again nothing fancy, but you may notice that the Shell is responsible for creating the Commands it can activate. This is a bit of a shortcut – but since noone else is using the ShellCommands, I chose the pragmatic simple way.
Now, the glue to making it all function – we need to somehow create all the classes involved in the views. And we want to make sure that none of our classes need to know actual implementations, but only rely on the interface. This is called Dependency Injection (DI) and Inversion of Control (IoC). I chose something that is a bit of an anti-pattern – the static IoC. Since this is still a simple example, I don’t want the added complexity of passing the IoC container around at startup. So, bear with me on that one. Okay – a brief explanation of the static IoC – basically I use the App.xaml.cs to register which implementations I will use for each interface. Then when it comes to actually use the implementations, I call the Resolve<TService> method on the static IoC. Then the IoC will ‘magically’ find all the dependencies and resolve everything so that I do not need to worry about it. (It is of course not magic – the IoC container keeps track of which interface should resolve into what and instantiates them according to the constructor parameters of the class to be resolved). The resolve-method is used in the CustomerCommands and ShellCommands classes.
For http://goblincave.net/06/2010/m-v-vm-building-an-erp-system-%E2%80%93-part-8-of-n-rounding-up-customers.php, I’ll take a breather and look back on what we have actually done so far.
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!
Okay – so in post 5 we have managed to get and find data – now we need to be able to perform interactions with the data. We need an implementation of interface ICustomerDetailViewModel:
public class CustomerDetailViewModel : EntityViewModel<ICustomer, ICustomerRepository>, ICustomerDetailViewModel
{
private string _accountNumber;
private string _companyName;
public CustomerDetailViewModel(ICustomerRepository repository)
: base(repository) { }
protected override void ResetEntity()
{
_entity.AccountNumber = _accountNumber;
_entity.CompanyName = _companyName;
}
protected override void CopyFromEntity()
{
_accountNumber = _entity.AccountNumber;
_companyName = _entity.CompanyName;
OnPropertyChanged(“AccountNumber”,”CompanyName”);
}
public string AccountNumber
{
get { return _entity.AccountNumber; }
set
{
_entity.AccountNumber = value;
OnPropertyChanged(“AccountNumber”);
}
}
public string CompanyName
{
get { return _entity.CompanyName; }
set
{
_entity.CompanyName = value;
OnPropertyChanged(“CompanyName”);
}
}
}
public abstract class EntityViewModel<TEntity, TRepository> :NotifyPropertyChangedBase, IEntityViewModel<TEntity> where TEntity : class, IAggregateRoot where TRepository : IPersistRepository<TEntity>
{
protected TEntity _entity;
private readonly TRepository _repository;
private Action _onClose;
protected EntityViewModel(TRepository repository)
{
_repository = repository;
SaveChanges = new RelayCommand(DoSaveChanges, CanSave);
CancelChanges = new RelayCommand(DoCancelChanges);
}
private void DoCancelChanges(object obj)
{
ResetEntity();
Close();
}
protected abstract void ResetEntity();
public ICommand CancelChanges { get; private set; }
private string[] _validationErrors;
public string[] ValidationErrors
{
get { return _validationErrors; }
}
public ICommand SaveChanges { get; private set; }
public void SetActionOnClose(Action action)
{
_onClose = action;
}
public void SetEntity(TEntity entity)
{
_entity = entity;
CopyFromEntity();
}
private bool CanSave(object obj)
{
if(_entity == null) return false;
var isValid = _entity.IsValid(out _validationErrors);
OnPropertyChanged(“ValidationErrors”);
return isValid;
}
protected abstract void CopyFromEntity();
private void DoSaveChanges(object obj)
{
_repository.TrySave(_entity);
Close();
}
private void Close()
{
_entity = null;
if (_onClose != null) _onClose();
}
}
The CustomerDetailViewModel is pretty basic – it only handles user interactions and undo-functionality if the user cancels. There are a ton of ways to do undo (haha) – here I chose a very basic one which will not work for advanced entities. Here an alternative could be to reload it from the database or let the entity implement IEditableObject (often creating an exact clone to reflect the changes).
The base class is somewhat more interesting – a note on the implementation:
Why is there no IsValid method?
I’m relying on the SaveChanges command’s CanExecute – this will run everytime a property is changed in the view, and since this triggers the IsValid of the entity – these things happen automatically in the Command-structure of WPF.
You might think that it looks kinda simple for such an important task – but that is the whole point, I want my classes to be small and easy to comprehend. The inheritance adds complexity, but not a whole lot, and I do not need to explain a lot with comments or the like. The less lines of code, the less can go wrong – and the less will need to be maintained. Now all that is left for the solution to be able to handle customers is a few views and a few finishing touches.
This will be in parts 7 and 8.
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!
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 IEnumerable GetPagedData(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!
« Previous Page — « Forrige indlæg« Previous Page · Next Page »Næste indlæg » — Next Page »