Welcome to Shaun Luttin's public notebook. It contains rough, practical notes. The guiding idea is that, despite what marketing tells us, there are no experts at anything. Sharing our half-baked ideas helps everyone. We're all just muddling thru. Find out more about our work at bigfont.ca.

Parts, Fields, Items, Types, Content, Shapes, and Templates

Tags: orchard, sebastienros

 

The blog post is NOT an expert opinion. Rather, it is my current understanding. Much of it is probably wrong!

A tweet on Twitter precipitated a phone call with that sexy bastion of rock solid knowledge: Sebastien Ros. I wrote:

Wow. I understand with a flash of understanding. #orchardcms content items are not shapes - they are composed of shapes.

He replied:

You’re wrong.

Actually, he replied:

both statements are wrong actually, call me

That was a lovely thing for him to do. I still might be wrong but I learned a great deal. I now think that… Content Items are not shapes, they are collections of parts. When Orchard is going to render a content item, it turns the content item and each of its parts into shapes that become part of the shape tree.


Let me elaborate with way too many words.

Orchard uses templates to render shapes. Shapes represent content (and most other rendered things.) Content includes content items, which are instances of content types. Content types are composed of content parts and content fields. Parts and fields contain end user data that’s stored in the database. Depending on the stereotype of their type, content items are sometimes mapped to URLs and other times placed in widget zones. It’s a beautiful, modular, hierarchical puzzle that extends ASP.NET MVC into a content management framework.

Templates are fundamentally MVC views. In Orchard, templates are organized into a hierarchy that starts at the document and works through the layout, layout zones (in which items render,) and content item zones (in which parts and fields render.) The layout zones are what we target in the Widgets user interface, while the content item zones are what we target in placement.info files. Template implementation is most often a CSHTML file but sometimes a method with the [Shape] attribute (it would make more sense to refactor this into a ShapeTemplate attribute.) The most important thing is that templates render shapes.

Shapes are fundamentally dynamic view models. Shapes most often mediate between content and views, but also pass on to views data about zones (and other esoterica about which I don’t have a clue.) Orchard models content as Content Types composed of Parts and Fields. The parts and fields are what contain the end user data. Orchard has an object-relational mapping in which Content Parts and Content Fields have static models. The content manager persists and retrieves these models from the database. On retrieval, drivers may turn the static models into shapes, and the shapes will go into the shape trees (according to their placement.info file.) The Orchard view engine maps shapes to templates. In the end, it’s the shape tree that the view will render. The shape tree is a dynamic view model that templates render.

Content in Orchard is both stored in the database and represented in code by static models. Orchard’s content is organized into stereotypes, types, parts, and fields. A content type’s composition, stereotype, and other settings are stored in the database – types do not have static models. When we create an instance of a content type, it becomes a content item – in that way, content types are like classes, and content items are like objects, but the analogy breaks down, because content types are stored in the database and are not modeled with C# classes. When it comes time for rendering, the stereotype of the content type determines the content items shape type. In other words, we could rename “stereotype” to “shape type.” Types are composed of reusable parts and data fields. It’s these parts and fields that actually store end-user data and that have static C# models (e.g. TitlePart, BodyPart, InputField, BooleanField.) These static models inherit from ContentPart and ContentField classes. They, like most other things, turn into shapes prior to Orchard sending them to templates for rendering.

  • Content Type
    • has no C# class
    • is defined in the database
    • has one stereotype
    • has zero to many content parts
    • has zero to many content fields
    • is “instantiated” into a content item

Let’s look at an example of how Orchard and ASP.NET MVC cooperate. Say a URL such as “/Contents/Item/Display/72” comes in from the web.

ASP.NET routing cooperates with Orchard.Alias and Orchard.Autoroute to find a specific controller action: Orchard.Core.Contents.Controllers.ItemController.Display(int id). Notice the use of the default MVC route of “Area/Controller/Action/Id” (and that aqua and pink are an awesome colors.) This controller action will return a View bound to a shape.

public ActionResult Display(int id) {
    var contentItem = _contentManager.Get(id, VersionOptions.Published);
    var model = _contentManager.BuildDisplay(contentItem);
    return View(model);
}

Inside that Display action, three important things happen. First, Orchard’s ContentManager uses its Get() method to retrieve the appropriate content item from the database. (Remember: a content item is an “instance” of a content type and is composed of several Content Parts.) Second, the ContentManager’s BuildDisplay method invokes all the ContentHandlers for all those Content Parts.  One of those ContentHandlers is going to be the ContentPartDriver. Each driver will turn its part’s static data model into zero to many dynamic shapes. As each driver creates the shapes, it references placement.info to place the shape into the appropriate content item zone (c.f. layout zone) in the shape tree. When BuildDisplay returns, it has a shape that represents the content item and all its component parts. Now, the Display action can do its third step: return a View that’s bound to the shape that represents the content item.

Parts do not call drivers; rather, BuildDisplay resolves all the ContentHandlers, including ContentPartDriver, which cooperates with placement.info to give the shape tree more shapes. ContentPartDriver is a special ContentHandler that creates shapes from parts.

