orchard module walk through

35
WARNING: THIS TUTORIAL IS NOW OBSOLETE. Please use http://orchardproject.net/docs/Creating-a-module-with-a- simple-text-editor.ashx and the documentation that can be found on http://orchardproject.net/docs/ instead. Hands-on Walkthrough: Writing an Orchard Module Orchard is designed with modular extensibility in mind. The current application contains a number of built-in modules by default, and our intent with writing these modules has been to validate the underlying CMS core as it is being developed – exploring such concepts as routable content items, their associated “parts” (eventually to be bolted on using metadata), UI composability of views from separate modules, and so on. While there are many CMS core concepts that remain unimplemented for now, there are still many things you can do with the current system. The module concept is rooted in MVC 2 Areas [1 ,2 ], with the idea that module developers can opt-in to Orchard- specific functionality as needed. You can develop modules in-situ with the application as “Areas”, using Visual Studio’s MVC tools: Add Area, Add Controller, Add View, and so on (in either VS08 or VS2010). You can also develop modules as separate projects, to be packaged and shared with other users of Orchard CMS (the packaging story is still to be defined, along with marketplaces for sharing modules). This is how the Orchard source tree is currently organized. There is also a “release” build of Orchard that contains all the modules pre-built and ready to run (without source code), that you can extend using the VS tooling for MVC Areas – this can be downloaded from http://orchard.codeplex.com/releases .

Upload: sina-eghrari

Post on 28-Nov-2014

313 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: Orchard Module Walk Through

WARNING: THIS TUTORIAL IS NOW OBSOLETE. Please use http://orchardproject.net/docs/Creating-a-module-with-a-simple-text-editor.ashx and the documentation that can be found on http://orchardproject.net/docs/ instead.

Hands-on Walkthrough: Writing an Orchard ModuleOrchard is designed with modular extensibility in mind. The current application contains a number of built-in modules by default, and our intent with writing these modules has been to validate the underlying CMS core as it is being developed – exploring such concepts as routable content items, their associated “parts” (eventually to be bolted on using metadata), UI composability of views from separate modules, and so on. While there are many CMS core concepts that remain unimplemented for now, there are still many things you can do with the current system. The module concept is rooted in MVC 2 Areas [1,2], with the idea that module developers can opt-in to Orchard-specific functionality as needed. You can develop modules in-situ with the application as “Areas”, using Visual Studio’s MVC tools: Add Area, Add Controller, Add View, and so on (in either VS08 or VS2010). You can also develop modules as separate projects, to be packaged and shared with other users of Orchard CMS (the packaging story is still to be defined, along with marketplaces for sharing modules). This is how the Orchard source tree is currently organized. There is also a “release” build of Orchard that contains all the modules pre-built and ready to run (without source code), that you can extend using the VS tooling for MVC Areas – this can be downloaded from http://orchard.codeplex.com/releases.

Page 2: Orchard Module Walk Through

Let’s take a walk through building an Orchard module as an MVC Area in VS. We’ll start simple (Hello World), and gradually build up some interesting functionality using Orchard.

Installing Software PrerequisitesFirst, install these MVC 2 and Orchard releases to your machine, along with Visual Studio or Visual Web Developer for code editing:

1. Install VS08 or VS2010 (Express or higher)2. Download and Install MVC 2 RTM3. Download and extract the latest “release” build from http://orchard.codeplex.com4. Double-click the csproj file in the release package to open it in VS

Note: If you are running Orchard under IIS, you will need to configure the startup URL in Visual Studio project properties to point to the correct URL under IIS:

1. In Visual Studio, right-click the Orchard.WebPI project node in Solution Explorer.2. Select the “Web” tab3. Select the “Use Local IIS Web server” radio button (instead of “Use Visual Studio

Development server”).4. Type the appropriate URL to your Orchard site under “Project url”5. Type Ctrl-S to save and exit the Project Properties window.

1. Getting Started: A Simple Hello World Module (“Area” in VS)Our objective in this section is to build a very simple module that displays “Hello World” on the front-end using the applied Orchard theme. We’ll also wire up the navigation menu to our module’s routes.

Objectives:

1. A simple custom area that renders “Hello World” on the app’s front-end2. Views in the custom area that take advantage of the currently applied Orchard theme3. A menu item on the front-end for navigating to the custom area’s view

Follow These Steps:

