Professional Documents
Culture Documents
Messaging, Channels, Rules and Tokens Part 1
leverages not only basic Orchard stuff, but also more advanced
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/messagingchannelsrulesandtokenspart1 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:
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.
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/messagingchannelsrulesandtokenspart1 2/29
4/10/2015 IDeliverable Messaging, Channels, Rules and Tokens Part 1
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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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.
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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 9/29
4/10/2015 IDeliverable Messaging, Channels, Rules and Tokens Part 1
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.
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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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.
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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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).
Handlers/MessageTemplatePartHandler.cs:
http://www.ideliverable.com/blog/messagingchannelsrulesandtokenspart1 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; });
}
}
}
http://www.ideliverable.com/blog/messagingchannelsrulesandtokenspart1 17/29
4/10/2015 IDeliverable Messaging, Channels, Rules and Tokens Part 1
Views/EditorTemplates/Parts/MessageTemplate.cshtml:
http://www.ideliverable.com/blog/messagingchannelsrulesandtokenspart1 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>
@Html.EditorFor(m => m.LayoutId, new { Model.Layouts })
http://www.ideliverable.com/blog/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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/messagingchannelsrulesandtokenspart1 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>
http://www.ideliverable.com/blog/messagingchannelsrulesandtokenspart1 22/29
4/10/2015 IDeliverable Messaging, Channels, Rules and Tokens Part 1
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/messagingchannelsrulesandtokenspart1 23/29
4/10/2015 IDeliverable Messaging, Channels, Rules and Tokens Part 1
Summary
http://www.ideliverable.com/blog/messagingchannelsrulesandtokenspart1 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!
1 comment
giannis 11/07/2014 07:42 AM
Leave a comment
Name:
Email address:
URL:
Comment:
How to Format
Type the text
Privacy & Terms
(http://www.google.com/intl/en/policies/)
Submit
Topics
autofac (1) (/Tags/autofac) azure (2) (/Tags/azure) cloud services (2) (/Tags/cloud%20services)
powershell (2) (/Tags/powershell) ssl (1) (/Tags/ssl) startup tasks (2) (/Tags/startup%20tasks)
http://www.ideliverable.com/blog/messagingchannelsrulesandtokenspart1 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)
(/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/messagingchannelsrulesandtokenspart1 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
http://www.ideliverable.com/blog/messagingchannelsrulesandtokenspart1 29/29