Monday, January 4, 2010

The ViewModel

In large applications with large models the UI will soon get filled with business logic that does not belong there. The logic is left there but the developer is someday going to return and clean it all up (Sure!).

Every developer knows that the degrees of freedom rapidly decrease once you fill your UI with business logic:

  1. You cannot easily reuse the logic placed in a UI so you copy it and increase the maintenance (BAD!)
  2. You forget about rules placed in UI so your system gets a life of its own (BAD!)
  3. Once you have logic in the UI you cannot be expected to know it all so you get afraid to make changes to your system because something will or may break (BAD!)
  4. You dare not give the UI to that brilliant designer because the designer will break logic for sure (BAD!)

Still I see business logic in the UI everywhere I go. When I ask about it I always get answers like: “Well this is not finished yet”, or “Well this is really special stuff, only used in one place”, or “Yea I know it sucks, I will fix that later”. But “Later” never comes, does it? It will always be just “Later”.

I am no superman for sure. I see business logic in UI I have written myself too.

If everyone or at least most of us is getting into these situations could it be that we are doing things the wrong way? Could it be that doing things the right way is just a tad bit too complicated?

These are some strategies to make developers do the correct thing and actually follow the coding guidelines:

  1. Automated review tools like FXCop
  2. Peer review (that usually will be done “later”)
  3. Some other strategy that will force violators (you and me) to mend our ways – even if the correct way is really complicated (maniac team leader with rabies foam in the face)
  4. Make it easier and faster to follow the “coding guidelines” then to be quick and dirty.

To no surprise I am in favor of making things easier. But to be able to make things easier we need to understand what cause things to go wrong in the first place: Why is the UI filling up with business logic? I think there are a couple of reasons, depending on how far you are from being model driven some of these reasons will apply:

  1. The UI will need to transform data (could be business logic) in order to display it according to specification.
  2. Actions performed in UI act on selections from UI-components, that has data in the transformed presentation format, and need to be transformed back (could be business logic) to model-scope in order to let the model work on the data (business logic). And you also need to check that the parameters sent to the model method are valid (could be business logic).
  3. The existing business logic may not match 100% what your action should do, you may want to call two, three of more methods to get the job done (new business logic) and you want to do some small checks based on the results of each method (could be business logic).
  4. Validation – your UI will do a lot of small checks to see that the data fulfills the overall rules for your application (business logic)

How do we make these reasons go away?

  1. We do it by offering a good and easy way to transform model-elements (or data if you will) into data elements suitable for render to match the specification.
  2. We do it by making it real easy to add business logic where it belongs (in the model), and make it easy to call it.
  3. And we offer a clean and easy way to add validation logic.

By setting the model in focus, and making it dirt simple to add methods, derived attributes and derived associations you can do everything you need for a specific UI in the model. This is sort of a good thing . The problem is that if you have a 100 UI’s your model will get filled with 100 times x derived attributes and derived links. And that does not sound like a good thing to me. It will eventually get hard to see the trees for the forest in a model like that.

And further more when a UI is dropped and deleted for some reason, will we remember to drop the derived associations and derived attributes we added to the model to accommodate it? Probably not.

A transformation layer between the UI and the Model can fix this. The transformation layer is allowed to have business logic, as long as it is unique for the context it works on. If the logic is not unique, it should go in the model ready to be re-used. We call this transformation Layer for a ViewModel.

A ViewModel transforms a piece of the model for a specific purpose. The purpose is often, but not always, to prepare the model information for interaction with a user for a specific use case – a UI. I said often but not always; it can also be for creating data transfer objects that are suitable for exposure for another application or system, or for some reporting engine or the like.

