Orchard Gems: The "dynamic page" pattern

Tags: Orchard, Orchard gems, contents, pattern, events, extensibility, pages, C#

The Orchard content model is a beautiful thing: the concept of extensible content types, the ability to fine-tune every aspect of content management gives us a very powerful toolbox when it comes to handling any type of content.

This engine can be leveraged even with "singleton" content types, where there will be only one content item ever. What's more, you don't need to persist anything: content items can be thrown together on the fly. Why this is awesome is shown us by the following pattern. Let's call it "dynamic page" pattern.

Consider you have to create a page that should be extensible by other modules, e.g. some settings page or similar. Now using Orchard's content items for this is just fine: without determining its structure statically, you can alter a content type's content item dynamically on runtime:

    [Themed]
    public class PageController : Controller
    {
        private readonly IContentManager _contentManager;
        private readonly IEnumerable<IPageProvider> _pageProviders;

        // Requesting IEnumerable<IPageProvider> all the available providers will be injected
        public PageController(IContentManager contentManager, IEnumerable<IPageProvider> pageProviders)
        {
            _contentManager = contentManager;
            _pageProviders = pageProviders;
        }

        public ShapeResult Index()
        {
            // MyDynamicPage is not described anywhere, there is no need for migrations
            var page = _contentManager.New("MyDynamicPage");

            foreach (var provider in _pageProviders)
            {
                provider.Build(page);
            }

            return new ShapeResult(this, _contentManager.BuildDisplay(page));
        }
    }

    // A simple IDependency
    public interface IPageProvider : IDependency
    {
        void Build(ContentItem page);
    }

    public class MyPageProvider : IPageProvider
    {
        public void Build(ContentItem page)
        {
            // Depending on the needs, here an instantiation and welding could be enough
            var part = new PagePart
            {
                Message = "This is a dynamic page"
            };

            // This attaches the PagePart instance to the content item of type MyDynamicPage
            page.Weld(part);
        }
    }

    // Complex parts can be used too
    public class PagePart : ContentPart
    {
        public string Message { get; set; }
    }

This is a very simple implementation of course, but I hope it gives you the idea that creatively used content items are even more than the ones editable from the dashboard. Page parts can and should have drivers too, so they can actually render something.

Following the above pattern you can have a single page that (through implementations of IPageProvider) can be extended from other modules or features.

In one project I use multiple events on the page lifetime (like OnPageInitializing, OnPageBuilt) for providers to hook into. Since not all normal ContentHandler events can be used with this technique (e.g. OnLoaded doesn't make sense) this provides an additional level of customization.

The Helpful Libraries module now contains a framework to let you create such dynamic pages easily.

Happy dynamic page generation!

No Comments

  • Wolf said

    You missed "Happy dynamic page generation!" from end of the article. :)

  • Mounhim said

    How would this page be extended from other modules or features? Didn't get that from the example.

  • Piedone said

    You can have an arbitrary number of providers. Here's only one, but in another module you could have a MyPageProvider2, welding another part to the item. Because of dependency injection, these providers are auto-discovered.

  • Mounhim said

    I have tried the ad-hoc content item route. The example provided there doesnt work in 1.5.
    I get an error on the line "public ContentPartDefinition PartDefinition { get { return TypePartDefinition.PartDefinition; } }" in the file ContentPart.cs. The object TypePartDefinition is null. If I dont debug I get an empty content page.

    I think I understand the concept.

  • Piedone said

    That's strange because I use this exact same pattern in a 1.5.1 site with no problems.
    However, I've got such errors (indeed with 1.5, with 1.4 it was fine) when dynamically welding parts in the same manner on regular content types, i.e. content types that also have a static schema (like the built-in Page). If you want to do this, it's possible, but you have to weld the parts on the content item as late as possible. For this you can use regular content events, I advice to weld parts on regular content items in the OnGetEditorShape, OnGetDisplayShape, OnUpdating.

Add a Comment