IDeliverable - Writing An Orchard Webshop Module From Scratch - Part 4
IDeliverable - Writing An Orchard Webshop Module From Scratch - Part 4
Writing an Orchard Webshop Module from scratch Part 4
This post has been updated to be compatible for Orchard >= 1.4.
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.
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 1/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 2/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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; }
}
}
}
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 3/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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 { get; set; }
public virtual string Sku { get; set; }
}
}
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
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 4/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 5/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 6/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 7/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 8/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
public int UpdateFrom1(){
// Create (or alter) a part called "ProductPart" and configure it to be "attach
able".
ContentDefinitionManager.AlterPartDefinition("ProductPart", part => part
.Attachable());
return 2;
}
Migrations.cs:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 9/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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 "attach
able".
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:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 10/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
To verify that we have a ProductPart available, we navigate to Content -> Content Parts and
marvel at our work so far:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 11/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 12/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 13/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
Drivers/ProductPartDriver.cs:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 14/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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, dyna
mic shapeHelper) {
updater.TryUpdateModel(part, Prefix, null, null);
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 15/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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".
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:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 16/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
web.config:
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configur
ation.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, Pu
blicKeyToken=31BF3856AD364E35">
<remove name="host" />
<remove name="pages" />
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, Syste
m.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" re
quirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection
, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364
E35" 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, PublicKey
Token=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=B
77A5C561934E089"/>
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31b
f3856ad364e35" />
<add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToke
n=31bf3856ad364e35"/>
</assemblies>
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 17/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
</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:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 18/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
<?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=neu
tral, 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=31B
F3856AD364E35, 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>
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 19/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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 (http://weblogs.asp.net/bleroy/archive/2011/06/30/so-what-are-zones-
really.aspx)) 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 (http://weblogs.asp.net/bleroy/archive/2011/03/26/dispatching-orchard-shapes-to-arbitrary-
zones.aspx).
When we now add a Book Content Item, we will see that our Product editor template looks like
we expected:
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 20/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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:
DVDs:
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?
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 21/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 22/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
To recap, there are a couple of steps to follow to create a ContentPart that can persist
information in the database:
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
(http://orchardprofile.codeplex.com/) defines a ProfilePart which has no physical ProfilePart
class. In that case, you only need to follow just one step:
Optionally, when you want to render a content part, you might define a Driver that will create
Shapes.
Alternatively, you could create a ShapeTableProvider
(http://www.szmyd.com.pl/blog/customizing-orchard-shapes), 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
(/blog/writing-an-orchard-webshop-module-from-scratch-part-5)
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 23/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
52 comments
Jay_Arr 01/13/2012 08:20 PM
I created the folder 'Models' for the two classes, but VS doesn't recognize it as
a namespace
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
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 24/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
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?
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?
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 25/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
Getting the following error in the ProductHandler class for this line
Filters.Add(StorageFilter.For(repository));
Nevermind, after some googling for what this error is, it seems I forgot to
have ProductRecord inherit from ContentPartRecord. Missed that step.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 26/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
There is some issue, you must create ProductHandler, before adding a books
items.
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:
http://downplay.co.uk/tutorial... (http://downplay.co.uk/tutorials/clay/down-
the-rabbit-hole).
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 27/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 28/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
Ok, Im not sure what did it, but i added the commonpart and also made it
draftable and hey presto
To get this to work, I needed to change the namespace for ProductPart and
ProductRecord to include Models.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 29/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
namespace Orchard.Webshop.Models
Dear !
I have this problem when create ProductPart at
ContentManagement.ContentPart.cs !
I Recreated the project name "Orchard.WebShop" and use SQL Express, the
problem is solved
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
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 30/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
allow site visitors to comment on the book and the Tags part allows the
administrator to add tags to the book.
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?
When I go to add the view I can't add it? How do I make the view type show
up?
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 31/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
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.
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.
Thanks for a great post. How would I go about removing the Permalink
textbox from my admin view? I tried <place parts_autoroute_edit="-">
</place> from within my module's placement.info (http://placement.info) file,
but that doesn't seem to work.
BTW, I did capitilise the template name in the above comment, but it got
reformatted.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 32/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
Very thorough and clear, really helped getting my head around the concepts.
keep up the good work.
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?
Sure, simply create your controller and apply the [Themed] attribute to
either the controller or each individual action method. All set.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 33/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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:
///
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'
///
I admit that I have not started from scratch and created the project by hand -
I'm impatient that way :/. Any thoughts?
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.
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 34/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
get
return "Product";
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.
Howdy,
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.
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 35/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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 ?
Howdy,
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.
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 ?
me too, even i copy and past and there no thing happened :( what can i do :??
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 36/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
I really wants to thank you for the wonderful tutorial. it really helped me
much. I'll continue the series.
I really wants to thank you for the wonderful tutorial. it really helped me
much. I'll continue the series.
I was wondering, why we don't just create the SKU and unit price as fields
from the admin panel?
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 37/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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.
Leave a comment
Name:
Email address:
URL:
Comment:
How to Format
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 38/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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)
Authors
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 39/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
(/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)
January (18)
(/blog/archive/2012/1)
http://www.ideliverable.com/blog/writinganorchardwebshopmodulefromscratchpart4 40/41
4/10/2015 IDeliverable Writing an Orchard Webshop Module from scratch Part 4
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/writinganorchardwebshopmodulefromscratchpart4 41/41