In this tutorial, you will learn how to develop a simple commerce module using only a text editor.

If you do not have the Web Platform Installer on your computer, download it before beginning this tutorial.

This guide has been marked for review. If you are just getting started with Orchard module development you should read the Getting Started with Modules course first. It will introduce you to building modules with Orchard using Visual Studio Community, a free edition of Visual Studio.

Setting Up the Orchard Site

First, you will set up a new Orchard website. If you already have a site set up, you can skip this section and jump directly to the code generation section. To start the setup, open IIS Manager, right-click Sites, and click Add Web Site.

Figure 1. Creating the Orchard site

In the Add Web Site dialog box, fill in the fields to point the new website to a folder, such as \inetpub\wwwroot\Orchard. Name the new site Orchard and give it an unused port, such as 90. Use the default application pool (.NET 4.0 integrated pipeline). Click OK.

Figure 2. Configuring the site

From the Windows Start menu, launch Web Platform Installer, select Orchard CMS, click Add, and then click Install.

After you accept the license terms, Orchard is installed.

Open a command window and change the current directory to point to the root of the site. Then run bin\orchard.exe.

Figure 6. The Orchard command line

Type help commands to get the list of available commands. For now, only the help and setup commands are available. However, as Orchard is developed and modules are activated, new commands will become available. (The Orchard command-line executable actually discovers the commands from the modules inside of the application.)

To set up the site, enter the following command:

setup /SiteName:Orchard /AdminUsername:admin /AdminPassword:123456
      /DatabaseProvider:SqlCe

This is equivalent to setting up the site from the web interface.

