Because the framework is so new, there are still a few kinks and the patterns for how to do some common things haven't yet sorted themselves out. One of those areas is how to handle content in web applications.
All of these projects are web applications that *do* stuff. Most of these apps are filled with pages to edit things, retrieve data, manage queues, etc. In other words, the custom business logic that's unique to my clients' and my businesses. However, every single one also needs a batch of "pages" that *are* just basic content, with a few variables (like who's logged in) sprinkled through them.
The pattern that appears to be recommended by the ASP.NET team (via what is in the project template for the "home" and "about us" pages) basically requires a new view and a new action in a controller for each page, which, for those playing along at home, also requires rebuilding the app and redeploying.
Experience has taught me that solutions like that inevitably lead to pain. Once deployed, people always want to add pages, change them and otherwise mess with things more often than anyone wants to go through the build/deploy cycle. That leaves me looking for something that has the content more separated from the aspx setup than the default pattern seems to suggest.
It's at this point in the conversations I've had about this topic that a variety of ASP.NET content management systems are brought up. The problem with all of them that I've checked out from this angle boils down to what you consider the "center" of the app.
If your app is mostly content, with some custom functionality, these CMS solutions can work well. That's because all of them push you to do your custom functionality as a plugin to their framework. Beyond that, most of them don't handle or do well with things like variables being embedded in random paragraphs, extreme personalization, switching out paragraphs based on roles, etc.
OK, so that leads to using a CMS *library*. I looked at a few of those too. Several didn't do variables at all, another few were basically just wiki article storage, another few made everything "news" or "blog posts" and shoehorned it into that. The choices left me a little cold.
After looking at all of these possibilities, I wanted to take a stab at exploring a solution in a "hands-on" way as that approach tends to let me really get inside the problem. Often after working through a proof of concept, even if I end up going with one of the pre-packaged solutions, I can do a much better job of picking the right one and understanding the pro's and con's of each.
So, I took a bit of time to run through this problem and work it out in POC code.
The first place I looked was at the alternate view engines for MVC. Several of them look interesting, but I'm planning on sticking with the default WebForms engine for all of the non-content functionality. That actually means not using the alternative view engine that actually implements the technology that I ended up using in my POC.
There are reasons for sticking to the WebForms view engine for at least the short term. One of them is that the default path inside the MVC ecosystem is going to get the most attention to work through patterns that work. Another is to leverage familiarity in the area where the hardest work has to be done in these apps (the custom business logic and rules). Several of the projects involve other devs, so going to MVC is already going to be an adaptation for them (though one I think is worth it).
OK. On to what I did in my POC. I knew that I wanted to leverage a templating system in some way. One that came up over and over when doing research was Jakarta Velocity, a Java solution from the Apache Foundation (a group that usually thinks things through pretty carefully).
NVelocity brings that engine to .NET, but hasn't been updated since 2003. Fortunately, the folks over at the Castle Project picked NVelocity up and have been maintaining it improving it as well. As I mentioned earlier, NVelocity is also one of the alternate view engines. Though I have reasons for not using it in that way, it is an additional vote of confidence for it.
NVelocity templates are written in the Velocity Templating Language, which gives all of the elements I need for the problems I'm looking at right now.
The first problem I did run into when using NVelocity inside of my POC MVC app was that the basic Castle implementation wants the NVelocity templates on the disk as files. That defeats most of my purpose here as I want the templated content to come out of the database and be editable there as well.
Fortunately, this CodeProject code adds the ability to use NVelocity with a template that comes from
a string instead of having to have them in individual files on disk by creating a memory-based engine implementation.
I basically aimed to have pages made up of snippets for this POC. A PageController action of Details() takes a string I called "Slug" which is the URL-based name of the requested page. That base page is loaded and is parsed for tokens that point to template snippets. Those snippets are then integrated into the composite template before feeding it to the NVelocity engine.
I chose a token for handling includes (double braces on each side of the named key). My initial proof of concept only did one level of inclusion: a page and snippets of content, but this method could handle whatever level of recursion that's necessary.
So, a page named "home" could have a token:
which would be replaced by the snippet of template code that has the unique key "user-details" from the content repository. The recursion would be handled by tracing through the tree of those included tokens for a given page until the nested tree is built.
The controller needs to build up objects that fulfill all of the variables in the templates. A production implementation of this would need to provide a way to report on the dependencies that a given composite template requires to avoid empty slots.
However, since NVelocity handles doing things like passing objects more complex than basic strings, this can be much easier than it might be otherwise. So, you can do something like pass (User)CurrentUser to the template, which can have properties pulled into the template from there. By passing that one object, you can have a template like:
The account for $CurrentUser.FirstName $CurrentUser.LastName is currently $CurrentUser.AccountStatus with a balance of $CurrentUser.AccountBalance
That ability also handles one of the peripheral problems I hadn't included as part of the scope of this POC, but became an obvious fit along the way: internationalization. If you provided an object with all of the label text, button text, menu names, etc. as UTF-8 text in the properties, you could fill all of those bits in as:
$Labels.Home | $Labels.AboutUs | $Labels.MyAccount
Overall, I like the flexibility and don't see anything really standing out as a roadblock to this solving the CMS needs I have in this range of projects. There will definitely have to be some work on the back-end administration of this content to catch missing objects and judicious use of caching of both objects and composite templates to keep it from slowing things down, but I think those are definitely solvable.
NVelocity handles the kind of dictionary objects I'd like to hand off to CMS pages, conditional rules, basic looping, etc. At the same time, it lets you manage most of this stuff outside of code (provided that you provide some decent structure to the objects available to the PageController for all of the pages (things like CurrentUser, SiteSettings, ThemeInformation, etc.).
That ThemeInformation object actually hits on one other requirement of one of the projects in particular where I need user-defined theming for things like fonts and colors, thus lining up the birds nicely for a stone to be thrown.
The template language is pretty much just basic HTML with simple variables and loops, making it pretty easy for people outside of the dev team to work with as well, without a WYSIWYG editor (which would make the back end a much bigger pain to develop). It also can be re-used for template-based emails from these same systems pretty much wholesale.
At this point, I'd really like input on the approach and opportunities to improve it. Solving this problem well is something that will make all of my MVC projects much easier and be really re-usable.