Design-time data is still data

It’s strange how common application architecture concerns are often ignored when it comes to supporting design-time data. By “design-time data” I am referring to pre-canned/fake data that is shown in Views while they are being displayed and edited in Expression Blend, or the Visual Studio visual design surface (a.k.a. Cider). There are two commonly seen approaches to supporting design-time data, each of which has severe drawbacks.

The most commonly seen approach is to use the d:DataContext and d:DesignInstance settings, which assign a designer-only data context for a View. The d:DataContext setting is ignored at run-time. This approach might seem great because it appears to keep design-time concerns consolidated in the View layer, which is the application layer in which “design-time” applies most directly. Nothing could be further from the truth. While having support for design-time data settings in the View layer might be great for early UI prototyping, it quickly becomes problematic as the rest of the application layers fall into place. The main problem with d:DataContext is that you now have to maintain two separate data contexts: the real one and the design one. Often people create entirely separate classes (or the designer tools generate dynamic classes) that mimic the real data objects and ViewModel objects that are used at run-time. This introduces a grotesque amount of duplication in the code base, just for the sake of showing some fake data in Blend. Having duplicated classes and extra settings in XAML just for the sake of design-time data strikes me as patently absurd. Have fun maintaining that application over the years…

Another common approach to creating design-time data is to keep the Views unaware of being in design-time, and to have their ViewModel objects handle it for them. This can easily lead to ViewModel classes that are littered with conditional logic that does one thing at design-time and something different at run-time. For example, a ViewModel that exposes a collection of Foo objects might issue a network call to get Foos at runtime, but just new up a bunch of Foos at design-time. Complicating ViewModels with repetitive conditional logic has a distinctly rotten code smell. If a ViewModel is a Model of a View, and a View should not be smart enough to know about “design-time” then, by extension, neither should its Model (i.e. the ViewModel). There has to be a better way!

Let’s take a step back and return to basics. In a layered application architecture, you separate the responsibilities and concerns into various layers. The View layer is responsible for showing things. In MVVM, the ViewModel layer is responsible for maintaining the logical state of Views, and processing user interactions. So far, we have not mentioned anything about data access. That’s the job of a layer below the ViewModel, perhaps called the Model layer or the DataAccess layer. If the application needs data, it goes to the DataAccess layer to get it. Design-time data is still data. It, too, should come from the DataAccess layer. Everything above the DataAccess layer, such as ViewModels and Views, should not be responsible for making decisions about how data is accessed: including design-time data.

In an application that I’m working on these days, I created an interface that the ViewModel objects use to access data and save data. It’s a modest sized application, so only one interface was needed so far. In a larger system you could have as many interfaces as you need to meet your data access requirements. The data access interface is implemented by three classes: one for run-time data access, one for design-time data access, and another for unit test-time data access. I use dependency inversion (specifically, the Service Locator pattern) to supply an implementation of the data access interface to the ViewModels. When the Views and ViewModels are created and start living their life, they are blissfully unaware of the run-time context in which they exist. When running my unit tests, I provide the data access implementation with whatever data I want it to return to the ViewModel that invokes it. The design-time implementation loads up some fake data from disk and passes it back into the ViewModels, which then bubbles it up to the Views on the design surface. Of course, at run-time the “real” data access implementation is used to retrieve the actual data processed by the application.

To me, the primary benefit of this approach is that almost the entire system has no concept of “run-time” vs. “design-time” vs. “test-time.” The only place in the code base that is aware of the run-time context is the tiny bit of code that determines which implementation of the data access interface to make available to the ViewModels.

Share this:

Like this:

LikeLoading...

Related

This entry was posted on Sunday, April 4th, 2010 at 10:04 am and is filed under mvvm, Theoria. You can follow any responses to this entry through the RSS 2.0 feed.
Both comments and pings are currently closed.

Post navigation

13 Responses to Design-time data is still data

If you check my MIX10 session, you will see that I took this approach too (simply because it is my favorite). I used an IDataService interface, implemented by a DataService (for runtime) and a DesignDataService (for design time). This allows separating the design code completely. Also, IDataService can be mocked, so you don’t really have to generate data yourself if you don’t want. But what i find the biggest advantage is that having a separate class for DesignDataService, I can (with a little more work) place it in a completely different DLL which is built and referenced only at runtime (by adding a condition to the CSPROJ file).

