Running with Code Like with scissors, only more dangerous


Model-View-Controller and Assembly Dependencies

Posted by Rob

The title of this blog post might be a slight misnomer because it isn't dealing with MVC directly.  It's dealing with something a little more high level (oddly enough).

I'm working on a personal project when I have time.  I haven't played around with in a while and there's still a fairly active user community around it (not to mention that Starcraft 2 is going to be released one day).  On top of everything else, I learned so much about all kinds of topics (design patterns, reflection, UI design) when I was working on clients in college that I still think it's valuable.  My latest project is giving me the opportunity to leverage some new additions to .NET 3.5 -- the System.AddIns namespace -- which will be a first for me, and it's the first time I'm using Model-View-Controller to design the client.  The primary motivator in using MVC is that I want to also build up an AJAX-powered rich web interface, and so the use of MVC allows me to incorporate multiple controller listeners to the model's events, to which I can hook up a web service.  Finally, I want to just make clean software.  MVC seems to be a particularly good pattern for this project, so I'm going to run with it.

In addition to the fact that it makes sense to separate out the model from the view, I've run into a little snag.  It's taken me three days to work out the problem in my head, but I think I'm finally at a point where I can be satisfied with the solution.

One of the great things about .NET is its rich extensibility model exposed via attributes.  By allowing developers to create their own metadata to be used in assemblies, we can create contracts in such a way that the extension code can tell us about itself.  Now, of course, we can create contracts in C++, too, but arguably it's not as clean.  Where a plugin system for a C++ library might require the code to provide us with a naked function, one for C# or another .NET language might be exposed via an assembly-level attribute:

// C++
__declspec(dllexport) extern "C" PPLUGIN_FACTORY GetPluginFactory();

// C#
[assembly: Plugin(typeof(MyPluginFactory))]

These are, of course, very similar, and they're used in a similar way.  The .NET app will know to look for that PluginAttribute on the assembly, and the C++ loader will know to look for an exported function called GetPluginFactory.  But I digress.

Among other things, I'd like in my app that the model classes be decorated with View-defined attributes.  For example, I plan on incorporating some of the features from Shiny Design into my app, and one of the things I'd like to do is create an adapter class to create a proxy for events.  Essentially, it's annoying to me to have to generate code that handles the configuration of events; specifically, each event type can have a color associated with it.  I'd decorate my event named "UserJoined" with [Name("User Joined Channel")] and [DefaultValue("Yellow", typeof(ColorConverter))].  It should know enough to enumerate the events, pull out the special data, and save to XML.  Then at runtime, access them via a special dictionary.

My dilemma was what to do about the dependencies.  Ultimately all of this will be open-source; I don't want there to be dependencies between my view classes and my model classes.  But how could I release my model classes without any dependency on my view classes if I wanted to have these attributes?

Then, tonight I realized: conditional compilation constants.

   2:  [Name("User Joined Channel")]
   3:  [DefaultValue("Yellow", typeof(ColorConverter))]
   4:  #endif
   5:  public event UserEventHandler UserJoined;