1. Right-click the project node in VS Solution Explorer, and choose “Add > Area…”2. Type “Commerce” for the area name and click [OK].3. Right-click the newly created “Commerce > Controllers” folder, and choose “Add > Controller…”4. Name the Controller “HomeController”5. Right-click on the “Index()” method name and choose “Add View…”6. Selected the “Create a partial view” option and click [Add]7. Add the following HTML to the View page: <p>Hello World</p>8. Add the following namespace imports to the HelloController.cs file:

using Orchard.Themes;using Orchard.UI.Navigation;

Page 3: Orchard Module Walk Through

9. Add an [Themed] attribute to the HelloController class:

namespace Orchard.Web.Areas.Commerce.Controllers {

[Themed]public class HomeController : Controller

10. Add another class to create a new Menu item:

public class MainMenu : INavigationProvider { public String MenuName { get { return "main"; } }

public void GetNavigation(NavigationBuilder builder) { builder.Add(menu => menu.Add("Shop", "4", item => item .Action("Index", "Home", new { area = "Commerce" }))); } }

11. Type Ctrl-F5 to build and run in VS.12. Navigate to this URL in the browser: http://localhost:<port>/Commerce

2. Hooking into the Admin PanelThe next thing we want to do is wire up our custom module to the admin panel, so we have a way to perform administration tasks on the back-end.

Objectives:

1. Add an admin panel page for the custom area2. Add a menu item to the admin panel for the custom area

Follow These Steps:

1. Right-click the “Commerce > Controllers” folder and choose “Add > Controller…”2. Type “AdminController” for the controller name and click [OK].

Page 4: Orchard Module Walk Through

3. Add the following namespaces and attributes on the controller class:

using Orchard.Themes;using Orchard.UI.Admin;

namespace Orchard.Web.Areas.Commerce.Controllers{ [Admin] [Themed] public class AdminController : Controller

4. Right-click on the “Index()” method name and choose “Add View…”5. Selected the “Create a partial view” option and click [Add]6. Add the following HTML to the View page: <p>Commerce Area Admin</p>7. Type Ctrl-F5 to build and run in VS.8. Navigate to this URL in the browser: http://localhost:<port>/Admin/Commerce

Note that you will need to log in as admin before you can view this page. By convention, controllers named “Admin*” are protected from access by anonymous site visitors.

Hooking into the admin menu…

1. Add an AdminMenu.cs file to the root of the “Commerce” folder. For convenience, you may copy this from another module folder (and change the namespace and area name appropriately).

using Orchard.Pages.Services;using Orchard.UI.Navigation;

namespace Orchard.Web.Areas.Commerce{ public class AdminMenu : INavigationProvider { private readonly IPageService _pageService;

public AdminMenu(IPageService pageService) { _pageService = pageService; }

public string MenuName { get { return "admin"; } }

public void GetNavigation(NavigationBuilder builder) {

Page 5: Orchard Module Walk Through

builder.Add("Commerce", "1.1", menu => menu .Add("Manage Products", "1.0", item => item .Action("Index", "Admin", new { area = "Commerce" }))); } }}

2. Type Ctrl-F5 to build and run in VS.3. Navigate to this URL in the browser: http://localhost:<port>/Admin

Orchard uses dependency injection to provide services to the application. In the example above, the INavigationProvider interface derives from IDependency. Simply by including a constructor that accepts INavigationProvider as a parameter, an implementation of this interface will be provided by the application framework to this class when it is constructed. We use this interface to define a main menu item for our Commerce > Manage Products screen. In the next section, we will see dependency injection again when we use the IRepository interface to access the database.

3. Working with Data Let’s now look at how to work with data. The objective for this section is to create a ProductRecord type that is persisted to the database. We’ll then update the Commerce area admin pages to be able to list and create new products.

Objectives:

1. Create a ProductRecord type that is persisted to the database2. Add admin pages for creating and listing products

Page 6: Orchard Module Walk Through

Follow These Steps:

1. Create “ProductRecord.cs” in “Models” folder

using System.ComponentModel.DataAnnotations;using System.Web.Mvc;

namespace Orchard.Web.Areas.Commerce.Models { public class ProductRecord { public virtual int Id { get; set; } [Required] public virtual string Sku { get; set; } [Required] public virtual string Description { get; set; } [Required] public virtual decimal Price { get; set; } }}

Orchard generally favors convention over configuration, and in the example above, there are a few conventions at work. First, the *Record suffix on the class, coupled with the fact that the class lives under the *.Models namespace, tells Orchard this is a persistent class that should be backed by a database table. Second, the property named “Id” is conventionally used as the primary key for the record.

2. Add the “IRepository<ProductRecord>” to the AdminController. This is another example of using dependency injection to receive access to services (in this case, the database).

using Orchard.Data;using Orchard.Web.Areas.Commerce.Models;

//… public class AdminController : Controller { private readonly IRepository<ProductRecord> _repository;

public AdminController(IRepository<ProductRecord> repository) { _repository = repository; }

3. Create the 2 “Admin/Create” actions (typical MVC pattern for entity creation)

using Orchard.Web.Areas.Commerce.Models; //… public ActionResult Create() {

return View(new ProductRecord()); }

[HttpPost] public ActionResult Create(ProductRecord product) { if (!ModelState.IsValid) { return View(product); }

_repository.Create(product); return RedirectToAction("Index");

Page 7: Orchard Module Walk Through

}

Now, we are going to add the corresponding “Create” view…

4. Build the project first, so the ProductRecord type is available in the Add View dialog.5. Right-click the Create action and choose “Add View…”6. Select the “Create a partial view (.ascx)” option7. Select the “Create a strongly-typed view” option and type

“Orchard.Web.Areas.Commerce.Models.ProductRecord” for the View data class. Select “Create” as the View content.

8. In the view markup/code, change the ID field to be hidden:

<div class="editor-label"> <%= Html.HiddenFor(model => model.Id) %> </div> <div class="editor-field"> <%= Html.HiddenFor(model => model.Id) %> <%= Html.ValidationMessageFor(model => model.Id) %> </div>

9. In AdminController.cs, update the Index() method to pass the list of Products from the repository:

public ActionResult Index(){

Page 8: Orchard Module Walk Through

return View(_repository.Table);}

10. Delete “Views\Admin\index.ascx” file (we are going to re-create it as a “List” view).11. In AdminController.cs, right-click the “Index()” action and chose “Add View…” to recreate the

“Index” view.a. Choose the partial view optionb. Select the strongly-typed view option, typing

“Orchard.Web.Areas.Commerce.Models.ProductRecord” for the View data class.c. Select “List” as the View content.

12. Open Index.ascxd. Update the table class: <table class="items"> (so it looks nice)

13. Type Ctrl-F5 to build and run the site in Visual Studio.14. Go to the “Manage Products” admin page15. You can create a product (with validation)16. The “Index” view shows the list of product from the db:

17. Enter a few products to use as sample data…

4. Creating a Content TypeRather than work with database records directly, let’s start using some of the higher-level abstractions that Orchard CMS offers. Fundamental to the Orchard CMS is the idea of multiple content types – the built-in pages and posts types are examples. Our sample Commerce module/area can also introduce one or more content types into the system. The objective of this section is to convert our use of ProductRecord to a Product content type instead. This won’t buy us much at first (although we do get some things, such as versioning of our persisted objects). However, we will take advantage of it in the next section, when we explore composition of additional “parts” on the content types (the way that comments and tags are parts applied to pages and posts). Assembling the data and UI of the application from multiple types and parts allows the system to remain loosely coupled and allows installed modules to extend each others types in interesting ways.

Page 9: Orchard Module Walk Through

Objectives:

1. Create a Product content type2. Add a ProductHandler for Product type

a. Associate repository and ProductRecord typeb. Associate Product type to content type name

Follow These Steps:

1. Go to the ProductRecord.cs file and update it like this:

using System.ComponentModel.DataAnnotations;using System.Web.Mvc;using Orchard.Data;using Orchard.ContentManagement;using Orchard.ContentManagement.Records;using Orchard.ContentManagement.Handlers;

namespace Orchard.Web.Areas.Commerce.Models{ public class ProductRecord : ContentPartRecord

2. Add a Product and ProductHandler class as follows:

public class Product : ContentPart<ProductRecord> { public int Id { get { return Record.Id; } set { Record.Id = value; } }

[Required] public string Sku {

Page 10: Orchard Module Walk Through

get { return Record.Sku; } set { Record.Sku = value; } }

[Required] public string Description { get { return Record.Description; } set { Record.Description = value; } }

[Required] public decimal Price { get { return Record.Price; } set { Record.Price = value; } } }

public class ProductHandler : ContentHandler { public readonly static ContentType ContentType = new ContentType { Name = "product", DisplayName = "Product" };

public ProductHandler(IRepository<ProductRecord> repository) { Filters.Add(new ActivatingFilter<Product>(ProductHandler.ContentType.Name)); Filters.Add(StorageFilter.For(repository)); } }

The Product type is an Orchard content type, using ProductRecord for storage. For convenience, we’ve wrapped the properties of the record on the Product type, but this is optional, as the Product type also exposes these properties directly from its Record property, for example Product.Record.SKU. The ProductHandler is responsible for registering the content type with the system, ensuring the the Product type is activated whenever a new “Product” content item is created, and associating the IRepository<ProductRecord> for storage.

We also need to update our Commerce admin pages to use the new content type instead of the record. To do this, we will go through the IContentManager interface to query for products instead of talking to the repository directly. The ContentManager is responsible for resolving the requested content type to a handler (in this case, ProductHandler) and instantiating the type for us.

1. In AdminController.cs…

a. Change all references from “ProductRecord” to “Product”…b. Change all references to “IRepository<ProductRecord>” to “IContentManager”…

[OrchardTheme(Admin = true)] public class AdminController : Controller { private readonly IContentManager _contentManager;

public AdminController(IContentManager contentManager) { _contentManager = contentManager;

Page 11: Orchard Module Walk Through

}

public ActionResult Index() { return View(_contentManager.Query<Product>().List()); }

public ActionResult Create() { return View(_contentManager.New<Product>("product")); }

[HttpPost] public ActionResult Create(FormCollection input) { var product = _contentManager.New<Product>("product");

if (!TryUpdateModel(product)) { return View(product); }

_contentManager.Create(product);

return RedirectToAction("Index"); } }

2. Change the “Create.ascx” view to use “Product” instead of “ProductRecord” in the first line (the view model):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Orchard.Web.Areas.Commerce.Models.Product>" %>

3. Change the “Index.ascx” view to use “Product” instead of “ProductRecord” in the first line (the view model):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Orchard.Web.Areas.Commerce.Models.Product>>" %>

4. Type Ctrl-F5 to build and run in Visual Studio.

5. Attaching Parts to a Content TypeAlthough we’ve made Product a content type, the application has pretty much the same behavior as it used to. We are now going to do something useful with the content type, adding “parts” that extend our type. By adding parts to a content type, other modules in the system may impact and impart both UI and behavior on that type. The objective of this section is to attach a single “Common” part to the Product content type. The “Common” part attaches things like owner, versioning and create/publish dates for a content item. We will hard-code the UI layer in this section, and then remove that hard-coding in favor of UI composition in the next section.

Objectives:

1. Attach the “Common” aspect to the Product type2. Expose the Common aspect’s “Owner” property on the list of Products UI

Follow These Steps:

1. Go to the ProductRecord.cs file and update it to include these namespaces:

Page 12: Orchard Module Walk Through

using System.ComponentModel.DataAnnotations;using Orchard.Core.Common.Models;

Now, let’s illustrate the power of the data composition story… We are going to add a “Common” part to the “Product” content type. The “Common” part is an Orchard extension which automatically keeps track of Owner, Container, CreationDate, etc. for content items.

Here is the definition of the ICommonAspect interface (no need to type this – it’s already defined by Orchard in the Orchard.Core.Common.Models namespace). Once we add this part to our Product type, the CommonAspect data will be saved (as a “Content Part”) with each instance of “Product”.

public interface ICommonAspect : IContent {

IUser Owner { get; set; } IContent Container { get; set; }

DateTime? CreatedUtc { get; set; } DateTime? PublishedUtc { get; set; } DateTime? ModifiedUtc { get; set; }

DateTime? VersionCreatedUtc { get; set; } DateTime? VersionPublishedUtc { get; set; } DateTime? VersionModifiedUtc { get; set; } }

2. Change the ProductHandler class to add a “CommonAspect” part to the “Product” content type:

public ProductHandler(IRepository<ProductRecord> repository) {

Filters.Add(new ActivatingFilter<Product>(ProductHandler.ContentType.Name));

Filters.Add(new ActivatingFilter<CommonAspect>(ProductHandler.ContentType.Name));

Filters.Add(StorageFilter.For(repository));

}

That’s it! Now, whenever a “Product” is going to be manipulated by the application, a “CommonAspect” will go with it. Under the hood, the content manager will activate the “CommonAspect” part when “Products” are activated. The “CommonAspect” implementation will keep track of creation date, modification date and publication dates automatically for us.

3. Let’s illustrate that by displaying the “Created”, “Published” date for each product in the Index.ascx view. Add these 2 lines at the beginning of the file:

<%@ Import Namespace="Orchard.Core.Common.Models"%><%@ Import Namespace="Orchard.ContentManagement"%>

a. Add “Created” and “Published” columns:

Page 13: Orchard Module Walk Through

<th> Price </th> <th> Created </th> <th> Published </th>

b. Access to the “Created” and “Published” dates as follows:

<td> <%= Html.Encode(String.Format("{0}", item.As<CommonAspect>().CreatedUtc))%> </td> <td> <%= Html.Encode(String.Format("{0}", item.As<CommonAspect>().PublishedUtc)) %> </td>

4. Type Ctrl-F5 to build and run in Visual Studio.

Note: If you don’t empty the database at this point, the product created until now will have no “Common” data value. Orchard doesn’t have a story for database upgrade for now. To reset the database (destroy all data for the site), delete the App_Data/Sites folder in your project. You may need to “Show All Files” in Visual Studio’s Solution Explorer before you can see this folder. After deleting the database, you’ll need to walk through the Orchard setup screen again.

Notice how the table now shows created and published dates (they have been setup automatically by the content manager, stored in a “Common” aspect table).

Let’s look at what happens when a product instance is created. Set a debug breakpoint in “AdminController.Create” action, on the line of code that invokes _contentManager.Create(product). Type F5 in Visual Studio to begin debugging, and then create a new product using the admin panel in order to hit the breakpoint:

Page 14: Orchard Module Walk Through

The “Product” content item has 2 parts: the Product part and the “CommonAspect” part. If we look at the data maintained by the “CommonAspect”, we see that everything is taken care of internally. Here is the “Product” instance after the call to “Create” of content manager:

Page 15: Orchard Module Walk Through

6. Composing Views from PartsIn the previous section, we hard-coded the UI that displays the properties of the attached “Common” part/aspect. While this works, Orchard also provides a method for composing user interface elements, aggregating the various partial views of content items and their attached parts, and ensuring the correct Model (data) is passed to the composite view. In this section, we will explore this UI composition. The objective of the section is to add two additional content parts, Comments and Tags, to our Product type. Then we will take advantage of Orchard UI composition to assemble the editor view of a product, including the editors for the attached parts.

First, let’s discuss some background information that will help explain how UI composition works:

The idea behind UI composition is that the view of a product (in our example, the editor view in the admin panel) should be assembled from the constituent views from all the attached parts. Each part defines its own view templates that expect a specific view model type to be passed to it. The obtain these view models, the Controller action calls the BuildXxxModel methods on ContentManager. The ContentManager delegates to ContentDriver types (one for each part) to retrieve the appropriate view models for each part. The ContentManager then aggregates those into a single view model that is handed back to the Controller. What is actually passed back from the ContentDriver is a ContentPartTemplate object – the view, the model for that view, and the zone and location/position within the zone to render the part (note that the zone/position information will eventually be driven from metadata/templates instead of code).

Objectives:

1. Add the Comments and Tags parts to the Product type2. Update the “Create” view for a Product to use Orchard HTML helpers to create the form3. Write a ProductDriver type and associated template for rendering the Product form fields

Page 16: Orchard Module Walk Through

4. Update the AdminController to use the ContentManager.BuildEditorModel to obtain the view model for a Product editor view.

Follow These Steps:

1. First of all, let’s enable the “Comments” and “Tags” part for our product content type:

using Orchard.Tags.Models; using Orchard.Comments.Models; … public ProductHandler(IRepository<ProductRecord> repository) { Filters.Add(new ActivatingFilter<Product>(ProductHandler.ContentType.Name)); Filters.Add(new ActivatingFilter<CommonAspect>(ProductHandler.ContentType.Name)); Filters.Add(new ActivatingFilter<HasComments>(ProductHandler.ContentType.Name)); Filters.Add(new ActivatingFilter<HasTags>(ProductHandler.ContentType.Name)); Filters.Add(StorageFilter.For(repository)); }

For now, the association of content parts is done in code. However, eventually this will move to a metadata system that enables association of parts to items on-the-fly, using the Orchard admin panel instead of writing code to do this. This would enable our Product type to be completely independent, with no compile-time dependency on specific parts.

2. Now let’s update the “Create” method of our AdminController to create the ContentItemViewModel for the product:

public ActionResult Create() { var product = _contentManager.New<Product>("product"); var model = _contentManager.BuildEditorModel(product); return View(model); }

3. We also need to update the “Create.ascx” view to use the “ContentItemViewModel” and dispatch the form composition to the Orchard HtmlHelper.

<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Mvc.ViewModels.ContentItemViewModel<Orchard.Web.Areas.Commerce.Models.Product>>" %>

<h1><%=Html.TitleForPage(T("Create Product").ToString()) %></h1><% using (Html.BeginForm()) { %> <%=Html.ValidationSummary() %> <%=Html.EditorForItem(Model) %> <fieldset><input type="submit" value="Create"/></fieldset><% } %>

<div> <p><%=Html.ActionLink("Back to List", "Index") %></p></div>

Page 17: Orchard Module Walk Through

4. Next, we will define a driver and template for the Product type that participates in this UI composition process. Define a new ProductDriver class (that derives from ContentItemDriver). This class is responsible for returning the appropriate view model, template, and location for the various views of a Product (in our example, the editor view):

using Orchard.ContentManagement.Drivers;…

public class ProductDriver : ContentItemDriver<Product> { protected override bool UseDefaultTemplate { get { return true; } }

protected override DriverResult Display(Product product, string displayType) { return ContentPartTemplate(product, "Product.Fields").Location("primary", "1"); }

//GET protected override DriverResult Editor(Product product) { return ContentPartTemplate(product, "Product.Fields").Location("primary", "3"); } //POST protected override DriverResult Editor(Product part, IUpdateModel updater) { updater.TryUpdateModel(part, Prefix, null, null); return Editor(part); }

}

This code says that the template to use for displaying the product is “Product.Fields”. Notice how we pass the “products” instance as the view model for that template. We also mention it’s going to be displayed in the “primary” zone, at the top of the zone (position=”1”).

5. Let’s now create “Product.Fields.ascx” under the Commerce > Views > EditorTemplates folder. This is essentially the same as our former “Create” view, which displayed the editors for properties for the Product type.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Orchard.Web.Areas.Commerce.Models.Product>" %>

<fieldset> <legend>Fields</legend>

<%=Html.HiddenFor(model=>model.Id) %>

Page 18: Orchard Module Walk Through

<div class="editor-label"> <%= Html.LabelFor(model => model.Sku) %> </div> <div class="editor-field"> <%= Html.TextBoxFor(model => model.Sku)%> <%= Html.ValidationMessageFor(model => model.Sku)%> </div> <div class="editor-label"> <%= Html.LabelFor(model => model.Description)%> </div> <div class="editor-field"> <%= Html.TextBoxFor(model => model.Description)%> <%= Html.ValidationMessageFor(model => model.Description)%> </div> <div class="editor-label"> <%= Html.LabelFor(model => model.Price) %> </div> <div class="editor-field"> <%= Html.TextBoxFor(model => model.Price) %> <%= Html.ValidationMessageFor(model => model.Price) %> </div> </fieldset>

6. Type Ctrl-F5 to build and run the site.

Now when we click on “Create new Product” link, we see a form which contains the fields of the products, but also tags, comments and owner fields.

Page 19: Orchard Module Walk Through

7. The last thing we need to do is re-implement the “Create” “Post” action in AdminController.cs:

[HttpPost] public ActionResult Create(FormCollection input) { var product = _contentManager.New<Product>("product"); var model = _contentManager.UpdateEditorModel(product, this);

if (!ModelState.IsValid) { return View(model); }

_contentManager.Create(product); return RedirectToAction("Index"); }

Note the “this” parameter, passed to UpdateEditorModel. To enable validation of the model, we need an interface between the MVC controller and the content drivers. We are going to implement this interface in the “AdminController” itself, by simply delegating the default MVC base class.

using Orchard.Localization; … public class AdminController : Controller, IUpdateModel {

Page 20: Orchard Module Walk Through

bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) { return TryUpdateModel(model, prefix, includeProperties, excludeProperties); }

void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) { ModelState.AddModelError(key, errorMessage.ToString()); }

The update/validation of the model is going through the ContentManager, to all the content drivers, and then back to our AdminController. The last thing we need to do is implement the model update/validation for the “Product” part. We do that in the ProductDriver:

public class ProductDriver : ContentItemDriver<Product> { protected override DriverResult Editor(Product part, IUpdateModel updater) { updater.TryUpdateModel(part, Prefix, null, null); return Editor(part); }

8. Type Ctrl-F5 to build and run the site.

We can now create a new product, including model validation!

Page 21: Orchard Module Walk Through

7. Tying It All Together: Building the Front-EndAside from our Hello World example in the beginning of this tutorial, we’ve been primarily focused on developing the admin panel functionality for our Commerce module, illustrating the data and UI composition concepts in Orchard along the way. We’ll complete this tutorial by tying together the same concepts to build out the front-end behavior of the module. Now that we have the “Product” content type in place, building a simple front-end will be easy. We are going to first create a page which shows the list of products, and then we’ll add the ability to add a product to a simple shopping cart.

Objectives:

1. Display a list of products on the front-end “Shop” page2. Implement an “Add to Cart” action on a product, to add the item to a shopping cart3. Implement the display of the Shopping Cart in the application’s sidebar zone

Follow These Steps:

1. Let’s first update our HomeController query the ContentManager for the list of products, passing the model to the View:

using Orchard.Web.Areas.Commerce.Models; using Orchard.ContentManagement; … [Themed] public class HomeController : Controller { private readonly IContentManager _contentManager;

public HomeController(IContentManager contentManager) { _contentManager = contentManager; }

public ActionResult Index() { return View(_contentManager.Query<Product>().List()); } }

2. Now let’s update the Home/Index.ascx view page to render the list of products. To make this easier, delete the existing Index.ascx page and right-click the Index() action to “Add > View” in Visual Studio. This time, we’ll add a strongly-typed view that takes the Product type, and choose “List” for the View content type:

Page 22: Orchard Module Walk Through

3. In Index.ascx, add this at the top of the file:

<div class="page-title"><%=Html.TitleForPage("Product list")%></div>

4. Type Ctrl-F5 to build and run the site. Click the “Shop” menu item on the front-end to reveal your list of products.

Page 23: Orchard Module Walk Through

5. Let’s now create a “Details” view so we can verify we can see tags and add comments (recall the UI composition section above). In the HomeController, add this:

using Orchard.Mvc.Results; … public ActionResult Details(int id) { var product = _contentManager.Get<Product>(id); if (product == null) return new NotFoundResult();

var viewModel = _contentManager.BuildDisplayModel<Product>(product, "Details"); return View(viewModel); }

6. Let’s then create the “Details.ascx” view under Views > Home:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Orchard.Mvc.ViewModels.ContentItemViewModel<Orchard.Web.Areas.Commerce.Models.Product>>" %>

<div class="page-title"><%=Html.TitleForPage("Product details")%></div><%= Html.DisplayForItem(Model) %>

<div> <%=Html.ActionLink("Back to List", "Index") %></div>

Note the “Details” string. It a convention for the type of display (other include summary, list, etc.). Let’s update the ProductDriver to handle the composition:

protected override DriverResult Display(Product product, string displayType) { return ContentPartTemplate(product, "Product.Fields").Location("primary", "1"); }

Page 24: Orchard Module Walk Through

This is very similar to the “Editor” method. Product.Fields is the template used to render the fields. Let’s create Product.Fields.ascx in the “DisplayTemplates” folder.

7. Add Product.Fields.ascx under the “Views > DisplayTemplates” folder:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Orchard.Web.Areas.Commerce.Models.Product>" %><fieldset> <legend><%= Html.Encode(Model.Sku)%></legend> <p></p> <h3><%= Html.Encode(Model.Description)%></h3> <p>Price: <%= Html.Encode(String.Format("{0:F}", Model.Price))%></p></fieldset>

8. And finally update the Index view to link the “sku” field to the product details page:

Replace this:

<%= Html.Encode(item.Sku) %>

With this:

<%= Html.ActionLink(item.Sku ?? "sku", "Details", new { id = item.Id })%>

9. Type Ctrl-F5 to build and run the site. Now, when clicking on a “sku” in the index view, we get to the product details page (with comments and tags).

Page 25: Orchard Module Walk Through

Implementing a basic shopping cartTo give another take on UI composition, we are going to implement a simple shopping cart, and we will display it in the “sidebar” zone using the MVC 2 RenderAction method.

1. Let’s first define the data model we are going to use to store the shopping cart data (we are using simple records, not content items for this simple example). Place this class in the Models folder:

using Orchard.Data.Conventions;using System.Collections.Generic;

namespace Orchard.Web.Areas.Commerce.Models{ public class ShoppingCartRecord { public ShoppingCartRecord() { Entries = new List<ShoppingCartEntryRecord>(); }

public virtual int Id { get; set; } public virtual int UserId { get; set; } [CascadeAllDeleteOrphan] public virtual IList<ShoppingCartEntryRecord> Entries { get; set; } }

public class ShoppingCartEntryRecord { public virtual int Id { get; set; } public virtual ProductRecord Product { get; set; } public virtual int Quantity { get; set; } }}

2. Next, let’s update the HomeController class to use more services we are going to need:

using Orchard.Data; using Orchard.UI.Notify; using Orchard.Security; using Orchard.Localization; … public class HomeController : Controller { private readonly IContentManager _contentManager; private readonly IRepository<ShoppingCartRecord> _repository; private readonly INotifier _notifier;

public virtual IUser CurrentUser { get; set; } public Localizer T { get; set; }

public HomeController(IContentManager contentManager, IRepository<ShoppingCartRecord> repository, INotifier notifier) { _contentManager = contentManager; _repository = repository; _notifier = notifier; }

Some things to note about this code:

Page 26: Orchard Module Walk Through

“IRespository<ShoppingCartRecord> gives access to the database “CurrentUser” gives access to the logged-in user “T” gives access to the localization service “INotifier” gives access to the notification area of the application

Now, let’s implement the “AddToCart” action…

We’ll create the cart for the user if needed, add 1 product to the list, and display a “Product added” message to the notification area of the site.

Note that in this simple example, we don’t handle anonymous users very well – there is a single shopping cart for the whole site. We also don’t support “merging” the shopping cart content when an anonymous user decides to log in after creating his shopping cart. These are more advanced applications of a shopping cart implementation that we will skip for now.

3. In HomeController, add the AddToCart action method:

public ActionResult AddToCart(int id) {

// Retrieve shopping cart for current user ShoppingCartRecord cart; if (CurrentUser == null) { cart = _repository.Fetch(c => c.UserId == 0).SingleOrDefault(); } else { cart = _repository.Fetch(c => c.UserId == CurrentUser.Id).SingleOrDefault(); }

// Create cart if none found if (cart == null) { cart = new ShoppingCartRecord(); _repository.Create(cart); cart.UserId = (CurrentUser == null ? 0 : CurrentUser.Id); }

// Add product to cart entry var product = _contentManager.Get<Product>(id).Record;

var entry = cart.Entries.Where(e => e.Product == product) .SingleOrDefault(); if (entry == null) { entry = new ShoppingCartEntryRecord { Product = product, Quantity = 0}; cart.Entries.Add(entry); } entry.Quantity++;

_notifier.Add(NotifyType.Information, T("Added {1} to shopping cart", entry.Quantity, product.Sku));

// Back to product list return RedirectToAction("Index"); }

4. Finally, update the “Index.ascx” view (under Views > Home) to include a “Add to cart” column:

Page 27: Orchard Module Walk Through

a. Add a “Buy” column…

<th>Buy</th>

b. And add an “Add To Cart” link…

<td><%= Html.ActionLink("Add to cart", "AddToCart", new { id= item.Id })%></td>

5. Type Ctrl-F5 to build and run the site.

We now have a working “Add to cart” link next to each product in the list We also get notification that a product has been added to the cart

6. Now let’s create a “ShoppingCart” Action and corresponding view.a. In HomeController.cs, add a new action:

public ActionResult ShoppingCart() { // Retrieve shopping cart for current user ShoppingCartRecord cart; if (CurrentUser == null) { cart = _repository.Fetch(c => c.UserId == 0).SingleOrDefault(); } else { cart = _repository.Fetch(c => c.UserId == CurrentUser.Id).SingleOrDefault(); }

return PartialView(cart); }

Note that this action returns PartialView instead of View. This prevents the view from being rendered as part of the 3-pass Theme rendering (Document > Layout > View). We don’t want the site header, footer, etc to be rendered for this partial view.

7. Add a “ShoppingCart.ascx” file (under Views > Home) for the View:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Orchard.Web.Areas.Commerce.Models.ShoppingCartRecord>" %>

<% if (Model == null) {%> <p>Shopping card is empty</p>

Page 28: Orchard Module Walk Through

<%}else{%><table class="items"> <tr> <th>Sku</th> <th>Product</th> <th>Quantity</th> <th>Price</th> </tr>

<%foreach (var entry in Model.Entries) {%> <tr> <td><%=Html.Encode(entry.Product.Sku) %></td> <td><%=Html.Encode(entry.Product.Description) %></td> <td><%=Html.Encode(entry.Quantity) %></td> <td><%=Html.Encode(entry.Quantity * entry.Product.Price) %></td> </tr> <%}%></table><%}%>

8. Finally, open “Areas\Commerce\Views\Home\Index.ascx” (the Product list front-end page) and add the following line anywhere in the page:

<%Html.AddRenderAction("sidebar", "ShoppingCart");%>

We basically tell the rendering engine that the “ShoppingCart” action should be rendered in the “sidebar” zone.

9. Type Ctrl-F5 to build and run in Visual Studio.

In the list of product page, we can now see the shopping cart partial view:

Page 29: Orchard Module Walk Through

Feedback

Where to send feedback about this tutorialWhere to send feedback about Orchard in generalSubmitting bugs