Writing an Orchard Webshop Module from scratch - Part 4

atible for Orchard >= 1.4.

This is part 4 of a tutorial on writing a new Orchard module from scratch.
For an overview of the tutorial, please see the introduction.

In Orchard, content items are really nothing more than a collection of Content Parts. Take for example the Page content type: It's really just a content type definition named "Page" and a set of parts attached to it, such as BodyPart, CommonPart, TitlePart and AutoroutePart.

For our Webshop module, we want the user to be able to turn every conten type into a product. The way we can do this is by creating a custom content part ourselves. We'll call it ProductPart. When we attach a ProductPart to a content type, that content type will become a product. Let's define exactly what it means to be a product in our module.

  • A product has a unit price and a sku number.
  • The user can add a product to a shopping cart.
  • When the user checks out, an order is created that holds details of the purchased products.


In this tutorial, we will define a Book content type and a DVD content type as example content types, and attach the ProductPart to them. This will effectively turn the book type and the dvd type into a product.


Content Parts

If you're completely new to Orchard, you may be wondering what a content part is exactly.

Well, the short version: A content part is a reusable entity that can contain data and logic and can be attached to any content type.
The long version: A content part is defined as a record in the database and has a name and optionally a list of content fields (the fields are stored in another table, and there is a linking table that connects content fields with a content part). In addition, we can create a class that matches this name and derives from ContentPart or ContentPart<T> if you have a entity class that you wish to associate with your content part.
So even though a content part is just an entity loaded from a database record, it is possible to provide behavior for that content part by creating a class. When Orchard loads a content type, it asks the database which parts are attached to it. For each part, it checks if there is a class that derives from ContentPart and matches the name.
It then welds that part on to the content type. Welding is the process of adding an instance of the content part class to a list of parts managed by the content type instance. If no matching content part class was found, Orchard simply instantiates a ContentPart and adds tit to the list of parts.

The strength of this design is that content parts can now contain logic instead of just data.


The ProductPart

Our ProductPart will introduce 2 properties of its own and will be derived from ContentPart<T>.
The 2 properties are: UnitPrice (float) and Sku (string).

Because our ProductPart needs to be able to store data into the database (UnitPrice and Sku), we need to create a data entity as well. Orchard will map classes that end with the word "Record" and live in the Models namespace. So in our case, we will create a class called ProductPartRecord in a folder called Models. Because our data entity will store information for a content part, we will also derive it from ContentPartRecord. This is required so that Orchard can link our ProductPartRecord data to a content item of which it is a part of. This linking is done by giving each part the same ID as the ID of the content item it is attached to.

FYI, Orchard uses NHibernate as its ORM.

Let's create the ProductPart and ProductPartRecord classes:

  1. Reference the Orchard.Framework project in order to be able to inherit from Orchard.ContentManagement.Records.ContentPartRecord.
  2. Add a new Folder named Models
  3. Create a new public class named ProductPartRecord inside the "Models" folder and make it inherit from ContentPartRecord.
  4. Create a new public class named ProductPart inside the "Models" folder which inherits from ContentPart<ProductPartRecord>
  5. The ProductPartRecord will have the following properties: Price (decimal) and Sku (string). Important: the properties need to be virtual.
  6. The ProductPart class will have the same properties as ProductPartRecord, implemented as wrappers (see code below)

ProductPart.cs:

using Orchard.ContentManagement;
 
namespace Skywalker.Webshop.Models
{
    public class ProductPart : ContentPart<ProductPartRecord>
    {
        public decimal UnitPrice
        {
            get { return Record.UnitPrice; }
            set { Record.UnitPrice = value; }
        }
 
        public string Sku
        {
            get { return Record.Sku; }
            set { Record.Sku = value; }
        }
    }
}


Although the wrapping properties of ProductPart is not required, it makes it easy to use for users of the class. If you don't create the wrapper properties, users of the class would have to reference the Record property first, e.g. productPart.Record.Sku). 

 

 ProductPartRecord.cs:

 

using Orchard.ContentManagement.Records;
 
namespace Skywalker.Webshop.Models
{
    public class ProductPartRecord : ContentPartRecord
    {
        public virtual decimal UnitPrice { getset; }
        public virtual string Sku { getset; }
    }
}

 

Notice that the properties of ProductPartRecord are marked as virtual. This is not so much a requirement of Orchard, but a requirement of NHibernate and has something to do with the way NHibernate generates derived proxy classes. If you omit to do this, you will most likely get an error. When I tried this, I received a 404: the resource could not be found. However, what really happended is that an exception occurred in the entity mapping layer, stating that the entity contains properties that could not be mapped.


