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:
- 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
- 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:
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):
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.
Over the weekend, I had the pleasure of going to Vino 100. It’s a small wine shop in Wauwatosa, WI (they also have one in the 3rd Ward). I should begin with a bit of background on my wine knowledge, though.. While I’m not a huge wine drinker, I do enjoy a glass now and then. Half priced wino wednesdays have really brought out my inner wino and I’ve been drinking wine more frequently lately. I just don’t have a lot of knowledge when it comes to relating wine names with flavors.
Vino 100 takes a lot of the unknowns out of choosing wine. The store is laid out with red wines along one wall and whites along another, with tasting tables and a small bar occupying the space between walls. The genius is how they’ve organized the wines, though. The wines are organized by taste rather than name. For the white wine wall, they start with fruity, sweet wines on the left and gradually move to dry, full bodied wines on the right. Each wine has a description and a visual representation for the wine’s flavor and body. If the wine is on their menu, you can even sample the wine before making a purchase. The red wine wall is organized in much the same way.
For me, they really hit it out of the park. Instead of catering to the wine connoisseur, they cater to novices like myself and really make it easy to improve wine knowledge without an unexpected flavor or taste.
These are my photos from my Flickr page…
As I mentioned in my previous post, I am playing around with SubSonic 3. While the templates that come with it are a good starting point, I made some changes to make things feel a little more like the SubSonic I know and love…
You can download my updated SubSonic templates here.
What have I changed?
- I added a template called Settings.tt that contains settings used across all templates
- You can now specify a namespace that is different from the db provider name
- I added StripTableText and StripSPText, allowing you to remove table & sproc prefixes
- I moved ExcludeTables to a central location
- I removed redundant assembly and import directives
- Pluralized Queries in Provider.tt
- Public fields were converted to automatic properties
This wont be the last update to the templates, but I think it’s a step in the right direction…
I was excited to get going with SubSonic 3, but soon realized that website projects do not support T4 files (Text Template Transformation Toolkit). I found that there is a command-line tool that you can use to generate the output, but I wanted something even easier. So I set out to create a right-click context menu item to generate the code files. This is one way for how you can do it.
- Add an external tool command to the command-line tool
- Click Tools -> External Tools…
- Click Add
Title: Generate T4 Code
Command: C:Program FilesCommon FilesMicrosoft SharedTextTemplating1.2TextTransform.exe
Arguments: $(ItemFileName)$(ItemExt) -out $(ItemFileName).cs
Initial Directory: $(ItemDir)
- Check "Use Output Window" and click Ok
- Add it to the website project context menu
- Click View -> Toolbars -> Customize…
- Check Context Menus
- With the Customize dialog still open, click the Tools menu
- While holding Ctrl, left click and drag your External Tool command to the Project and Solution Context Menus toolbar, which opens allowing you to continue dragging the external command it to Web Item
- Click the Project and Solution Context Menus toolbar, Click Web Item, and Right Click External Command
- Change the Name to Generate T4 Code and click enter
- Close the Customize dialog
- Now you have a right click context menu to generate code for .tt files
There is an annoyance with this method, however. The context menu item will show up regardless of file type/extension. If someone knows how to fix this, or has a better way of adding context menu items, I’m all ears. I did find this article for how to add context menu items with VSIP, but that seemed a bit overkill…
Those who know me, probably know that I enjoy listening to Dave Matthews. Over the weekend, I built a site that allows me to listen to some live shows wherever I may be. It’s nice to not have to lug around a usb drive full of music. The site is http://dmbstream.com with a main purpose of allowing you to stream bootlegs of dmb shows. It’s like Pandora, but allows you to rewind, fast forward and listen to entire concerts as many times as you like.
It was a blast to create and allows me to "play" with new technologies that might be harder to implement in other projects. I must say that I am still pretty amazed at how quickly it all came together. I had the core framework built over the weekend (including css and photoshop hacking). Search, comments, and photo integration were added shortly afterward. There’s still a lot that I want to do with the site, but I feel that it’s in a good enough place for people to go and play with it.
I’d like to hear what you think, so if you have any questions or comments please let me know.
Though it’s a trend with some bloggers right now, I decided to pass on setting up Disqus comments with this blog, for now. I really like the idea and implementation, but my concerns are what swayed me from installing it:
- The comments are not stored with my blog
- When disqus.com goes down, I lose comments
Maybe if I get complaints I’ll change my mind, but for now I’m going to hold off…
Internet Explorer shines yet again… I had to write a login sync for two sites (Single sign-on). So that you can log into one site and (behind the scenes) get logged into the other site. I chose to do this with iframes and all was happy. Until I had to verify that it worked in IE. IE chooses to dismiss cookies set/removed through the iframe. Setting a header on the page that creates/deletes the cookies will remedy this, though.
Add this to your pages that create/delete the cookies:
It creates a p3p "compact policy" that gets sent to the client. This convinces IE to accept the cookie in some magical way…
I was working with the latest drop of the asp.net mvc framework and had a need to stream a pdf back to the user… A quick glance around the System.Web.Mvc namespace only yielded ContentResult. While that’s cool for string content, I needed something more flexible for binary data. So I wrote the BinaryResult:
public override void ExecuteResult(ControllerContext context)
context.HttpContext.Response.ContentType = ContentType;
((IsAttachment) ? "attachment;filename=" : "inline;filename=") +
You can use this in your controllers to stream any type of binary data as the ActionResult for that controller. So for my example, I call it like:
return new BinaryResult
ContentType = "application/pdf",
FileName = "Sample.pdf",
IsAttachment = true,
Data = stream.ToArray()
btw, sorry if I’m reinventing the wheel with this, but Google gave no love when I tried searching for it
Have you needed to do a complex where clause with a query, but didn’t want to write raw SQL? That’s where SubSonic expression constraints come in handy. Say you want to do something like:
Using SubSonic 2.1, you can do the following:
List<Product> products = DB.Select().From<Product>()
Basically, the AndExpression part translates to a SQL "and" operator followed by a beginning parentheses. The CloseExpression translates to the closing parentheses. Theoretically, you could nest these bad boys as deep as you’d like. Note: SubSonic also comes with an OrExpression as well.
Hope this helps