So What Exactly is a View-Model?

After being introduced to the term “view-model,” many developers are left with more questions than answers. While it is clear that view-models need to deal with the junction between views, models, and external services, the exact approach is often glossed over. And with no clear list of what should, or should not, be in the view-models, they often end up being a dumping ground for everything. While this article does not claim to offer a definitive answer, it does explore some of the many roles assumed by the view-model.

As you read through the various roles and patterns, please these keep three things in mind:

I have seen each of these examples used in a real project.

Most view-models are going to assume multiple roles.

Exact adherence to a pattern isn’t as important as a working application.

Model-Side Roles

Offering data to the views is an essential role of the view-model. However, there are many different ways data can be offered while still using XAML’s data binding technology.

View-Model as a Surrogate Data Context

Related Vendor Content

This is the simplest and probably most common version of the model-side patterns. Here, the view-model acts as a surrogate data context. The real data is exposed by the view-model as one or more simple properties.

In a way, this is a non-pattern. It only exists so the view-model can be associated with the view’s real data context property and inject other functionality such as ICommands that wrap navigation or service calls. We’ll return to this topic later in the article.

View-Model as an Active Record

An unfortunately common mistake is the “View-Model as an Active Record” pattern. With this pattern there are no true models in the application. Instead, all the fields are offered directly by the view-model itself.

For example, the CustomerViewModel would have fields such as FirstName, LastName, and CustomerId. Since this is a view-model, it will most likely have external service hooks as well. These would be exposed as ICommands such as LoadCustomerCommand and SaveCustomerCommand, effectively turning the view-model into an Active Record.

It should be noted that the Active Record pattern itself can be quite effective under some circumstances. The real problem is that active records are already somewhat bloated and asking them to also deal with the other roles of a view-model quickly pushes them into “god object” territory.

As you can see from the diagram, there is no good place to attach unit tests. You can create a fake unit test by supplying an integration test with a mock service, but such workarounds tend to be very time consuming and error prone.

If you don’t have models, you are not using MVVM.

View-Model as an Adapter or Decorator

View-models may be used as an adapter or decorator, temporarily wrapping a model to provide additional information or to add formatting. This, however, is a dangerous practice and should be avoided whenever possible.

Using the classic FullName as an example, here are two ways risk is introduced.

Push-based Wrappers

With a push-based wrapper, we assume the view-model is the only thing pushing updates to the model.

Here, the risk comes from attaching property-changed notifications from the model to the view-model. If the same model is accessed by multiple view-models, a memory leak can occur.

(Click on the image to enlarge it)

XAML-based controls get away with listening to model events because the data binding infrastructure prevents memory leaks. And In WinForms you get the Component infrastructure to dispose of event handlers. But the view-model’s life cycle isn’t tied to the control, nor can it be because controls can be reloaded, so we are left without a good place to unwind the event handlers.

This will not necessarily be a problem. If you are 100% sure nothing else is holding onto a reference to the model then you can get away with a view-model wrapper. But it is much safer to either enhance the model itself (e.g. by putting the FullName property inside it) or to use value converters. Both options also give you the ability to write unit tests without getting the view-model involved.

If your view-model is listening to events on a model, you need to check for memory leaks.

View-Model as an Aggregate Root

A collection of objects that are bound together by a root entity, otherwise known as an aggregate root. The aggregate root guarantees the consistency of changes being made within the aggregate by forbidding external objects from holding references to its members

The key difference between an adaptor/decorator and an aggregate root is in the latter the model is never exposed. There is simply no direct way to access any of the models, their properties, or events. Since nothing but the aggregate root can hold a reference to the models, this completely prevents the possibility of the memory leak we saw in the previous section.

If your view-model is designed to completely hide the details of complicated object graph, it may be an aggregate root.

View-Model as a Web MVC Model

This is a relatively new phenomenon that I have been seeing in the ASP.NET MVC community. Many practitioners have taken to calling the class created or loaded by a controller and passed to a view a “view model”. This is done in contrast to “domain models” and “input models”. A good explanation of this can be found in Dino Esposito’s The Three Models of ASP.NET MVC Apps.

This sort of view-model can easily be recognized because it doesn’t have any other role or responsibility other than holding data and business rules that act solely upon that data. It thus has all the advantages of any other pure model such as unit-testability.

At this point there is no sense railing against this nomenclature. Like the difference between Classic and Web MVC, it is one of those idiosyncrasies that we just have to accept.

Be careful not to confuse “models for views” with “view-models”, especially in MVVM style applications that may have both.

View-Side Roles

There are different degrees of coupling between views and view-models. We will start with the tightest coupling before talking about the more idealistic view-models.

