Geeks With Blogs
On the Ledge The general evolution (and occasional regression) of a .NET developer

One of my friends read my last post and, after an analogy involving candy stores and Japanese wrappers (or was it Japanese rappers?  I think both would have worked), I decided it might be a good idea to put up some sample code to demonstrate some of the things I was talking about.

I'm going to use some test code from the MVC module - mostly because I'm the most deeply into that code right now, but also because writing my own MVC framework makes me look like a total stud.  At least to people who consider the definition of "total stud" to include things like "can write own MVC framework."  Hell, just recognizing that fact makes me a total stud.  Once again, with said group of people.

Let's take a look at a Controller:

namespace Test.MVC.ControllerCommon
{
  [ControllerRegistration("/I/Am/Many/Levels/Deep/{id}")]
  [ViewFilePreference("/Default.aspx")]
  public class DeepController : StandardController
  {
    [CommandRegistration("Default")]
    public override void Index()
    {
      MVCContext.Current.Context.Items["TextToDisplay"] = "Index";
    }

    [CommandRegistration("List")]
    [ViewFilePreference("/List.aspx")]
    public void List()
    {
      MVCContext.Current.Context.Items["TextToDisplay"] = "List";
    } 

    [CommandRegistration("Edit")] 
    public void Edit(int id)
    { 
      // Not implemented in this test
    }
  }
}

When the website is first started up, Global.asax runs an Initialize routine that does a bunch of stuff, one of which is to create a static instance of URL routing information for that application.  As part of this, it will go through the DLLs for a website and look for classes that have that ControllerRegistration attribute on them.  For each one that it finds, it creates an instance of that class and stores it in a collection (specifically, a tree hierarchy where each piece of that ControllerRegistration value is a node).  It also creates a ControllerDescriptor object that has properties that mirror all of those attributes and contain those attribute values and stores that in the tree as well along with the controller object.

In this case, this object will be instantiated and put in the tree at /I/Am/Many/Levels/Deep (the {id} is a parameter field and not used directly in matching the pattern).  In that same spot will also be a ControllerDescriptor object whose properties will be some of those attributes along with the values supplied as well as a reference to the Controller object whose attribute values it holds.  There's some other stuff in there, too, that a Controller might want to work with, but that's the basic idea.

For every derived Controller class, you have a ControllerDescriptor object that holds the values of the attributes you put in your Controller class.

So, the website receives a request to www.whatever.com/I/Am/Many/Levels/Deep/Default.aspx, and our Router class starts looking through those descriptors for a pattern that matches.  It finds one and uses that descriptor to pull this controller object and supply whatever additional information (via the associated ControllerDescriptor) it needs to route properly.  For example, Default.aspx will kick off the Index() method, while Edit.aspx will fire off the Edit method.  Obviously, I'm glossing over a lot of other things that go on, but that's the basic idea.

The point here isn't to explain how controllers work.  The point is to illustrate how the metadata is used.  In this case, the metadata is stored in those custom attributes, as opposed to a database lookup table or an XML file.  What does this buy us?

  • The Reflection stuff in .NET is slow, so we eat that performance hit up front when the application comes online (or gets refreshed after a release).  Most of the reflection happens right then to build the objects that we need.  You could theoretically use this same tactic with any means of storing metadata, like database tables or XML.
  • Using attributes allows you to configure your stuff right there in the code, and you also have all the constraints and functionality available to you through System.Attribute.  You can even unit test them.  After some annoyances with NHibernate and XML files, I enjoyed this.
  • Using attributes also makes it easy to handle multiple assignments.  I could, in theory, have this controller respond to multiple URL patterns, or have the same command trigger several different methods, just by adding attributes.
  • Adding new routes and commands is a breeze.  I just add my method to the controller and slap the command it's listening for on it, and even a preferred display page.
  • Using attributes allows me another option to set a hierarchy of precedence.  I'm still free to configure these settings in the database or XML and, in my routing code, tell it which one wins.

One could argue that a downside is, when you make changes, you have to recompile as opposed to changing the database or a config file.  This is true and, for some code, we store the metadata in the database specifically for this reason.

However, you have to look at how your code is being used and come up with your best option for that code.  It's not a universal truth that storing metadata in the database is always best because you don't have to recompile, just as it's not a universal truth that storing metadata in attributes is the best because it's fast, self-documenting, and testable.

In this case, we're talking about how our controllers map to URLs and the functionality we want to serve up.  This is something that is not going to change dynamically on the fly.  If there is a change to be made to existent mappings, then I've obviously changed functionality somewhere else, and a recompile is going to be necessary, anyway.  If I haven't changed functionality anywhere, why would I need to change a mapping?  Granted, someone might put in an incorrect mapping, but since we're talking about actually serving up the right web page, even a lazy tester is going to notice that they're not on the Edit screen.  I mean, different places have different levels of testing rigor, but I assume "being on the right page" is a pretty fundamental priority.

So, in this case, using attributes for metadata makes a lot of sense.  It's fast, easy, and there's lots of functionality and testability behind it, and it's very easy to make additions or changes.

Posted on Tuesday, September 15, 2009 1:18 PM Architecture | Back to top


Comments on this post: Alexander the Great's Code Samples

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © ledge | Powered by: GeeksWithBlogs.net