Why is it having the rules in a ViewModel is much better than having them in the UI? There are a lot of reasons:

  1. Testing; it is a good thing to be able to test the logic separated from the UI, because it is awkward and error prone to test and read the results back from the UI.
  2. ViewModel re-use ; you may have different UI’s for the exact same use case (beginner/advanced, Web-api/Rich client etc).
  3. Design time type checking; most UI-binding strategies rely on using strings that can only be checked at runtime (true for winforms, WPF, Silverlight and ASP.NET), whereas a good ViewModel is type checked at design or compile time.
  4. The designer working on the UI can harm important logic if the logic is in the UI.
  5. If we have dedicated designers we will not want to wait for them to release a UI file in order to fix business logic within.
  6. The UI may be on the other side of a network (another physical tier) so the UI cannot have access to the domain layer tier
  7. UI and logic have very different motivators and hence will often change for different reasons (looking good versus working correctly), mixing them up add confusion regarding the distinction between these important aspects.
  8. Security, designer that get access to the ViewModel cannot go beyond the ViewModel and unintentionally expose information that should not get exposed in the use case at hand.

The thing is that you do not have to use a ViewModel pattern to create a great application, it is just that is a good idea to use the ViewModel pattern when building applications that are going to be worked on for a long time, released in several versions over several iterations, and will most likely see different developers and UI-designer, and may very well be radically changed with regards to presentation framework during its lifespan. In short – it is always a good idea for successful applications to use a ViewModel pattern.

The declarative ViewModel

Presented with a model that I got from Rick Weyrauch, that incidentally helps out as a Hockey Referee when he is not coding, I wanted to create a ViewModel for the use-case “Set up new Hockey game”.

image

The Game class has a state machine:

image

I took a piece of paper and draw the UI I wanted to achieve:

image

Now I know what the requirements are on the ViewModel since I can see from the drawing what data the ViewModel needs to hold.

And then created this ViewModel That I named GameSetup:

image

Notice that it is just a series of named ocl expressions. Some expressions are nested to other list definitions like Home_PickList that states that if the Game has a picked GameType, then we know what teams that can be picked – namely those teams that are associated to that GameType

I created some test data so that UI can show something:

   1: private Game CreateSomeTestData()
   2: {
   3:     // Game types
   4:     var gtboys15 = new GameType(_es) { Name = "15 years, Boys" };
   5:     var gtboys16 = new GameType(_es) { Name = "16 years, Boys" };
   6:     var gtgirls15 = new GameType(_es) { Name = "15 years, Girls" };
   7:  
   8:     // team types
   9:     var ttb15=new TeamType(_es) { Name = "Boys 15 years" };
  10:     var ttb16 = new TeamType(_es) { Name = "Boys 16 years" };
  11:     var ttg15 = new TeamType(_es) { Name = "Girls 15 years" };
  12:  
  13:     // Valid team-game combinations
  14:     gtgirls15.TeamTypes.Add(ttg15);
  15:  
  16:     gtboys15.TeamTypes.Add(ttb15);
  17:     gtboys15.TeamTypes.Add(ttg15); // girls can play in boys 15 year
  18:     gtboys16.TeamTypes.Add(ttb15); // 15 year boys can enter 16 year games 
  19:     gtboys16.TeamTypes.Add(ttb16);
  20:     gtboys16.TeamTypes.Add(ttg15); // girls can play in boys 16 year
  21:  
  22:  
  23:  
  24:     new Team(_es) { Name = "Brynäs",Image=GetImage(imagebrynäs), TeamType=ttb15 };
  25:     new Team(_es) { Name = "Brynäs", Image = GetImage(imagebrynäs), TeamType = ttb16 };
  26:     new Team(_es) { Name = "Brynäs", Image = GetImage(imagebrynäs), TeamType = ttg15 };
  27:  
  28:     new Team(_es) { Name = "Luleå", Image = GetImage(imageluleå), TeamType = ttb15 };
  29:     new Team(_es) { Name = "Luleå", Image = GetImage(imageluleå), TeamType = ttb16 };
  30:     new Team(_es) { Name = "Luleå", Image = GetImage(imageluleå), TeamType = ttg15 };
  31:  
  32:     new Team(_es) { Name = "Djurgården", Image = GetImage(imagedjurgården), TeamType = ttb15  };
  33:     new Team(_es) { Name = "Djurgården", Image = GetImage(imagedjurgården), TeamType = ttb16 };
  34:     new Team(_es) { Name = "Djurgården", Image = GetImage(imagedjurgården), TeamType = ttg15 };
  35:  
  36:     return new Game(_es)
  37:     {
  38:         ScheduledDate = DateTime.Now
  39:     };
  40:  
  41: }

