Introduction
As you might already 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 source code (as it is a proprietary app), but rather explain the guts of the underlying architecture and some information 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 developing 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;}
}
Although I have to say that I still have to implement the ActionLink
class.
Gluing 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"))
{
}
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 !!!