Migrations

Great! Now that we have a data entity, what we need to do next is create a table that can store these type of entities. One way we could do this is to create the table manually. However, that wouldn't be the best solution if we were to redistribute the module to others, for example via the Orchard Gallery. So surely we could generate a SQL script and distribute it along with the module? Well, that could work, as long as you instruct your users to execute that script before enabling the feature. But that isn't optimal either. Lucky for us, there is a better way, and it's called: Migrations.

A Migration in Orchard is an abstraction over running SQL scripts and is implemented as an API that we can use to create and modify database tables.
We implement migrations as a class that inherits from DataMigrationImpl.
Inside the migration, we tell Orchard things like what tables to create, modify or drop and what content types and parts to create, modify or delete.
Migrations are automatically executed when you enable a module (or more specifically, a feature. Modules are never enabled: only their features. Most modules will have a default feature that has the same name as the module).

Let's see how to create a migration that creates a database table for us.

We'll start by creating a new class file in the root of our module called Migrations and write the following code:

Migrations.cs:

 

using Orchard.Data.Migration;
 
namespace Skywalker.Webshop
{
    public class Migrations : DataMigrationImpl {
        public int Create() {
 
            SchemaBuilder.CreateTable("ProductPartRecord", table => table
 
                // The following method will create an "Id" column for us and set it is the primary key for the table
                .ContentPartRecord()
 
                // Create a column named "UnitPrice" of type "decimal"
                .Column<decimal>("UnitPrice")
 
                // Create the "Sku" column and specify a maximum length of 50 characters
                .Column<string>("Sku", column => column.WithLength(50))
                );
 
            // Return the version that this feature will be after this method completes
            return 1;
        }
    }
}



When a feature is enabled, Orchard will invoke a method called "Create" on alle classes that derive from DataMigrationImpl if the feature is not yet registered in the database or its version is 0.
If a migration for feature was previously executed, the version for that migration will be greater than 0. If the version would be 5 for example, then Orchard will invoke a method called UpateFrom5.
From within that method, you would return a new value of 6. Orchard will use that value to update the current version of the migration.


The ContentPartRecord method is a convenient method that will create an Id column, configured as the primary key.
The implementation of that method looks like this:

 

Orchard.Framework/Data/Schema/CreateTableCommand.cs:

 /// <summary>
        /// Defines a primary column as for content parts
        /// </summary>
        public CreateTableCommand ContentPartRecord() {
            Column<int>("Id", column => column.PrimaryKey().NotNull());
 
            return this;
        }

 


Although we already enabled our feature, Orchard will detect that there is a DataMigration available and checks the currently stored migration version number for our module. Because no migration has yet run for our module, there will be no migration version available in the Orchard_Framework_DataMigrationRecord table:

However, when you refresh the Orchard Admin, Orchard will execute the migration in the background, after which our feature will be at version 1


As you can see, Orchard inserted a new record into the Migrations table, holding the typename of our Migrations class and the last version number it returned.
The Create method returned a value of 1.

The next thing we need to do is to create a ProductPart. Although we created a class called ProductPart that derives from ContentPart, it's not enough: we need to register the part by creating a record into the ContentPartDefinition table. We also need to tell Orchard that the ProductPart is attachable: this will enable users to attach the ProductPart via the admin.

To do this, we'll add an UpdateFrom1 method to our Migration class, which will instruct Orchard to create a part called ProductPart if it doesn't already exist and to make the ProductPart attachable using the Attachable extension method.
In order to be able to use the AlterPartDefinition method, we need to import the Orchard.ContentManagement.MetaData namespace.
In order to use the Attachable extension method, we need to first reference the Orchard.Core project and import the Orchard.Core.Contents.Extensions namespace.

Add the following method to the Migrations class:

 public int UpdateFrom1(){
 
            // Create (or alter) a part called "ProductPart" and configure it to be "attachable".
            ContentDefinitionManager.AlterPartDefinition("ProductPart", part => part
                .Attachable());
 
            return 2;
        }

 

Tip: Resharper will tell you which assemblies to reference and namespaces to import, which is really helpful in al sorts of projects, expecially in Orchard.
The complete migration should now looks like this:

 

Migrations.cs:

using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
 
namespace Skywalker.Webshop
{
    public class Migrations : DataMigrationImpl {
        public int Create() {
 
            SchemaBuilder.CreateTable("ProductPartRecord", table => table
 
                // The following method will create an "Id" column for us and set it is the primary key for the table
                .ContentPartRecord()
 
                // Create a column named "UnitPrice" of type "decimal"
                .Column<decimal>("UnitPrice")
 
                // Create the "Sku" column and specify a maximum length of 50 characters
                .Column<string>("Sku", column => column.WithLength(50))
                );
 
            // Return the version that this feature will be after this method completes
            return 1;
        }
 