Now I am ready to build the UI.

   1: <Window x:Class="WPFBinding.Window2"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:eco="clr-namespace:Eco.WPF;assembly=Eco.WPF"
   5:     xmlns:ecoVM="clr-namespace:Eco.ViewModel.WPF;assembly=Eco.WPF"
   6:     xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
   7:     xmlns:local="clr-namespace:WPFBinding"
   8:     xmlns:ecospace="clr-namespace:WPFBinding;assembly=WPFBinding.EcoSpace"
   9:     Title="Window2" Height="300" Width="500" >
  10:     <Window.Resources>
  11:         <ecoVM:ViewModelContent x:Key="VM1" ViewModelName="GameSetup" EcoSpaceType="{x:Type ecospace:WPFBindingEcoSpace}" ></ecoVM:ViewModelContent>
  12:         <local:ImageBlobConverter x:Key="ImageBlobConverter"/>
  13:     </Window.Resources>
  14:     <Grid>
  15:         <Grid Name="vmrootStackPanel" DataContext="{StaticResource VM1}">
  16:             <Grid.ColumnDefinitions>
  17:                 <ColumnDefinition></ColumnDefinition>
  18:                 <ColumnDefinition></ColumnDefinition>
  19:                 <ColumnDefinition Width="50"></ColumnDefinition>
  20:             </Grid.ColumnDefinitions>
  21:             <Grid.RowDefinitions>
  22:                 <RowDefinition></RowDefinition>
  23:                 <RowDefinition></RowDefinition>
  24:                 <RowDefinition></RowDefinition>
  25:                 <RowDefinition></RowDefinition>
  26:                 <RowDefinition></RowDefinition>
  27:                 <RowDefinition></RowDefinition>
  28:                 <RowDefinition></RowDefinition>
  29:                 <RowDefinition></RowDefinition>
  30:             </Grid.RowDefinitions>
  31:  
  32:             <TextBlock Grid.Row="0" Grid.Column="0" Text="GAME : " HorizontalAlignment="Right" ></TextBlock>
  33:             <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding  Path=Class[GameSetup]/Presentation,Mode=OneWay}"></TextBox>
  34:  
  35:             <TextBlock Grid.Row="1" Grid.Column="0" Text="Type of game : " HorizontalAlignment="Right" ></TextBlock>
  36:             <ComboBox Grid.Row="1" Grid.Column="1"  DisplayMemberPath="Name"  ItemsSource="{Binding Path=Class[GameType_PickListPresentation]}" SelectedValuePath="self"  SelectedValue="{Binding Path=Class[GameSetup]/GameType}"  ></ComboBox>
  37:  
  38:             <TextBlock Grid.Row="2" Grid.Column="0" Text="Home team : " HorizontalAlignment="Right" ></TextBlock>
  39:             <ComboBox Grid.Row="2" Grid.Column="1"  DisplayMemberPath="Name"  ItemsSource="{Binding Path=Class[Home_PickListPresentation]}" SelectedValuePath="self"  SelectedValue="{Binding Path=Class[GameSetup]/Home}"></ComboBox>
  40:             <Image Grid.Row="2" Grid.Column="2"  Source="{Binding Path=Class[GameSetup]/Home_Image,Mode=OneWay,Converter={StaticResource ImageBlobConverter} }"></Image>
  41:  
  42:  
  43:             <TextBlock Grid.Row="3" Grid.Column="0" Text="Visitor team : " HorizontalAlignment="Right" ></TextBlock>
  44:             <ComboBox Grid.Row="3" Grid.Column="1"  DisplayMemberPath="Name"  ItemsSource="{Binding Path=Class[Visitor_PickListPresentation]}" SelectedValuePath="self"  SelectedValue="{Binding Path=Class[GameSetup]/Visitor}"></ComboBox>
  45:             <Image Grid.Row="3" Grid.Column="2"  Source="{Binding Path=Class[GameSetup]/Visitor_Image,Mode=OneWay,Converter={StaticResource ImageBlobConverter} }"></Image>
  46:  
  47:             <TextBlock Grid.Row="4" Grid.Column="0" Text="Scheduled date : " HorizontalAlignment="Right" ></TextBlock>
  48:             <Controls:DatePicker Grid.Row="4" Grid.Column="1"  ></Controls:DatePicker>
  49:             
  50:             <Button Grid.Row="5" Grid.Column="0" IsEnabled="{Binding Path=Class[GameSetup]/CanStartGame}" Click="ButtonStartGame_Click">
  51:                 <TextBlock Text="Start Game"></TextBlock>
  52:             </Button>
  53:  
  54:             <Button  Grid.Row="5" Grid.Column="1"  IsEnabled="{Binding Path=Class[GameSetup]/CanEndGame}" >
  55:                 <TextBlock Text="End Game" ></TextBlock>
  56:             </Button>
  57:  
  58:         </Grid>
  59:          <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
  60:             <Image Height="50" Name="imagebrynäs" Stretch="Fill" Width="50" Source="/WPFBinding;component/brynäs.jpg" />
  61:             <Image Height="50" Name="imagedjurgården" Stretch="Fill" Width="50" Source="/WPFBinding;component/djurgården.jpg" />
  62:             <Image Height="50" Name="imageluleå" Stretch="Fill" Width="50" Source="/WPFBinding;component/Luleå.jpg" />
  63:         </StackPanel>
  64:     </Grid>
  65: </Window>

