I've been spending some time on this bistro stuff, and it's ending up being rather interesting. Let's not go into details of the MVC stuff. That's all fairly standard - render with a Velocity port (NVelocity), controllers do your dirty work, blah, blah, blah. Let's get to the fun stuff. I started by saying that this whole thing makes sense because of REST and AOP. And that's definitely the case:

 
namespace NoRecruiters3.Controllers.Content
{
    [Bind("/content/resume/{resumeId}")]
    public class Resume: AbstractController
    {
        protected string resumeId;
 
        public override void ProcessRequest(HttpContext context, IContext requestContext)
        {
            Posting resume = PostingHelper.Instance.LoadPosting(null, resumeId);
            requestContext.Add("resume", resume);
            requestContext.Add("contentType",
                        ContentType.Resume.Equals(
                                    ContentTypeHelper.Instance.GetLastDefault()) ? "resume" : "ad");
            requestContext.RenderWith = "/Templates/Resume/view.bistro";
        }
    }
}

You can see the explicit bind statement (those are optional, btw. if you strip away the namespace prefix of NoRecruiters3.Controllers you get Content.Resume as your class name, so it would be invoked that way). So when we perform a GET on resource /content/resume/some_resume_id, we invoke this controller with (resumeId = some_resume_id) .

On the interception side I also have my security stuff:

 
namespace NoRecruiters3.Controllers.Content
{
    [Bind("/content/resume")]
    public class ResumeAccessValidator: AbstractController
    {
        public override void ProcessRequest(HttpContext context, IContext requestContext)
        {
            if (!User.Authenticated)
                requestContext.Transfer("/action/login");
        }
    }
}

You can see that we're intercepting all method calls (let's talk in those terms) and redirecting elsewhere when some auth condition isn't met. Cool. We've got our classic AOP example.

Here's the tangent. As i started porting my app to it, i saw an interesting opportunity. Let's take the search example. I've got a search controller that's invoked by /action/search/{contentType}:

 
[Bind("/action/search/{contentType}")]
[DependsOn("currentTags")]
public class Search : AbstractController
{
    protected string contentType;
 
    public override void ProcessRequest(HttpContext context, IContext requestContext)
    {
        prepareSearch(context, requestContext);
        prepareTags(context, requestContext);
 
        requestContext.RenderWith = "/Templates/Search/search.bistro";
    }
}

Okay. Now, let's factor in that I also want to be able to search by tags, in addition to the query. Well, there's lots of ways of doing that, but what about... overloading our url? Let's try this: /action/search/{contentType}/with-tag/{tag}:

 
namespace NoRecruiters3.Controllers.Actions
{
    [Bind("/action/search/{contentType}/with-tag/{tagList}")]
    [Provides("currentTags")]
    public class Tag : AbstractController
    {
        protected string tagList;
 
        public override void ProcessRequest(HttpContext context, IContext requestContext)
        {
            List currentTags = (List)context.Session["currentTags"];
 
            if (currentTags == null)
            {
                currentTags = new List();
                context.Session.Add("currentTags", currentTags);
            }
 
            foreach (string tag in tagList.Split(','))
                if (!currentTags.Contains(tag))
                    currentTags.Add(tag);
        }
    }
}

Check that out. We now have a separate controller that deals with managing selected tags on the session. So what about search? Well the way our url matching rules are going to work, is that we'll actually match both - the search controller will be invoked in addition to the tag controller. So with that one URL we kill both birds.

It's not really AOP, but ... then it kind of is. We can argue that tag management is a cross-cutting concern. The interesting thing here is that we can organize our controllers into very concise, almost atomic operations.  The only remaining question is this - we have a rule of most-generic-first, i.e. the most generic match gets executed the first. In our example, the search controller is more generic than the tag. But the tag modifies data that the search needs to use. That's when the requires/dependsOn/provides attributes come in. They tell the system that the controller has input/output dependencies, and the system makes sure that controllers are executed in an order that will satisfy those dependencies.

Yet more later...