        public int UpdateFrom1(){
 
            // Create (or alter) a part called "ProductPart" and configure it to be "attachable".
            ContentDefinitionManager.AlterPartDefinition("ProductPart", part => part
                .Attachable());
 
            return 2;
        }
    }
}

 

When you made a change to your module as we just did, you only need to save the file: Orchard will recompile the module when you refresh the page.
When you refresh the admin, Orchard will once again run the migration in the background.
This will cause the migration record to be updated to version number 2, as well as make our ProductPart attachable: It will create a new record in the Settings_ContentPartDefinitionRecord table with the Attachable setting set to true:

 

To verify that we have a ProductPart available, we navigate to Content -> Content Parts and marvel at our work so far:

 

Notice that even though our part is called "ProductPart", Orchard displays the part here as "Product". Now let's go ahead and create 2 new ContentTypes, called "Book" and "DVD", and attach the ProductPart to both of them.

To create a new Content Type:

  1. Go to Content -> Content Types and click the Create new Type button;
  2. Enter "Book" as both the display name and the Content Type Id (which will be done for you automatically) and press the "Create" button;
  3. The Book content type has been created. Define what a Book is by attaching some product parts that make sense: select Body, Comments, Product, Title, Autoroute, and Tags and hit "Save"
  4. Repeat step 1 to 3, this time using "DVD" as the content type name.


Now we have a Book and a DVD content type. Because they have the ProductPart attached, we can treat them as products. 
We attached the AutoroutePart so that our books and dvds will be reachable via user friendly urls. The TitlePart enables the user to specify a title. The CommentsPart allows site visitors to comment on the book and the Tags part allows the administrator to add tags to the book and dvd.

Let's continue and try to create a new book. As you'll see, we see all sorts of input fields, but what about the Price and Sku ifields?


They're not showing. Why is that? The way this works in Orchard is that in order to render a Content Type, Orchard invokes a so called Driver for each ContentPart of the content type. A driver is somewhat analogous to an MVC Controller, but it is just responsible for handling the content part itself instead of the entire HTTP request.

Drivers

The Driver typically has 3 methods: one for displaying the part on the front end of the site, one for displaying the part in edit mode on the back end of the site, and one to handle the postback when the user saves a content item on which the content part is attached.

Each method returns a DriverResult, and most of the times that is a ShapeResult (which derives from DriverResult). A ShapeResult tells Orchard which Razor template to use to render the part, and also contains a dynamic object that will act as the model of the Razor template. This model is called a Shape, and is implemented as a dynamic Clay object.


You could think of a Razor template as the "skin" of a shape. The shape itself will be the Model of that view.

This concept of shapes and drivers is one of Orchard's most powerful features, but if also one of the most challenging concepts to get your head around when you're getting started.
However, once you do get your head around it, it is quite simple.

Now, to make our ProductPart appear in the Create/Edit Content page, we simply need to create a driver for it and implement the Editor methods.

Let's create a driver for our ProductPart:

  1. Create a new folder called Drivers
  2. Inside that folder, create a new class called ProductPartDriver

The ProductPartDriver class looks like this:

Drivers/ProductPartDriver.cs:

using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Skywalker.Webshop.Models;
 
namespace Skywalker.Webshop.Drivers
{
    public class ProductPartDriver : ContentPartDriver<ProductPart> {
 
        protected override string Prefix {
            get { return "Product"; }
        }
 
        protected override DriverResult Editor(ProductPart part, dynamic shapeHelper) {
            return ContentShape("Parts_Product_Edit", () => shapeHelper
                .EditorTemplate(TemplateName: "Parts/Product", Model: part, Prefix: Prefix));
        }
 
        protected override DriverResult Editor(ProductPart part, IUpdateModel updater, dynamic shapeHelper) {
            updater.TryUpdateModel(part, Prefix, nullnull);
            return Editor(part, shapeHelper);
        }
 
    }
}

Our ProductPartDriver class has two methods: Editor and its overloaded version for handling a postback.
When Orchard wants to display the editor for the ProductPart, it calls the Editor method of the Driver. When the administrator submits the editor form, the overloaded Editor (with the IUpdateModel argument) gets called.
Also note that we are overriding the Prefix property. Although this is optional, it is good practice to always provide your own prefix. This helps avoiding naming conflicts when names for input fields are generated.