As you said, small apps will probably have one such data service only, while large appps can easily have multiple ones. The VM doesn’t have to know where the data comes from, and everyone’s happy.

Got into a debate with the MS tooling folks on this score … they really like the d:Design… markup and were unimpressed by my maintenance argument … the same one you’re making here.

To be honest, I *kind* of do it your way. One ViewModel, different dependencies … that’s where I want to be.

However, I am reluctant to use ServiceLocator inside my runtime ViewModel; I want the DataService – and all other dependencies – injected into the VM, not pulled in by the VM.

ServiceLocator verges on an anti-pattern. I definitely don’t want to lean on a *static* ServiceLocator at runtime or test time; it might be acceptable at design time.

I’m guessing you use a static ServiceLocator. You didn’t mention how you set up your ServiceLocator at Design time where you do not have access to an IoC container.

So how do you like to do the toggle among the different environments (runtime, design time, test)?

I’m not tipping my hand … because I still don’t have a pattern that I like.

But … to your main point … separate design ViewModels that can diverge from the real thing are going to be trouble. I reserve them for demos.

—

@Laurent – toggling references in the project XML is a nice touch.

That’s much harder to do if there are d:DesignInstance notations in the XAML because then your XAML holds reference to your design-time assemblies. I guess that’s another reason to be wary of d:DesignInstance and its ilk.

I suppose you could tap dance out of the assembly reference issue by defining custom “xmlns” instead of the “clr-namespace” jazz but … isn’t this getting harder than it should be?

[Aside: yes, I know it’s a Singleton. The point is you call the *static* method ServiceLocator.Instance to get the locator.]

As I said, I avoid ServiceLocator where possible (and it almost always is possible). I avoid it whether static or not.

Why? Why is it an anti-pattern?

In brief, because it is a kitchen-sink class … a thinly disguised Globals variable. You can get anything with a ServiceLocator. And that makes the code harder to understand and more frail than it should be.

The crux of the matter is this: I cannot inspect the API of your class to know what it depends upon. One day you care about IFoo; the next day you care about IBar; then IBaz. And there is nothing in the API of the class that tells me so. Therefore, I cannot know what services your class will need to function correctly at runtime … not unless I open up your component and look for all calls to ServiceLocator. That is, I can’t know what’s going on without reading your source code or cracking it with Reflector. I should do neither.

I find that for smaller apps, it works fine. In larger apps, I don’t mind having a “global repository” of commonly used services like MessageBoxService. Then again, I’ve never tried injecting everything everywhere, so I can’t argue against it either.

I wholeheartly support your claim steer design data from the data layer. Usualy I use RhinoMock to provide data for unit tests AND design time. To be honest I do not separate design and test data. Why? Because every time you put new special cases in the test datas you might or might not get data which needs to be special treated by the UI. If at least should show the correct validation error. So in my opinion test data best suited for design data without maintaining to generators for both cases.

I find your article very interesting and I definitely think you are right: the design data belong to the data layer.
Yet, I am not really sure about how I can use this. Let’s say, for example, that I have an application displaying user information. I have a User model class, a User view and a User view-model. Let’s take the case where in my MainViewModel I have a collection of UserViewModels that I bind in my MainView to a tab control: everytime I want to display a User, I add a UserViewModel to the collection and a new tab is created using the correct DataTemplate, specified in the view layer. In this case, the UserView has no knowledge about the UserViewModel (which is all good).
My question is: in this scenarion, how can I display design-data in Blend or Cider for my UserView? Where do I specify to retrieve a UserViewModel, without messing with my runtime behaviour described above?

It all depends on when you retrieve or create Model objects. If you are loading Model objects in response to creation of the View, then just use a design-time data service to load dummy objects. If you are only showing data in response to the user explicitly adding them to the UI, then this is no longer just a DataAccess layer concern. In that case, the ViewModel is also responsible for causing data to be loaded, so the VM should have design-time awareness too.