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 LineItem
s 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.