The ContentShape method is defined in the ContentPartDriver<T> base class and returns a ContentShapeResult. A ContentShapeResult tells Orchard the name of the shape as well as what the shape looks like. In this case, we are creating a "well-known" shape called EditorTemplate, which has a TemplateName property, a Model property and a Prefix property.

We named our shape "Parts_Product_Edit", and its template can be found in "Parts/Product". Because we are talking about editing a Content Part, Orchard will prefix that path with "~/Orchard.Webshop/Views/EditorTemplates", so the full path will be: "~/Orchard.Webshop/Views/EditorTemplates/Parts/Product.cshtml".

And that's where we will create the Razor template file:

Views/EditorTemplates/Parts/Product.cshtml:

@using System.Web.Mvc.Html
@model Skywalker.Webshop.Models.ProductPart
<fieldset>
    <legend>Product Fields</legend>
 
    <div class="editor-label">@Html.LabelFor(x => x.Sku)</div>
    <div class="editor-field">
        @Html.EditorFor(x => x.Sku)
        @Html.ValidationMessageFor(x => x.Sku)
    </div>
    <div class="hint">Enter the Stock Keeping Unit</div>
 
    <div class="editor-label">@Html.LabelFor(x => x.UnitPrice)</div>
    <div class="editor-field">
        @Html.EditorFor(x => x.UnitPrice)
        @Html.ValidationMessageFor(x => x.UnitPrice)
    </div>
    <div class="hint">Enter the sales price per unit</div>
</fieldset>

The CSS classes we're using here are styled by stylesheets that are stored in TheAdmin theme, which is active when we're inside the admin.
Since we're using Razor, let's add two references to our project:

System.Web
System.Web.Mvc
System.Web.WebPages

We also need to copy 2 web.config files: one to our Views folder and one to the root of our project.
This is required in order to work with Razor.

Create a new web.config file in the root of your project and paste in the following code:

web.config:

<?xml version="1.0"?>
<configuration>
 
  <configSections>
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <remove name="host" />
      <remove name="pages" />
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>
 
  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <pages pageBaseType="Orchard.Mvc.ViewEngines.Razor.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages" />
        <add namespace="System.Linq"/>
        <add namespace="System.Collections.Generic"/>
        <add namespace="Orchard.Mvc.Html"/>
      </namespaces>
    </pages>
  </system.web.webPages.razor>
 
  <system.web>
    <compilation targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        <add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      </assemblies>
    </compilation>
  </system.web>
 
</configuration>

 

Next, create a web.config file in the root of the Views folder and paste in the following code:

Views/web.config:

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="webpages:Enabled" value="false" />
  </appSettings>
  <system.web>
    <httpHandlers>
    </httpHandlers>
 
    <!--
        Enabling request validation in view pages would cause validation to occur
        after the input has already been processed by the controller. By default
        MVC performs request validation before a controller processes the input.
        To change this behavior apply the ValidateInputAttribute to a
        controller or action.
    -->
    <pages
        validateRequest="false"
        pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35, processorArchitecture=MSIL"
        pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35, processorArchitecture=MSIL"
        userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35, processorArchitecture=MSIL">
      <controls>
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35, processorArchitecture=MSIL" namespace="System.Web.Mvc" tagPrefix="mvc" />
      </controls>
    </pages>
  </system.web>
 
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <handlers>
    </handlers>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="2.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

 

 

Before Orchard will render the shape as returned from the Editor method, we need to define the placement of the shape.

Placement is a system that helps determining at what position and in what local zone (which is also really just a shape) a certain shape needs to be rendered.
We define the placement of our "Parts_Product_Edit" (we defined the name of the shape in the Editor method of our ProductDriver) shape by creating a text file called Placement.info in the root of our Module project, and it looks like this:

<Placement>
  <Place Parts_Product_Edit="Content:1" />
</Placement>

 

This tells Orchard to place any shape called "Parts_Product_Edit" in a local zone called "Content" at the second position (the first position starts at 0, and is used by the RoutablePart. Try different positions to see what looks best to you).
Note that we're using the term "local zone" instead of "zone". This is because Placement.info only supports adding shapes to zones local to the content item being rendered. Orchard 1.5 will allow you to specify "global" zones as well. If you need to render certain shapes to other zones than the local zone, you need to write some code to do so. For more details on this, check out this great post.


When we now add a Book Content Item, we will see that our Product editor template looks like we expected:

Let's test it by creating 3 books and 3 dvds (don't forget to publish them in order for them to see them on the site):


Books:

  • The Hobbit, $50, SKU-1001
  • Wizard's First Rule, $39, SKU-1002
  • The Hunger Games, $29, SKU-1003