View-Model as Code Behind

This unfortunate anti-pattern is all too common amongst novice XAML developers. It is often said one should not put too much code into a “xaml.cs” or “xaml.vb” file. Well-meaning novices often misinterpret this to mean one should never put code in there. Instead, they dump literally everything into their “view-model”, effectively making it an inconveniently located code-behind file.

When this happens, one ends up with all the same problems they would if they dumped everything into code-behind. Additionally, this tends to make static analysis harder and introduces opportunities for memory leaks.

If you remove all of the code from Xxx.xaml.cs and shove it into XxxViewModel.cs, your view-model is now your “code behind” file.

View-Model as a Classic MVC Controller

A key difference between Classic and Web MVC is the relationship between controllers and views. In Web MVC the controller can create and return any view it wants. Aside from providing a data model to the view, it doesn’t really interact with it.

In Classic MVC views and controllers are always found in tightly-coupled pairs. Each controller is built specifically to service user-generated events for a particular view. This pattern can be found in MVVM as well, where the view-model plays the role of the controller and ICommands replace events.

There is a fine line between using a view-model as a controller and using a view-model as a code-behind file, so here are some general guidelines.

Listens only for generic events on the view such as loaded and unloaded

Signs you may have a View-Model as a Code Behind file

Uses EventToCommand to handle all events in the view-model

Triggers Visual State changes by directly invoking the Visual State Manager

Interacts directly with controls

Is dependent on the exact layout of the view

Exposes events that controls must respond to for the view-model to work correctly

Below is a layer diagram showing the difference between controller and code-behind style view models.

(Click on the image to enlarge it)

This distinction between controller and code-behind doesn’t matter for the application itself, but does come into play when trying to test the view-model independently from the view. While I don’t recommend trying to unit test integration components like view-models, performing integration tests to ensure they are correctly working with external resources like databases and web services can be quite helpful. Bidirectional coupling between the view and view-model makes those integration tests much harder.

If there is a one-to-one mapping between view-models and views, your view-model is a controller.

View-Model as a Shared Controller

There are many, myself included, who preach that something isn’t truly a view-model unless it can be shared by multiple views. While I no longer take a hardline position on this theory, I still see it as a useful design pattern.

The shared controller approach is used when you want to synchronize multiple views that are looking at literally the same piece of data. For example, you may have a data grid in one window and a chart displaying that data in another. When doing this you can share the model directly, but it is often less error prone to share the view-model as well. That way if you switch to a completely different piece of data both views are informed at the same time.

Note that shared view-models are much harder to write than controller style view-models. They are also far less flexible, forcing you to push more code into the code-behind than you may be comfortable with. On the plus side, shared view-models tend to be much easier to test because so much complexity is necessarily excluded from them.

A viable alternative to this is the classic MVC pattern in which models, but not controllers, are shared. XAML data binding supports both designs equally well, so it is just a question of which is least disruptive to the overall design.

If you design your view-models to be shared, they will also be easier to test.

View-Model as a Navigator

Navigation-style applications are available in all four major XAML-based UI frameworks, but are of particular interest for Windows Phone or Windows 8 Metro where the navigation framework is an essential part of the application.

Fortunately, the navigation framework lends itself well to unit and integration testing, if you wrap it in something else such as a top-level view model. When something triggers a page transition, instead of sending the URI to the navigation framework, the view-model instead sends it to a mock that you can examine. You can see an example of this in Granite.Xaml’s NavigatorViewModel and SimpleNavigator classes.

Abstracting the navigation framework is essential for building testable view-models.

View-Model as a MVP Presenter

The key difference between the Model-View-Presenter pattern and the classic MVC pattern is the interaction between the model and the view. With the MVC pattern, the model fires events that the view listens for directly. In the MVP pattern, the presenter listens for the events and then updates the view itself.

Given the power of XAML data binding, one would assume this pattern is virtually non-existent in the technologies we are talking about, but in reality it serves an important role in multi-threaded applications.

This pattern is often used when models are being updated by background threads without any sort of user interaction. In this scenario, the view-model is responsible for marshaling the notifications onto the UI thread. This may involve copying the data to models that are data-bound or directly updating the controls themselves.

It is important to note that this isn’t the only way to handle multi-threading. If the computational complexity is low, it may be better to simply marshal all asynchronous messages onto the UI thread and process them there. Libraries such as Reactive Extensions can make this easier.

Consider using a presenter pattern to keep asynchronous message processing off the UI thread.

Service/Resource Roles

