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.