It seems that every WPF developer has written a Model-View-ViewModel library, and I was starting to feel left out. Having written an MVC and MVP framework, I figured I may as well write an MVVM library. But I want it to be different - I want the ViewModels to be as small as possible. That's how MicroModels was born.
MicroModels is inspired by Fluent NHibernate and uses TypeDescriptors to dynamically define properties, collections and commands.
In the example below, the view model exposes the FirstName and LastName properties of the customer object. The LastName property is renamed to Surname, and a FullName property is defined using the two names. It also exposes a Save ICommand property that saves the customer to the repository.
public class EditCustomerModel : MicroModel
{
public EditCustomerModel(Customer customer, CustomerRepository customerRepository)
{
Property(() => customer.FirstName);
Property(() => customer.LastName).Named("Surname");
Property("FullName", () => string.Format("{0} {1}", customer.FirstName, customer.LastName));
Command("Save", () => customerRepository.Save(customer));
}
}
What? That's it? Where's the INotifyPropertyChanged? The getters and setters? Don't worry, it's all done at runtime by MicroModels :)
The dynamic properties are available for data binding in XAML:
<DockPanel>
<ToolBar DockPanel.Dock="Top">
<Button Content="Save" Command="{Binding Path=Save}" />
</ToolBar>
<Border Background="#f0f0f0">
<StackPanel>
<WrapPanel Margin="1">
<Label Margin="1" Width="130">FirstName</Label>
<TextBox Margin="1" Width="50" Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged}" />
</WrapPanel>
<WrapPanel Margin="1">
<Label Margin="1" Width="130">Surname</Label>
<TextBox Margin="1" Width="200" Text="{Binding Path=Surname, UpdateSourceTrigger=PropertyChanged}" />
</WrapPanel>
<WrapPanel Margin="1">
<Label Margin="1" Width="130">Full Name</Label>
<Label Margin="1" Width="200" Height="50" Content="{Binding Path=FullName}" />
</WrapPanel>
</StackPanel>
</Border>
</DockPanel>
Dependency Analysis
MicroModels makes use of expression trees to analyse the dependencies a property has. In the code example above, MicroModels can figure out that the FullName property depends on the FirstName and LastName properties. If the customer raises a PropertyChanged event for either of those properties, MicroModels will raise a property changed event for FullName.
Multiple Sources
Perhaps instead of a view model exposing properties from a single object, your ViewModel will union multiple objects together. That's easy:
public class CompareCustomersModel : MicroModel
{
public CompareCustomersModel(Customer left, Customer right)
{
AllProperties(left).WithPrefix("Left");
AllProperties(right).WithPrefix("Right");
}
}
From XAML you could bind to properties such as LeftFirstName and RightFirstName.
Wrapping Child ViewModels
If you had Order and LineItem business objects, it's common to create an OrderViewModel and LineItemViewModel, and to expose the Order's LineItems as a collection of LineItemViewModel's.
With MicroModels, this is no longer necessary. MicroModels can expose a collection of child items, and automatically wrap each item in a MicroModel. The example below shows how LineItems might be exposed as a collection, and adds a LineTotal property to each child item:
public class InvoiceViewModel : MicroModel
{
public InvoiceViewModel(Order order, IEnumerable<LineItem> lineItems, IOrderService orderService)
{
AllProperties(order);
Collection("LineItems", () => lineItems)
.Each((item, model) => model.Property("LineTotal", () => item.UnitPrice * item.Quantity));
Command("Save", () => orderService.Save(order, lineItems));
}
}
When the Quantity of a single LineItem changes, MicroModels detects the changes and raises an event for the dynamic LineTotal property.
Get Started
You can download the binaries and reference MicroModels.dll. ViewModels just have to inherit from the MicroModel base class. You can also check out the source which includes a couple of samples. There is some work to do around documentation, improving the extensions system and cleaning it up. The public API needs some work, and I'd be really interested in what people think about some of the naming conventions used.
Discussion
Steve Burman
We want the Func
Give up the Func
We need the Func
Gotta have the Func
Consider this a positive review for MicroModels. Feel free to use it on any promotional materials.
(Blog comments on a Friday afternoon : where musical references and geek collide.)
DamianM
Nice one Steve, Parliament references! (Or should that be Parliamen<T> ? doubly bad geek humour there)
Paul, could you add in some auto / convention based mapping ? Then it could be even smaller.
Paul Stovell
@Damian, there's an
AllProperties()method to map all properties on an object - e.g.,:Instead of:
There are also some methods like WithPrefix and Exclude to further change how
AllPropertiesworks. Are there other conventions you would like to see?Rob
Nice work. This may come as a shock though: TypeDescriptors don't work in Silverlight. The binding engine is fundamentally different under the covers... Just so you know.
Paul Stovell
Hi Rob, nothing about anything not working in Silverlight shocks me anymore :)
Ollie Riches
Love it!
Cameron MacFarland
I really like this idea.
What I don't like is that using Expressions is slow, especially when you're initializing a couple of thousand ViewModels to put in a list of some sort.
Could you put the mapping in the static constructor, and pass in the type into the setup?
That way the reflection is done once, and cached, speeding up the whole process.
Jonas Follesø
Awesome - but a shame it doesn't work in Silverlight.
MicroModel would be useful in many cases and would compliment many MVVM designs/frameworks well. Having the AllProperties call + a Command would be enough VM in many cases. If MicroModel becomes - well - too Micro you could always write the more complicated models by hand, or just add tradition properties to the class deriving from MicroModel.
Ryan Riley
Heck yes! I started to do something like this some time ago and was told that it wasn't possible. I had just started looking at TypeDescriptor when I decided to try something else instead. While I didn't write it, I feel vindicated that it is possible and even elegant! The comments about Silverlight remind me that was the reason I finally gave up.
Ryan Riley
@Cameron - You don't need the static constructor for each micro-model implementation. A better solution might be a common, static cache in the MicroModel base class. Then, no matter how many times you assign it, you could look up the type and expression from a Dictionary and return the cached mapping. That would begin to approximate how the DLR caches its calls, I believe. Of course, we are now getting to the point where we should begin asking why we don't just use something like IronRuby or IronPython, which are supposed to be allowed to bind in Silverlight 4, iirc.
Cameron MacFarland
@Ryan - That's what I was thinking of, caching the definition in a dictionary. I was just doing it in the static constructor to avoid threading issues. But the issue with that is how does the definition get the instance information. Currently Pauls solution is using closures to capture the instance, which means each definition is different for each model.
Anyway, it's the lambda being assigned to an Expression object that takes the time. If the lambda is assigned to a delegate it's fine. So you want to avoid doing lambda assignments to Expressions as much as possible. I've found them slower than straight reflection.
Urs Enzler
I fully agree that a lot of MVVM "frameworks" put too much into the ViewModel.
A question about your MicroModels: As far as I understand, using MicroModels results in a direct coupling (through binding) from the view to the DOM (instance of Customer in your example) - meaning that everytime the user modifies data and data binding kicks in, the DOM is updated?
Or am I missing something?
Cheers Urs
Stefan Dobrev
Hi all,
I was able to port this great library to Silveright. You can find more information here.
Rafael Romão
Really nice!
I use Caliburn, so I changed my ViewModel base class to extend MicroModel instead of Presenter and then changed MicroModel source code to make MicroModelBase extend Presenter.
It worked for me. Now I have MicroModel and Caliburn working together.
Jack Ukleja
Paul,
I like this idea.
It seems everyone is trying to solve the same problems with object mapping/transform/binding.
What are your thoughts on bi-directional transforms? From quickly looking over your code my assumption is that the transforms for the non-simple properties is one-way?
It still feels like there must be a more elegant way to do this mapping/transformation. DSL with a visual tool? Compiler support? etc
It seems to me everything is pointing in the direction of a generic object mapping/transformation framework...
Yes there is AutoMapper but this is not "Live". There is WPF Binding but this is no good for POCO. There are various CLINQ style derivatives for mapping/transforming collections, but they are one-way by their nature.
Jack
Adrian Hara
I'm probably missing something, but the Property method is protected, so the "Collection().Each((x,model) => model.Property...)" doesn't work.
Do I have the wrong bits?
Rafael Romão
The Create method, in the PropertyReaderFactory:
fails if the intended property is overrided. I fixed by doing:
Regards,
Rafael Romão
Adrian Hara
Well, I WAS missing something, Property() is both a method on the MicroModel class AND an extension method on the IMicroModel interface, and I wasn't importing the namespace of the ext. method. I only noticed today that the source code is available :) (weird?)