Building an MVVM framework for both .Net and .Net CF
Introduction
As you might allready know, I am maintaining a .Net CF application for one of my customers. Unfortunately, the design of the app is a total disaster, and maintaining it is actually a living hell. Fortunately however, my client decided to order a complete rewrite, which I started today. This post will not contain the sourcecode (as it is a proprietary app), but rather explain the guts of the underlying architecture and some info about the design decisions used.
Maybe I will release some of the reusable components in the future under open source, but I am not quite sure yet...
Yet another framework ?
Actually, yes !! My main concern about this app framework is keeping it lightweight, flexible, ultraportable and easy to use. By ultraportable I mean that the app should both run on the default .Net, as well as on the Compact Framework, without recompilation.
Since no framework known to me meets my criteria, I decided to get started on yet another framework.
The main design decisions are heavily based on my previous experiences with MvcExtensions, my other framework, as well as a very inspiring and well-know lecture by Rob Eisenberg: "Build your own MVVM framework".
Service tools
I have written and implemented the following interfaces which help me in developping the app.
A simple IOC container interface that looks like this
public interface IIOC
{
T Resolve<T>();
T Resolve<T>(string name);
object ResolveObject(string TypeName);
object ResolveObject(string name,string TypeName);
}
And to register something into the container I have the following methods:
T Register<T>(T instance);
T Register<T>(string name,T instance);
T Register<T>(Func<IIOC,T> factory);
T Register<T>(string name,Func<IIOC,T> factory);
Next to this I also implemented a lightweight AutoMapper, which I use for just about everything:
public interface IMapper
{
public void Map(object source,object destination);
}
And again, the implementation allows me to register some mappings using the following function:
void Register<TSource,TDestination>(Action<TSource,TDestination,IMapper> map);
Next to this I also created a class called ActionResult (looks familiar, doesn't it ;) ).
{
public object Viewmodel {get;set;}
public ActionLink Redirect {get;set;}
public bool Terminate {get;set;}
}
Allthough I have to say that I still have to implement the ActionLink class.
Glueing everything together
Everything you have read so far might seem awfully familiar to some people, which is a good thing imho. But having these tools available is still not the same as having an app framework, or is it ?
Actually, it is almost; my main focus is in the convention-based Model-View-ViewModel approach, which allows me to have controller actions like this:
public class MainController
{
IMapper sMap;
IOrderService sOrder;
IViewModelResolver sVMResolver
public MainController(IMapper sMap, IOrderService sOrder,IViewModelResolver sVMResolver)
{
this.sMap = sMap;
this.sOrder = sOrder:
}
public ActionResult Index()
{
var vm = sVMResolver.Resolve<VMIndex>();
sMap.Map(sOrder.GetOrders(),vm);
return new ActionResult() { Viewmodel=vm };
}
}
Which should also look very familiar !!!
.... in a single form
Now, we have an ActionResult which contains a model, how do we know which form to show ??? It is quite simple : I use a convention. All my Viewmodels start with the letters "VM", and all my views (winforms usercontrols) end with the word "View".
I use a veiwmodel-first approach, i.e. based on my viewmodel, I choose which view to show. This looks like the best approach to me...
I have one Form named MainForm, which contains the following property:
object _Viewmodel;
public object Viewmodel
{
get
{
return _Viewmodel;
}
set
{
if (_Viewmodel == value)
return;
_Viewmodel = value;
var t = this.Namespace+"."+_Viewmodel.GetType().Name.Substring(3)+"View";
var vw = IOC.ResolveObject(t) as UserControl;
this.SuspendLayout();
this.Controls.Clear();
sMapper.Map(_Viewmodel,vw);
this.Controls.Add(vw);
foreach (var prop in _Viewmodel.GetType().GetProperties())
{
var ctrl = vw.Controls.Where(x=>x.Name==prop.Name).FirstOrDefault();
if (ctrl==null) continue;
sMapper.Map(prop.GetValue(_Viewmodel,null),ctrl));
}
foreach (var meth in _Viewmodel.GetType().GetMethods().Where(x=>x.Name.StartsWith("On"))
{
// do something similar for the methods; bind them to the corresponding actionlinks
}
this.ResumeLayout();
}
}
And then my main loop will look something like this (not implemented yet):
void Run()
{
var frm = new MainForm();
var ar = new ActionResult() {Redirect=new Redirect<MainController>(x=>x.Index())};
while (!ar.Terminate)
{
if (ar.Redirect!=null)
ar = InvokeActionLink(ar.Redirect);
else
{
frm.Model = ar.Model;
ar = WaitForActionResult(frm);
}
}
}
Conclusion
While the framework is not yet complete, this is the way I am going to implement the whole thing... If you have any suggestions or comments please do let me know what you think !!!