You are on page 1of 29

4/10/2015 IDeliverable 

­ Messaging, Channels, Rules and Tokens ­ Part 1

Messaging, Channels, Rules and


Tokens - Part 1
orchard (/Tags/orchard)

In this series we will be implementing a custom module to see what

it takes to implement a Templated Messaging System that

leverages not only basic Orchard stuff, but also more advanced

APIs such as Rules, Tokens, Messaging, Channels, Site settings and

Content Type/Part settings.

This module will enable our users to do the following:

Create and manage Messaging Templates (which will typically be used for email messages)
using parser engine specific syntax (which will be Razor by default);
Setup Rules that sends a templated message when one or more events occur;
Use Tokens in the Subject field as well in a field that is part of the SendTemplatedMessage
action;
Configure which parser engine to use by default using site scope settings and content type
scope settings.

At the end of this tutorial we will not only have a useful module, we will have learned how we
can wire things together with the Rules and Tokens modules and have a better understanding of
how they work and can be leveraged in other modules.

Template Management
http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 1/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

In this first part, we will start simple by creating the module and implementing a basic Template
Management System.

We will:

Define a MessageTemplatePart that stores information about a template, such as the


template text itself;
Define an EmailTemplate content type that uses the MessageTemplatePart;
Implement an IMessageTemplateService that helps with quering templates.
Learn how to implement import/export for custom parts that reference other content
items using the ImportContentContext.GetItemFromSession method.

All in all, this first part will handle nothing but basic module development except maybe for a
couple of small but interesting details. For those who are just getting started with Orchard
Module development, this is a probably a good intro.

For those who have done this a hundred times already, you could simply download the zip file
and head over to the next part, but I recommend skimming over this post to get quick glimpse of
what we're doing and where we're headed.

Let's get started!

Creating the Module


I'll assume you know how to generate a new module by now. If not, please check out the
following resources:

