Introducing MicroModels
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.
Trackbacks
- @Lab « Tech Tock | http://goldmanalpha.wordpress.com/2010/11/20/lab-13/
- Dew Drop – January 15, 2010 | Alvin Ashcraft's Morning Dew | http://www.alvinashcraft.com/2010/01/15/dew-drop-january-15-2009-2/
- http://topsy.com/tb/bit.ly/8szcil | http://topsy.com/tb/bit.ly/8szcil
- Patrón Modelo-Vista-Modelo de Vista (MVVM) Explicado | Maromas Digitales | http://maromasdigitales.net/2010/05/patron-mvvm-explicado/
- The Morning Brew - Chris Alcock » The Morning Brew #519 | http://blog.cwa.me.uk/2010/01/18/the-morning-brew-51-2/
- Translating lambda expressions « Cultivating code | http://cultivatingcode.com/2010/01/27/translating-lambda-expressions/
No new comments are allowed on this post.
Comments
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?)
Rob Cecil
Paul,
I'm trying to track down something - does MicroModels presume that the source objects (dto's, business objects, etc) that your Models reference are themselves observable things (i.e. they need to implement INotifyPropertyChanged)? Is there a way that the Models can be observable independent of the dto/data/business layer? Is there a difference in observability between explicit properties (lambda or otherwise), and implicit properties (AllProperties)? It seems that the AllProperties route always uses ReflectPropertyDescriptors as the bottom layer and preclude some kind of notification mechanism. I'm working in a project where the DTO's come from WCF and are simple datacontract objects.
Thanks
RobC
bobble79
Hey Paul,
Just came across this and it looks really cool! I'm actually working on something very similar, perhaps there is some crossover / collaboration we could do? Take a look at my library on codeplex and let me know what you think!
Cheers,
Robert
Dave
I'm a bit concerned about the performance loss, yet the way you define objects is simply briliant. Perhaps a similar approach using a DSL will worth some researching; write the code generic using a custom language, and let the "framework" generate the specific code.
Jack Ukleja
The examples and library seem to favour a "model first" approach to MVVM, whereby the model is already loaded before the VM is constructed.
If that is the case how do you handle things like "loading..." screens?
xxxprod
Hi Jack,
i had a similar question because how i've implemented my viewmodels they get their data after they've been constructed and adding the properties after the construction of the viewmodel results in an error because the modelBase is "sealed". Now I add the properties in the constructor with a dummy object and change the object later in the process and call "RaisePropertyChanged" for each property. For that I also added a new method to "Modelbase" which simply iterates all properties and calls automatically the RaisPropertyChanged method.
Her my example:
And the new method in ModelBase looks like that:
And by the way: Great tool you've created here Paul!
Jimbo
Is MicroModels still available for download anywhere? Can't seem to find a valid link...
CikaPero
Download link is broken! Please fix it so I can investigate source code of this interesting library. Thank you!
Stipo
For anyone interested, this project is hosted on Codeplex.
You can found it here.
Andrew Weaver
@Rob Cecil
If your business objects are not observable, perhaps you want to look at a hybrid solution with UpdateControls which appears to do what you're describing.