The ViewModel
(Automatically adding template at the end of the page.)
m ((username removed) (log details removed))
(No difference)

Revision as of 05:24, 13 February 2024

In large applications with large models, the UI will soon fill up with unnecessary business logic. The logic remains but the developer has to return someday and clean it all up (Sure!).

Every developer knows that the degrees of freedom rapidly decrease once you fill your UI with business logic, which can turn out to be unhelpful:

  1. You cannot easily reuse the logic placed in a UI so you copy it and increase the maintenance
  2. You forget about rules placed in UI so your system gets a life of its own
  3. Once you have logic in the UI, you cannot be expected to know it all so you become 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

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”, “Well, this is really special stuff, only used in one place”, or “Yea I know it sucks, I will fix it later”. But “Later” never comes, does it? It will always just be “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 are 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 a bit too complicated?

These are some strategies to help 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” than to be quick and dirty.

Unsurprisingly, I am in favor of making things easier. But to be able to make things easier, we need to understand what causes 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 have 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). 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% with what your action should do; you may want to call two, three, or more methods to get the job done (new business logic) and 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 simple way to transform model-elements (or data if you will) into data elements suitable for rendering to match the specification.
  2. We do it by making it easy to add business logic where it belongs (in the model) and call it.
  3. We offer a clean and easy way to add validation logic.

By setting the model in focus, and making it simple to add methods, derive attributes, and derive 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 100 UIs, your model will fill up with 100 times x derived attributes and derived links. It will eventually become difficult to see the trees for the forest in a model like that.

Furthermore, 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 into 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. 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 does having rules in a ViewModel much better than having them in the UI? There are several 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 you have dedicated designers, you will not want to wait for them to release a UI file in order to fix the business logic.
  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 adds confusion regarding the distinction between these important aspects.
  8. Security; a designer that has access to the ViewModel cannot go beyond the ViewModel and unintentionally expose confidential information in the use case at hand.

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, which incidentally helps him 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”.

View Model - 1.png

The Game class has a state machine:

View Model - 2.png

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

View Model - 3.png

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

Then I created this ViewModel That I named GameSetup:

View Model - 4.png

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

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

Code Snippet