Leave the command window open. (In fact, don't close it until you have finished this tutorial.)

Generating Code for the Module

Now you are ready to start developing the commerce module. Orchard provides a code generation feature that sets up the structure of an empty module to help you get started. By default, code generation is disabled. So you must first install and enable the feature. The easiest way to do this is to go to Modules in the admin UI and then click the "Gallery" tab. Do a search for "code generation" and then install the module.

To enable code generation, if you didn't do so right after install, you may enter the following command in the command window:

feature enable Orchard.CodeGeneration

You will use a code-generation command to create a commerce module. Enter the following command:

codegen module SimpleCommerce

Open a Windows Explorer window and browse to the newly created \inetpub\wwwroot\Orchard\Modules\SimpleCommerce folder. Open the Module.txt file using a text editor.

Change the description to "A simple commerce module". Change the description of the feature to be "A simple product part". Save the file and close it. The following example shows the complete Module.txt file after the changes.

Name: SimpleCommerce
AntiForgery: enabled
Author: The Orchard Team
Website: http://orchardproject.net
Version: 0.5.0
OrchardVersion: 0.5.0
Description: A simple commerce module
Features:
    SimpleCommerce:
        Name: Simple Commerce
        Description: A simple product part.
        Category: Commerce

Creating the Model for the Part

Next, you will create a data model that is a representation of what will be stored in the database.

In Modules/SimpleCommerce/Models, create a Product.cs file and add the following content:

using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;

namespace SimpleCommerce.Models {
  public class ProductPartRecord : ContentPartRecord {
    public virtual string Sku { get; set; }
    public virtual float Price { get; set; }
  }

  public class ProductPart : ContentPart<ProductPartRecord> {
    [Required]
    public string Sku {
      get { return Retrieve(r => r.Sku); }
      set { Store(r => r.Sku, value); }
    }

    [Required]
    public float Price {
      get { return Retrieve(r => r.Price); }
      set { Store(r => r.Price, value); }
    }
  }
}

This code has two properties, Sku and Price, that are virtual in order to enable the creation of a dynamic proxy that will handle persistence transparently.

The code also defines a content part that derives from ContentPart<ProductPartRecord> and that exposes the SKU and price from the record as public properties and infoset. You can find more info about infoset here. The properties have attributes that will surface in the UI as validation tests.

In order for the application to pick up the new file, you need to add it to the module's project file. Open the SimpleCommerce.csproj file and look for "assemblyinfo.cs". After that line, add the following:

<Compile Include="Models\Product.cs" />

Save the file, but leave it open, because you will make additional changes to it throughout the tutorial.

Navigate to the site in your browser to make sure the application's dynamic compilation feature picks up the new part and record. You will know that everything is working if you go to the Features administration screen and see the new SimpleCommerce feature.

In the command window, enable the new feature using the following command:

feature enable SimpleCommerce

Creating the Initial Data Migration File

Data migration is a pattern that enables an application or component to handle new versions gracefully, without any data loss. The main idea is that the system keeps track of the current version installed and each data migration describes the changes to go from one version to the next. If the system detects that there is a new version installed and the current data is from a previous version, the administrator of the site is prompted to upgrade. The system then runs all necessary migration methods until the data version and the code version are in sync.

Start by creating the initial migration for the new module, which will just create the data tables that are needed. In the command window, enter the following command:

codegen datamigration SimpleCommerce

This creates the following Migrations.cs file:

using System;
using System.Collections.Generic;
using System.Data;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;

namespace SimpleCommerce.DataMigrations {
    public class Migrations : DataMigrationImpl {

        public int Create() {
            // Creating table ProductPartRecord
            SchemaBuilder.CreateTable("ProductPartRecord", table => table
                .ContentPartRecord()
                .Column("Sku", DbType.String)
                .Column("Price", DbType.Single)
            );



            return 1;
        }
    }
}

The method name Create is the convention for the initial data migration. It calls the SchemaBuilder.CreateTable method that creates a ProductPartRecord table that has Sku and Price columns in addition to the columns from the basic ContentPartRecord table.

Notice that the method returns 1, which is the version number for the migration.

Add another migration step to this in order to illustrate how you can later alter the existing schema and type metadata as the module evolves. In this case, you will take this opportunity to add a feature that will enable the part to be attached to any content type. Add the following method to the data migration class:

public int UpdateFrom1() {
  ContentDefinitionManager.AlterPartDefinition("ProductPart",
    builder => builder.Attachable());
  return 2;
}

This new migration is named UpdateFrom1, which is the convention for upgrading from version 1. Your next migration should be called UpdateFrom2 and return 3, and so on.

Make sure the following line is present in the .csproj file. (It should already have been added by the code generation command.)

<Compile Include="Migrations.cs" />

Navigate to the Features screen in the dashboard. You see a warning that indicates that one of the features needs to be updated, and the Simple Commerce module is displayed in red. Click Update to ensure that the migrations are run and that the module is up to date.

Adding a Handler

A handler in Orchard is analogous to a filter in ASP.NET MVC. It's a piece of code that is meant to run when specific events happen in the application, but that are not specific to a given content type. For example, you could build an analytics module that listens to the Loaded event in order to log usage statistics. To see what event handlers you can override in your own handlers, examine the source code for ContentHandlerBase.

The handler you need in the module is not going to be very complex, but it will implement some plumbing that is necessary to set up the persistence of the part. We hope that this kind of plumbing will disappear in a future version of Orchard, possibly in favor of a more declarative approach such as using attributes.

Create a Handlers folder and add a ProductHandler.cs file to it that contains the following code:

using Orchard.ContentManagement.Handlers;
using SimpleCommerce.Models;
using Orchard.Data;

namespace SimpleCommerce.Handlers {
  public class ProductHandler : ContentHandler {
    public ProductHandler(IRepository<ProductPartRecord> repository) {
      Filters.Add(StorageFilter.For(repository));
    }
  }
}

Add the file to the .csproj file so that dynamic compilation can pick it up, using the following line:

<Compile Include="Handlers\ProductHandler.cs" />

Adding a Driver

A driver in Orchard is analogous to a controller in ASP.NET MVC, but is well adapted to the composition aspect that is necessary in web content management systems. It is specialized for a specific content part and can specify custom behavior for well-known actions such as displaying an item in the front end or editing it in the administration UI.

A driver typically has overrides for the display and editor actions. For the product part, create a new Drivers folder and in that folder create a ProductDriver.cs file that contains the following code:

using SimpleCommerce.Models;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement;

namespace SimpleCommerce.Drivers {
  public class ProductDriver : ContentPartDriver<ProductPart> {
    protected override DriverResult Display(
        ProductPart part, string displayType, dynamic shapeHelper)
    {
      return ContentShape("Parts_Product",
          () => shapeHelper.Parts_Product(
              Sku: part.Sku,
              Price: part.Price));
    }

    //GET
    protected override DriverResult Editor(ProductPart part, dynamic shapeHelper)
    {
      return ContentShape("Parts_Product_Edit",
          () => shapeHelper.EditorTemplate(
              TemplateName: "Parts/Product",
              Model: part,
              Prefix: Prefix));
    }

    //POST
    protected override DriverResult Editor(
        ProductPart part, IUpdateModel updater, dynamic shapeHelper)
    {
      updater.TryUpdateModel(part, Prefix, null, null);
      return Editor(part, shapeHelper);
    }
  }
}

The code in the Display method creates a shape to use when rendering the item in the front end. That shape has Sku and Price properties copied from the part.

Update the .csproj file to include the following line:

<Compile Include="Drivers\ProductDriver.cs" />

The Editor method also creates a shape named EditorTemplate. The shape has a TemplateName property that instructs Orchard where to look for the rendering template. The code also specifies that the model for that template will be the part, not the shape (which would be the default).

The placement of those parts within the larger front end or dashboard must be specified using a Placement.info file that is located at the root of the module. That file, like a view, can be overridden from a theme. Create the Placement.info file with the following contents:

<Placement>
    <Place Parts_Product_Edit="Content:3"/>
    <Place Parts_Product="Content:3"/>
</Placement>

Add the Placement.info file to the .csproj file using the following line:

<Content Include="Placement.info" />

Building the Templates

The last thing to do in order for the new content part to work is to write the two templates (front end and admin) that are configured in the driver.

Create the front-end template first. Create a Parts folder under Views and add a Product.cshtml file that contains the following code:

<br/>
@T("Price"): <b>$@Model.Price</b><br />
@Model.Sku<br/>

This is very plain rendering of the shape. Notice the use of the T method call to wrap the "Price" string literal. This enables localization of that text.

The administration view is a little heavier on HTML helper calls. Create an EditorTemplates folder under Views and a Parts folder under that. Add a Product.cshtml to the Parts folder that contains the following code:

@model SimpleCommerce.Models.ProductPart
<fieldset>
    <label class="sub" for="Sku">@T("Sku")</label><br />
    @Html.TextBoxFor(m => m.Sku, new { @class = "text" })<br />
    <label class="sub" for="Price">@T("Price")</label><br />
    @Html.TextBoxFor(m => m.Price, new { @class = "text" })
</fieldset>

Add those two templates to the .csproj file using the following lines:

<Content Include="Views\Parts\Product.cshtml" />
<Content Include="Views\EditorTemplates\Parts\Product.cshtml" />

Putting it All Together into a Content Type

The content part that you've put together could already be composed from the administration UI into a content type (see Creating Custom Content Types), but per the goal of this topic, you will continue by writing code using a text editor.

You will now build a new Product content type that will include the Product part and a number of parts that you can get from Orchard. So far, you have been focused on your specific domain. This will now change and you will start integrating into Orchard.

To build the content type from a new migration, open the Migrations.cs file and add the following method to the class:

public int UpdateFrom2() {
  ContentDefinitionManager.AlterTypeDefinition("Product", cfg => cfg
    .WithPart("CommonPart")
    .WithPart("RoutePart")
    .WithPart("BodyPart")
    .WithPart("ProductPart")
    .WithPart("CommentsPart")
    .WithPart("TagsPart")
    .WithPart("LocalizationPart")
    .Creatable()
    .Indexed());
  return 3;
}

Also add using Orchard.Indexing; to the top of the file.

What you are doing is creating (or updating) the Product content type and adding to it the ability to have its own URL and title (RoutePart), to have a rich text description (BodyPart), to be a product, to be commented on (CommentsPart), to be tagged (TagsPart) and to be localizable (LocalizationPart). It can also be created, which will add a Create Product menu entry, and it will also enter the search index (Indexed).

To enable your new module, open the Orchard dashboard and click Modules. Select the Features tab, find the SimpleCommerce module, and click Enable.

To add a new Product content type, click Content on the dashboard, select the Content Types tab, find Product, and click Create New Product.

You now have a product editor that features your Sku and Price fields.

The code for this module can be downloaded from the following page: Orchard.Module.SimpleCommerce.0.5.0.zip