DVDs:

  • Prometheus, $30, SKU-1004
  • The Shawshank Redemption, $25, SKU-1005
  • The Dark Knight, $20, SKU-1006
     

This will indeed create 3 new Books and DVDs. However, when you edit one of the books, you will see that the fields Price and Sku are empty!
So what is going on here?

StorageFilters

The problem is that Orchard has no way of knowing where to store that information. What we need to do is to add a StorageFilter from within a ContentHandler that will tell Orchard to use an IRepository<ProductRecord> to save product parts to.
This storage filter will be used for the specified content part whenever Orchard needs to load and save data.

 

To create a ContentHandler that adds a StorageFilter:

  1. Create a new folder named Handlers
  2. Create a new class named ProductPartHandler that derives from ContentHandler

Write the following code:

using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Skywalker.Webshop.Models;
 
namespace Skywalker.Webshop.Handlers
{
    public class ProductPartHandler : ContentHandler
    {
        public ProductPartHandler(IRepository<ProductPartRecord> repository)
        {
            Filters.Add(StorageFilter.For(repository));
        }
    }
}

 

What we are seeing here is the constructor dependency injection pattern at work: Orchard injects a repository of ProductPartRecord into our handler because we asked for it by simply declaring it to be a dependency of our class constructor.
Next, we add a StorageFilter to the Filters collection, which enables Orchard to save and load our ProductPart information when it needs to.
When you now try updating one of the books or dvds, you'll notice that the Price and Sku fields actually get saved.

To recap, there are a couple of steps to follow to create a ContentPart that can persist information in the database:

  1. Create a Record class that represents your entity
  2. Create a ContentPart class that derives from ContentPart<TRecord>
  3. Create a Migration for your content part that defines the database schema
  4. Create a Driver for your content part
  5. Create an editor template for your content part
  6. Add a StorageFilter for your content part using a Handler


Sometimes you might find that you just want to create a content part that doesn't require custom properties to be persisted into the database. For example, the Orchard Profile Module defines a ProfilePart which has no physical ProfilePart class. In that case, you only need to follow just one step:

  1. Create a Migration that defines the content part

Optionally, when you want to render a content part, you might define a Driver that will create Shapes.
Alternatively, you could create a ShapeTableProvider, which is simply a class that derives from Orchard.DisplayManagement.Descriptors.IShapeTableProvider.

At first this may all seem a bit uneasy, until you find an actual need for it. Which we will soon enough!

Stay tuned for the upcoming Part 5 -> Defining and rendering the ProductCatalog Content Type


Download the source code for this post here: Part04.zip

