A few days ago, a friend asked me if you should put the business logic in the Models and the ViewModels from MVVM. Basically, my answer was NO, HELL NO!
I will talk about just two of the reasons why I think this.
The first one is that you will have to rewrite your business logic for every app where you will use it. Let’s say that you have a client that wants an app for handling his account, so you make him a desktop app with WPF. You use MVVM, and you have your logic there, but what do you do when the client wants a Windows Phone app? The logic is in the ViewModels from the WPF app, so now you have to rewrite it again for the Windows Phone app. Wouldn’t it be better if the business logic was in some Portable Class Library that you could reuse for every platform instead of binding it with the ViewModels? I thought so.
The second reason is that I think the ViewModels should handle some of the UI logic as well. If you’re doing TDD, then you will want to test as much of your code as possible. If the ViewModels don’t contain the business logic, what’s stopping you from using them to control your UI? Imagine the following scenario, you have an app that shows a popup with a login form. When the user clicks on the login button, and he is logged in, you want the popup to dissappear. The problem is that you don’t know if the login was successful or not, so you can’t just close the popup when the button was pressed. So how do you make the View wait for an answer from the ViewModel? Or better, how do you write the code for this task so you could test it?
If you’re using Caliburn.Micro
, you could bind the Visibility
property of the popup to a boolean property in the ViewModel
. The code for the login function could be like this:
public async Task Login(string username, string password)
{
var loginResult = await LoginUser(username, password);
if (loginResult)
LoginPopupIsOpened = false;
else MessageBox.Show("An error occurred");
}
Because the popup visibility is binded to our property, then when I will set it to false
, the popup visibility will be set to Collapsed
. The upside here is that I can write a test for this very easily.
[Test]
public void successful_login_should_close_popup()
{
var viewModel = new LoginViewModel();
viewModel.LoginPopupIsOpened = true;
BindAuthenticationServiceToSuccess(); await viewModel.Login();
viewModel.LoginPopupIsOpened.Should().BeFalse();
}
If you want a high code coverage, try to make your ViewModel
handle the UI, but don’t do it directly, so don’t use objects that are related to the UI (Button
, Grid
, TextBox
, etc… or properties like Visibility
, Text
, etc.). You can see that I haven’t used the Visibility enum
above, that’s because I want to keep the UI related stuff out of my ViewModel
. So I’m binding Visibility
to boolean properties, Image source Uri to string
properties, events to functions, and so on. The rule is to keep all your functionality in the ViewModel
, and the strictly UI stuff in the View. So far, this is working great for me, especially because I’m using Caliburn which makes the separation between the View
and the ViewModel
very easy.