Lets try this again… the first time I wrote this post, my computer restarted and I lost it entirely 🙁
I have created a handful of sites that require both "static" pages and dynamic pages. I put static in quotes because there is a dedicated page with unique content that can’t be created with a wysiwyg editor. For example, show a list of the last 10 visitor ip addresses. In the past, I’ve used a combination of webforms and isapi_rewrite rules to accomplish what I needed. I’ve felt that the isapi_rewrite part was a little flaky, but it worked and isapi_rewrite is extremely fast. Anyway, I’ve been using the Asp.Net MVC bits since preview 1 and have become fairly happy using it as an alternative to webforms. I wanted to see how hard it was to allow for a situation like this with mvc. Turns out, it’s pretty damn easy, and fairly clean (maintenance wise).
I tried a few approaches before finding this solution. There may be other ways to accomplish this same method, but the following solution works. I’m not going to publish all of the code, but I’ll go through the core concepts:
First, this method only supports single hierarchy url depths. Meaning, the dynamic pages can have urls like /MyUrl, /my-url, or /this-is_valid. They cannot (currently) have urls like: /my/url, blog/2008/my-blog-title. Again, I doubt it would be hard to support cases like that, but I wanted to keep things simple for this explanation. Onto the setup:
The controller setup is as follows:
- HomeController
- Index action – Consider this a "static" page with some custom content on it. It has a view associated with it in the Views directory.
- InvalidPage action – Used to show the 404 error
- DynamicPageController
- Index action – Only action and it takes a string parameter named id. Id stands for the friendlyName of the dynamic page to show. This action grabs the page information from the db and sends it to its related view, that just acts as a template for all of the dynamic pages. Side note: I left the parameter named id so that I wouldn’t have to add an extra route to accommodate a more appropriate friendlyName parameter. Minor, and largely irrelevant.
In order to have MVC attempt to locate dynamic pages, I had to create my own controller factory class. I wanted the original logic of finding a controller, but if one was not found, I wanted send the logic to the DynamicPageController Index action. This allows for the "static" pages to take precedence and then fallback to the dynamic page content. The following code does just that:
public class BiaControllerFactory : DefaultControllerFactory
{
public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
try
{
return base.CreateController(requestContext, controllerName);
}
catch (Exception ex)
{
requestContext.RouteData.Values["id"] = controllerName;
controllerName = "DynamicPage";
requestContext.RouteData.Values["controller"] = controllerName;
return base.CreateController(requestContext, controllerName);
}
}
}
It’s pretty straightforward. The base.CreateController call will throw an exception when it can’t find the specified controller. Since this example only allows for dynamic urls like /my-dynamic-page, the default routes interpret that as a my-dynamic-page controller, index action. Easy enough to set the correct RouteData values and send the logic to the DynamicPageController Index action, with the correctly specified id parameter.
If the DynamicPageController Index action doesn’t find the dynamic page specified by the id parameter, it’ll redirect to the HomeController InvalidPage action. As a bonus, I setup the InvalidPage action to send the proper 404 response code back to the client.
To have MVC use the new controller factory class, add the following in the same place that you define your routes (Usually Global.asax):
ControllerBuilder.Current.SetControllerFactory(new BiaControllerFactory());
That’s basically it… I plan to use this method for smaller sites that require some custom coded pages as well as simple CMS capabilities. Let me know if you have any questions, comments, or suggestions related to this.