http://docs.orchardproject.net/Documentation/Command-line-scaffolding
(http://docs.orchardproject.net/Documentation/Command-line-scaffolding)
http://www.skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-
module-from-scratch-part-3
(http://www.skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-
module-from-scratch-part-3)

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 2/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

Go ahead and codegen a new module called Skywalker.Messaging.

The Model
Our module's purpose is to provide message template management features. But what type of
template would the user want to work with? An Email template? SMS? Or perhaps a template
that can be processed by MailChimp?

We can't possibly know upfront. So what we will do is provide the essentials while allowing users
and developers to extend the system. Good thing we are working with Orchard, because it has
just the mechanism we need: Content Parts!

That's right, we'll create a content part called MessageTemplatePart, which can be used to build
any template type a user wants. By default, we will include a migration that sets up an
EmailTemplate content type, as such a type is probably the most commonly used one.

Models/MessageTemplatePart.cs: 

using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.ContentManagement.Records;
using Orchard.ContentManagement.Utilities;
using Orchard.Data.Conventions;
 
namespace Skywalker.Messaging.Models {
 /// <summary>
 /// Represents the essentials of a message template.
 /// Can be attached to any content type that needs to function as a message template.
 /// </summary>
 public class MessageTemplatePart : ContentPart<MessageTemplatePartRecord>, ITitleAspect {
  internal LazyField<MessageTemplatePart> LayoutField = new LazyField<MessageTemplatePart>(
);
 
  /// <summary>
  /// The title of this template. Primarily used to easily identify this template in the da
shboard.
  /// </summary>
  public string Title {

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 3/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

   get { return Record.Title; }
   set { Record.Title = value; }
  }
 
  /// <summary>
  /// The subject to use when sending a message using this template. Supports tokens.
  /// </summary>
  public string Subject {
   get { return Record.Subject; }
   set { Record.Subject = value; }
  }
 
  /// <summary>
  /// The actual template text to be processed.
  /// </summary>
  public string Text {
   get { return Record.Text; }
   set { Record.Text = value; }
  }
 
  /// <summary>
  /// Indicates whether this template is to be used as a Layout.
  /// </summary>
  public bool IsLayout {
   get { return Record.IsLayout; }
   set { Record.IsLayout = value; }
  }
 
  /// <summary>
  /// Layout is optional.
  /// If it references another MessageTemplatePart,
  /// it will be used as the "Master" layout when parsing the template.
  /// </summary>
  public MessageTemplatePart Layout {
   get { return LayoutField.Value; }
   set { LayoutField.Value = value; }
  }
 }
 
 /// <summary>
 /// The storage record class for the <see cref="MessageTemplatePart"/>
 /// </summary>
 public class MessageTemplatePartRecord : ContentPartRecord {
  public virtual string Title { get; set; }
  public virtual string Subject { get; set; }
 
  [StringLengthMax]
  public virtual string Text { get; set; }
  public virtual int? LayoutId { get; set; }
  public virtual bool IsLayout { get; set; }
 }
}
http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 4/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

As you will have noticed, we are including a property that can reference another
MessageTemplatePart. The reason for this is so that we can support Master template scenarios.

Ususally, when you send out emails, you will want them to have a fixed header and footer, and
simply vary the content. If we didn't support the notion of a Template Layout, we would have to
duplicate the header and footer in all templates, which as you can imagine would quickly become
a maintenance nightmare if you have a lot of templates.

The Migration
Our migration will be nice and simple: it will create a table for our MessageTemplatePartRecord
class and define the MessageTemplatePart.

It will also define a content type called EmailTemplate that takes advantage of the
MessageTemplatePart.

Migrations.cs:

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 5/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
using Orchard.Environment.Extensions;
 
namespace Skywalker.Messaging {
    [OrchardFeature("Skywalker.Messaging")]
    public class Migrations : DataMigrationImpl {
         public int Create() {
 
             // Create a table for the MessageTemplatePartRecord class
             SchemaBuilder.CreateTable("MessageTemplatePartRecord", table => table
                 .ContentPartRecord()
                 .Column<string>("Title", c => c.WithLength(256))
                 .Column<string>("Subject", c => c.WithLength(256))
                 .Column<string>("Text", c => c.Unlimited())
                 .Column<int>("LayoutId", c => c.Nullable())
                 .Column<bool>("IsLayout", c => c.NotNull()));
 
             // Define the MessageTemplatePart
             ContentDefinitionManager.AlterPartDefinition("MessageTemplatePart", part => pa
rt.Attachable());
 
             // Define an EmailTemplate content type. Other types can be defined by the use
r using the dashboard and by developers to implement advanced scenarios.
             ContentDefinitionManager.AlterTypeDefinition("EmailTemplate", type => type
                 .WithPart("CommonPart")
                 .WithPart("MessageTemplatePart")
                 .DisplayedAs("Email Template")
                 .Draftable()
                 .Creatable());
 
             return 1;
         }
    }
}

The Driver
To provide an editor UI to our users, we will implement a driver for the MessageTemplatePart.
This driver will only implement the 2 Editor methods and the Importing and Exporting methods.

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 6/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

The Driver will take a dependency on a service called IMessageTemplateService, makes use of a
class called MessageTemplateViewModel and some string extension method called TrimSafe.
We'll include all that code next, so don't worry if your code doesn't compile after including the
driver code.

Let's have a look at the code:

Drivers/MessageTemplatePartDriver.cs:

using System.Linq;
using System.Xml;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.Environment.Extensions;
using Skywalker.Messaging.Helpers;
using Skywalker.Messaging.Models;
using Skywalker.Messaging.Services;
using Skywalker.Messaging.ViewModels;
 
namespace Skywalker.Messaging.Drivers {
    [OrchardFeature("Skywalker.Messaging")]
    public class MessageTemplatePartDriver : ContentPartDriver<MessageTemplatePart> {
        private readonly IContentManager _contentManager;
        private readonly IMessageTemplateService _messageTemplateService;
 
        public MessageTemplatePartDriver(IContentManager contentManager, IMessageTemplateSe
rvice messageTemplateService) {
            _contentManager = contentManager;
            _messageTemplateService = messageTemplateService;
        }
 
        /// <summary>
        /// Always implement Prefix to avoid potential model binding naming collisions when
 another part uses the same property names.
        /// </summary>
        protected override string Prefix {
            get { return "MessageTemplate"; }
        }
 
        protected override DriverResult Editor(MessageTemplatePart part, dynamic shapeHelpe
r) {
            return Editor(part, null, shapeHelper);
        }
 
        protected override DriverResult Editor(MessageTemplatePart part, IUpdateModel updat
er, dynamic shapeHelper) {
            var viewModel = new MessageTemplateViewModel {

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 7/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

 
                // The editor will render a dropdown list of available templates that are m
arked as Layout.
                // The following call will return all content items with a MessageTemplateP
art where IsLayout == true.
                // We are also filtering out the current content item ID, because we cannot
 allow the user to set the layout of a template to itself.
                Layouts = _messageTemplateService.GetLayouts().Where(x => x.Id != part.Id).
ToList()
            };
 
            if (updater != null) {
 
                // We are in "postback" mode, so update our part
                if (updater.TryUpdateModel(viewModel, Prefix, null, null)) {
                    part.Title = viewModel.Title.TrimSafe();
                    part.Subject = viewModel.Subject.TrimSafe();
                    part.Text = viewModel.Text;
                    part.Layout = viewModel.LayoutId != null ? _contentManager.Get<MessageT
emplatePart>(viewModel.LayoutId.Value) : null;
                    part.IsLayout = viewModel.IsLayout;
                }
            }
            else {
 
                // We are in render mode (not postback), so initialize our view model.
                viewModel.Title = part.Title;
                viewModel.Subject = part.Subject;
                viewModel.Text = part.Text;
                viewModel.LayoutId = part.Record.LayoutId;
                viewModel.IsLayout = part.IsLayout;
            }
 
            // Return the EditorTemplate shape, configured with prope values.
            return ContentShape("Parts_MessageTemplate_Edit", () => shapeHelper.EditorTempl
ate(TemplateName: "Parts/MessageTemplate", Model: viewModel, Prefix: Prefix));
        }
 
        protected override void Exporting(MessageTemplatePart part, ExportContentContext co
ntext) {
            context.Element(part.PartDefinition.Name).SetAttributeValue("Title", part.Title
);
            context.Element(part.PartDefinition.Name).SetAttributeValue("Subject", part.Sub
ject);
            context.Element(part.PartDefinition.Name).SetAttributeValue("Text", part.Text);
            context.Element(part.PartDefinition.Name).SetAttributeValue("IsLayout", part.Is
Layout);
 
            // Here it gets interesting: "Layout" references another content item, and we c
an't simply serialize its Id because that may change across databases.
            // So instead, we leverage the content manager's GetItemMetadat method to ask f
or the content's Identity.
http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 8/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

            // The resulting identity is built by various attached parts to the content ite
m.
            // For example, the AutoroutePartHandler will contribute the DisplayAlias.
            // The IdentifierPartHandler will contribute a GUID.
            // This will of course only happen if the content item has these parts attached
.
            // In our case, we don't require the content item to have either one attached, 
so we will have our own content handler contributing an identity.
            // We'll use this identity value in the Importing method to locate the referenc
ed content item.
            if (part.Layout != null)
                context.Element(part.PartDefinition.Name).SetAttributeValue("Layout", conte
xt.ContentManager.GetItemMetadata(part.Layout).Identity.ToString());
        }
 
        protected override void Importing(MessageTemplatePart part, ImportContentContext co
ntext) {
            context.ImportAttribute(part.PartDefinition.Name, "Title", x => part.Title = x)
;
            context.ImportAttribute(part.PartDefinition.Name, "Subject", x => part.Subject 
= x);
            context.ImportAttribute(part.PartDefinition.Name, "Text", x => part.Text = x);
            context.ImportAttribute(part.PartDefinition.Name, "IsLayout", x => part.IsLayou
t = XmlConvert.ToBoolean(x));
 
            // If "Layout" is specified, it will be the identoity of the referenced content
 item.
            // The context argument has a ethod called "GetItemFromSession" which will retu
rn the content item corresponding
            // to the identity value we used in the Exporting method.
            context.ImportAttribute(part.PartDefinition.Name, "Layout", x => {
                var layout = context.GetItemFromSession(x);
 
                if (layout != null && layout.Is<MessageTemplatePart>()) {
                    part.Layout = layout.As<MessageTemplatePart>();
                }
            });
        }
    }
}

That looks like a lot of code, but the implementation is simple: we implement both Editor
methods and the Importing and Exporting methods.

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 9/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

In the Editor method, we are instantiating a view model of type MessageTemplateViewModel.


The reason that we are using an additional class as the view model instead of using the
MessageTemplatePart directly as a view model is simple: we want to include additional data with
the view model (a list of available Layouts). We could have implemented this differently, as is
described here: http://www.skywalkersoftwaredevelopment.net/orchard-development/content-
part-editors (http://www.skywalkersoftwaredevelopment.net/orchard-development/content-
part-editors)

That would have worked out just as fine, but I it's interesting to see different approaches.

As described elaborately in the comments, we are taking advantage of content identities and the
GetItemFromSession method to export and import related content. The challenge with exporting
and importing related content is: how to identify content. We can;t simply use their primary key
values, as those will surely change from database to database.

So instead, Orchard provides a mechanism to build an Identity. This is done via


ContentHandlers. We'll see how to implement this later on. For now it's enough to know that we
can get the Identity of any content item using the GetItemMetadata method of the content
manager.

Before we move on to the content handler, let's have a look at the missing pieces:
MessageTemplateViewModel, IMessageTemplateService and TrimSafe.

MessageTemplateViewModel
MessageTemplateViewModel is a simple class that looks like this:

ViewModels/MessageTemplateViewModel.cs:

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 10/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Skywalker.Messaging.Models;
 
namespace Skywalker.Messaging.ViewModels {
    public class MessageTemplateViewModel {
        [Required, StringLength(256)]
        public string Title { get; set; }
 
        [Required, StringLength(256)]
        public string Subject { get; set; }
        public string Text { get; set; }
 
        [UIHint("TemplateLayoutPicker")]
        public int? LayoutId { get; set; }
        public bool IsLayout { get; set; }
        public IList<MessageTemplatePart> Layouts { get; set; }
    }
}

Pretty basic stuff that you would see in many a MVC application: a bunch of properties
annotated with DataAnnotations. We're using the UIHintAttribute to specify a specific editor
template for the LayoutId property. We'll see how that looks later on.

IMessageTemplateService
IMessageTemplateService contains our "business logic" if you will, although be it rather
simplistic: it enables us to easily retrieve templates and layouts (which are exactly the same as
templates, but with its IsLayout property set to true). It also contains a method to parse a
template into the final output. This takes Layouts and view engine specific constructs into
account. The engine to be used will be configurable by the user. Out of the box we will provide 2
parser engines: SimpleTextParserEngine and RazorParserEngine.

Services/IMessageTemplateService.cs:

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 11/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

using System.Collections.Generic;
using Orchard;
using Skywalker.Messaging.Models;
 
namespace Skywalker.Messaging.Services {
    public interface IMessageTemplateService : IDependency {
        IEnumerable<MessageTemplatePart> GetLayouts();
        IEnumerable<MessageTemplatePart> GetTemplates();
        IEnumerable<MessageTemplatePart> GetTemplatesWithLayout(int layoutId);
        MessageTemplatePart GetTemplate(int id);
        string ParseTemplate(MessageTemplatePart template, ParseTemplateContext context);
        IEnumerable<IParserEngine> GetParsers();
        IParserEngine GetParser(string id);
        IParserEngine SelectParser(MessageTemplatePart template);
    }
}

This service has 3 primary related responsibilities: Querying the available templates, querying
the available parsers, and actually parsing a template (which will automatically select the correct
parser based on the template and its settings).

As we all know, an important software design principle is the Single Responsibility Principle
(SRP). This basically means that all classes should ideally have no more than one responsibility.
This is an important principle, but it is als important to be practical, and in this case the 3
mentioned responsibilities are in my opinion closely related enough that it would be overkill to
separate them out into their own interfaces.

We will implement this interface partially in this part, and complete it in the next part.

MessageTemplateService
The initial implementation looks like this:

Services/MessageTemplateService.cs:

using System.Collections.Generic;
using System.Linq;

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 12/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

using Orchard.ContentManagement;
using Orchard.Environment.Extensions;
using Skywalker.Messaging.Models;
 
namespace Skywalker.Messaging.Services {
 [OrchardFeature("Skywalker.Messaging")]
 public class MessageTemplateService : IMessageTemplateService {
  private readonly IContentManager _contentManager;
  private readonly IEnumerable<IParserEngine> _parsers;
 
  public MessageTemplateService(IEnumerable<IParserEngine> parsers, IContentManager content
Manager) {
   _parsers = parsers;
   _contentManager = contentManager;
  }
 
  public IEnumerable<MessageTemplatePart> GetLayouts() {
   return _contentManager.Query<MessageTemplatePart, MessageTemplatePartRecord>().Where(x =
> x.IsLayout).List();
  }
 
  public IEnumerable<MessageTemplatePart> GetTemplates() {
   return _contentManager.Query<MessageTemplatePart, MessageTemplatePartRecord>().Where(x =
> !x.IsLayout).List();
  }
 
  public IEnumerable<MessageTemplatePart> GetTemplatesWithLayout(int layoutId) {
   return _contentManager.Query<MessageTemplatePart, MessageTemplatePartRecord>().Where(x =
> x.LayoutId == layoutId).List();
  }
 
  public MessageTemplatePart GetTemplate(int id) {
   return _contentManager.Get<MessageTemplatePart>(id);
  }
 
  public IEnumerable<IParserEngine> GetParsers() {
   return _parsers;
  }
 
  public IParserEngine GetParser(string id) {
   return _parsers.SingleOrDefault(x => x.Id == id);
  }
 
  public string ParseTemplate(MessageTemplatePart template, ParseTemplateContext context) {
   throw new System.NotImplementedException();
  }
 
  public IParserEngine SelectParser(MessageTemplatePart template) {
   throw new System.NotImplementedException();
  }
 }
}
http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 13/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

The methods ParseTemplate and SelectParser will be implemented in the next part.

IMessageTemplateService and its implementation MessageTemplateService reference 2 more


types the we will define next: ParseTemplateContext and IParserEngine.

IParserEngine
IParserEngine is the contract that all template parsers must implement in order to be usable as a
template parser. In the upcoming parts, we will be implementing a SimpleTextParserEngine and
a RazorParserEngine.

Services/IParserEngine.cs:

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 14/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

using Orchard;
using Skywalker.Messaging.Models;
 
namespace Skywalker.Messaging.Services {
 public interface IParserEngine : IDependency {
  /// <summary>
  /// The unique ID of the parser. The default base implementation will use the FullName va
lue of its type.
  /// </summary>
  string Id { get; }
 
  /// <summary>
  /// The user friendly text of the parser.
  /// The default base implementation will use the Name property of its type, but is typica
lly overridden in derived classes.
  /// </summary>
  string DisplayText { get; }
 
  /// <summary>
  /// The string / expression that will be replaced with child templates.
  /// For example, if a template is configured to be a Layout, it will replace its LayoutBe
acon expression with the output of its child template.
  /// </summary>
  /// <example>
  /// @RenderBody() in the case of a Razor engine, [Body] in the case of the SimpleTextPars
erEngine.
  /// </example>
  string LayoutBeacon { get; }
 
  /// <summary>
  /// Processes the specified template and returns the resulting string.
  /// </summary>
  /// <param name="template">The template to parse</param>
  /// <param name="context">Additional context to the parser engine.</param>
  string ParseTemplate(MessageTemplatePart template, ParseTemplateContext context);
 }
}

All members should be self explanatory, except maybe the LayoutBeacon. We'll see what this is
and how its used later on. It's pretty simple, you'll see.

ParseTemplateContext
 
http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 15/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

This is a simple class that simply holds all the (potentially) useful stuff a template parser could
use. For now, it looks like this:

Models/ParseTemplateContext.cs:

namespace Skywalker.Messaging.Models {
 public class ParseTemplateContext {
  public object Model { get; set; }
  public object ViewBag { get; set; }
 }
}

If these members strike you as eerily familiar, you're right: they are typically used in Razor views.
Both members are typed as object so they are generic enough to be used in all sorts of parsers.

That's it for the Driver for now. Next we will need to implement the
MessageTemplatePartHandler, the editor templates and place the shapes with Placement.info.

MessageTemplatePartHandler
MessageTemplatePartHandler is your typical ContentHandler responsible for adding a
StorageFilter to the Filters collection. We will also use it to setup the LazyField we defined in
MessageTemplatePart. To learn more about LazyFields, check out
http://www.skywalkersoftwaredevelopment.net/orchard-development/lazyfield-t
(http://www.skywalkersoftwaredevelopment.net/orchard-development/lazyfield-t).

Let's see how it looks:

Handlers/MessageTemplatePartHandler.cs:

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 16/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Environment.Extensions;
using Skywalker.Messaging.Models;
using Skywalker.Messaging.Services;
 
namespace Skywalker.Messaging.Handlers {
    [OrchardFeature("Skywalker.Messaging")]
    public class MessageTemplatePartHandler : ContentHandler {
        private readonly IMessageTemplateService _messageTemplateService;
 
        public MessageTemplatePartHandler(IRepository<MessageTemplatePartRecord> repository
, IMessageTemplateService messageTemplateService) {
            _messageTemplateService = messageTemplateService;
            Filters.Add(StorageFilter.For(repository));
            OnActivated<MessageTemplatePart>(PropertyHandlers);
        }
 
        private void PropertyHandlers(ActivatedContentContext context, MessageTemplatePart 
part) {
            part.LayoutField.Loader(x => part.Record.LayoutId != null ? _messageTemplateSer
vice.GetTemplate(part.Record.LayoutId.Value) : null);
            part.LayoutField.Setter(x => { part.Record.LayoutId = x != null ? x.Id : defaul
t(int?); return x; });
        }
    }
}

Were basically doing 2 things here:

1. We inject an IRepository of MessasgeTemplatePartRecord and construct a StorageFilter


for it which we add to Filters. A filter in this context is a sort of reusable piece of
ContentHandler. Orchard comes with 7 implementations of them out of the box, one of
them being the StorageFilter, which get invoked when content needs to be loaded and
saved.
2. We add a pointer to a function called PropertyHandlers to a method called OnActivated of
MessageTemplatePart. The OnActivated<TPart> method is invoked whenever a content
item is being instantiated whose type has TPart attached (MessageTemplatePart in our
case). This is the perfect place for us to do any part specific initialization, such as setting up
the LayoutField lazy field. The implementation is straighforward: the loader is setup to get
the template using the injected IMessageTemplateService, and the setter is setup to update

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 17/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

the LayoutId of the part's record.

MessageTemplate Editor Template


In this part we will use a very simple control to render the template editor: it's called a TextArea.
In the next part, we will turn this into a much better looking editor using CodeMirror and even
implement a Preview pane.

For now we will add the following markup to MessageTemplate.cshtml:

Views/EditorTemplates/Parts/MessageTemplate.cshtml:

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 18/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

@model Skywalker.Messaging.ViewModels.MessageTemplateViewModel
@{
    Style.Include("Common.css", "Common.min.css");
    Style.Include("TemplateEditor.css", "TemplateEditor.min.css");
}
<fieldset class="message‐template‐editor">
    <div class="editor‐field">
        @Html.LabelFor(m => m.Title, T("Title"))
        @Html.TextBoxFor(m => m.Title, new { required = "required", @class = "text large" }
)
        @Html.ValidationMessageFor(m => m.Title)
        <span class="hint">@T("The title of the message")</span>
    </div>
    <div class="editor‐field">
        @Html.LabelFor(m => m.Subject, T("Subject"))
        @Html.TextBoxFor(m => m.Subject, new { required = "required", @class = "text large 
tokenized" })
        @Html.ValidationMessageFor(m => m.Subject)
        <span class="hint">@T("The subject to use when sending a message")</span>
    </div>
    @if (Model.Layouts.Any()) {
        <div class="editor‐field layout‐selector‐wrapper">
            @Html.LabelFor(m => m.LayoutId, T("Layout"))
            @Html.EditorFor(m => m.LayoutId, new { Model.Layouts })
            <span class="hint">@T("Optionally select another template to use as the layout 
/ masterpage template")</span>
        </div>
    }
    <div class="editor‐field">
        @Html.LabelFor(m => m.Text, T("Template"))
        @Html.TextAreaFor(m => m.Text, new { @class = "template‐editor"})
        @Html.ValidationMessageFor(m => m.Text)
        <span class="hint">@T("The template of the message")</span>
    </div>
    <div class="editor‐field">
        <input type="checkbox" name="@Html.FieldNameFor(m => m.IsLayout)" id="@Html.FieldId
For(m => m.IsLayout)" value="True" @if(Model.IsLayout){<text>checked="checked"</text>} />
        <label for="@Html.FieldIdFor(m => m.IsLayout)" class="forcheckbox">@T("This is a La
yout")</label>
        <span class="hint">@T("Check this option to use it as the layout (a.k.a. Master Pag
e) for other templates.")</span>
    </div>
</fieldset>

Nothing fancy going on here, except maybe for this line:

@Html.EditorFor(m => m.LayoutId, new { Model.Layouts })

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 19/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

We're using the Html.EditorFor method to render an editor template for the LayoutId property,
and passing in a list of Layouts (which was build by our driver). If you recall, the
MessageTemplateViewModel class decorated the LayoutId property with a UIHintAttribute. The
EditorFor HTML helper will use the specified name as the template to render an editor for this
property. We'll create this template after we created the stylesheets: Common and
TemplateEditor. If you have the Web Essentials 2012 (http://vswebessentials.com/)
extension installed, you'll be able to use LESS (http://lesscss.org/):

Styles/Common.less:

.editor‐field {
    margin‐bottom: 1em;    
}

Styles/TemplateEditor.less:

.template‐editor {
    height: 400px;
}

If you're unfamiliar with LESS and the Web Essentials 2012 extension: LESS is basically a higher
level language on top of CSS, which allows nicer syntax, variables, mixins, nesting and much
more. Right now we aren't using any of that, but we will use some LESS constructs in later parts.

The Web Essentials 2012 extension provides us with syntax coloring, intellisense and generates
both a a css and a minified css file for us, which is what we will be actually including with the
output.

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 20/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

As you will have noticed, we are using the overloaded version of Style.Include that takes two
arguments: an "debug" version of a stylesheet and a "release" version of it. That means that when
we set debug="false" in web.config, our application will be compiled in release mode, which will
cause Orchard to automatically include the minified stylesheet. Nice!

TemplateLayoutPicker
We decorated the LayoutId property of the MessageTemplateViewModel with a UIHintAttribute:

[UIHint("TemplateLayoutPicker")]
public int? LayoutId { get; set; }

This enables us to customize the editor template being used for that specific property. In our
case, we want to render a list of available Layout Templates. We already prepared that list from
the driver, so all we have to do now is implement the TemplateLayoutPicker editor template.
Just to be clear, this is not an Orchard specific editor template; it's just a regular MVC editor
template, being leveraged by the Html.EditorFor helper method in MessageTemplate.cshtml.

Views/EditorTemplates/TemplateLayoutPicker.cshtml:

@using System.Linq
@using Skywalker.Messaging.Models
@{
    var layouts = ((IEnumerable<MessageTemplatePart>)ViewBag.Layouts).ToList();
    var currentValue = ViewData.TemplateInfo.FormattedModelValue as int?;
    var options = layouts.Select(x => new SelectListItem { Text = x.Title, Value = x.Id.ToS
tring(), Selected = x.Id == currentValue });
}
@Html.DropDownList("", options, "")

The template copies the Layouts passed via the ViewBag into a local variable and projects them
to a list of SelectListItem objects which in turn are used by the Html.DropDownList helper
method. All basic MVC stuff.

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 21/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

Placement
Finally, we need to create a Placement.info file to specify where our editor shape is to be
rendered.

Placement.info:

<Placement>
  <Place Parts_MessageTemplate_Edit="Content:0" />
</Placement>

That's it for coding! Let's see what we've got so far.

Enabling the module

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 22/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

Creating a Master Template


Enabling the module will create a new content type called Email Template.

Let's create a Master Template (by checking the "This is a Layout" option) and a template that
references this master:

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 23/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

Creating an Email Template that references a


Master Layout
http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 24/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

Summary

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 25/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

In this part, all we really did is setup a migration that creates a table, defines a part and a type,
implemented a driver, a handler, a service and some templates. Basic stuff, for sure, but this
provides us with a solid groundwork for the next parts.

In the next part, we will actually implement the parsing logic and a preview pane and enhance
the code editor with CodeMirror!

Download the source code: Skywalker.Messaging.Part1.zip


(/media/default/tutorials/messaging/Skywalker.Messaging.Part1.zip).

Read Part 2 (/blog/messaging-channels-rules-and-tokens-part-2)

 11/18/2014 09:31 PM by Sipke Schoorstra (/blog/author/sipke)

1 comment
giannis 11/07/2014 07:42 AM

another great article . Thanks sipke

Leave a comment
Name:

Enter your name

Email address:

Enter your email address

Not displayed on the site, used to obtain Gravatar image.

URL:

Enter your website URL


http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 26/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1
Enter your website URL

Comment:

How to Format

Type the text
Privacy & Terms
(http://www.google.com/intl/en/policies/)

Submit

 Subscribe to IDeliverable Blog (RSS)


(/rss?containerid=32)

 Subscribe (email) (/subscribe)

Topics
autofac (1) (/Tags/autofac) azure (2) (/Tags/azure) cloud services (2) (/Tags/cloud%20services)

codemirror (1) (/Tags/codemirror) globalization (1) (/Tags/globalization) jquery (1) (/Tags/jquery)

knockoutjs (1) (/Tags/knockoutjs) linqjs (1) (/Tags/linqjs) orchard (33) (/Tags/orchard)

orchard harvest (1) (/Tags/orchard%20harvest) performance (2) (/Tags/performance)

powershell (2) (/Tags/powershell) ssl (1) (/Tags/ssl) startup tasks (2) (/Tags/startup%20tasks)

webapi (1) (/Tags/webapi)

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 27/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

Authors
Daniel Stolt (/blog/author/daniel)
Daniel is the go-to guy here at IDeliverable for all things Azure. He
blogs about his experiences developing for the cloud.

(/blog/author/daniel)

Sipke Schoorstra (/blog/author/sipke)


Sipke is the resident Orchard CMS specialist here at IDeliverable. He
blogs about site building and module development.

(/blog/author/sipke)

Archive
2014 2013 2012
November (6) December (2) October (1)
(/blog/archive/2014/11) (/blog/archive/2013/12) (/blog/archive/2012/10)
September (3) June (5) September (3)
(/blog/archive/2014/9) (/blog/archive/2013/6) (/blog/archive/2012/9)
March (9) August (1)
(/blog/archive/2013/3) (/blog/archive/2012/8)
January (1) April (7)
(/blog/archive/2013/1) (/blog/archive/2012/4)
February (4)
(/blog/archive/2012/2)

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 28/29
4/10/2015 IDeliverable ­ Messaging, Channels, Rules and Tokens ­ Part 1

January (18)
(/blog/archive/2012/1)

Postal address:
IDeliverable, Ltd.
PO Box 58341
3733 Limassol
CYPRUS

Visiting address:
IDeliverable, Ltd.
Sotiri Tofini 4, 2nd floor
Agios Athanasios
4102 Limassol
CYPRUS

VAT number: CY10318155U


TIC number: 12318155W
info@ideliverable.com (mailto:info@ideliverable.com)
http://www.ideliverable.com (http://www.ideliverable.com)

All information on this website copyright © 2015 by IDeliverable, Ltd.

http://www.ideliverable.com/blog/messaging­channels­rules­and­tokens­part­1 29/29

You might also like