Multiple Views of the Same ContentItem

Topics: Developer Forum
Oct 16, 2008 at 3:04 PM
Here is my business case: There are many Realtors. Each Realtor has many Listings. Both Realtor and Listing are ContentItems. The Realtor and Listing data is displayed on a web page as normal (for N2).$0$0$0$0Now the client wants to be able to render the Listing data as a PDF. Which, to my mind, is just another view of the Listing data.$0$0$0$0$0So, my question is how best to implement this new view in N2, and I do not mean the PDF rendering.$0$0$0$0$0Obviously I could create a new sub-page of Listing and add it to each listing, but that seems unnecessarily redundant. And I don’t need a PDFListing ContentItem.$0$0$0$0$0I could hack-up the existing Listing view with some conditional logic. If view is html then show this; if view is pdf then show something else. This kind of view logic is not easy to support in webforms, especially when using master pages.$0$0$0$0$0My instinct is to create a single “Utility” page used by all listings called RenderPDF.aspx that would take a querystring parameter of the ID of the listing and render that listing’s data. However, this page would not inherit from TemplatePage<Listing> and really would not need to be Editable in the N2 sense. If the page does not inherit from TemplatePage<T>, then it does not have access to the underlying N2 engine context that it needs.$0$0$0$0$0So, I’m looking for an implementation suggestion of this new view page that is consistent with N2 best practices.$0$0$0$0$0Thanks in advance.$0$0$0$0$0PC$0
Coordinator
Oct 16, 2008 at 7:37 PM
I've been using overrides of the GetChild method to provide multiple views functionality. Whenever an url is resolved this method is invoked.

In GetChild I examine the remaining url and store information about it on a property on the content item. Then I return the content item instead of looking further down for children. Later on the TemplateUrl property is called and here I return different templates depending on the previously stored information.

The add-on catalog is implemented in this way: http://n2cms.com/add-ons.aspx

You can look at the source here: http://code.google.com/p/n2cms/source/browse/trunk#trunk/src/wwwroot/Addons/AddonCatalog
Oct 16, 2008 at 9:57 PM
Thank you, Thank you, Thank you! That looks like it will work great.
PC

On Thu, Oct 16, 2008 at 12:38 PM, libardo <notifications@codeplex.com> wrote:

From: libardo

I've been using overrides of the GetChild method to provide multiple views functionality. Whenever an url is resolved this method is invoked.

In GetChild I examine the remaining url and store information about it on a property on the content item. Then I return the content item instead of looking further down for children. Later on the TemplateUrl property is called and here I return different templates depending on the previously stored information.

The add-on catalog is implemented in this way: http://n2cms.com/add-ons.aspx

You can look at the source here: http://code.google.com/p/n2cms/source/browse/trunk#trunk/src/wwwroot/Addons/AddonCatalog

Read the full discussion online.

To add a post to this discussion, reply to this email (n2@discussions.codeplex.com)

To start a new discussion for this project, email n2@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Nov 10, 2008 at 2:15 PM
Overriding GetChild / TemplateUrl is my favorite trick too. Now that TemplateAttribute appeared in N2, I'd like to highlight one advanced use of this pattern, involving some bits from Windows Communication Foundation (WCF) machinery. My case is primary targeted on implementing a complex set of URLs/commands. 

Imagine that we have to design a content item, supposed to act as a business entity for a hypothetical web email page functionality.

1) Let's start from defining an enumeration with every possible command user may execute over this entity (plain strings here and there as command identifiers would suffice, but i prefer a statically typed approach with enums):

public enum ActionEnum
{
List, // show message list
Reply, // reply to selected message
Forward, // forward selected message
Create, // create a new message
Delete, // delete selected message
Restore, // take selected message out of recycle bin
Destroy // purge selected message permanently
}

2) Now let's define a set of URLs serving as entry points to our commands. Since .Net Framework 3.5 there is an excellent piece of functionality to achieve just that: UriTemplate and UriTemplateTable. (For deeper insight please see Steve Maine's writing: [ http://hyperthink.net/blog/uritemplate-match/ ] ) (notice, i'm using intermediate ad-hoc caching with storage property and helper method):

readonly static Uri BaseUri = new Uri("http://localhost/");

static UriTemplateTable s_routes;
static UriTemplateTable Routes { get { return s_routes ?? (s_routes = GetRoutes()); } }

static UriTemplateTable GetRoutes()
{
var _uris =
from _pair in new Dictionary<string, ActionEnum> {
{ "folder/{folder}/filter/{filter}", ActionEnum.List },
{ "folder/{folder}", ActionEnum.List },
{ "reply/{id}", ActionEnum.Reply },
{ "forward/{id}", ActionEnum.Forward },
{ "delete/{id}", ActionEnum.Delete },
{ "restore/{id}", ActionEnum.Restore },
{ "destroy/{id}", ActionEnum.Destroy },
{ "new", ActionEnum.Create } }
select
new KeyValuePair<UriTemplate, object>(
new UriTemplate(_pair.Key),
_pair.Value);
return new UriTemplateTable(BaseUri, _uris);
}

3) Now, having defined our RESTful API, let's skip a task of generating appropriate URLs inside views and move directly to parsing incoming commands inside GetChild(string) override. Here is how parsing occurs just at the beginning of GetChild(..):

public override ContentItem GetChild(string childName)
{
var _matches = Routes.Match(new Uri(BaseUri, childName)); //process arbitrary incoming strings basing on a UriTemplate dictionary

if (_matches.Any()) {

var _match = _matches.First();//take the first match
this.Action = (ActionEnum)_match.Data; //take advantage of statically typed command identifiers
switch (this.Action) {
//1) process each command ...
//2) extract matched variables inside URLs, for example:
case ActionEnum.List:
this.Folder = _match.BoundVariables["folder"];
this.Filter = _match.BoundVariables["filter"];
break;

4) Now all what's we are due is to implement a TemplateUrl override basing on information we've collected so far.
Coordinator
Nov 10, 2008 at 7:23 PM
Those UriTemplates are cool. Are you using this much? I'm afraid the upcoming TemplateAttribute will break this model.

To justify the change:
  • Ease of use. The current model requires heavy modification of the content item. This makes it possible to re-use the routing functionality.
  • I'm considering re-introducing caching of url to template. Storing contextual data on the content item would cause trouble.
You can continue to use uri-template approach with some modifications (by moving the code to FindTemplate or creating an attribute). What do you think about this change? Is it worth a breaking change?
Nov 11, 2008 at 6:20 AM
I anticipate upcoming [TemplateAttribute] and would not miss my approach too much: from architectural perspective, i'm somewhat dissatisified with too tight coupling of Model-Controller when using GetChild / TemplateUrl approach, that is: having data entity too much aware of controller logic. So, if something more generic will come in place, i'll be happy to try it. The only thing i worry about is to have as much control of URIs as possible, i.e.: to be able to design a RESTful APIs using either path, query args and hash URL parts. With a present approach it's possible to pass the whole URL reminder to GetChild(..) and parse it as i wish, which is very nice.
Coordinator
Nov 11, 2008 at 7:23 PM
Great. The templateattribute kicks in when there is a remaining url, so you can parse it as you like if you create a custom template attribute.