Download

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.

Last revised: 15 Jan, 2010 06:30 AM History

Trackbacks

Discussion

15 Jan, 2010 06:43 AM

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.)

15 Jan, 2010 07:30 AM

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.

15 Jan, 2010 07:35 AM

@Damian, there's an AllProperties() method to map all properties on an object - e.g.,:

AllProperties(customer);

Instead of:

Property(() => customer.FirstName);
Property(() => customer.LastName);
...

There are also some methods like WithPrefix and Exclude to further change how AllProperties works. Are there other conventions you would like to see?

Rob
Rob
15 Jan, 2010 07:37 AM

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.

15 Jan, 2010 08:50 AM

Hi Rob, nothing about anything not working in Silverlight shocks me anymore :)

Ollie Riches
Ollie Riches
15 Jan, 2010 09:15 AM

Love it!

Cameron MacFarland
Cameron MacFarland
15 Jan, 2010 10:03 AM

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?

static EditCustomerModel()
{
    Property<Customer>(customer => customer.FirstName);
    Property<Customer>(customer => customer.LastName).Named("Surname");
    Property<Customer>("FullName", customer => string.Format("{0} {1}", customer.FirstName, customer.LastName));
}

public EditCustomerModel(Customer customer, CustomerRepository customerRepository)
{
    // Still need to define the command on the intance, but since it's not using reflection...
    Command("Save", () => customerRepository.Save(customer));
}

That way the reflection is done once, and cached, speeding up the whole process.

15 Jan, 2010 10:05 AM

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.

15 Jan, 2010 02:46 PM

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.

15 Jan, 2010 03:05 PM

@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
Cameron MacFarland
15 Jan, 2010 03:46 PM

@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.

16 Jan, 2010 12:57 PM

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

21 Jan, 2010 02:56 AM

Hi all,

I was able to port this great library to Silveright. You can find more information here.

22 Jan, 2010 05:29 PM

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.

05 Feb, 2010 02:12 AM

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
Adrian Hara
23 Feb, 2010 12:54 PM

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?

04 Mar, 2010 01:57 PM

The Create method, in the PropertyReaderFactory:

public static IPropertyReader<TCast> Create<TCast>(Type objectType, string propertyName)

fails if the intended property is overrided. I fixed by doing:

//Try to find the property at the declared type level
var propertyInfo = objectType.GetProperty(
    propertyName,
    BindingFlags.Instance |
    BindingFlags.NonPublic |
    BindingFlags.Public |
    BindingFlags.DeclaredOnly
);

//If could not find at the declared type level, look in the base types
propertyInfo = propertyInfo ?? objectType.GetProperty(
    propertyName, 
    BindingFlags.Instance | 
    BindingFlags.NonPublic | 
    BindingFlags.Public
);

Regards,

Rafael Romão

Adrian Hara
Adrian Hara
07 Mar, 2010 05:01 PM

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
Rob Cecil
21 Apr, 2010 12:17 AM

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

03 May, 2010 06:27 AM

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
Dave
11 May, 2010 05:20 PM

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
Jack Ukleja
09 Aug, 2010 08:18 AM

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?

Your Comments

Used for your gravatar. Will not be public.

Posting code? Indent it by four spaces to make it look nice. Learn more about Markdown.

Preview