What happens in the xaml above is that we have a ViewModelContent component in the resource section. We initiate it to the name of the ViewModel, and we also provide the Type of the ecospace.

The ViewModelContent object, now keyed as VM1, is put in the DataContext of the Grid where I place the UI components. If a WPF Binding does not get an explicit source it will use whatever it finds in the DataContext (the datacontext is propagated down the logical tree, so for us it is everywhere).

In code behind we hook up the EcoSpace and the rootobject-property (dependencyproperty so that you bind as target and as source) to our demo Game object:

   1: (Resources["VM1"] as Eco.ViewModel.WPF.ViewModelContent).SetEcoSpace(_es);
   2:  
   3: (Resources["VM1"] as Eco.ViewModel.WPF.ViewModelContent).RootObject=CreateSomeTestData();

The UI looks like this:

image

Yes it looks bad; but hey, you can hand it to any WPF savvy designer in the world –  the data and the rules are safe in the ViewModel.

It already shows some of the good effects of separating UI from logic. If you run the sample you will see and hopefully appreciate that:

  1. The PickLists for Home and Visitor are filtered based on Type of Game
  2. The Picklist for Home team filters away the Visitor team if set (and vice versa)
  3. Start game is enabled only after both home and visitor are set
  4. The End game button is disabled until the Game is started

These are some examples of business logic that would have easily ended up in the UI if we did not have a good place to define it.

Taking it further still

If the cost of creating and maintaining a ViewModel is high fewer ViewModels will be created. So our mission is to reduce the cost of creating and maintaining them. Can we do more? I will argue that we can.

WPF is a declarative way to describe the UI. This mean that the same basic lookless components like TextBlock, TextBox, CheckBox, Combobox and Image etc will be used again and again and they will be given a look by an external style or template. 

What if we use this fact and provide some basic rendering/placing hints for the ViewModel columns? We could then use those clues to spill out the correct lookless control in the intended relative position so we would not need to mess about with xaml every 5 minutes… I am excited… Xaml is a bit too scripty for my old strongly typed ways…

This is what the ViewModel-Editor looks like without rendering hints:

image

And this is the way it looks when I have checked the “Use Placing Hints” checkbox:

image

Given the extra fields for “Presentation”, “Column”,”Row”,”Span” etc I can work the ViewModel - preview to look like this:

image

Now I really need to stress this so that I do not get misunderstood: We do not mix presentation with UI, we do however allow for adding optional placing hints or clues on what you have in mind while designing the ViewModel.

Having a ViewModel with placing hints, you can add a ViewModelWPFUserControl  to your form with just one row:

   1: <ecoVM:ViewModelWPFUserControl Grid.Row="2" x:Name="VMU1" EcoSpaceType="{x:Type ecospace:WPFBindingEcoSpace}" ViewModelName="GameSetup" ></ecoVM:ViewModelWPFUserControl>