private Game CreateSomeTestData() {
  
 // Game types
   var gtboys15 = new GameType(_es) { Name = "15 years, Boys" };
   var gtboys16 = new GameType(_es) { Name = "16 years, Boys" };
   var gtgirls15 = new GameType(_es) { Name = "15 years, Girls" };

   // team types
   var ttb15=new TeamType(_es) { Name = "Boys 15 years" };
   var ttb16 = new TeamType(_es) { Name = "Boys 16 years" };
   var ttg15 = new TeamType(_es) { Name = "Girls 15 years" };

   // Valid team-game combinations
   gtgirls15.TeamTypes.Add(ttg15);

   gtboys15.TeamTypes.Add(ttb15);
   gtboys15.TeamTypes.Add(ttg15); // girls can play in boys 15 year
   gtboys16.TeamTypes.Add(ttb15); // 15 year boys can enter 16 year games
   gtboys16.TeamTypes.Add(ttb16);
   gtboys16.TeamTypes.Add(ttg15); // girls can play in boys 16 year



   new Team(_es) { Name = "Brynäs",Image=GetImage(imagebrynäs), TeamType=ttb15 };
   new Team(_es) { Name = "Brynäs", Image = GetImage(imagebrynäs), TeamType = ttb16 };
   new Team(_es) { Name = "Brynäs", Image = GetImage(imagebrynäs), TeamType = ttg15 };

   new Team(_es) { Name = "Luleå", Image = GetImage(imageluleå), TeamType = ttb15 };
   new Team(_es) { Name = "Luleå", Image = GetImage(imageluleå), TeamType = ttb16 };
   new Team(_es) { Name = "Luleå", Image = GetImage(imageluleå), TeamType = ttg15 };

   new Team(_es) { Name = "Djurgården", Image = GetImage(imagedjurgården), TeamType = ttb15  };
   new Team(_es) { Name = "Djurgården", Image = GetImage(imagedjurgården), TeamType = ttb16 };
   new Team(_es) { Name = "Djurgården", Image = GetImage(imagedjurgården), TeamType = ttg15 };

   return new Game(_es)
   {
       ScheduledDate = DateTime.Now
   };


Now I am ready to build the UI.

Code Snippet

<Window x:Class="WPFBinding.Window2"   
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:eco="clr-namespace:Eco.WPF;assembly=Eco.WPF"
           xmlns:ecoVM="clr-namespace:Eco.ViewModel.WPF;assembly=Eco.WPF"
           xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
           xmlns:local="clr-namespace:WPFBinding"
           xmlns:ecospace="clr-namespace:WPFBinding;assembly=WPFBinding.EcoSpace"
           Title="Window2" Height="300" Width="500" >
           <Window.Resources>
               <ecoVM:ViewModelContent x:Key="VM1" ViewModelName="GameSetup" EcoSpaceType="{x:Type ecospace:WPFBindingEcoSpace}" ></ecoVM:ViewModelContent>
               <local:ImageBlobConverter x:Key="ImageBlobConverter"/>
           </Window.Resources>
           <Grid>
               <Grid Name="vmrootStackPanel" DataContext="{StaticResource VM1}">
                   <Grid.ColumnDefinitions>
                       <ColumnDefinition></ColumnDefinition>
                       <ColumnDefinition></ColumnDefinition>
                       <ColumnDefinition Width="50"></ColumnDefinition>
                   </Grid.ColumnDefinitions>
                   <Grid.RowDefinitions>
                       <RowDefinition></RowDefinition>
                       <RowDefinition></RowDefinition>
                       <RowDefinition></RowDefinition>
                       <RowDefinition></RowDefinition>
                       <RowDefinition></RowDefinition>
                       <RowDefinition></RowDefinition>
                       <RowDefinition></RowDefinition>
                       <RowDefinition></RowDefinition>
                   </Grid.RowDefinitions>
        
                   <TextBlock Grid.Row="0" Grid.Column="0" Text="GAME : " HorizontalAlignment="Right" ></TextBlock>
                   <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding  Path=Class[GameSetup]/Presentation,Mode=OneWay}"></TextBox>
        
                   <TextBlock Grid.Row="1" Grid.Column="0" Text="Type of game : " HorizontalAlignment="Right" ></TextBlock>
                   <ComboBox Grid.Row="1" Grid.Column="1"  DisplayMemberPath="Name"  ItemsSource="{Binding Path=Class[GameType_PickListPresentation]}" SelectedValuePath="self"  SelectedValue="{Binding Path=Class[GameSetup]/GameType}"  ></ComboBox>
        
                   <TextBlock Grid.Row="2" Grid.Column="0" Text="Home team : " HorizontalAlignment="Right" ></TextBlock>
                   <ComboBox Grid.Row="2" Grid.Column="1"  DisplayMemberPath="Name"  ItemsSource="{Binding Path=Class[Home_PickListPresentation]}" SelectedValuePath="self"  SelectedValue="{Binding Path=Class[GameSetup]/Home}"></ComboBox>
                   <Image Grid.Row="2" Grid.Column="2"  Source="{Binding Path=Class[GameSetup]/Home_Image,Mode=OneWay,Converter={StaticResource ImageBlobConverter} }"></Image>
        
        
                   <TextBlock Grid.Row="3" Grid.Column="0" Text="Visitor team : " HorizontalAlignment="Right" ></TextBlock>
                   <ComboBox Grid.Row="3" Grid.Column="1"  DisplayMemberPath="Name"  ItemsSource="{Binding Path=Class[Visitor_PickListPresentation]}" SelectedValuePath="self"  SelectedValue="{Binding Path=Class[GameSetup]/Visitor}"></ComboBox>
                   <Image Grid.Row="3" Grid.Column="2"  Source="{Binding Path=Class[GameSetup]/Visitor_Image,Mode=OneWay,Converter={StaticResource ImageBlobConverter} }"></Image>
        
                   <TextBlock Grid.Row="4" Grid.Column="0" Text="Scheduled date : " HorizontalAlignment="Right" ></TextBlock>
                   <Controls:DatePicker Grid.Row="4" Grid.Column="1"  ></Controls:DatePicker><Button Grid.Row="5" Grid.Column="0" IsEnabled="{Binding Path=Class[GameSetup]/CanStartGame}" Click="ButtonStartGame_Click">
       
                     <TextBlock Text="Start Game"></TextBlock>
                   </Button>
                   <Button  Grid.Row="5" Grid.Column="1"  IsEnabled="{Binding Path=Class[GameSetup]/CanEndGame}" >
       
       <TextBlock Text="End Game" ></TextBlock> <nowiki></Button>
 </Grid>
        <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
           <Image Height="50" Name="imagebrynäs" Stretch="Fill" Width="50" Source="/WPFBinding;component/brynäs.jpg" />
           <Image Height="50" Name="imagedjurgården" Stretch="Fill" Width="50" Source="/WPFBinding;component/djurgården.jpg" />
           <Image Height="50" Name="imageluleå" Stretch="Fill" Width="50" Source="/WPFBinding;component/Luleå.jpg" />
       </StackPanel>
   </Grid>
</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 also provide the Type of the ecospace.

The ViewModelContent object, now keyed as VM1, is put in the DataContext of the Grid where I placed 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:

Code Snippet

(Resources["VM1"] as Eco.ViewModel.WPF.ViewModelContent).SetEcoSpace(_es);
(Resources["VM1"] asEco.ViewModel.WPF.ViewModelContent).RootObject=CreateSomeTestData();

The UI looks like this:

View Model - 5.png

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 the Type of Game
  2. The Picklist for the 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 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. 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 means that the same basic lookless components like TextBlock, TextBox, CheckBox, Combobox, and Image, etc will be used repeatedly and they will be given a look by an external style or template.

What if we use this fact to 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 - 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:

View Model - 6.png

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

View Model - 7.png

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

View Model - 8.png

Now, I really need to stress this so that I am not 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.

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

<ecoVM:ViewModelWPFUserControl Grid.Row=”2″ x:Name=”VMU1″

EcoSpaceType=”{x:Type ecospace:WPFBindingEcoSpace}”

ViewModelName=”GameSetup” ></ecoVM:ViewModelWPFUserControl>

And the result is:

View Model - 9.png

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

Having the ability to get simple UI automatically derived from the ViewModel placing hints lowers the effort to produce and maintain. Experience has shown that a lot of the administrative parts of your application are left automated so that more time can be spent on the signature screens that are most important for your users. 

Summing It Up

This has been a brief overview of the ViewModel concept. Things intentionally left out, for now, are 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. I also 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 unorthodox for sure, but efficient and easy to maintain.

Thanks to everyone who has been involved in the Modlr ViewModel approach by giving feedback, and to Rick for providing the sample model – hope I did not misuse it.

The MDriven Book - See also: What an Action can do

This page was edited 96 days ago on 02/13/2024. What links here