Let’s continue by assuming that “/Contents/Item/Display/72” routes to a content item of type Page. In Orchard, the page content type has a stereotype of “Content” (this is the default stereotype, others are “Widget” and “Media”). The stereotype of a content type becomes the content item’s shape type. Since Page has the “Content” stereotype, when the Display action (from the previous paragraph) returns a view object, the view maps to the Content.cshtml template or a more specific alternate that matches the shape type. If it were of the Widget of Media stereotype, it would map to the Widget.cshtml or Media.cshtml templates (or more specific alternates). Now, the rendering engine can combine the page content item’s shape with the content.cshtml template.

If we look at Content.cshtml, note that its Model has several zones: Title, Header, Meta, Content, Footer. It’s into the zones that the parts and fields will go. These content item zones are similar but distinct from the layout zones in the Layout.cshtml template. The zones in layout.cshtml are places that content items will render, and we place them there through the Widgets user interface in Orchard’s dashboard; other the other hand, the zones in content.cshtml are places that content parts and fields will render, and we place them there through placement.info files. The Page content type contains a Title and Body Part, let’s look at the placement.info file for the the title part. It’s brief. Among other things, it says that, if we are in the Detail display, to place the “Parts_Title” shape into its parent item’s “Content” zone at position 5.

Whew. Orchard has processed the Page content item that the end user requested with the “/Contents/Item/Display/72” URL. This involved routing to a controller, retrieval from the database, creation and placement of shapes out of content parts, and rendering through a template. The next steps involve incorporating the html that represents that page content item, along with any other widget content items, into the layout.cshtml file, and then incorporating of the layout.cshtml into the document.cshtml. All of this will involves shapes passing data to templates.

Appendix: Some Personal, Half Baked Notes

Orchard renders the Shape Tree. The Shape Tree is a bunch of nested shapes. Content Items, Widget Items, and Content Parts are not shapes. Rather, Orchard turns them into shapes prior to rendering.

Orchard Page Request Process

Url      
Routing      
Controller      
  ContentManager.Get    
  ContentManager.BuildDisplay    
    ContentPartHandlers  
    ContentPartDrivers  
      ContentShape
View      

The Shape Tree

The Orchard rendering engine renders the Shape Tree, which, among other things, contains a hierarchy of shapes that represent instances of Zones, Content Types, and Content Parts. For instance, while Widget Items, Content Items and Content Parts are not Shapes, Orchard turns them into Shapes before it renders them. The Shape Tree contains Shapes to represent Content Parts, Content Zones, Content Items, Widget Items, Layout Zones, the Layout, and the Document. The shape tree contains…

  • Shape that represents the document.
    • Shape that represents the layout.
      • Shapes that represents each layout zone (header, navigation, featured, beforeMain…)
        • Shapes that represent content type instances (content items, widget items, media items).
          • Shapes that represent each content zone (header, content, footer)
            • Shapes that represent content part instances.
              • Shapes the represent field instances.

Let’s focus on Content Items and Content Parts.

Methods like… takes a… and returns zero to many… where the Shape type comes from the…
ContentPartDriver.Action Content Part Shapes ContentShape’s shapeType parameter.
(e.g. Parts_Title, Parts_Title_Summary)
BuildDisplay Content Item Shape Content Type’s stereotype.
(e.g. Content, Widget, Media)

TitlePartDriver.Display

Take a TitlePart, return zero to three Shapes of type PartsTitle, PartsTitleSummary, and/or PartsTitleSummaryAdmin.

protected override DriverResult Display(TitlePart part, string displayType, dynamic shapeHelper) {
    return Combined(
        ContentShape("PartsTitle",
            () => shapeHelper.PartsTitle(Title: part.Title)),
        ContentShape("PartsTitleSummary",
            () => shapeHelper.PartsTitleSummary(Title: part.Title)),
        ContentShape("PartsTitleSummaryAdmin",
            () => shapeHelper.PartsTitleSummaryAdmin(Title: part.Title))
        );
}

The Page Type Shape Tree

Orchard has a Content Type called Page. It contains a TitlePart and BodyPart. It has stereotype Content (which is the default Stereotype). When we render that Page, the Shape Tree looks a bit like this:

  • Zone [Header]
  • Zone [Content]
    • Content
      • Parts_Title
      • Parts_Common_Body
  • Zone [AsideSecond]
  • Zone [Footer]

The Page Content Type has stereotype Content, so it shows up in the Shape Tree as Content. The TitlePart and BodyPart have shapeType Parts_Title, and Parts_Common_Body, so that’s how they show up in the tree.

The Html Widget Type Shape Tree

Orchard has a Content Type called Html Widget that contain a WidgetPart and BodyPart. It has the stereotype Widget. When we render the Html Widget, the Shape Tree looks a bit like this:

  • Zone [Header]
  • Zone [BeforeMain]
    • Widget
      • Parts_Common_Body
  • Zone [AsideSecond]
  • Zone [Footer]

The HTML Widget Content Type has stereotype Widget, so it shows up in the Shape Tree as Widget. The BodyPart has shapeType Parts_Common_Body, so that’s how it shows.