And the result is:

image

And remember that these auto layouted controls also adheres to external set styles. (Notice the datetime picker is gone? Datetime picker is in WPFToolkit so we do not use it by default. Implement ViewModelWPFUserControl.OnColumnUIOverride to add your own components to its layout engine.)

Summing it up

This has been a brief overview of the ViewModel concept. Things intentionally left out for now was Actions, Validation-rules, Variables, Master-detail (since there were no details in the sample ), Style references and Tab order.

I have written about the benefits of having a ViewModel in the first place. Then I wrote about the Modlr approach with a strictly declarative ViewModel. We looked at the sample using such a ViewModel and some of the effects it gave in separating logic from UI. Then I showed you a ViewModel with placing hints – a bit un-orthodox for sure, but efficient and easy to maintain.

Thanks goes out to everyone that has been involved in the Modlr ViewModel approach by giving feedback, and to Rick for providing the sample model – Hope I did not abuse it.

6 Comments:

Anonymous Daniel Rail said...

Very nice.

The one problem that I do see, is that you replaced the property EcoSpaceTypeName with EcoSpaceType(in the ViewModelWPFUserControl). I do not have a problem with that. But, for the developers that started developing using the ECO ViewModels and do have a good amount of ViewModel usage in their applications will find it very unpleasant that they will have to change all the XAML that is using the property EcoSpaceTypeName (here I can talk for myself). I would prefer to see that EcoSpaceTypeName would still exist for the next version, but that in the release notes and the help that it is mentioned that it is deprecated and will no longer exist in the version thereafter.

Also, can you update the samples in the daily builds to reflect this change in properties?

And, keep up the excellent work.

January 5, 2010 at 3:01 PM  
Blogger Hans Karlsen said...

Thanks Daniel!

Samples are updated this morning; they will be in the daily build tomorrow (6 jan,2010) .
And sorry for the API-break - I hate API breaks and I do them very reluctantly. In this case you need to change EcoSpaceTypeName="YourEcoSpaceType" to EcoSpaceType="{x:Type yourns:YourEcoSpaceType}"
The good thing is that this new syntax will force the developer to actually add the EcoSpaceType reference to the xaml wich will ensure that the preview of the ViewModelWPFUserControl actually works, and also the new syntax makes it more stronlgy typed.

January 5, 2010 at 4:29 PM  
Anonymous Anonymous said...

Hans,

I think your explanatory blog posts are excellent and a sorely needed resource for people trying to understand the real depth of ECO.

Many thanks

Mitch

January 5, 2010 at 5:48 PM  
Anonymous Anonymous said...

We have viewmodel support for win apps and wpf apps, but what about silverlight and asp.net?

Enjoying your blogs, Hans. Very helpful resource.

Throg

January 5, 2010 at 7:34 PM  
Anonymous Oleg Zhukov said...

Hello Hans,

The approach of having an intermediate layer between the UI and Domain layers is extremely useful. And using ECO in order to model this layer leverages the approach even more. I personally use ECO to model Presenter classes (according to the MVP pattern). And my presenter classes contain such attributes as "canAdd" and "canDelete" (similarly to your "CanStartGame" and "CanEndGame").

However in real-world applications it's too time consuming to have corresponding ViewModel attributes for each and every UI element. That's why I often bind UI elements directly to the Domain. But each time when some intermediate processing is required I place it into the middle layer (specifically into ECO-modeled ViewModel classes).

Overall, great article, thank you :)

--
Oleg Zhukov

January 6, 2010 at 5:33 AM  
Anonymous Anonymous said...

Thanks for the excellent blog post. Can you explain more about what you meant here?

Quote:

(Notice the datetime picker is gone? Datetime picker is in WPFToolkit so we do not use it by default. Implement ViewModelWPFUserControl.OnColumnUIOverride to add your own components to its layout engine.)

February 4, 2010 at 12:21 PM  

Post a Comment

Subscribe to Post Comments [Atom]

<< Home

 
Contact Us | Terms of Use | Privacy Statement © 2009 CapableObjects