55 comments

  • ... Posted by Jay_Arr Posted 01/13/2012 08:20 PM

    Having a few difficulties here:<br><br>Did the Orchard.Webshop.Models namespace get created somewhere that I missed? Because when I do follow the instructions, the Razor view doesn't accept it, and neither does the ProductHandler class. They both work if I drop the Models part of the namespace and just use Orchard.Webshop.<br><br>I created the folder 'Models' for the two classes, but VS doesn't recognize it as a namespace

  • ... Posted by Jay_Arr Posted 01/13/2012 09:32 PM

    This is what I did to work around the issue:<br><br>In the ProductPart and ProductRecord classes, I used the Orchard.Webshop.Models namespace instead. The Razor view then worked fine with the original statements.<br><br>I had to add 'using' lines into ProductDriver and ProductHandler to specify Orchard.Webshop.Models<br><br>In the migration, I had to change the AlterPartDefinition(typeof) so that it included to full namespace for ProductPart. A 'using' line didn't work at all here.<br><br>Why I had to do this, I have no idea, but I'm assuming it has something to do with inheritance - I'm still relatively new to this though. Any suggestions on how to fix this properly are welcome. Maybe I missed something without realizing it, but I went over these instructions 3 times from scratch with the same result.

  • ... Posted by Rey Posted 01/15/2012 03:31 PM

    hey man thanks for the tutorials. I'm having an issue, everything has worked thus far but when I refresh the page to show the contentpart of the product, it doesn't show at all. What can be could be happening?

  • ... Posted by Sipke Schoorstra Posted 01/15/2012 03:50 PM (Author)

    You're absolutely right, at some stage I stored the ProductRecord and ProductPart classes in the Orchard.Webshop.Models namespace, but I didn't update the screenshots that sow the ProductPart code. I will update these screenshots to avoid future confusion. Thanks for pointing it out!

  • ... Posted by Sipke Schoorstra Posted 01/15/2012 03:53 PM (Author)

    You're welcome! Which page are you talking about? The admin page where you should see all the content parts attached to the Book content type?

  • ... Posted by Guest Posted 01/18/2012 10:51 PM

    Getting the following error in the ProductHandler class for this line Filters.Add(StorageFilter.For(repository));<br><br><br>The type 'Orchard.Webshop.Models.ProductRecord' cannot be used as type parameter 'TRecord' in the generic type or method 'Orchard.ContentManagement.Handlers.StorageFilter.For<trecord>(Orchard.Data.IRepository<trecord>)'. There is no implicit reference conversion from 'Orchard.Webshop.Models.ProductRecord' to 'Orchard.ContentManagement.Records.ContentPartRecord'. </trecord></trecord>

  • ... Posted by Guest Posted 01/18/2012 10:58 PM

    Nevermind, after some googling for what this error is, it seems I forgot to have ProductRecord inherit from ContentPartRecord. Missed that step.

  • ... Posted by sartar singh Posted 01/20/2012 12:45 PM

    That has been great explanation.

  • ... Posted by blogger60 Posted 01/23/2012 03:32 AM

    Product driver class editor method contains a dynamic parameter shapeHelper. How do I know which methods it contains and what is the appropriate method to call in editor method

  • ... Posted by Nick Bratym Posted 02/04/2012 10:38 AM [http://www.facebook.com/nbnick]

    There is some issue, you must create ProductHandler, before adding a books items.

  • ... Posted by Sipke Schoorstra Posted 02/04/2012 02:03 PM (Author)

    You're right, and I created the ProductHandler afterwards on purpose, to demonstrate the reason for the ProductHandler and the StorageFilter. I think that making mistakes and then solving them is a great way to learn and understand.

  • ... Posted by Sipke Schoorstra Posted 02/04/2012 02:11 PM (Author)

    That's one of the hardest things I found with Orchard as well: how do you know what methods can be called on dynamic objects? Firstly, we should just look at the docs which methods they discuss and use these. Secondly, we could use the debugger and the stack trace to trace back to where the dynamic shapeHelper object was created. You will soon find that the shapehelper is a Clay object, which can have several ClayBehaviors attached to it. In the end, it's these behaviors that define what methods and properties can be called. For an excellent post on Clay, check out this link: <a href="http://downplay.co.uk/tutorials/clay/down-the-rabbit-hole" rel="nofollow">http://downplay.co.uk/tutorial.... <br><br>In any case you should know that when you're being passed a shapeHelper from the Editor() method of a driver, you just have the EditorTemplate available.<br>When you're in the Display() method, you call a method with any name you like. Clay will use that method name as the name for the shape it will create (it's one of the ClayBehaviors who's responsible for this behavior). So, you can call any method you like: all it does is create a shape with the name of the method.

  • ... Posted by blogger60 Posted 02/09/2012 08:28 PM

    Thanks Sipke...The information you provided was very helpful in clearing confusion around Drivers and how orchard shape rendering work. Your articles are really great and detailed. Your articles really helped me in understanding how to do development/customization in Orchard(A very good open source CMS in .Net).<br><br>Keep it up the good work.

  • ... Posted by Reno Dante Posted 02/24/2012 04:25 PM

    Orchard.Webshop.Models.ProductPart I am getting exception Object reference not set to an instance of an object. I am using Orchard.sdf database

  • ... Posted by IRSOG Posted 02/27/2012 05:33 AM

    Thanks Sipke niceeeeeeeeeeeeeeeee

  • ... Posted by Reno Dante Posted 03/01/2012 09:46 AM

    You need to add handler.

  • ... Posted by Reno Dante Posted 03/01/2012 09:47 AM

    find out myself problem. Didn't add handler.

  • ... Posted by Reno Dante Posted 03/01/2012 09:49 AM

    Did you set path like \src\Orchard.Web\Modules

  • ... Posted by Michael Artz Posted 03/05/2012 02:02 AM

    Sorry to post here, but I've been chasing down a problem. I can create the products as described, and add a new one via the admin screen. They appear in the database, but they are not listed on the content screen or indeed in the "list" beside the content Type.

    Can you give me a suggestion of where I might need look?

  • ... Posted by Michael Artz Posted 03/05/2012 04:30 AM

    Ok, Im not sure what did it, but i added the commonpart and also made it draftable and hey presto

  • ... Posted by Adam Bowman Posted 03/09/2012 02:13 PM [http://profiles.google.com/abowman]

    To get this to work, I needed to change the namespace for ProductPart and ProductRecord to include Models.

    namespace Orchard.Webshop.Models

  • ... Posted by Sonns1990 Posted 04/23/2012 02:48 AM
    <p>Dear !<br>I have this problem when create ProductPart at ContentManagement.ContentPart.cs !<br></p>
  • ... Posted by Sipke Schoorstra Posted 04/23/2012 02:46 PM (Author)

    Did you include the Orchard.Webshop namespace?

  • ... Posted by Sonns1990 Posted 04/26/2012 03:20 AM

    I Included both namspace "Orchard.WebShop" or "WebShop" but it still error.The first time created the project, i did named it "WebShop" and used SQL CE.

    <p>I Recreated the project name "Orchard.WebShop" and use SQL Express, the problem is solved<br></p>
  • ... Posted by Software Development Company Posted 05/05/2012 10:56 AM [http://www.mindinventory.com/]

    They attached the Containable part so that we can add the book to a List or other ContentTypes that have the Container part attached. They also attached a Route, our book will have both a title and a url. The Comments allow site visitors to comment on the book and the Tags part allows the administrator to add tags to the book.

  • ... Posted by Roland Posted 05/11/2012 03:53 PM

    Using 1.4.1, when i created the data migration part it never says there is an update. On checking it looks like it has automatically updated it?

  • ... Posted by Sipke Schoorstra Posted 05/11/2012 05:48 PM (Author)

    Yes, that's the new behavior since 1.4.0.

  • ... Posted by Roland Posted 05/11/2012 07:16 PM
    <p>Ah rite - thanks. Wish they had updated the docs... <br></p>
  • ... Posted by Chad Haney Posted 07/14/2012 09:27 PM

    When I go to add the view I can't add it? How do I make the view type show up?

  • ... Posted by Chad Posted 07/19/2012 05:29 PM

    Just add a Html page and change the extension to .cshtml

  • ... Posted by Sipke Schoorstra Posted 07/21/2012 11:14 AM (Author)
    <p>The reason is that the class library is just a plain class library. To turn it into a real Orchard library, you need to open the project file with a program like Notepad and add and change some properties. <br>To find out what exactly, you can open the ModuleCsProj.txt file in the CodeGenerationTemplates folder of the Orchard.CodeGeneration module and compare that file with your own project file. <br>Alternatively, you may as well use the code generation command to generate a project for you and include the files you have so far in that project.</p>
  • ... Posted by Will Posted 08/07/2012 06:37 PM
    <p>Thanks for a great post. How would I go about removing the Permalink textbox from my admin view? I tried &lt;place parts_autoroute_edit="-"&gt;&lt;/place&gt; from within my module's <a href="http://placement.info" rel="nofollow">placement.info</a> file, but that doesn't seem to work.</p>
  • ... Posted by Will Posted 08/07/2012 06:39 PM

    BTW, I did capitilise the template name in the above comment, but it got reformatted.

  • ... Posted by Public Coders Posted 09/15/2012 09:27 AM [http://twitter.com/pubscode]

    i am getting following error. please suggest. PubsCode

    Assembly Orchard.Framework, Version=1.5.1.0, Culture=neutral, PublicKeyToken=null uses System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 which has a higher version than referenced assembly System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

  • ... Posted by Mohammed Sepahvand Posted 09/16/2012 11:47 AM [http://www.facebook.com/mohammed.sepahvand]

    Very thorough and clear, really helped getting my head around the concepts. keep up the good work.

  • ... Posted by Michael Posted 10/21/2012 11:32 PM

    This is a great series of articles. You're essentially doing CRUD in the Admin section. Is there any way to do this type of thing in the front-end? I am not thinking full blown line of business CRUD, just something simple for users. I believe something like this could be modified to do that, but I am just having trouble figuring out how to hook it into a front-end page (say ~/CustomerName/BasicDetails). Any thoughts?

  • ... Posted by Sipke Schoorstra Posted 10/21/2012 11:37 PM (Author)

    Sure, simply create your controller and apply the [Themed] attribute to either the controller or each individual action method. All set.

  • ... Posted by Michael Posted 10/23/2012 12:01 PM
    <p>Sipke, another (maybe less challenging question ;)): I am playing around with 1.6RC and am trying to figure out how to update the project to work with it. I keep getting an NHIbernate error: <br>///<br>A tenant could not be started: DefaultNHibernate.InvalidProxyTypeException: The following types may not be used as proxies:Skywalker.Webshop.Models.OrderRecord: method set_Details should be 'public/protected virtual' or 'protected internal virtual'<br>///<br>I admit that I have not started from scratch and created the project by hand - I'm impatient that way :/. Any thoughts?</p>
  • ... Posted by Michael Posted 10/23/2012 12:04 PM

    I had this error at some point. If you are using VS2012 (like I was) and MVC 4, then that might be your problem. In the project click "Add Reference" > Go to Orchard.Framework, ...Web.Mvc, and probably (but you didn't show it) ...Web.Pages. Remove those references (uncheck the box) and add (check the box) for the more recent versions.

  • ... Posted by Sipke Schoorstra Posted 10/23/2012 06:10 PM (Author)

    It sounds like the Details property is not marked as virtual. virtually all members of a class that you use to map with NHibernate needs to be virtual. Hope that helps.

  • ... Posted by Abhishek Luv Posted 11/09/2012 10:00 AM [http://twitter.com/AbhishekLuvTwee]

    What does this line of code do

    protected override string Prefix

    {

    get

    {

    return "Product";

    }

    }

  • ... Posted by Sipke Schoorstra Posted 11/09/2012 08:53 PM (Author)

    It returns a string that ise used as a prefix when rendering the name and ID of the part editor. You can see this when you view the HTML source of the rendered part editor. It's used to distinguish between different parts and their input fields.

  • ... Posted by Guest Posted 11/10/2012 07:54 PM
    <p>Howdy, <br>This may be a silly question but I cannot get the Part to render. I am following the tutorial for my own purposes so I don't have Products. However, I checked the database and all of my parts are created once the module was enabled. </p>

    I created a new Content Type and added the Parts that I wanted. I set a breakpoint in the Editor method within my Driver and it does not get hit. The Tags module Editor method does get hit if I set a breakpoint there. I must be missing some kind of hook into the framework ?

  • ... Posted by Guest Posted 11/10/2012 07:54 PM
    <p>Howdy, <br>This may be a silly question but I cannot get the Part to render. I am following the tutorial for my own purposes so I don't have Products. However, I checked the database and all of my parts are created once the module was enabled. </p>

    I created a new Content Type and added the Parts that I wanted. I set a breakpoint in the Editor method within my Driver and it does not get hit. The Tags module Editor method does get hit if I set a breakpoint there. I must be missing some kind of hook into the framework ?

  • ... Posted by Guest Posted 11/14/2012 10:47 AM

    me too, even i copy and past and there no thing happened :( what can i do :??

  • ... Posted by Nour Berro Posted 11/14/2012 11:04 AM
    <p>Solved, my error was with <a href="http://placement.info" rel="nofollow">placement.info</a> file name, i rename it to the correct name and every thing is working now... thanks</p>
  • ... Posted by Ahmed Mostafa Posted 11/14/2012 07:10 PM [http://www.facebook.com/ahmed.mostafa]
    <p>Make sure that MVC is version 3 and MVC.Webpages is version 1.<br>because the web.config is adjusted with this version</p>
  • ... Posted by Ahmed Mostafa Posted 11/14/2012 07:12 PM [http://www.facebook.com/ahmed.mostafa]

    I really wants to thank you for the wonderful tutorial. it really helped me much. I'll continue the series.

  • ... Posted by Ahmed Mostafa Posted 11/14/2012 07:12 PM [http://www.facebook.com/ahmed.mostafa]

    I really wants to thank you for the wonderful tutorial. it really helped me much. I'll continue the series.

  • ... Posted by Ahmed Mostafa Posted 11/14/2012 08:30 PM [http://www.facebook.com/ahmed.mostafa]

    I was wondering, why we don't just create the SKU and unit price as fields from the admin panel?

  • ... Posted by Sipke Schoorstra Posted 12/17/2012 01:20 PM (Author)

    You could totally do that of course. Personally I prefer to implement characteristics of an entity as part of a content part record, but it would totally work when implemented as fields (and would have it's own benefits, such as configuration options).

  • ... Posted by Meni Posted 01/07/2015 01:40 PM

    The following text appears twice, at the beginning of the tutorial: "atible for Orchard >= 1.4". Probably a copy and paste error. Please fix that.

    Besides, it's a great tutorial.

  • ... Posted by Jimmy Posted 09/09/2015 08:48 AM [http://www.ihven'twebsite.com]

    I want to know how to load data that already existed in database. For example, I built a PRODUCT model and create some records, now I want to modify some of them, how can I fetch these data and load it to the page?

  • ... Posted by omid nasri Posted 02/25/2016 07:57 PM [http://www.omidnasri.com]

    Thx.

  • ... Posted by Oryza Anggara Posted 04/13/2016 05:30 AM

    I tried to follow these steps but stuck on "Migrations" section. My problem is I don't see my "Product" Content Part. I've enable the module, even i tried another way by creating a module by codegen. FYI : Im using Orchard version 1.10.

Leave a comment