Contents
Introduction
This article is about extending Orchard CMS. Orchard is a Content Management System that is built upon ASP.NET MVC and it's Open Source, community driven.
Orchard websites are composed using different types of content, where every content type can have one or more content parts or fields. There are predefined content types, like 'page', 'blog', 'comment', 'widget' etc. For instance, a 'blog post' is composed of multiple parts.
You can also define custom content types within Orchard using the available parts and fields, without any coding. All the features are housed inside modules and with that, you can do anything.
With Orchard, you can create content types that represent real-life objects, 'car', 'flower', 'stamp', 'dvd', etc. and show that (as collections) on your website.
This article shows how to create a module to extend existing content of an Orchard website. First a little background on which main problem Orchard solves for me. Then we start building a part.
Background
In the past, when I was working with content management systems (Which-Shall-Not-Be-Named), I used modules/features that came with the CMS and also modules/extensions created by others. And created modules myself of course. Mostly, these modules worked fine by them selves. Each module having it's own code, database table(s), scripts, stylesheets, etc. whatever was needed for the module.
But, there was no collaboration between modules of different companies/developers. As a simple example; suppose you use a module that enables you to have a list of products on your website. Each product having a title, description, picture, price, etc. The products are nicely shown and the user adds more products to it. Nice.
Now later (the amount of products grew) the site owner wants the products to be categorized. What do you do? If you're the author of the products module, you could create a new version that supports categories. If you're not the author, you could ask the developer to make a new version. What else can you do. Wouldn't it be nice to leave the product module untouched and use a 'category module' to categorize the products? Then you don't have to alter existing code. What if the website owner wants to see something not so related to the products, for each product? Are you going to stuff that up in your product module?
Requirements change, always. With Orchard, you can 'weld' your additions to existing content. Of course, you can also grind parts off again. All this, without modifying existing (other ones) code!
Let's make a Module
There are a lot of modules and themes already created and are available in the gallery and can be used for your Orchard website. Sure, there can be modules that are not completely fulfilling your wishes. You could ask the module owner to make a new version or you can contribute code to that project. But anyone is free to make what he/she wants, so let's start building!
The module being made here contains a feature that enables you to weld an external photo album to your content type. In this case, a Picasa web photo album. To access a Picasa web album, we need a reference (id) to the user, the album and optional, an authorization key. You can get the id's from the album url (via RSS link or Google+ link). The authorization key is used for non-public albums, so called 'anyone with the link' protection.
Architecture
Now how do we build a module. It should be clear that this is an MVC pattern, of course. Because every module is implemented as an ASP.NET MVC area. But Orchard poors a little bit more juice to it, to enable a lot more functionality. Basically, the ContentPart is the model, the driver is partly a (simplified) controller, and the View receives the stuff to display through a shape, whether it's the ContentPart or something composed by the driver.
Setup development environment
Just to show (again and again) how easy it is to setup your development environment.
Start downloading code
The Orchard code is on CodePlex and you can download the latest release here.
Go for the ´Orchard.Source.x.x.x.zip´ file. Currently 1.5.1 is the latest.
Load it up in Visual Studio
Unzip the file to a local drive and double click the .sln file, assuming you have Visual Studio installed.
Build and run, setup website
Hit Control-F5 and you'll see the 'Get Started' page. Give your site a name and make up some credentials. Remember them of course.
Setup the database. You can use SQL Server Compact but for speed use SQL Server.
New up the database inside SQL Server Management Studio.
Then you will need a connection string. Since I have a history screwing this up, here is the connection string based on my local SQL Server database called 'OrchardModuleDev'.
Data Source=localhost;Initial Catalog=OrchardModuleDev;Persist Security Info=True;Integrated Security=SSPI;Trusted_Connection=yes;
If all goes well, you'll end up with your new, locally running website, using the default theme.
Create the module
Add a new project
Add new Class library project for the module to the solution.
Give your Module a nice project name. Make sure that the location of your project is the 'Modules' folder.
Remove the Class1.cs file. You'll end up with this:
Add Module.txt
Name: External Album
AntiForgery: enabled
Author: Geert Doornbos
Website: http:Version: 1.0
OrchardVersion: 1.5
Description: Show an external photo album on your website
Category: Media
Dependencies: Orchard.jQuery
As I know up front I will need the jQuery module, I put that as our single module dependency.
Add web.config (copy that from the source)
Now we have an empty module. Go to your site and look at the installed modules at /admin/modules. Type 'external' in the search box and hit search. The module shows up as 'installed'. Orchard recognized the module just by placing a file called module.txt.
Enable module
Now the module shows installed but it´s not yet enabled. Go to /admin/modules/features
type 'external' in the filter box and enable it.
Add code
Add folders
MVC - Add these folders to your project: Models, ViewModels, Views, Drivers, Handlers, Scripts, Styles
Some folders have meaning (conventions), some folders are for neatly storing files.
Add references
Add project references to Orchard.Core and Orchard.Framework.
Add ContentPart class
Add a class derived from ContentPartRecord to the Models folder. Fill it up with virtual properties necessary to access a Picasa album.
using Orchard.ContentManagement.Records;
namespace Orchard.ExternalAlbum.Models
{
public class PicasaAlbumPartRecord : ContentPartRecord
{
public virtual string UserID { get; set; }
public virtual string AlbumID { get; set; }
public virtual string AuthKey { get; set; }
}
}
Add a class derived from ContentPart to the Models folder. Add properties and link them to the record.
using Orchard.ContentManagement;
using Orchard.ExternalAlbum.Models;
namespace Orchard.ExternalAlbum.Models
{
public class PicasaAlbumPart : ContentPart<PicasaAlbumPartRecord>
{
public string UserID
{
get { return Record.UserID; }
set { Record.UserID = value; }
}
public string AlbumID
{
get { return Record.AlbumID; }
set { Record.AlbumID = value; }
}
public string AuthKey
{
get { return Record.AuthKey; }
set { Record.AuthKey = value; }
}
}
}
Add a migrations class
Add a migrations class to specify the database table and columns. Also define the content part and make it attachable.
using Orchard.Data.Migration;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;
namespace Orchard.ExternalAlbum
{
public class Migrations : DataMigrationImpl
{
public int Create()
{
SchemaBuilder.CreateTable("PicasaAlbumPartRecord", table => table
.ContentPartRecord()
.Column<string>("UserID", column => column.WithLength(255))
.Column<string>("AlbumID", column => column.WithLength(255))
.Column<string>("AuthKey", column => column.WithLength(255))
);
ContentDefinitionManager.AlterPartDefinition("PicasaAlbumPart", part => part
.Attachable());
return 1;
}
}
}
Build the project and our part will now be visible /Admin/ContentTypes/ListParts
Add a driver class
Add a driver class for the content part. By overriding Editor and Display, the driver determines what to do when the album is edited or being displayed.
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.ExternalAlbum.Models;
namespace Orchard.ExternalAlbum.Drivers
{
public class PicasaAlbumPartDriver : ContentPartDriver<PicasaAlbumPart>
{
protected override string Prefix
{
get { return "PicasaAlbum"; }
}
protected override DriverResult Editor(PicasaAlbumPart part, dynamic shapeHelper)
{
return ContentShape("Parts_PicasaAlbum_Edit", () => shapeHelper
.EditorTemplate(TemplateName: "Parts/PicasaAlbum", Model: part, Prefix: Prefix));
}
protected override DriverResult Editor(PicasaAlbumPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
protected override DriverResult Display(PicasaAlbumPart part, string displayType, dynamic shapeHelper)
{
return ContentShape("Parts_PicasaAlbum",
() => shapeHelper.Parts_PicasaAlbum(
UserID: part.UserID,
AlbumID: part.AlbumID,
AuthKey: part.AuthKey
));
}
}
}
Add the editor template
Add visual editor for the part called PicasaAlbum.cshtml and place it in /Views/EditorTemplates/Parts. That's what the driver specified, so that must match.
@using System.Web.Mvc.Html
@model Orchard.ExternalAlbum.Models.PicasaAlbumPart
<fieldset>
<legend>@T("Picasa Album Fields")</legend>
<div class="editor-field">
@Html.LabelFor(x => x.UserID, T("User ID"))
@Html.EditorFor(x => x.UserID)
@Html.ValidationMessageFor(x => x.UserID)
</div>
<div class="hint">@T("Enter the Picasa User ID")</div>
<div class="editor-field">
@Html.LabelFor(x => x.AlbumID, T("Album ID"))
@Html.EditorFor(x => x.AlbumID)
@Html.ValidationMessageFor(x => x.AlbumID)
</div>
<div class="hint">@T("Enter the Picasa Album ID")</div>
<div class="editor-field">
@Html.LabelFor(x => x.AuthKey, T("Authorization Key"))
@Html.EditorFor(x => x.AuthKey)
@Html.ValidationMessageFor(x => x.AuthKey)
</div>
<div class="hint">@T("If the album is shared using a key, enter the Picasa Authorization Key (including trailing #)")</div>
</fieldset>
Add references to:
System.Web
System.Web.Mvc
System.Web.WebPages
At this stage, you could create a custom content type, hook up the PicasaAlbumPart, create a content item of that and edit the fields. Since that would be a newly created object, there has not been any interaction to the storage layer for the album part. We need to let Orchard know that we want to persist the part. This is done through a handler.
Add a handler class
Add a handler class for the content part (for persistence)
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.ExternalAlbum.Models;
namespace Orchard.ExternalAlbum.Handlers
{
public class PicasaAlbumPartHandler : ContentHandler
{
public PicasaAlbumPartHandler(IRepository<PicasaAlbumPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
}
Add display template
Add visual display for the part called PicasaAlbum.cshtml and place it in /Views/Parts.
@{
Script.Require("JQueryPicasa").AtHead();
Style.Require("JQueryPicasa");
Script.Require("prettyPhoto").AtHead();
Style.Require("prettyPhoto");
string albumId = DateTime.Now.Ticks.ToString();
}
<article id="@albumId">
</article>
@using(Script.Foot()) {
<script type="text/javascript">
$(function() {
var gallery = $("#@albumId");
var userID = '@Model.UserID';
var albumID = '@Model.AlbumID';
var authKey = '@Model.AuthKey';
gallery.picasaGallery(userID, albumID, authKey, function (images) {
var animation, picasaAlbum;
picasaAlbum = "<ul class='picasa-album'>\n";
$.each(images, function(i, element) {
picasaAlbum += " <li class='picasa-image'>\n";
picasaAlbum += " <a class='picasa-image-large' rel='prettyPhoto[picasa]' href='" + element.url + "'>\n";
picasaAlbum += " <img class='picasa-image-thumb' src='" + element.thumbs[0].url + "'/>\n";
picasaAlbum += " </a>\n";
return picasaAlbum += " </li>\n";
});
picasaAlbum += "</ul>";
gallery.append(picasaAlbum);
animation = function() {
$(".picasa-image").each(function(index, element) {
return $(element).css("visibility", "visible").css("opacity", 0).delay(50 * index).animate({
opacity: 1}, 300);
});
return $(".picasa-album a", gallery).prettyPhoto({
slideshow: 5000,
autoplay_slideshow: false,
social_tools: ''
});
};
return setTimeout(animation, 300);
});
});
</script>
}
As you can see, the file starts with adding stylesheets and javascript files. If you need to see more of that, download te code.
Now, you might think "but I want to visualize the album different way". That's possible in your theme. If adding css to your theme is not enough, you can override the view by adding an alternate.
Take it for a spin
First we're setting up a situation without the Picasa album. Then we add the part, fill it in and show you the end result.
Setup a demo scenario
Suppose you have a DVD collection and want to show/sell them on your website. In Orchard you can create a content type for that. To keep it simple, we create a DVD type and add a title, some text and a price. For the demo, we create the content type manually, but keep in mind that it can also be a module developed by someone else.
The CommonPart is added automatically to each content type.
Go to /admin and create a new type called DVD.
The type has been created, choose the parts you want to add. We choose a title and a body and hit save.
We're adding the price as a field.
Click on 'Add Field' and choose the field type.
After saving that, open up the 'price' field and set some properties and hit save again.
We're done specifying our content type. Let's add a DVD. On the left side, under 'New', click 'DVD' and fill it in. You can then save it and publish it later, or hit 'Publish Now' to do it directly.
On the left side, choose 'Content' and your DVD will show up. Choose 'View' to take a look.
This is how it looks with the default theme.
Add the album part
Now we have a situation with existing content. We're going to add the Picasa album now, so that every DVD on the site can have a Picasa album attached.
Go to the 'Content Type' page and hit 'edit' for the DVD type.
Add the PicasaAlbum part and hit save.
Go back to 'Contents' and choose 'edit' for the DVD. Fill in the picasa properties for this album:
https://picasaweb.google.com/data/feed/base/user/111570895459168143940/albumid/5511777957142111713?alt=rss&kind=photo&hl=nl
and hit 'Publish Now'.
Go to the 'Contents' page again and see the end result with 'View'.
End result
Now we see our DVD with a Picasa album below.
You can click a photo to bring up a larger one and navigate through the album.
The module is available for use in the gallery. You can download it manually or install it directly from within your Orchard website at /Packaging/Gallery/Modules.
It's a NuGet package, so just zip file containing the code.
Conclusion
While reading this article, you might have noticed that Orchard can already do a lot by it self. That's true, this article shows only some basics, just to make my point. And of course it shows you how to extend it with your own parts. Take a look at the gallery too, to see the available modules.
The nice thing is, that we can 'weld' the picasa album to any content type now. Think about that for a while. What if you were building and maintaining a website like that. That's one big reason to go for Orchard!
What do you think? Express your opinion by a vote or comment. Any feedback is welcome.
Bonus
I went ahead and added something nice to the code. With a few lines of code, you can make any content part a widget. So you don´t have to attach the album part to something. Now you can place a Picasa album anywhere on your website as a widget.
What's next
I'm planning to add support for importing & exporting, so your album information is not lost when moving content to another website.
Support for 'Projections' is just an idea, don't know if it can be useful. With that you can make a view (a projection) of all content types that have the album part attached and make an album overview/index.
I will give this project to the community by putting it on codeplex. Anyone can make contributions to the module there.
There are other web albums available, for example a SkyDrive part could be added to the project. Vote for it
History
- Version 1 - Initial version