Url Resolution in FubuMVC

Continuing the thread from my earlier update on the “Fubu Reboot.”  In an MVC web application (I think this really could apply to WebForms as well, but not to the same extent) you frequently need to resolve the Url that points to a specific subject.  In our application at Dovetail, we have the route pattern:  “sites/edit/{Id}” for the page that edits a “Site” object.  When we place links in the views for a given “Site” object, we need to replace “{Id}” in the route with the value of the Site.Id property.  In another circumstance, we have the routing pattern “query/for/{QueryName}/{QueryParam1}” for a controller action that takes in this object as its single argument:

    // The [RouteInput] attributes are *a* way to direct Fubu to

    // make these properties by automatically scanned as part of the

    // route pattern.

    // This should only be necessary in exception cases

    // My hope is that conventions take you 90% of the way home

    public class QueryForRequest

    {

        [RouteInput]

        public string QueryName { get; set; }

 

        [RouteInput("")]

        public string QueryParam1 { get; set; }

    }

At many, many times in our application we need to determine the Url string that points to a particular subject or occasionally to a controller action.  At the same time, it would be very, very nice to keep the individual controllers and views ignorant of exactly what those Url patterns happen to be in order to make them easier to change.  In FubuMVC, that’s all done with the IUrlRegistry interface that is automatically placed into your IoC container:

 

    // This service is injected into your IoC tool of choice as a singleton

    // to give you access to url’s in a type safe way

    // Please note that this implementation in no way, shape, or form

    // locks you into a rigid url structure

    public interface IUrlRegistry

    {

        string UrlFor(object model);

        string UrlFor(object model, string category);

        string UrlFor<TController>(Expression<Action<TController>> expression);

 

        string UrlForNew<T>();

        string UrlForNew(Type entityType);

 

        // Not sure these two methods won’t get axed.  They could just be extension methods in Dovetail code

        string UrlForPropertyUpdate(object model);

        string UrlForPropertyUpdate(Type type);

 

        string UrlFor(Type handlerType, MethodInfo method);

    }

 

In the FubuMVC model, we’re basically assuming that controller actions (Fubu actions don’t have to be on special Controller classes, btw) take in 0 or 1 objects as their single input.  Taking another step, if you make the input model types unique per controller action, FubuMVC can actually use that type to “know” what controller action receives that type.  Therefore, when I need the Url string that points to a particular Site object, I just pass in that Site object to the UrlRegistry.For(object) method.  In the more complex case of the QueryForRequest object above, I do the exact same thing – even though QueryForRequest clearly points to a different Route.  For controller actions that don’t take in any input arguments (think HomeController.Index()), you can still use UrlRegistry.UrlFor<HomeController>(x => x.Index()). 

For those of you familiar with ASP.Net MVC’s model, here’s some other facts:

  • The lookup of a Url for a Controller Type / Method combination makes no, let me repeat that, no assumptions about the Url pattern.  SomethingController.Method1() does not imply that the Url is “something/method1.”  FubuMVC is literally hashing the exact Route pattern for each Controller action and looks up the exact Url at runtime. 
  • The call to UrlFor() is completely independent of whether or not the Route in question was registered as part of the main application or as part of an Area/Slice.  Unlike MVC2, when you’re determining the Url to a certain controller action or input object, you do not have to worry about where   I think the MVC team thoroughly screwed up their Area support and I’d surely hope they scrap it for something better in MVC3.  If you’re using the MVC framework today, I’d strongly recommend you use the bits in MvcContrib instead of MVC2 for areas.  
  • No magic strings of any kind.  Anywhere.

 

Lastly,

The Url resolution is static typed.  That’s valuable to help prevent coding mistakes and Intellisense is also nice.  Honestly, my favorite part is how much more traceable it makes the code rather than relying on strings.  One quick CTRL-B shortcut takes you the the controller action behind the Url.  In the case of finding the Url for an object, it’s one more bounce with CTRL-ALT-F7 (one of my favorite R# shortcuts).  In real usage, we have convenience methods on our view types to get at action urls, as well as consuming the IUrlRegistry in our FormFor() and ActionUrlFor() type HtmlHelpers.

Now that we mostly rely on IUrlRegistry.For(object), IUrlRegistry is relatively easy to mock in most tests.  If your tests have to rely on an Expression in IUrlRegistry.UrlFor<T>(x => Method()), I’d go for some sort of hand rolled stub.

 

Ok, this may be vague, so please ask question.  Also, this stuff isn’t locked down, so we can actually change it to suit.  And I won’t even get all paternalistic on you telling you that “UrlFor() doesn’t really mean UrlFor()” if you don’t like the API.

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in FubuMVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • nieve

    Jeremy,
    Many many thanks, that was very helpful :)
    Just one last question- how do you get your route mapped so that it would take the {Id} ?
    I was trying in the example I gave to pass an Id (4) but the Id passed in my ProductInputModel was always 0… hence the question how do you map the route “Site/edit/{Id}”?

  • http://codebetter.com/members/jmiller/default.aspx Jeremy D. Miller

    @nieve,

    That’s not a newbie question, and besides, that doesn’t count for a framework that is definitely not complete yet.

    It will be, yes. We just haven’t added the UrlFor() method to the View yet.

    In your second example, you’d take in IUrlRegistry from the ctor of the controller. But otherwise, that’s the way we see it being consumed inside the controller.

    We consume UrlRegistry in both the view and the controller, depending on circumstance. If there’s any need for logic to pick out the Url, it goes in the Controller. Otherwise, dunno. I do it by feel.

  • nieve

    erm, just to clarify- is this fubus equivalent to MVCs’ Html.ActionLink()? I tried this in a small test helloFubu app and the only place I ended up with the call to this method was in the controllers action where the link is supposed to appear. I wanted just to have a link to an edit page of Product with Id = 4, and done that:
    public HomeViewModel Home(HomeInputModel model)
    {
    IUrlRegistry urlRegistry = ObjectFactory.GetInstance();
    return new HomeViewModel(“Hello Fubu!!”, urlRegistry.UrlFor(new ProductInputModel(4)));
    }
    Which I’m sure was miles away from how it should be…

  • nieve

    Hey Jeremy,
    I’m trying to get my head around this (and more generally speaking get to know Fubu)- and try to understand where exactly where would the call to UrlRegistery.UrlFor(model) take place. Sorry for this utter noob question…