Up until now we have treated “External Services” as a black box representing the file system, databases, web services, and other external resources that your application may need to access. Now it is time to consider how those services are actually accessed. There are two basic ways you can do it.

View-Model as the Data Access Layer

For small applications and well-defined external services, it is easy to call the external service directly from the view-model. This model is ideal for simple applications and the interface-focused design of WCF makes dependency injection easy to implement.

The down side is it doesn’t scale well. As the complexity of your application increases you may find that your view-models simply have too much logic in them. This is especially true prior to .NET 4.5 and the introduction of the async/await keywords.

If you find that your view-models are overwhelmed by callback logic, it is time to look into adding a separate client-side service layer.

Note: when using this pattern some developers choose to forego testing the external services as a unique component. The theory being if the only thing hitting your service layer is your view-model, then your view-model tests can serve double-duty as your service layer tests.

View-Model with a Data Access Layer

You find yourself repeating the same external service logic across multiple view-models.

This design pattern can be surprisingly hard to get right. If care isn’t taken to define the application specific roles of the DAL and the VM, all the logic can end up inside one or the other. Not only does that defeat the purpose of the pattern, it will frustrate maintenance developers who see the nearly empty components as needless boilerplate.

When it comes to writing the integration tests you have two options. You can hang them off the View-Model as we do elsewhere, hitting both components at the same time. Alternately, you can hang them off the Data Access Layer.

The advantage of the latter is that it gives you a clean place to introduce interface-based dependency injection. This in turn allows you to use mocked unit tests for the view-models.

If a layer looks like code-generated boilerplate, it probably doesn’t need to exist.

Conclusion: Taming the View-Model

The only way to truly tame the view-model and prevent it from becoming a dumping ground is to decide up front what the term means to you in concrete terms. Write out a list of every concern and which component it belongs to.

Here is an example from one of my applications; yours may be remarkably similar or vastly different.

Concern

Component

Navigation between Pages

Primary View Model (Silverlight navigation framework)

Formatting

View (value converters)

Showing Dialogs within Views

View Code Behind

Loading Dependencies

Primary View Model

Creating Controllers

Primary View Model

Invoking WCF Calls to load/save models

View-Specific Controllers

Validating User Input

Models (INotifyDataErrorInfo)

Error Reporting

Primary View Model

Of course this is just the client-side chart. For the real project another chart was created for the server-side code and a third for which tables go in which database schema. As the project evolves and the design drifts, either the chart is updated to reflect the new code or the code is refactored until it matches the chart again. By doing this we always know where each new piece of functionality belongs before we add it.

If you take away only one idea from this article let it be this:

Design patterns are merely suggestions; it is up to you to form them into actual designs that meet your needs.

About the Author

Jonathan Allen has been writing news report for InfoQ since 2006 and is currently the lead editor for the .NET queue. If you are interested in writing news or educational articles for InfoQ please contact him at jonathan@infoq.com.

In "View-Model as Surrogate Data Context", when you say "real data is exposed by the VM as one or more simple properties:- do you mean that the VM has one property for each Model is exposes- or do you mean it has one property for each property (field) in each of the Models is exposes

I'm fairly new to MVVM, but I've always assumed that the appropriate pattern to use is the one you labeled "Event-based Wrappers". However, I don't understand the liability you described. Can you please elaborate on how this approach can lead to memory leaks? That isn't obvious to me at all.

InfoQ Weekly Newsletter

Join a community of over 250 K senior developers by signing up for our newsletter. If you are based in the EEA, please contact us so we can provide you with the protections afforded to you under EEA protection laws.

InfoQ Weekly Newsletter

Join a community of over 250 K senior developers by signing up for our newsletter. If you are based in the EEA, please contact us so we can provide you with the protections afforded to you under EEA protection laws.

Is your profile up-to-date? Please take a moment to review and update.

Email Address

Note: If updating/changing your email, a validation request will be sent

Company name:

Keep current company name

Update Company name to:

Company role:

Keep current company role

Update company role to:

Company size:

Keep current company Size

Update company size to:

Country/Zone:

Keep current country/zone

Update country/zone to:

State/Province/Region:

Keep current state/province/region

Update state/province/region to:

Subscribe to our newsletter?

Subscribe to our architect newsletter?

Subscribe to our industry email notices?

By subscribing to this email, we may send you content based on your previous topic interests. See our privacy notice for details.

You will be sent an email to validate the new email address. This pop-up will close itself in a few moments.

We notice you're using an ad blocker

We understand why you use ad blockers. However to keep InfoQ free we need your support. InfoQ will not provide your data to third parties without individual opt-in consent. We only work with advertisers relevant to our readers. Please consider whitelisting us.