Writing an Orchard Webshop Module from scratch - Part 6

been updated to be compatible for Orchard >= 1.4.

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

In this part, we will enable our users to add products to their shopping cart.
To create such functionality, we need:

  • An "Add to shopping cart" button on each of our product, both in list view (the [roduct catalog) as well as in detail view. Clicking the button will add the product to the shopping cart
  • Some sort of shopping cart service that stores the added items
  • An overview of the items in our shoppingcart, as well as a "Proceed to checkout" button
  • A widget that is visible on every page that shows a link to our shoppingcart page as well as the number of items currently stored

Let's start with the first item: rendering an "Add to shopping cart" button on our product catalog.

Rendering the "Add to shoppingcart" button

As seen in the previous post, our product catalog is basically a list of content items rendered by the Projector module.
Each individual item being rendered in the catalog consists of a collection of content parts. In this tutorial, the catalog holds a list of any content item whose content type has the ProductPart attached we introduced in part 4 - Defining the ProductPart.

When Orchard renders a content item, it invokes the part drivers for each part of that content item. In turn, each part driver creates a new shape, which is then rendered using a Razor template.
We already have a template for our "Parts_Product" shape that is created by the ProductPart driver, so let's modify the template to show an "Add" button.

Open the file "Views/Parts/Product.cshtml" and replace the markup with the following:

Views/Parts/Product.cshtml:

@{
    var price = (decimal) Model.Price;
    var sku = (string) Model.Sku;
}
<article>
    Price: @price<br/>
    Sku: @sku
    <footer>
        <button>Add to shoppingcart</button>      
    </footer> 
</article>

 

Now our product catalog looks like this:

That was just too easy. Now, in real world themes, you might want to customize the look of each content item when being rendered in a list. For example, you might want to display the "Add to shoppingcart" button after the body text, but have the Price and Sku fields stay at their current location. We can achieve this in at least two ways:

A. We take over the rendering of our list completely by creating a new layout for our query, or
B. We create a new shape representing our button in the ProductPart driver and use Placement.info to place that shape at any location we wish within the Content Item.

Both methods would be fine, but method B is much simpler. A good rule of thumb is to go with the easiest of solutions if you're not sure which way to go.
If you are writing a custom theme with specific requirements that affect the entire content item's look and or behavior, then you could choose method A, as it provides you with complete freedom.

We will apply method B to render the button below the BodyPart (you can read more about shapes and zones here).
To create a new shape, we first modify our ProductPart driver:

protected override DriverResult Display(ProductPart part, string displayType, dynamic shapeHelper)
        {
            // To return more than 1 shape, use the Combined method to create a "CombinedShapeResult" object.
            return Combined(
 
                // Shape 1: Parts_Product
                ContentShape("Parts_Product", () => shapeHelper.Parts_Product(
                    Price: part.UnitPrice,
                    Sku: part.Sku
                )),
 
                // Shape 2: Parts_Product_AddButton
                ContentShape("Parts_Product_AddButton", () => shapeHelper.Parts_Product_AddButton())
                );
        }


The Display method has been modified so that it creates an extra shape named "Parts_Product_AddButton" and have that shape returned as part of a CombinedResult object that is created by the Combined method. The CombinedResult class is a simple class that derives from DriverResult and holds an IEnumerable of DriverResults.
In effect, we are telling Orchard to render both a shape named Parts_Product as well as a shape named Parts_Product_AddButton. This is very useful because it allows us to skin and position the shapes individually.

Next, we'll create a new template called "Views/Parts/Product.AddButton.cshtml" that will contain the markup of the shape:

Views/Parts/Product.Addbutton.cshtml:

<button>@T("Add to shoppingcart")</button>

 

(Noticed that we are using the T property delegate to render the text. This prepares our module to be localized using Orchard's localization features)

We'll also undo the change we made earlier when we added the "Add" button directly to Product.cshtml:

Views/Parts/Product.cshtml:

@{
    var price = (decimal) Model.Price;
    var sku = (string) Model.Sku;
}
<article>
    Price: @price<br/>
    Sku: @sku
</article>

 

 

Next we need to tell Orchard where the shape has to be rendered by modifying the Placement.info file as follows:

<Placement>
  <Place Parts_Product_Edit="Content:1" />
  <Place Parts_Product="Content:0" />
  <Place Parts_Product_AddButton="Content:after" />
</Placement>

 


We added a third <Place/> element that configures the Parts_Product_AddButton shape to be rendered after the Content zone. The key thing to understand here is that the Content Item being rendered is a shape, and each shape can have child shapes. The immediate child shapes are know as local zones, but that's just a name: they are simply shapes. "Content" is the name of a shape inside the Content shape itself. To visualize, use the Shape Tracer (Shape Tracing is a feature that you'll find in the Modules section; enable it and navigate to the front-end. You should see a small bar at the bottom of the page in the front end. If not, press CTRL + F5 (to clear the cache), or just F5):

What we see in the left pane is the entire tree of shapes that have been created.
The List shape is created by default by the ProjectionPartDriver because we didn't specify a layout. Each content shape underneath is built by each driver of each part attached to the content item, using "Summary" as the display type by default (the Projector module allows you to specify a different display type for even more flexibility), and contains the shapes as created by each content part driver.
As you can see, our ProductPartDriver created two shapes: Parts_Product and Parts_Product_AddButton. Notice also that the Parts_Product_AddButton is last in the list. The ordering as configured by the Placement.info file is taken into account when adding a shape to the parent shape.

You may have noticed that the Shape Tracing feature slows down the rnedering of the page significantly. However, the shape tracer will only be rendered for local requests and/or when the admin user is logged in.

Adding the product to the ShoppingCart

Now that we have a button, we need to make it do something useful when someone clicks it!
But how do we do that? Well, as it turns out, an Orchard module is essentially an MVC Area, which can have controllers.
So, good MVC developers that we are, we'll create a controller with an action handling a POST request. We will invoke that action whenever a user clicks the "Add to shoppingcart" button.
Nice and simple!

Let's go ahead and start by creating a Controllers folder to our module and add a controller named ShoppingCartController.
We'll also add an action method named Add that takes an id, representing the primary key value of the product that is to be added to our shopping cart. This "id" is actually the content item id.
We also need to decide what the user will see after he has clicked the button. For this demo, we will redirect the user to the shopping cart page (which we will create in a moment).
The initial code should look something like this:

Controllers/ShoppingCartController.cs:

using System.Web.Mvc;
 
namespace Skywalker.Webshop.Controllers
{
    public class ShoppingCartController : Controller {
 
        [HttpPost]
        public ActionResult Add(int id) {
            return RedirectToAction("Index");
        } 
    }
}

 

Notice that the "Add" action is decorated with the HttpPostAttribute. Although not required, the HTTP specification recommends that whenever a request modifies state on the server, you should issue a POST instead of a GET. Since our "Add" action method will change the shopping cart of our user, we use a the POST http method.
To have the Add button invoke this action method, we'll modify the markup of "Product.AddButton.cshtml" by surrounding it with a <FORM> element:

Views/Parts/Product.AddButton.cshtml:

@using (Html.BeginForm("Add""ShoppingCart"new { id = -1 })) {
    <button type="submit">@T("Add to shoppingcart")</button>
}



Notice that we are currently specifying a hardcoded value of -1 for the "id" route value. We need to replace that with the ID of the product content item we want to add.
In order to get that information, we need to pass it when our shape is created, so let's modify our ProductPartDriver:

Drivers/ProductPartDriver.cs:

protected override DriverResult Display(ProductPart part, string displayType, dynamic shapeHelper)
        {
            // To return more than 1 shape, use the Combined method to create a "CombinedResult" object.
            return Combined(
 
                // Shape 1: Parts_Product
                ContentShape("Parts_Product", () => shapeHelper.Parts_Product(
                    Price: part.UnitPrice,
                    Sku: part.Sku
                )),
 
                // Shape 2: Parts_Product_AddButton
                ContentShape("Parts_Product_AddButton", () => shapeHelper.Parts_Product_AddButton(
 
                    // Set a property on the shape called ProductId and set it to the content ID                     ProductId: part.Id                 ))
            );
        }

 

Notice the small change we made: we added a property to the Parts_Product_AddButton shape. The Id property of the part will be the same as the Id of the content item: parts and content items are bound to eachpther via the content item ID.

Now we're ready to modify the shape template:

Views/Parts/Product.AddButton.cshtml:

@{
    var productId = (int) Model.ProductId;
}
@using (Html.BeginForm("Add""ShoppingCart"new { id = productId })) {
    <button type="submit">@T("Add to shoppingcart")</button>
}

 


That was easy enough! Now that we have wired up our button with our shopping cart controller, it is time to create an actual ShoppingCart class that will manage our customers shopping cart.

Creating the ShoppingCart and related classes

Let's create a new folder called Services and create both an IShoppingCart interface and a ShoppingCart class that implements that interface.

Although it is not required to define an interface, it is considered good practice to make our controllers and other classes depend on abstractions instead of concrete implementations. This is mostly desirable when we write unit tests for our module, because it enables us to substitute IShoppingCart dependencies with mocked versions.

Our first draft of IShoppingCart will look like this:

Services/IShoppingCart.cs:

using System.Collections.Generic;
using Orchard;
using Skywalker.Webshop.Models;
 
namespace Skywalker.Webshop.Services
{
    public interface IShoppingCart : IDependency {
        IEnumerable<ShoppingCartItem> Items { get; }
        void Add(int productId, int quantity = 1);
        void Remove(int productId);
        ProductPart GetProduct(int productId);
        decimal Subtotal();
        decimal Vat();
        decimal Total();
        int ItemCount();
        void UpdateItems();
    }
}

 

We'll also create a ShoppingCartItem class inside the Models folder, which will look like this:

Models/ShoppingCartItem.cs:

 

using System;
 
namespace Skywalker.Webshop.Models
{
    [Serializable]
    public sealed class ShoppingCartItem {
        public int ProductId { getprivate set; }
 
        private int _quantity;
        public int Quantity {
            get { return _quantity; }
            set {
                if (value < 0)
                    throw new IndexOutOfRangeException();
 
                _quantity = value;
            }
        }
 
        public ShoppingCartItem() {
        }
 
        public ShoppingCartItem(int productId, int quantity = 1) {
            ProductId = productId;
            Quantity = quantity;
        }
    }
 
}


The ShoppingCartItem class will hold the ID of the product being added as well as the quantity. It is marked [Serializable] to indicate that this class can be serialized if we want to.
The initial ShoppingCart implementation will look like this:

Services/ShoppingCart.cs:

 

using System.Collections.Generic;
using System.Linq;
using System.Web;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Data;
using Skywalker.Webshop.Models;
 
namespace Skywalker.Webshop.Services
{
    public class ShoppingCart : IShoppingCart {
        private readonly IWorkContextAccessor _workContextAccessor;
        private readonly IContentManager _contentManager;
        public IEnumerable<ShoppingCartItem> Items { get { return ItemsInternal.AsReadOnly(); } }
 
        private HttpContextBase HttpContext {
            get { return _workContextAccessor.GetContext().HttpContext; }
        }
 
        private List<ShoppingCartItem> ItemsInternal {
            get {
                var items = (List<ShoppingCartItem>)HttpContext.Session["ShoppingCart"];
 
                if (items == null) {
                    items = new List<ShoppingCartItem>();
                    HttpContext.Session["ShoppingCart"] = items;
                }
 
                return items;
            }
        }
 
        public ShoppingCart(IWorkContextAccessor workContextAccessor, IContentManager contentManager)
        {
            _workContextAccessor = workContextAccessor;
            _contentManager = contentManager;
        }
 
        public void Add(int productId, int quantity = 1) {
            var item = Items.SingleOrDefault(x => x.ProductId == productId);
 
            if (item == null) {
                item = new ShoppingCartItem(productId, quantity);
                ItemsInternal.Add(item);
            }
            else {
                item.Quantity += quantity;
            }
        }
 
        public void Remove(int productId) {
            var item = Items.SingleOrDefault(x => x.ProductId == productId);
 
            if (item == null)
                return;
 
            ItemsInternal.Remove(item);
        }
 
        public ProductPart GetProduct(int productId) {
            return _contentManager.Get<ProductPart>(productId);
        }
 
        public void UpdateItems() {
            ItemsInternal.RemoveAll(x => x.Quantity == 0);
        }
 
        public decimal Subtotal() {
            return Items.Select(x => GetProduct(x.ProductId).UnitPrice * x.Quantity).Sum();
        }
 
        public decimal Vat() {
            return Subtotal() * .19m;
        }
 
        public decimal Total() {
            return Subtotal() + Vat();
        }
 
        public int ItemCount() {
            return Items.Sum(x => x.Quantity);
        }
 
        private void Clear() {
            ItemsInternal.Clear();
            UpdateItems();
        }
 
    }
}

It's basically just a wrapper around the HttpContext.Session object, where we store a list of ShoppingCartItems.
Notice that we are using a hardcoded value of 19% as the VAT rate, but we will make this configurable for our module user in a future post.

To get our hands on the current HttpContext, we injected an IWorkContextAccessor instance that gives us access to the current request and related data.

In order for our shopping cart to calculate totals, it needs to be able to load the product entities from the database. Therefore we are injecting an IContentManager, which is Orchard's API to content item management. It will enable us to query content.
The Add method creates a new ShoppingCartItem instance if there isn't already one in the list for th specified product. If an instance exists, it will simply increase the Amount property.

To use our ShoppingCart, our ShoppingCartController needs a reference to an instance of it. To easiest way to get one is to have Orchard inject one via the constructor. But in order for Orchard to be able to register our class with the dependency container, we need to inherit from IDependency.
That's why IShoppingCart derives from IDependency.

 Let's modify the ShoppingCartController to have a dependency on IShoppingCart and complete the "Add" action method:

Controllers/ShoppingCartController.cs:

using System.Web.Mvc;
using Skywalker.Webshop.Services;
 
namespace Skywalker.Webshop.Controllers
{
    public class ShoppingCartController : Controller
    {
        private readonly IShoppingCart _shoppingCart;
 
        public ShoppingCartController(IShoppingCart shoppingCart) {
            _shoppingCart = shoppingCart;
        }
 
        [HttpPost]
        public ActionResult Add(int id) {
            
            // Add the specified content id to the shopping cart with a quantity of 1.
            _shoppingCart.Add(id, 1); 
 
            // Redirect the user to the Index action (yet to be created)
            return RedirectToAction("Index");
        }
    }
 
}

 


In order for our users to be able to see the shoppingcart, we need to create a view for it. Create a new action called "Index":

public ActionResult Index() {
    return View();
}

Returning Shapes from Controllers

The next logical step would be to create a view for the "Index" action, which would be fine. However, we'll return a shape instead so that theme developers will be able to take advantage of shape overriding and shape alternates.

In order to create a shape, we will use the ShapeFactory to help us with that. We can get a ShapeFactory by having Orchard inject one via the constructor.
Replace the controller's code with the following:

Controllers/ShoppingCartController.cs:

using System.Web.Mvc;
using Orchard;
using Orchard.Mvc;
using Skywalker.Webshop.Services;
 
namespace Skywalker.Webshop.Controllers
{
    public class ShoppingCartController : Controller
    {
        private readonly IShoppingCart _shoppingCart;
        private readonly IOrchardServices _services;
 
        public ShoppingCartController(IShoppingCart shoppingCart, IOrchardServices services)
        {
            _shoppingCart = shoppingCart;
            _services = services;
        }
 
        [HttpPost]
        public ActionResult Add(int id) {
            
            // Add the specified content id to the shopping cart with a quantity of 1.
            _shoppingCart.Add(id, 1); 
 
            // Redirect the user to the Index action (yet to be created)
            return RedirectToAction("Index");
        }
 
        public ActionResult Index() {
 
            // Create a new shape using the "New" property of IOrchardServices.
            var shape = _services.New.ShoppingCart();
 
            // Return a ShapeResult
            return new ShapeResult(this, shape);
        }
    }
}

 

Notice that we are injecting an IOrchardServices instance. IOrchardServices provides access to often-used properties and services, one of them being a New property.
The New property is typed as dynamic, but holds a reference to an IShapeFactory.


Next, we'll create a template file for the "ShoppingCart" shape. The content will just contain a mall snippet of text for now, but will be fleshed out in the next part:

Views/ShoppingCart.cshtml:

TODO: display our shopping cart contents!

 

Let's go ahead and see what happens when we add an item to our shopping cart by clicking one of the "Add to shoppingcart" buttons:

Well, that doesn't look right. So why did we get a 404?
The answer is that since our module is really just an MVC area, we need to include the name of our module as the area when generating urls to controller actions.

We can fix that by modifying our "Product.AddButton.cshtml" template by including a value for the area route value:

Views/Parts/Product.AddButton.cshtml:

@{
    var productId = (int) Model.ProductId;
}
@using (Html.BeginForm("Add""ShoppingCart"new { id = productId, area = "Skywalker.Webshop" })) {
    <button type="submit">@T("Add to shoppingcart")</button>
}

 

Now let's try again:

 

Well, that's still not exactly what we want to see, but at least we got one step further.
The problem right now is that Orchard validates POST submissions by means of the AntiForgeryAuthorizationFilterAttribute.

Anti Forgery

Now, we could either turn anti forgery validation off in its entirety (using a setting in Module.txt), or include the required anti forgery field with the post back. We'll do the latter, because it's the right thing to do. Fortunately, that's very easy because Orchard comes with a helper method that can generate a form that automatically includes a hidden antiforgery field:

@{
    var productId = (int) Model.ProductId;
}
@using (Html.BeginFormAntiForgeryPost(Url.Action("Add""ShoppingCart"new { id = productId, area = "Skywalker.Webshop" }))) {
    <button type="submit">@T("Add to shoppingcart")</button>
}


That didn't hurt, now did it?

Let's try again by clicking any "Add to shoppingcart" button:

Spot on! Although, where did all of the other stuff go, like the main menu, the CSS and layout?
Do we need to provide a master page of some sorts around our ShoppingCart template?
Not at all: we just need to tell Orchard that the shape should be added to the Content zone of the Layout shape. We do that by adding the [Themed] attribute to our "Index" action method (which lives in the Orchard.Themes namespace, so be sure to import that namespace):

        [Themed]         public ActionResult Index() {
 
            // Create a new shape using the "New" property of IOrchardServices.
            var shape = _services.New.ShoppingCart();
 
            // Return a ShapeResult
            return new ShapeResult(this, shape);
        }

 


And when we try again:

 

We know that life is good. Even better, we have now limitless control over whatever features we want to add to our front end! We know the most important parts: how to create and render shapes and how to create controllers with action methods that return shapes that play nice with the rest of the layout.

Of course there are quite a few more things to learn, such as all of the other integration and extensibility points, how to extend the admin UI, or leverage the Caching module, and use the ContentManager to manage and query content, etc.
But the most important part at this stage is that if you know how to build ASP.NET MVC applications, you can safely start building modules for Orchard.

Rendering the shoppingcart

Rendering the shoppingcart is very easy, but we'll make it a bit more interesting by throwing a little client script library called knockoutJS into the mix.

Continue to part 7 - Rendering the ShoppingCart and ShoppingCartPanel.

 

Download the source: Part06.zip

181 comments

  • ... Posted by Mel Shellam Posted 01/10/2012 02:50 PM

    Looking forward to the next article, keep up the good work. It's great to see some 'practical' Orchard demonstrations.

  • ... Posted by Piedone Posted 01/10/2012 06:37 PM

    Excellent series! I wonder if there are better ways to store shopping cart data than the session? I think Orchard is not using session at all but is able to track the logged in user somehow...

  • ... Posted by Sipke Schoorstra Posted 01/10/2012 07:05 PM (Author)

    Thanks, glad you like it! <br><br>As a matter of fact, since Orchard is really just an ASP.NET MVC application (be it a rather advanced one), it is perfectly capable of using the session.<br>Orchard uses Forms Authentication to track the logged in user. Forms Authentication by default uses a cookie to track the user, but can be configured to use a cookieless method by storing the session identitifer inside of the url.<br><br>In any case, there are definitely other options you could choose to store the shoppngcart data. One being cookies, since the data being stored is very simple.<br><br>Another option could be to store the shopping cart data inside the Customer record (which we have yet to discus by the way). However, that obviously only works if there is actually a customer signed in into the website (unless we store unauthenticated user entries in the database, but I'm not a big fan of that).<br><br>The safest way I think is storing the cart in a cookie, because that way the data won't get lost during an application pool recycle. Once a user is signed in, I then store the shoppingcart data in the database.<br><br>Perhaps I will update this post that demonstrates how to use the cookies approach as well as storing the shoppingcart data in the database whenever we have a signed in user.

  • ... Posted by Piedone Posted 01/10/2012 08:03 PM

    I see, thanks. Have you considered using the approach of storing a unique, random identifier of the shopping "session" (session used not in technical sense here) in the session or in a cookie, not the whole whopping cart, than storing the shopping cart in the DB, indexed by this identifier?<br>BTW will you release the module in the gallery?

  • ... Posted by zgofmontreal Posted 01/11/2012 12:31 AM

    very clear and very help.<br>thanks very much

  • ... Posted by Sipke Schoorstra Posted 01/11/2012 02:24 AM (Author)

    I considered it, but doing that takes a bit more work without any real gain (unless I am missing something here). For example, the database will get polluted as visitors create shoppingcarts, because the session or cookie will eventually get lost, but the database records containing shoppingcart items will remain in store.<br><br>Perhaps you dislike the idea of storing the shoppingcart items themselves into a session or a cookie. But consider the fact that a ShoppingCartItem only consists of 2 integers: a product ID and a Quantity. Storing this in a session requires very little space; most of the time even less than a random identifier (unless you use a PK value of type int32 of course). <br><br>Because the shoppingcart data is so small, I think the simplicity of storing the shoppingcart data directly in a session or cookie (or even in a column of the customer record) outweighs saving a few bytes on cookie or session data. You do have a valid point however, when a customer tries to add over 75 unique items to his shopping cart, because that would mean the serialized data would take about 4.7 kb, which is too large for a cookie.<br><br>In any case, perhaps the ShoppingCart should be modified in such a way that the persistance mechanism should be delegated to another class: for example, an abstract ShoppingCartPersistenceProvider with a few default implementations.<br>That way, we all have a choice to implement storage in a way we think is best for our situation.<br><br>Yes - the webshop module will be published into the gallery at the end of this tutorial as well as be uploaded to Codeplex.<br><br>Thanks for starting the discussion.

  • ... Posted by Snygging_ Posted 01/11/2012 08:33 AM

    Really good, real world example!

  • ... Posted by Carl Berisford-Murray Posted 01/11/2012 04:18 PM [http://www.facebook.com/carlbm]

    Nice series. Thanks!<br>We did something similar in about July last year and it's refreshing to see that a lot of your decisions were the same as ours :) But definitely some improvements from our side. <br><br>Following on some of the other comments - we stored the cart in the database and referenced it by the username. We also used anonymous profiles (which create a GUID username) for anonymous carts which worked well - after a little bit of logic to upgrade anonymous carts to 'logged in' ones. This also had the advantage of never forgetting anything that a customer placed in their cart.

  • ... Posted by Piedone Posted 01/11/2012 07:30 PM

    I'm pretty new to ASP.NET, so have no real experience with sessions on this platform, but I've read some advices for not using sessions, so was just curious about your decisions. Thanks for the explanation!

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

    Glad to hear it!<br>I have a question about the anonymous profile. Where did you store the GUID? Using a cookie, or a session variable?<br><br>I love the idea of upgrading from anonyous carts to logged in ones, and will definitely include that in this tutorial in the upcomging parts.

  • ... Posted by Bobt59 Posted 01/13/2012 04:54 AM

    I've been following along and this is a great article! However, I had a problem and ther must be somthing I'm doing wrong. I add my UpdateFrom2() method to the migrations.cs class then open up the web, sign in and go to the dashboard and update the Webshop module. Then I see the Product Catalog in the New area. I click on it but the page I get is not like the one you show. I do not see the "Publish Now" button; I do not see the Title or Permalink button either. I do see both the "Show on Main Menu" and "Show on admin menu" check boxes. <br><br>I thought that maybe the migrations.cs UpdateFrom2() method should have included a .WithPart("CommonPart") and .WithPart("RoutePart") so I added a new UpdateFrom3() method to add that, but it didn't work; I still get the same page. Everything else up to there came out exactly as the tutorial.<br><br>I'm going to keep looking for my mistake, but maybe it's simple oversite and you know exactly what it is.<br>Thanks

  • ... Posted by Bobt59 Posted 01/13/2012 05:16 AM

    I added one more update in the migrations class and this got me most of it, but I'm still not seeing the "Publish" button at the bottom next to the "Save" button. But I get the rest. Do I need to include the .WithPart("Common") and .WithPart("RoutePart") in my method? It appears that those were not there when I followed the tutorial so I don't know how you got them. Maybe I don't have them spelled correctly? Maybe that's why the Publish button doesn't appear yet? I copied an image of what my method looks like (below)

  • ... Posted by Demetrios Seferlis Posted 01/13/2012 10:10 AM [http://twitter.com/dseferlis]

    One of the best tutorials ever seen for orchard module development.<br>We all thank you Sipke.

  • ... Posted by Sipke Schoorstra Posted 01/13/2012 05:47 PM (Author)

    I'm not sure what's causing the Publish button not to appear, but I'll include the source of the module so you can try it with a clean installation and perhaps resolve it. Perhaps I omitted something in the tutorial which I did in the project itself.

  • ... Posted by Bobt59 Posted 01/15/2012 09:05 PM

    Hi again. I'm following along in the code and ran up against another problem. In the section where you add the ViewModels folder and the UpdateShoppingCartItemVM class, I kept gettin a compile error saying:<br><br>'Orchard.Webshop.Services.IShoppingCart' does not contain a definition for 'UpdateItems' and no extension method 'UpdateItems' accepting a first argument of type 'Orchard.Webshop.Services.IShoppingCart' could be found (are you missing a using directive or an assembly reference?) H:\BT.CMS\BT.OrchardWebshopDemo\src\Orchard.Web\Modules\Orchard.Webshop\Controllers\ShoppingCartController.cs 97 27 Orchard.Webshop<br><br>I fixed this by adding a method definition to the IShoppingCart interface for UpdateItems by adding:<br>void UpdateItems();<br><br>My errors went away after that. Just wondering if that is the correct fix. I searched and searched the tutorial but I did not see where you indicated adding that to the interface.<br><br>This is the best article I've seen to date on how to use Orchard. Thanks for doing this! <br><br>

  • ... Posted by Piedone Posted 01/15/2012 10:05 PM

    Bertrand here: <a href="http://orchard.codeplex.com/discussions/254456" rel="nofollow">http://orchard.codeplex.com/di... also explains some implications of using sessions considering a web farm environment, that might not be closely related in this case.

  • ... Posted by Bobt59 Posted 01/15/2012 11:56 PM

    I hope I am not missing something, but I'm continuing on with following the tutorial and updating my code. After the section where you add the MVC3Futures, I was getting build errors because the IShoppingCart interface had no definition for Clear and AddRange. Again, I don't know if this is the correct fix, but i added these two definitions to the IShoppingCart class:<br><br>void Clear();<br><br>void AddRange(IEnumerable<shoppingcartitem> items);<br><br>And then in the ShoppingCart class I implemented those two methods by changing the<br><br>private void Clear(){...}<br> to <br>public void Clear(){...}<br><br>and then adding this AddRange method:<br><br> public void AddRange(IEnumerable<shoppingcartitem> items)<br> {<br> foreach (var item in items)<br> {<br> this.Add(item.ProductId, item.Quantity);<br> }<br> }<br><br>This eliminated the compile errors but I still got a couple of exceptions. The first was due to not having Microsoft.Web.Mvc.dll in the bin folder of the Orchard.Web application (it was in the Orchard.Webshop\bin folder) so I copied Microsoft.Web.Mvc.dll to the Orchard.Web\bin folder.<br><br><br>Nothing blew up after these changes so I'm continuing on. If anyone else is seeing this, I hope my comments will help. If I am wrong in what I'm doing, I hope someone will be kind enough to tell me. <br><br>Again, thanks for this wonderful tutorial!<br>Bob </shoppingcartitem></shoppingcartitem>

  • ... Posted by Bobt59 Posted 01/16/2012 12:56 AM

    I'm afraid I may have messed things up. I am not able to see any indication that GetItems is ever being invoked. I put a breakpoint at the beginning of the GetItems() method in the SHoppingCartController. I have the [AjaxOnly] attribute in front of the GetItems() method, but it never gets here. Perhaps my fixes (in my earlier comments) are not meant to be.<br><br>Thanks,<br>Bob

  • ... Posted by nxcong Posted 01/16/2012 04:21 AM

    data-update-shoppingcart-url="@Url.Action("Update", "ShoppingCart", new { area = "Orchard.WebShop" })"<br>A required anti-forgery token was not supplied or was invalid.

  • ... Posted by nxcong Posted 01/16/2012 05:04 AM

    I had resolved this problem.Reference : <a href="http://orchard.codeplex.com/discussions/249394" rel="nofollow">http://orchard.codeplex.com/di...

  • ... Posted by Sipke Schoorstra Posted 01/16/2012 06:12 AM (Author)

    The IShoppingCart interface should have an void UpdateItems() method, so your fix is correct. I accidentally omitted that step. Thanks for letting me know!

  • ... Posted by Sipke Schoorstra Posted 01/16/2012 06:18 AM (Author)

    Those are the correct fixes. I'll update this and the previous posts with these missing steps. With regards to the exception, copying Microsoft.Web.Mvc.dll to Orchard.Web\bin should indeed work. Alternatively, you could copy the file to Orchard.Web\App_Data\Dependencies. I believe that the Orchard module installer will copy dependencies to that folder.<br><br>Glad to see you got it working, and thanks for sharing your findings.

  • ... Posted by Sipke Schoorstra Posted 01/16/2012 06:23 AM (Author)

    It's probably something small. I'll be posting the source up until part 7 tomorrow, so you could compare it with your version. In the mean time, maybe you could use F12 Developer Tools or FireBug to see if any javascript errror occurs or if the call returns an HTTP error.

  • ... Posted by Sipke Schoorstra Posted 01/16/2012 06:28 AM (Author)

    Thanks for bringing that up! I haven't come cross this issue yet, but I can imagine that the Anti Forgery filter in Orchard will at some stage throw an exception when a user is authenticated, so the antiforgery token should be posted as well.

  • ... Posted by nxcong Posted 01/16/2012 08:12 AM

    I saw a problem about : HttpContext.Session["ShoppingCart"],when I delete a product,in session still have one,session has not been updated.

  • ... Posted by Carl Berisford-Murray Posted 01/16/2012 10:47 AM [http://www.facebook.com/carlbm]

    Had to look over the code :) We stored the shopping cart in the database with a unique key of the username. We then attached the items to the basket.<br>All of the shopping cart code is 'back end' and stored in the database, using AJAX for front end updates.<br>The GUID is generated and accessed using ASP.NET's anonymous profile. It's here for a non-logged in user: HttpContext.Profile.UserName and it changes to the user's username once they have logged in. Very little work was required to implement it as Orchard uses forms authentication.<br><br>HTH

  • ... Posted by Bobt59 Posted 01/16/2012 12:24 PM

    Silly me! Should have thought of that. When I used the F12 tools I discovered that it is not finding any of my scripts. The URL looks ok and it is there so I must be defining it wrong.<br><br>URL /OrchardLocal/Modules/Orchard.Webshop/scripts/knockout-2.0.0.js GET 404 <br><br>URL /OrchardLocal/Modules/Orchard.Webshop/scripts/jquery.linq.min.js GET 404 <br><br>URL /OrchardLocal/Modules/Orchard.Webshop/scripts/shoppingcart.js GET 404<br><br>These three scripts are present in my Scripts folder. My ResourceManifest class looks like:<br><br> public class ResourceManifest : IResourceManifestProvider<br> {<br> public void BuildManifests(ResourceManifestBuilder builder)<br> {<br> var manifest = builder.Add();<br> manifest.DefineScript("KnockoutJS").SetUrl("knockout-2.0.0.js").SetVersion("2.0.0");<br> manifest.DefineScript("LinqJS").SetUrl("jquery.linq.min.js").SetVersion("2.2.0.2").SetDependencies("jQuery");<br><br> manifest.DefineStyle("Webshop.Common").SetUrl("common.css");<br> manifest.DefineStyle("Webshop.ShoppingCart").SetUrl("shoppingcart.css").SetDependencies("Webshop.Common");<br> manifest.DefineScript("Webshop.ShoppingCart").SetUrl("shoppingcart.js").SetDependencies("jQuery", "KnockoutJS", "LinqJS");<br> manifest.DefineStyle("Webshop.ShoppingCart.Widget").SetUrl("shoppingcart-widget.css").SetDependencies("Webshop.Common");<br><br> } <br> }<br><br>Anyway, I'll take a look at the source when you post it. Thanks. Bob

  • ... Posted by Bobt59 Posted 01/16/2012 12:45 PM

    I found the problem. I had NOT copied the web.config file into my Scripts folder. You were correct! it was something simple. As you suggested, I used the F12 tools to discover that my scripts were not being found - thus always returning a 404 error. Thanks again for your excellent work! <br>Bob

  • ... Posted by David Posted 01/16/2012 02:02 PM

    Enjoying your tutorial thank you. <a href="http://curiousdeveloper.wordpress.com/2011/08/26/importing-cms-built-content-types-in-orchard/" rel="nofollow">http://curiousdeveloper.wordpr... makes the case that drivers in modules should include 'import' and 'export'. Is the omission to keep the tutorial simple, or because you have a different view of the matter? Thanks.

  • ... Posted by nxcong Posted 01/17/2012 04:14 AM

    //AntiForgeryToken.js<br><br>//-----------------------------<br>/// <reference path="jquery-1.4.2.js"><br><br>(function ($) {<br> $.getAntiForgeryToken = function (tokenWindow, appPath) {<br> // HtmlHelper.AntiForgeryToken() must be invoked to print the token.<br> tokenWindow = tokenWindow && typeof tokenWindow === typeof window ? tokenWindow : window;<br><br> appPath = appPath && typeof appPath === "string" ? "_" + appPath.toString() : "";<br> // The name attribute is either _RequestVerificationToken,<br> // or RequestVerificationToken{appPath}.<br> var tokenName = "RequestVerificationToken" + appPath;<br><br> // Finds the <input name="{tokenName}" type="hidden" value="..."> from the specified window.<br> // var inputElements = tokenWindow.$("input[type='hidden'][name=' + tokenName + "']");<br> var inputElements = tokenWindow.document.getElementsByTagName("input");<br> for (var i = 0; i < inputElements.length; i++) {<br> var inputElement = inputElements[i];<br> if (inputElement.type === "hidden" && <a href="http://inputElement.name" rel="nofollow">inputElement.name === tokenName) {<br> return {<br> name: tokenName,<br> value: inputElement.value<br> };<br> }<br> }<br> };<br><br> $.appendAntiForgeryToken = function (data, token) {<br> // Converts data if not already a string.<br> if (data && typeof data !== "string") {<br> data = $.param(data);<br> }<br><br> // Gets token from current window by default.<br> token = token ? token : $.getAntiForgeryToken(); // $.getAntiForgeryToken(window).<br><br> data = data ? data + "&" : "";<br> // If token exists, appends {<a href="http://token.name" rel="nofollow">token.name}={token.value} to data.<br> return token ? data + encodeURIComponent(<a href="http://token.name" rel="nofollow">token.name) + "=" + encodeURIComponent(token.value) : data;<br> };<br><br> // Wraps $.post(url, data, callback, type) for most common scenarios.<br> $.postAntiForgery = function (url, data, callback, type) {<br> return $.post(url, $.appendAntiForgeryToken(data), callback, type);<br> };<br><br> // Wraps $.ajax(settings).<br> $.ajaxAntiForgery = function (settings) {<br> // Supports more options than $.ajax(): <br> // settings.token, settings.tokenWindow, settings.appPath.<br> var token = settings.token ? settings.token : $.getAntiForgeryToken(settings.tokenWindow, settings.appPath);<br> settings.data = $.appendAntiForgeryToken(settings.data, token);<br> return $.ajax(settings);<br> };<br>})(jQuery);<br><br>//Edit file shoppingcart.js<br>//....<br>var saveChanges = function () {<br><br> var token = $.getAntiForgeryToken().value;<br> var data = $.Enumerable.From(shoppingCart.items()).Select(function (x) { return { productId: <a href="http://x.id" rel="nofollow">x.id, quantity: x.quantity() }; }).ToArray();<br> var url = $("article.shoppingcart").data("update-shoppingcart-url");<br> var config = {<br> url: url,<br> type: "POST",<br> data: data ? JSON.stringify(data) : null,<br> dataType: "json",<br> contentType: "application/json; charset=utf-8",<br> __RequestVerificationToken:token<br> };<br> $.ajax(config);<br> };<br>//....<br>//Edit file ResourceManifest.cs<br>//...<br>manifest.DefineScript("AntiForgeryToken").SetUrl("AntiForgeryToken.js").SetDependencies("jQuery");<br>manifest.DefineScript("Webshop.ShoppingCart").SetUrl("shoppingcart.js").SetDependencies("jQuery", "KnockoutJS", "LinqJS", "AntiForgeryToken", "Globalize.Cultures");<br>//....</reference>

  • ... Posted by Sipke Schoorstra Posted 01/17/2012 06:59 AM (Author)

    Glad to hear it! To be honest, I didn't know what the use of the Identity part was until you pointed it out. Based on that post, I think it's a good idea to attach the Identity part to any content type that is not routable. Thanks for pointing this out!

  • ... Posted by Sipke Schoorstra Posted 01/17/2012 07:06 AM (Author)

    You're welcome! Great to see that you have worked it out. In any case, you may download the sourcecode via the link at the top of this post.

  • ... Posted by Ha Doan Manh Posted 01/17/2012 10:51 AM

    Thank you for the tutorials

  • ... Posted by Sipke Schoorstra Posted 01/17/2012 11:31 AM (Author)

    I see. Please check the attached sourcecode to see if you're using the correct UpdateItems implementation. Let me now if you're still having problems.

  • ... Posted by Sipke Schoorstra Posted 01/17/2012 11:35 AM (Author)

    True, when running in a web farm you need to configure shared session state. Thanks for sharing that.

  • ... Posted by Jgross Posted 01/17/2012 01:42 PM

    Hello Sipke, it's a great tutorial. I enjoy it ver much. But in the New Product Ctalog, i did not see the title section so that I can't enter a title for the new Product Catalog. I did the things exactly as you described in the UpdateFrom2-Method. What's wrong?<br>Best regards

  • ... Posted by C M G Posted 01/17/2012 09:55 PM

    Just run into the same problem. I'm trying to back up to just before the ProductCatalog was created to see if I can work it out.

  • ... Posted by C M G Posted 01/17/2012 10:46 PM

    I deleted the Product Catalog content type and then checked in all of the database tables to make sure that those tables that mentioned content type in their names had no mention of the Product Catalog. Then I added the following line to my UpdateFrom2 function:-<br><br>.WithPart(typeof(RoutePart).Name)<br><br>Finally I edited the OrchardFrameworkDataMigrationRecord table to set the version number back to 2.<br><br>I don't know if there is an easier way to back out the changes nor do I know if I've done it correctly. I do know that I could add books to my new product catalog and then see them on the Books menu option.

  • ... Posted by Sipke Schoorstra Posted 01/18/2012 06:00 AM (Author)

    Thanks Jgross. I had a look at my installation, and I'm afraid I forgot to include the line in UpdateFrom2 method that attaches the RoutePart to the ProductCatalog. I added the RoutePart via the Admin so that it had a name and slug. I'll update this post to include that line, as it is quite confusing without it. Thanks for letting me know!

  • ... Posted by Sipke Schoorstra Posted 01/18/2012 06:08 AM (Author)

    That should work just fine. Another method is to create another UpdateFromX method, run it via the admin. You could also directly update the UpdateFrom2 method by including the RoutePart. Although Orchard will not perform the change for you, you then manually add the RoutePart via the admin.

  • ... Posted by C M G Posted 01/18/2012 01:53 PM

    Thanks for that Spike.<br><br>I'm new to Orchard so it's nice to know that I'm not barking up the wrong tree!<br><br>Great tutorial by the way so thanks for that too.

  • ... Posted by Andr3wll Posted 01/19/2012 02:25 PM

    Very good tutorial and great for learning! So far so good, only when I try the Dashboard > Books Catalog and click 'Choose Items' to add the 3 books it gave me 'Object reference not set to an instance of an object' error. Then I modified the 'Product Catalog' content type and add 'Common' content part to it and it works. I don't know maybe it just me.

  • ... Posted by Sipke Schoorstra Posted 01/19/2012 06:04 PM (Author)

    Thanks, very good to hear! I couldn't reproduce the problem you mentioned, but I just saw that I also added the CommonPart to the ProductCatalog. I ran into the same situation in another section. Anyway, I'm glad you figured it out.

  • ... Posted by Sipke Schoorstra Posted 01/20/2012 07:53 PM (Author)

    Excellent, so there actually was a Knockout module. Thanks!

  • ... Posted by Coach James Posted 01/21/2012 07:26 AM

    The force is strong within you Skywalker...<br><br>After 15 years of classic asp and object javascripting, I almost started supporting Wordpress and Drupal (back into Unix/Perl/PHP) because of the lack any proper ASP/CMS package, and almost built my own, until I started looking for any open source projects with ASP.NET, and finally found my first acceptable ASP.NET CMS package and its Orchard!<br><br>Then came the fun part of migrating my classic asp and json's into ASP.NET MVC 3 structure USING C# and utilizing the nice architectural rendering engine and other other great benefits by Orchard designers by the way of "razor", etc.<br><br>But it was taking way too long to figure out where everything goes, whats happening behing the curtains, and what options and limits I need to deal with when converting my database driven applications.<br><br>Thanks to Skywalker "Orchard can and will be a Big Hit this year!"<br>But don't let David Hyden know you let the cat out of the bag, cause he's been keeping it a secret.<br><br>Coach James<br>

  • ... Posted by Sipke Schoorstra Posted 01/21/2012 10:29 AM (Author)

    I'm sure that most of us can relate to that. I too started searching for a decent opensource .NET CMS many years ago, and even built my own on ASP.NET Webforms version (O3 CMS, which is long dead, good grace). Then at some point I sensed a disturbance in the force, and it was the inception of Orchard. It had it all: extensibility, composability, a decent looking back-end, themes, and a gallery. Plus, it was built on ASP.NET MVC into which I fell in love with.<br><br>But learning to create real world themes and building custom modules was quite challenging. Especially figuring out how to skin shapes and alternates and theming cost me some sleep.<br><br>Anyway, I couldn't have written this series without the other excellent Orchard blog posts out there.<br>So thanks for your comment, I'm glad you think it's useful!<br><br>It is my sincere hope that Orchard will be the Big Hit of this year, and become the default choice when implementors need to build a website.<br><br>I wouldn't be suprised if David Hayden's got something up his sleeve. Can't wait to see!

  • ... Posted by Ned Stoyanov Posted 01/23/2012 02:51 AM

    Is there any chance you can post the source code? I get an exception when in Part 5 i try and add items to the book list. It looks like it happens when it tries to execute item.As<commonpart>, I tried adding the common part to the Migration.cs file UpdateForm2 method but it didn't work.</commonpart>

  • ... Posted by Sipke Schoorstra Posted 01/23/2012 06:12 AM (Author)

    Hi Ned, you can download the source up until part 9 from here: <a href="http://skywalkersoftwaredevelopment.net/Media/Default/BlogPost/blog/writing-an-orchard-webshop-module-from-scratch-part-9/Orchard.Webshop.Part-9.zip" rel="nofollow">http://skywalkersoftwaredevelo...<br><br>You should be able to manually add the CommonPart to the ProductCatalog content type using the admin UI. Migrations should absolutely work, make sure that you're welding CommonPart on to the ProductCatalog content type.

  • ... Posted by J3ffb Posted 01/24/2012 01:38 PM

    Brilliant series of blog posts, great work :)<br><br>Just wondering if it would be better to cast the ContentItem as a ITitleAspect so that it can support ContentTypes that don't use the RoutePart?

  • ... Posted by Sipke Schoorstra Posted 01/25/2012 07:49 AM (Author)

    Interesting thought! I haven't tried it out, but it should work if both TitlePart and RoutePart implement that interface.

  • ... Posted by Kris van der Mast Posted 01/25/2012 07:57 AM [http://www.krisvandermast.com/]

    Hi,<br><br>you can also start by going through this hands on lab: <a href="http://www.krisvandermast.com/downloads.html" rel="nofollow">http://www.krisvandermast.com/....

  • ... Posted by Sipke Schoorstra Posted 01/25/2012 08:55 PM (Author)

    Nice work Kris, seems great for getting started!

  • ... Posted by nxcong Posted 01/29/2012 02:48 PM

    Hi,I get an error :<br>var customer = currentUser.ContentItem.As<customerpart>();<br> "Orchard.ContentManagement.ContentItem' does not contain a definition for 'As' and no extension method 'As' accepting a first argument of type 'Orchard.ContentManagement.ContentItem' could be found (are you missing a using directive or an assembly reference?)".Help me,please.Tks</customerpart>

  • ... Posted by Sipke Schoorstra Posted 01/29/2012 04:04 PM (Author)

    'As' is an extension method which exists in the Orchard.Framework assembly, so make sure you have a reference to that project. You also need to add a using statement for the 'Orchard.ContentManagement' namespace (since that's where 'As' is defined).

  • ... Posted by nxcong Posted 01/30/2012 12:56 AM

    Thanks.I have done.:)

  • ... Posted by Claire Botman Posted 01/31/2012 04:12 AM [http://clairebotman.id.au]

    Hi Sipke, you have a piece missing from your code for UpdateFrom2() above. There should be a .WithPart(typeof(CommonPart).Name) as per your source code, otherwise we get an exception as Ned said.<br>Working through your tutorials & learning lots, thanks for taking the time to give us this great resource.

  • ... Posted by Davi Posted 01/31/2012 08:29 PM

    hi, get an error when I click in the "Choose Items":<br><br>Object reference not set to an instance of an object.<br><br>Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. <br><br>Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.<br><br>Source Error: <br><br><br>Line 7: Layout.Title = T("Choose Items");<br>Line 8: <br>Line 9: var targetContainers = ((IEnumerable<contentitem>)Model.Containers).Select(<br>Line 10: contentItem => new SelectListItem {<br>Line 11: Text = T("Move to {0}", contentItem.ContentManager.GetItemMetadata(contentItem).DisplayText ?? contentItem.ContentType).ToString(),<br><br>Source File: d:\2TECNOLOGIA\ASP.NET\ORCHARDTRAINING\src\Orchard.Web\Modules\Orchard.Lists\Views\Admin\Choose.cshtml Line: 9 <br><br>Can some one help me</contentitem>

  • ... Posted by Mikael Östberg Posted 02/01/2012 12:15 AM

    The problem is that the Product Catalog Content Type is missing the CommonPart. Either add it though the Admin interface by Content -> Content Types -> Product Catalog -> Edit and the choose Add Parts. Tick the Common checkbox and save.<br><br>Or, which I think is the way it should have been done, add .WithPart(typeof(CommonPart).Name) to the code which defines it, like so<br><br>ContentDefinitionManager.AlterTypeDefinition("ProductCatalog", type => type<br> .WithPart(typeof(CommonPart).Name)<br> .WithPart(typeof(ContainerPart).Name)<br> .WithPart(typeof(MenuPart).Name)<br> .WithPart(typeof(AdminMenuPart).Name)<br> .WithPart(typeof(RoutePart).Name)<br> .Creatable()<br> );

  • ... Posted by Davi Posted 02/01/2012 11:50 AM

    Thanks ! It works fine...

  • ... Posted by Katy Posted 02/01/2012 09:02 PM

    Hi! I'm following along and I'm in Part 9 where I'm ready to enable the "Simulated Payment Service Provider" feature, but when I build the project, I'm getting an error that the type or namespace "PaymentResponseStatus" could not be found. Where is this being defined? I tried to look at the source code, but it looks like I have everything that is in the source code.<br><br>Thanks,<br>Katy

  • ... Posted by Katy Posted 02/01/2012 09:43 PM

    I found the file that I was missing, it was Extensibility/PaymentResponseStatus.cs. I added that file and then re-built the project and once the page opened, I was missing the Shopping Cart Widget, the price and sku on the books and the "Add to shopping cart" buttons. The code is all still in my project, but can you tell me how to get the pages to show the buttons again? <br><br>Thanks,<br>Katy

  • ... Posted by Alexander To Posted 02/07/2012 01:15 PM [http://www.facebook.com/profile.php?id=1365463738]

    Hi<br>May I know how does the constructor of ShoppingCartController work? I can't relate it to how Orchard know which class to instantiate and inject into the constructor. If I create another class called ShoppingCart2 which implements IShoppingCart, which one will be injected??<br>Thanks

  • ... Posted by Alexander To Posted 02/07/2012 02:59 PM [http://www.facebook.com/profile.php?id=1365463738]

    Nevermind, I trace the code and roughly know how it works now

  • ... Posted by Brent Arias Posted 02/07/2012 06:06 PM

    You have the requirement "Each catalog will show one type of product (e.g. Books, DVDs, CDs)" and you say "There is no technical restriction that prevents other product types to show up." But it seems as if it is a technical limitation. The admin drop-down to specify what the content type "contains" only allows one containable type to be selected. How then would "any/every containable" type, having the product part, be specified in a single product catalog?

  • ... Posted by Sipke Schoorstra Posted 02/07/2012 10:51 PM (Author)

    Hi Katy, I'm not sure why the buttons aren't showing, but perhaps you could check the log files in the App_Data/Logs folder to see if any exceptions are being thrown. It happened to me more than once that an error occurred during the execution of certain drivers. These exceptions are swallowed, so that the page itself gets rendered, except for the shapes created by the faulting drivers.

  • ... Posted by Sipke Schoorstra Posted 02/07/2012 10:55 PM (Author)

    Great. Although I didn't test it you will probbaly just get one or the other implementation, based on which implementation AutoFac will find first. Shoudl you ever have multiple implementations of a certain interface, you could inject them via the constructor by injecting an IEnumerable of the interface definition, e.g. "IEnumerable<ishoppingcart> shoppingCarts". That would give you all implementations that exist. Then, you can provide your own logic to determine which implementation to use.</ishoppingcart>

  • ... Posted by Sipke Schoorstra Posted 02/07/2012 10:56 PM (Author)

    Hi Nick, glad you like it!<br>I am curious about your thoughts, could you please share them? Thanks.

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

    Hi Brent, as it currently is, the Container part allows you to either allow all content types, or just a specific content type. Should I want to show different types of content items in my catalog, I would have to set the drop-down to "all types". That would work, although I agree that it would be much nicer if we could specify to only display content items that have a specific content part attached to it (ProductPart in this case).<br>Doing so is quite possible, but it would require us to create a customized ContainerPart of sorts.<br>However, I would then rather have a look at the Projector module that's coming with the next release of Orchard, where we could specify a query that selects all content items that have the content part attached.

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

    Hi Brent, as it currently is, the Container part allows you to either allow all content types, or just a specific content type. Should I want to show different types of content items in my catalog, I would have to set the drop-down to "all types". That would work, although I agree that it would be much nicer if we could specify to only display content items that have a specific content part attached to it (ProductPart in this case).<br>Doing so is quite possible, but it would require us to create a customized ContainerPart of sorts.<br>However, I would then rather have a look at the Projector module that's coming with the next release of Orchard, where we could specify a query that selects all content items that have the content part attached.

  • ... Posted by Sipke Schoorstra Posted 02/08/2012 06:19 PM (Author)

    That's true in case of the SimulatedPaymentServiceProvider, but that's ok since it's just a simulation. Other payment providers will require some sort of hash tag. Each payment provider will implement it's security mechanism differently, and it just so happens to be that the SimulatedPaymentServiceProvider doesn't implement any security at all. But I guess it would probably make for a nice example to do so.

  • ... Posted by Nick Bratym Posted 02/09/2012 09:47 AM [http://www.facebook.com/nbnick]

    Hi, again. I have one more question for you. It seems, when a product part displayed as content item in a content items list, in the dashboard, it's displayed by the product driver, and that why we got add to shopingcart button in the dashboard. Could you explain, how to avoid that?

  • ... Posted by nxcong Posted 02/09/2012 10:29 AM

    I want to ask how to set up the permission for users that they just can post on my site but can’t log in admin page to create their own product. I tried to log in permission to allow users can publish,unpublish,edit,delete product but they still can log in admin page.???Sorry about my english.

  • ... Posted by Sipke Schoorstra Posted 02/11/2012 11:22 AM (Author)

    Hi nxcong, as long as the user does not have the "AccessAdminPanel" permission, then the user will not be able to login into the admin. So, of you create a new user, be sure that he is in none of the following groups: Administrator, Editor, Moderator, Author or Contributor. Both Anonymous and Authenticated users will have permission to add comments on the front end.

  • ... Posted by Sipke Schoorstra Posted 02/11/2012 11:31 AM (Author)

    Hi Nick,<br><br>You're correct: when Orchard renders a list of Content Items, it will invoke all of the content part drivers of each content item. To avoid displaying the "ShoppingCart" button from appearing in the back end, you should create a so called "alternate" shape and template. By convention, Orchard supplies the "SummaryAdmin" displaytype when invoking the Display method on the drivers, which you can use to determine of you should create different shape specifically for being rendered in the admin.<br><br>I will update update the post and demonstrate how this is done.<br><br>Thanks for your question.

  • ... Posted by Sipke Schoorstra Posted 02/11/2012 11:36 AM (Author)

    You'e absolutely right: the Webshop module's OrderController should not simply trust the response from the PSP. Instead, it should use the protocol that the PSP uses in order to validate the response. All PSP's will provide some sort of hash value in the feedback, which the Webshop can then validate. <br><br>Since this security mechansim differs from PSP to PSP, it should be handled in the PSP specific implementation of IPaymentServiceProvider.ProcessResponse method.

  • ... Posted by Nick Bratym Posted 02/11/2012 04:48 PM [http://www.facebook.com/nbnick]

    Hi Spike,<br>I fixed it with the placement . info

  • ... Posted by Alexander To Posted 02/13/2012 03:06 AM [http://www.facebook.com/profile.php?id=1365463738]

    Hi, not sure why I am getting this error<br><br>Cannot implicitly convert type 'Orchard.Core.Common.Fields.TextField' to 'string'<br>Line 135: if (addressPart != null)<br>Line 136: {<br>Line 137: addressVM.Name = address.Name;<br>Line 138: addressVM.AddressLine1 = address.AddressLine1;<br>Line 139: addressVM.AddressLine2 = address.AddressLine2;<br><br><br>Source File: d:\Projects\orchard_marketplace\src\Orchard.Web\Modules\Orchard.WebShop\Controllers\CheckoutController.cs Line: 137 <br><br>I was able to get it working up to CheckOut Summary screen but today I am about to proceed to Part 9 then I get this error. I am still debugging and couldn't recall what I did to create this but I thought maybe someone got some insight that could save me sometimes?<br>Thanks very much<br>Regards<br>Alex

  • ... Posted by Alexander To Posted 02/13/2012 03:37 AM [http://www.facebook.com/profile.php?id=1365463738]

    Hmm, if I change those lines to <br><br> addressVM.Name = Convert.ToString(address.Name.Value);<br> addressVM.AddressLine1 = Convert.ToString(address.AddressLine1.Value);<br> addressVM.AddressLine2 = Convert.ToString(address.AddressLine2.Value);<br> addressVM.Zipcode = Convert.ToString(address.Zipcode.Value);<br> addressVM.City = Convert.ToString(address.City.Value);<br> addressVM.Country = Convert.ToString(address.Country.Value);<br><br>then it works. The form shows up correctly. I am wondering whether I am missing something here?<br>

  • ... Posted by Jean-Noël Gourdol Posted 02/14/2012 03:20 PM

    A truly excellent tutorial.<br>The only thing I disagree with is the use of the globalization javascript plug-in, as Orchard has pretty good built-in localization capabilities. Basically, you just have to write @T(myString) in any view to have it localized. You may have to work a little to have it accomodate numeric and date formats, but not a whole lot. I encourage you to try it if you have not already.

  • ... Posted by Markus Erlacher Posted 02/15/2012 06:04 AM [http://twitter.com/snukes73]

    Hi Skywalker,<br><br>I look for a way to add a CustomerPart and AddressPart to an existing User. Any tip?<br>Regards<br>Markus

  • ... Posted by ???????? ????? Posted 02/17/2012 08:01 AM

    Thank you for this awesome guide!<br>I walk from beginning to this chapter, step by step, but got exception, please help!<br><br>Exception details: when I try choos items for my Product catalog:<br><br><br>Object reference not set to an instance of an object.<br>Source Error: <br><br>Line 7: Layout.Title = T("Choose Items");<br>Line 8: <br>Line 9: var targetContainers = ((IEnumerable<contentitem>)Model.Containers).Select(<br>Line 10: contentItem => new SelectListItem {<br>Line 11: Text = T("Move to {0}", contentItem.ContentManager.GetItemMetadata(contentItem).DisplayText ?? contentItem.ContentType).ToString(),<br></contentitem>

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

    Hi Markus,<br><br>Sure, just create a class that derives from ContentHandler and add an ActivatingFilter to the Filters collection. It goes like this:<br><br>public class CustomerHandler : ContentHandler {<br> public CustomerHandler() {<br> Filters.Add(new ActivatingFilter<customerpart>("User"));<br> Filters.Add(new ActivatingFilter<addresspart>("User"));<br> }<br><br> }<br><br>Normally you could attach parts to any content type using the Migrations file, but there doesn;t seem to be a User content type in the database. That's why you need to attach the parts every time a "User" content type is being requested (I believe that's the purpose of the ActivatingFilter).<br><br>If you look at the Orchard.Users module, you'll see that it's done exactly the same to attach the UserPart to the User content type.<br><br>Regards,<br>Sipke</addresspart></customerpart>

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

    That's my fault; I forgot to attach the CommonPart to one or the other content type. I don't remember off the top of my head which content type it was exactly, but just go through all of the content types you are creating in your Migrations file, and make sure that all content types have the CommonPart attached. You can also attach the CommonPart after the fact using the Admin.<br><br>I believe a few guys on this thread were experiencing the same issue.<br>

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

    I agree that Orchard has great built-in localization capabilities, but do they also have client side scripts that are able to format numbers using a variety of cultures? The reason that I included the javascript Globalization plugin is because the ShoppingCart updates the price values clientside (by multiplying with the quantity). Since there's no servercode involved, I needed a way to format the calculated prices on the client. Hence the Globalization plugin. But if you know how to do it using Orchard javascripts, I'd be happy to hear it.<br><br>Thanks for the comment.

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

    Hi Alexander,<br><br>As an example, address.AddressLine1 is actually of type "TextField", while addressVM.AddressLine1 is of type "String". Therefore this line doesn't work:<br><br>// Error: cannot assign TextField to a String<br>addressVM.AddressLine1 = address.AddressLine1;<br><br>That's why you needed to use the Value property of the TextField (which is of type String):<br><br>// OK<br>addressVM.AddressLine1 = address.AddressLine1.Value;<br><br>Hope that clears it up.

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

    Hi Alexander,<br><br>As an example, address.AddressLine1 is actually of type "TextField", while addressVM.AddressLine1 is of type "String". Therefore this line doesn't work:<br><br>// Error: cannot assign TextField to a String<br>addressVM.AddressLine1 = address.AddressLine1;<br><br>That's why you needed to use the Value property of the TextField (which is of type String):<br><br>// OK<br>addressVM.AddressLine1 = address.AddressLine1.Value;<br><br>Hope that clears it up.

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

    Hi Claire, you're absolutely right, thanks for pointing it out! It's time that I fixed that.

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

    After reading some of the comments below I noticed that more people were having the same issue. To avoid the exception, you need to attach the CommonPart to the ProductCatalog content type. It should have bene included in the UpdateFrom2 method of the Migrations class. I updated Part 5 so that you can see how it looks. Again, you can also use the Admin to attach the CommonPart still.

  • ... Posted by ???????? ????? Posted 02/17/2012 03:55 PM

    thank you for reply

  • ... Posted by Dave Posted 02/19/2012 10:59 PM

    Thanks immensely for this excellent tutorial. It is the single best resource for learning Orchard (and you should have written it ages ago!). Please provide a blog search facility so I can more easily find stuff when I want to refer back. Many thanks, Dave.

  • ... Posted by Fernando Sonego Posted 02/21/2012 07:49 PM

    Hi, in the part 4 you didn't choose Admin Menu as part of a book, then when you do. WithPart (typeof (AdminMenuPart). Name) shows the error. <br><br>sorry for my horrible English. :P

  • ... Posted by Jack Richmond Posted 02/23/2012 05:48 AM [http://www.tatvasoft.co.uk/bespoke-development.php]

    You have really done a fabulous job by providing such great tutorials to us. As yet I am working on such application, my work has become much easier by following each and every steps of your tutorial. Little bit modification is required according to client priority, but that I will manage to handle it.

  • ... Posted by Mel Shellam Posted 03/02/2012 11:55 AM

    I don't seem to get the drop down admin menu. I only ever get the top item. Any snippets/clarification would be appreciated!

  • ... Posted by Sipke Schoorstra Posted 03/03/2012 11:34 AM (Author)

    In MVC, you setup routes to controllers and their actions, and it has no knowledge of Drivers. So forms are always posted back to one controlelr or the other. You CAN handle input though drivers, but these drivers are to be invoked from a controller. Also, Drivers are responsible for rendering and updating a single ContentPart. In this particular example, the ShoppingCart is no content part whatsoever; it's handled using plain old MVC.

    That said, if ShoppingCart were implemented as a Content Part, then you would still need to update the underlying session via some controller, as there is currently no way (that I know of) to handle postback from the front end (unless you invoke IContentManager.UpdateEditor, which is typically used from the backend to update content items).

  • ... Posted by Sipke Schoorstra Posted 03/03/2012 11:38 AM (Author)
    <p>The only snippet required is in this post: <a href="/blog/writing-an-orchard-webshop-module-from-scratch-part-10" rel="nofollow">http://skywalkersoftwaredevelo...</a></p>

    Did you copy the exact same code?

  • ... Posted by Erik Lenaerts Posted 03/03/2012 09:54 PM

    great work !

    Going to update my Module straight away with the new knowledge on globalization that you described very cleary. Thnx a lot :)

  • ... Posted by elenaerts Posted 03/11/2012 10:46 AM

    have a question on model errors during edit. I have similar code like you in my controller:

    <p> [HttpPost]<br> public ActionResult Edit(int id, FormCollection collection) {<br> var eventPart = _eventService.Get(id);<br> if (eventPart == null)<br> return HttpNotFound();</p> <p> // Validate form input<br> var model = _orchardServices.ContentManager.UpdateEditor(eventPart, this);<br> if (!ModelState.IsValid) {<br> return View(model);<br> }</p> <p> return RedirectToAction("Index");<br> }</p>

    When I edit a content item (event in this case) then I see validation messages on top. However the code above already saved any changes in the database. So, even though the event is invalid (for example, I deleted the title and changed the body text) the changes are still saved upon validation errors.

    I assume the UpdateEditor() method of the ContentManager also updates the database. So I was wondering if there's a way to postpone the actual save after the validation check on ModelState.

    cheers Erik

  • ... Posted by Ramon Hernandez Posted 03/12/2012 05:10 AM
    <p>Great Course,<br>Hope you can help me on something, I cant make the "Publish now" button to show in the Book Catalog,<br>I'm using Orchard 1.4 with Autoroute, <br>In Form2() method I used "AutoroutePart" and "TitlePart" instead of "RoutePart", </p> <p>public int UpdateFrom2()<br> {<br> ContentDefinitionManager.AlterTypeDefinition("ProductCatalog", type =&gt; type<br> .WithPart(typeof(CommonPart).Name)<br> .WithPart(typeof(ContainerPart).Name)<br> .WithPart(typeof(MenuPart).Name)<br> .WithPart(typeof(AdminMenuPart).Name)<br> .WithPart(typeof(AutoroutePart).Name)<br> .WithPart(typeof(TitlePart).Name)<br> .Creatable()<br> );</p> <p> return 3;<br> }</p>

    I've been trying to find out what makes "Publish now" appear, but no luck at all.

  • ... Posted by Michael Artz Posted 03/13/2012 10:54 PM

    Hmm...the whole subscribe to newsletter?? Where is that "collected"?

  • ... Posted by Sipke Schoorstra Posted 03/17/2012 12:08 PM (Author)

    Yet to be implemented ;)

  • ... Posted by Sipke Schoorstra Posted 03/17/2012 12:18 PM (Author)

    I haven't tried it myself, but I believe it's a setting on the Edit ContentType admin page itself (there's a "Draftable" option which you can check)

  • ... Posted by Sipke Schoorstra Posted 03/17/2012 12:23 PM (Author)

    You're almost right: although the call to UpdateEditor updates the content parts attached to the specified content item, the changes get submitted to the database at the end of the HTTP request. So what you can do before you leave the action method, you can cancel the current database transaction using the ITransactionManager service. If you have a look at the AdminController in Orchard.Core/Contents/Controllers, you'll see how it's done exactly in the CreatePOST method.

  • ... Posted by okdone Posted 03/17/2012 02:39 PM

    Seems Awesome. I'll sure try it. Was looking for something like this for long time. A tutorial to show actual data access happening instead of dumb pointless demos. And this one is a b o m b. Thanks a lot.

  • ... Posted by okdone Posted 03/17/2012 02:40 PM
    <p>Seems Awesome. I'll sure try it. Was looking for something like this for long time. A tutorial to show actual data access happening instead of dumb pointless demos. And this one is a b o m b. Thanks a lot.<br></p>
  • ... Posted by okdone Posted 03/17/2012 02:41 PM

    Sorry misplaced my post.

  • ... Posted by elenaerts Posted 03/17/2012 07:47 PM

    one line of extra code and it works. tyvm :)

  • ... Posted by Markus E. Posted 03/19/2012 12:07 AM

    Hi,

    I did try your module on Orchard 1.4, it's not working anymore Special around the RoutePart, i did removed but not help fully. Do you plan to update it for Orchard 1.4?

    <p>Thanks <br>Markus</p>
  • ... Posted by Sipke Schoorstra Posted 03/19/2012 12:18 AM (Author)

    You're right, with 1.4 we need to replace the RoutePart with the new AutoroutePart and the TitlePart. I'll have to update the tutorial. Thanks for pointing it out!

  • ... Posted by Jonathan Parker Posted 03/21/2012 02:37 PM

    Awesome tutorial! This is exactly the module I've been trying to write the last week!

  • ... Posted by Lior Grosman Posted 03/22/2012 09:49 PM
    <p>hey hello<br>I'm very intresting on orchard framework<br>i' m developer on microsoft crm dynamics so i'm new in cms concept (not mvc of course)<br>i would like to know i is any book on this framework.<br>i have some question ,i stop in a step that's need to publish and i had no button publish for "product catalog" ' i'm using orchard 1.4 and when i'm go to link "books catalog" his show me the "Edit Product Catalog" and not the list of the book</p>

    i

  • ... Posted by Terry Rodger Posted 03/28/2012 10:20 AM [http://www.tatvasoft.co.uk/]

    It's always being great here, as an developer I have got to learn lot of things from here. Good thing is that your coding style is quite simpler that can be easily understood by anyone. Your some of the tutorials has really proven helpful to me.

  • ... Posted by 2326220 Posted 03/30/2012 12:05 PM

    I think you lost the Index action method of SimulatedPaymentServiceProviderController.

  • ... Posted by Software Development Company Posted 04/17/2012 06:04 AM [http://mindinventory.com/]

    Great Blog with best coding update information that not only make the user information useful but can make tons of coding help spot. SO over great piece of information.

  • ... Posted by MoRady Posted 04/20/2012 05:50 PM
    <p>Why I can't just use the Model like @Model ProductPart, then use it normally like normal mvc views <br>&lt;article&gt;<br>@Model.Price<br>@Model.Sku<br>&lt;/article&gt;</p>
  • ... Posted by Sipke Schoorstra Posted 04/21/2012 12:09 AM (Author)

    If ProductPart is set as the model of your view, then you can use it like that. However, when rendering shapes, the shapes are the model of the view. What you could do is create a separate view specific to your ProductPart. So if you have a shape with a ProductPart property, your shape template may look like this:

    <p>@model dynamic<br>@Html.Partial("ProductPart", (ProductPart)Model.ProductPart)</p>

    Then you just need to create another view called ProductPart.cshtml:

    <p>@model ProductPart<br>&lt;article&gt;<br>@Model.Price<br>@Model.Sku<br>&lt;/article&gt;</p>

    Similarly, you could use the Html.EditorFor and Html.DisplayFor helper methods to render a strongly typed view model.

  • ... Posted by roja14 Posted 05/03/2012 11:02 AM

    Does anybody know how to use "localize" the string passed in ValidationResult()?

  • ... Posted by Roland Posted 05/11/2012 09:12 PM
    <p>I'm a bit stuck now - I have got to the part where I can create my Product Catalog, but I only have the Save option, not publish. When I save it and click on the admin menu link it creates it doesn't take me to the Manage Books, it just lets me edit my new catalog (like I was creating one).<br>I can't work out what I am doing wrong - Orchard 1.4.1</p>
  • ... Posted by Joomla Programmers Posted 06/12/2012 08:13 AM [http://www.mindinventory.com/joomla.php]

    This is one of the best blog with best coding information.I really appreciate this article.This coding is very useful to me.

  • ... Posted by Andy Posted 06/16/2012 09:44 AM

    I have the exact same thing in 1.4. The "Books Catalog"-item on the admin menu simply shows me "Edit Product catalog" instead of the Books list. How can this be resolved?

  • ... Posted by Best Joomla Programmers India Posted 06/18/2012 06:08 AM [http://www.mindinventory.com/joomla.php]

    This part was more about implementing commerce features than it was about Orchard specific features, but the take away here is once again that building custom modules is a lot like building an MVC Area.

  • ... Posted by Sukretniy Posted 06/22/2012 05:07 PM
    <p>I'm having the same problem. I managed this by editing each book and by setting, "Add To" combobox to "Books Catalog". <br>And also i have no "Choose items" option aviable when clicking "Books catalog" - just editing corresponding content item.</p>
  • ... Posted by Jason Posted 07/03/2012 09:37 PM

    Loving this series, thanks for putting it out there!

    I apologize for being dense, but I'm having trouble figuring out how to add items to the Product Catalog container. You have a screen shot with a "Choose Items" button, that seems to be reached by clicking the new "Books Catalog" link in the admin menu. When I click that link, I am just given an edit form nearly identical to that for creating new Product Catalogs.

    I'm using Orchard 1.4.2, but I can't seem to find any reference that this has changed in the release notes. Am I missing something obvious?

  • ... Posted by Jason Posted 07/03/2012 11:44 PM

    Big thanks to Sipke for answering this. Thought I'd record it here as I see others have had the same problem.

    The Lists feature, is apparently required for this behavior, and with Orchard 1.4.2 the Lists feature is disabled by default (not sure why, but sounds like it is in favor of projections?).

    So Enable Lists from the Module page, and you are good to go.

  • ... Posted by skizNat Posted 07/06/2012 05:06 PM

    Has this changed for 1.4? I can't seem to get past this. Either I can supply the antiforgerytoken but then the model binding doesn't seem to work, or I can submit the data but run into the antiforgerytoken issue. I've used the code above to no avail.

  • ... Posted by skizNat Posted 07/06/2012 05:26 PM

    I've updated the js method to the following (using the antiforgerytoekn.js):

    <p>var data = $.Enumerable.From(shoppingCart.items()).Select(function (x) { return { ProductId: <a href="http://x.id" rel="nofollow">x.id</a>, Quantity: x.quantity(), IsRemoved: false }; }).ToArray();<br> var url = $("article.shoppingcart").data("update-shoppingcart-url");<br> $.post(url, { items: data, __RequestVerificationToken: $.getAntiForgeryToken().value });</p>

    However, the model binding contains an array of items that are all empty (productid:0, quantity:0). Am I just overlooking something simple?

  • ... Posted by Solv Posted 07/13/2012 09:42 PM

    Thanks for "new" tutorial. And new stuff with filters! Unfortunately the query returns no results :( What do I need to check to find out the reason?

  • ... Posted by Guest Posted 07/13/2012 11:47 PM

    Outstanding work board member, keep it up!

  • ... Posted by Solv Posted 07/17/2012 08:23 PM

    Globalization doesn't work for me;

    <p>Unable to parse bindings.<br>Message: ReferenceError: Globalize is not defined;<br>Bindings value: text: Globalize.format(calculateSubtotal(), 'c')</p>
  • ... Posted by Sipke Schoorstra Posted 07/17/2012 08:52 PM (Author)
    <p>Can you verify that the script is actually included (view HTML source)? <br>What if you download the source code (attached at the end of the post)?</p>
  • ... Posted by Solv Posted 07/17/2012 11:21 PM
    <p>There is no globalize script included. <br>It seems I found the problem in ResourceManifest.<br>Wrong copy of the long line. Thanks.<br></p>
  • ... Posted by Solv Posted 07/17/2012 11:23 PM

    BTW, If I use the source attached I have got no Enable/Disable link on Modules page. What could be a problem?

  • ... Posted by Solv Posted 07/20/2012 12:40 PM

    I found the reason for it. In module.txt.

    <p>It should be <br>Dependencies: Orchard.Projections, Orchard.Forms, Orchard.jQuery</p> <p>instead of<br>Dependencies: Orchard.Projector, Orchard.Forms, Orchard.jQuery</p>
  • ... Posted by Sipke Schoorstra Posted 07/21/2012 11:14 AM (Author)

    Thanks :)

  • ... Posted by Sipke Schoorstra Posted 07/21/2012 11:14 AM (Author)

    You’re welcome. Make sure that the ProductPart is attached to your content types (Book and DVD) and that you actually created at least one content item. Also make sure that there are no errors in the filter by checking the logs or launching the debugger (make sure to check the “Common Language Runtime” and “Managed Debugging Assistants” in the “Thrown” column of the Exceptions settings (Debug->Exceptions). If you still aren’t seeing results, perhaps try and download the source included at the end of the post. Let me know if you’re still having problems displaying results.

  • ... Posted by Vishal Sharma Posted 07/22/2012 07:52 AM

    a very great post for beginners... very much helpfull.....

    <p>You actually forget to mention one thing here. Once you add some content <a href="http://types.eg" rel="nofollow">types.eg</a>. you added books and dvds... then you have to mark them publish from "Manage Content". Then only some one can see the result of the query. </p>

    It me some while to figure out what happened because I am following your article from part I and I was not able to identify what is missing.

    Finally it got solved by making "content items" published.

  • ... Posted by Roland Posted 08/02/2012 09:53 AM

    Great tutorial, and even better now it has been updated for the newer versions... One thing, for the dependency it says reference Orchard.Projector - should it not be Orchard.Projections - it wouldnt work until I did this.

  • ... Posted by Roland Posted 08/02/2012 10:42 AM

    I'be hit a bit of a brick wall on this part, I am using Orchard 1.5.1. I am fine until I add the ShoppingCartController then it just breaks.. when I load the site up and try to browse to the product catalog I get 'The best overloaded method match for 'System.Linq.Enumerable.Cast<object>(System.Collections.IEnumerable)' has some invalid arguments ':

    Breaks at Line 3, source file is c:\Users\rthompson\Desktop\Orchard Part 6a\src\Orchard.Web\Core\Shapes\Views\MenuItem.cshtml

    <p>Line 1: @{<br>Line 2: // odd formatting in this file is to cause more attractive results in the output.<br>Line 3: var items = (IEnumerable&lt;dynamic&gt;)Enumerable.Cast&lt;dynamic&gt;(Model);<br>Line 4: }<br>Line 5: @{<br> </p>

    Looking in from the admin side, it looks like my filter has dissappeared... any idea why?</dynamic></dynamic></object>

  • ... Posted by Roland Posted 08/02/2012 12:34 PM

    Sorted it... I think it is because I also have MVC 4 RC installed on my computer - If i set the Specific Version property of the System.Web.Mvc Reference to true it works now... just in case it happens for anyone else... on with the rest of your posts now :)

  • ... Posted by Kyd2000 Posted 08/08/2012 01:28 AM

    Good day Sipke, do you have an approximate date for when you'll update the remaining walk-throughs to 1.4+ ? Really love what you have achieved so far, it has propelled by understanding of Orchard more than I could've hoped for - thanks Kyd

  • ... Posted by Sipke Schoorstra Posted 08/11/2012 01:53 AM (Author)

    That's great to hear, thanks! I have just updated part 8. Part 9 will be updated within 3 weeks from now.

  • ... Posted by Sipke Schoorstra Posted 08/11/2012 01:58 AM (Author)

    You're absolutely right - thanks for pointing it out!

  • ... Posted by Sipke Schoorstra Posted 08/11/2012 02:02 AM (Author)

    I've so gotten used to publishing content items that I didn't think about mentioning it, but I will update it. Thanks for pointing it out!

  • ... Posted by Sipke Schoorstra Posted 08/11/2012 02:02 AM (Author)

    I've so gotten used to publishing content items that I didn't think about mentioning it, but I will update it. Thanks for pointing it out!

  • ... Posted by Andy Posted 08/20/2012 10:41 PM

    Hi, the Globalization part doesn't seem to work for me. No matter which cultures I pass to .SetCultures in ResourceManifest.cs, en-US is always used and $ is shown as per your screenshot. Also, when a user is signed on, all KnockoutJS calls fail due to an AntiForgeryToken not being passed or invalid. The provided solution of another reader doesn't seem to work for me. Could you please update your sample to use nl-NL and test it with signed in users? Help much appreciated... Regards, Andy.

  • ... Posted by Stelian Rusu Posted 09/09/2012 09:06 PM

    Thanks for the hint. I would be totally blocked wasn't there your solution at the error. How did you figure it out? Apparently, there is nothing you could rely on to resolve this error.

  • ... Posted by andy Posted 09/23/2012 01:48 PM
    <p>sign up or login a customer, then click sign out. i got OrchardSecurityException was unhandled by user code <br>{"Login required"}<br> at Skywalker.Webshop.Controllers.CheckoutController.SelectAddress() in g:\151ecommerceSkyWalker-zg\src\Orchard.Web\Modules\Skywalker.Webshop\Controllers\CheckoutController.cs:line 97 at lambda_method(Closure , ControllerBase , Object[] ) at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) at System.Web.Mvc.ControllerActionInvoker.&lt;&gt;c__DisplayClass15.&lt;invokeactionmethodwithfilters&gt;b__12() at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)<br>it seems that " If that's the case, we'll throw an OrchardSecurityException, which will be handled by Orchard by redirecting the user to the login page." is not correct, after I press F5, then it goes to login page.&lt;/invokeactionmethodwithfilters&gt;</p>
  • ... Posted by andy Posted 09/23/2012 01:50 PM
    <p>it seems that it is lack of SchemaBuilder.CreateForeignKey("Address_Customer", "AddressPartRecord", new[] { "CustomerId" }, "CustomerPartRecord", new[] { "Id"<br> });<br>in the migration.cs</p>
  • ... Posted by andy Posted 09/23/2012 04:18 PM
    <p>resolved :<br>if (currentUser == null)<br> return new HttpUnauthorizedResult();</p>
  • ... Posted by Hayri Posted 09/24/2012 08:55 PM

    i would be nice to have authorized role attached to the action link.

  • ... Posted by Sipke Schoorstra Posted 09/24/2012 09:05 PM (Author)

    Do you mean you would like to specify what permission is required in order to render the menu item? I think it would be nice to be able to determine per MenuItem what permissions are required. We have something like that for content items (content view permissions), perhaps it can be adjusted to have it work for menu items as well.

  • ... Posted by zgofmontreal Posted 09/25/2012 01:56 AM
    <p><a href="http://orchard.codeplex.com/discussions/396559" rel="nofollow">http://orchard.codeplex.com/di...</a></p>
  • ... Posted by toburger Posted 09/25/2012 08:15 AM

    So am I understanding it correctly: the security trimming for the Navigation isn't implemented by Orchard at the moment?

  • ... Posted by Sipke Schoorstra Posted 09/25/2012 08:29 AM (Author)

    Security trimming is implemented (see NavigationManager), but it looks like that the menu item has to be linked to a content item in order for it to work. In the case of the ActionLink menu type, that wouldn't work then. Unless the MenuItem.Content refers to the menu item content item itself. I didn't try this though so I'm not sure.

  • ... Posted by toburger Posted 09/25/2012 09:28 AM
    <p>Thanks for the advice with the NavigationManager! So the way to extend the trimming at the moment other than by having a MenuItem.Content is by providing an INavigationFilter?<br>But the Filter method is not the last call in the chain and it is only called in one of the two BuildMenu methods, so I think it would be better to provide an INavigaitonTrimming interface and create a Trim method, like for the Filter.</p>
  • ... Posted by Hayri Posted 09/25/2012 02:09 PM
    <p>Hi Sipke,<br>Yes, that's what I meant. The problem is, we can hide the content with the content permission depending on users role, but its menu remains right?? The same issue applies to the action link. So it would be nice to have a "authorized role" in a menu item. Also it would be nice to have an action level attribute that renders menu automatically. It can be in both in controller or in a driver. For example</p> <p>[MenuName="DefaultNavigation", ParentNode="Orders", AuthorizedRole="View Orders"]<br>Display {}</p>

    [MenuName="DefaultNavigation", ParentNode="Manage Orders", AuthorizedRole="Edit Orders"]

    Edit {}

  • ... Posted by Hayri Posted 09/25/2012 02:31 PM

    Hi Sipke,

    <p>Can we see this in the next version of orchard? <br></p>
  • ... Posted by Sipke Schoorstra Posted 09/26/2012 03:13 AM (Author)

    Maybe. For sure if I need it myself. For now, if you use the other menu item types that have related content items, you can configure permissions using the content view permissions feature.

  • ... Posted by Sipke Schoorstra Posted 09/26/2012 03:16 AM (Author)
    <p>I'm not sure - the source code looks like to skip rendering the menu items whose Content aren't accessible for the current user. Perhaps try it and see what happens.<br>I like your suggestion regarding the menu attribute, thanks. I will think about it. My only concern is with localizing the menu item names when using attributes.</p>
  • ... Posted by Sipke Schoorstra Posted 09/26/2012 03:18 AM (Author)

    Yo may be right, I don't know. I will investigate this as soon as I need security trimming. If you find anything, please share it. Thanks.

  • ... Posted by Sipke Schoorstra Posted 09/26/2012 03:18 AM (Author)

    Yo may be right, I don't know. I will investigate this as soon as I need security trimming. If you find anything, please share it. Thanks.

  • ... Posted by Tobias Burger Posted 09/26/2012 10:02 AM [http://tobiburger.tumblr.com/]
    <p>I've made a very simple INavigationFilter implementation for ActionLinks here: <a href="https://gist.github.com/3787089" rel="nofollow">https://gist.github.com/378708...</a><br>Sorry, I'm not into the Orchard development for very long so please correct me if I'm doing some crappy things...<br>The access check is hardcoded (show the ActionLink only if the user is Anonymous) but the logic for the access check could be provided via the ActionLinkPart from a tokenized property (like for the RouteValues)... or by the Content Item Permission module (like in the NavigationManager's Reduce method), although Menu Items aren't really Content Items.</p>
  • ... Posted by Artsiom Posted 10/03/2012 04:18 PM

    After creating ViewModels/SignupViewModel.cs: I get an error when I click to products menuItem. Shopping Cart widget is also not showing.

    <p>Error:<br>'The best overloaded method match for 'System.Linq.Enumerable.Cast&lt;object&gt;(System.Collections.IEnumerable)' has some invalid arguments ':</p> <p>Breaks at Line 3, source file is c:\Users\rthompson\Desktop\Orchard Part 6a\src\Orchard.Web\Core\Shapes\Views\MenuItem.cshtml<br>Line 1: @{<br>Line 2: // odd formatting in this file is to cause more attractive results in the output.<br>Line 3: var items = (IEnumerable&lt;dynamic&gt;)Enumerable.Cast&lt;dynamic&gt;(Model);<br>Line 4: }</p> <p>setting the Specific Version property of the System.Web.Mvc Reference to "true",as it was mentioned in the comments, haven't made an effect.<br>I use Orchard 1.5.1, VS2012.&lt;/dynamic&gt;&lt;/dynamic&gt;&lt;/object&gt;</p>
  • ... Posted by Artsiom Posted 10/03/2012 04:26 PM

    This instructions didn't help me. Maybe you did something more ?

  • ... Posted by Artsiom Posted 10/06/2012 05:02 PM
    <p>Resolved. I use .Net 4.5 and it has some problems with CompareAttribute (field RepeatPassword in ViewModels/SignupViewModel.cs)<br>Use instead:</p> <p>[StringLength(255), Required, DataType(DataType.Password), System.Web.Mvc.Compare("Password"), Display(Name = "Repeat password")]<br> public string RepeatPassword { get; set; }<br></p>
  • ... Posted by PPehrs Posted 10/09/2012 01:22 PM

    really good work, super tutorial

  • ... Posted by Orc151 Posted 10/09/2012 11:54 PM

    Any news on when you'll finish the tutorial updates? Love it BTW, thanks

  • ... Posted by neTp9c Posted 11/03/2012 06:42 PM
    <p>This code dont works with orchard 1.6. Can you explain why? I create post on orchard forum <a href="http://orchard.codeplex.com/discussions/401777" rel="nofollow">orchard.codeplex.com/discussio...</a></p>
  • ... Posted by Lars Posted 11/06/2012 09:02 AM

    Html.BeginFormAntiForgeryPost() does not worke, the page reloade but no ShoppingCart is loading on content ... the request header says nothing useful to solve the problem. If I using Html.BeginForm() I get the Exception as in the Tutorial, but the following change does not worke for me, has anyone any idea why nothing is loading?

    Regards Lars

  • ... Posted by Lars Posted 11/06/2012 09:02 AM

    Html.BeginFormAntiForgeryPost() does not worke, the page reloade but no ShoppingCart is loading on content ... the request header says nothing useful to solve the problem. If I using Html.BeginForm() I get the Exception as in the Tutorial, but the following change does not worke for me, has anyone any idea why nothing is loading?

    Regards Lars

  • ... Posted by Lars Posted 11/06/2012 09:09 AM

    I dont know, but I see that the Add methode on ShoppingCart.cs not called ...

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

    you are perfect. it's the third night I can't sleep without complete it :)

  • Maybe. For sure if I need it myself. For now, if you use the other food selection kinds that have relevant material products, you can set up authorizations using the material perspective authorizations function.

  • ... Posted by Nour Berro Posted 11/28/2012 12:44 PM

    I suggest to use the content type as product category and view it inside the cart table as well

    var cat = titlePart != null ? titlePart.Record.ContentItemRecord.ContentType.Name : "not categoruized item";

  • ... Posted by Dmitri Posted 12/10/2012 03:50 PM

    Hallo Sipke!

    I am running your tutorials right now with Orchard 1.6 and actually I am not alone, we are running your tutorials as part of a WebDev program, so whole class is going through these tutorials.

    <p>For now there was some problems with it:<br>1. We figured out that it is better to generate Module from the beginning using codegen. Otherwise we run into problems when we had to build our module with first View using Razor Syntax. WebConfig did not solve the problem. Project has to be web application not class library, at least it worked for everyone.</p>

    2. Some typos were found here and there. Also when in Resource manifest you are defining styles in one of them you are setting dependency to "Webshop.Common" I assume it has to be "Skywalker.Webshop.Common" ?

    3. When I started to work with KnockoutJS, Jquery, AIM LinqJs I mentioned that modules are not added to solution folder "Modules" thus cause trouble with JQuery (delete button was not working). I manually added all these modules into solution folder "Modules" and button started to work.

    4. Not sure that Globalization is working correctly.

    5. On your screen shot in the end of this part I cannot see Update button, whereas I have it. Is it because my javascript is not wokring or you have made some with that button without writing about it?

    6. ShopingCartController.UpdateShoppingCart method is calling to two method that are not defined in IShoppingCart.cs and those are Clear() and AddRange(). No problem with Clear, is it just needs to be added to interface, whereas AddRange() is not defined in interface and also no implementation in ShoppingCart.cs. Nothing said about it in tutorials.

    7. Also in UpdateShoppingCartItemViewModel your are using type "decimal" for ProductId and it creates trouble in ShoppingCartController.UpdateShoppingCart method when ProductId is passed to ShoppingCartItem constructor, because it expects type "int" fro first parameter (ProductId).

    8. In ShoppingCart.cshtml there is

    <p>@if {<br> </p>

    You don't have any items in your shopping cart.

    <br> <a class="button" href="#" rel="nofollow">Continue shopping} else {<br>} else {

    <p>&lt;div data-bind="visible: !hasItems()"&gt;<br> </p>

    You don't have any items in your shopping cart.

    <br> <a class="button" href="#" rel="nofollow">Continue shopping<br> </div><br>....<br>}

    these creates trouble with me when I add product to shopping cart "you have nothing in your shopping cart" is still displayed and is overlaid on top of shopping cart and creates unpleasant effect. Solution? I moved first div block in else block to if block and deleted old statement that was in there by replacing.

    So far I am stuck on tutorials 7 because looks like something is not working properly. Could you please provide your comments to each number. Much appreciated. Thank for tutorial anyway, good job

  • ... Posted by Barry Posted 12/11/2012 10:33 PM

    Second ^ Sipke. Do you have any time / or still have plans to update the tutorial set?

  • ... Posted by Thomas Cox Posted 12/18/2012 05:17 AM [http://caleblopez.weebly.com/]

    how the software development function fits in the wider organisation, get to understand where the organisation fits in its wider commercial ecosystem, and in society as a whole.

  • ... Posted by LarryBS Posted 12/18/2012 04:01 PM

    Great! Great!

    But only one thing, how to make the "Customer" ContentType to appear in the Content Item list ?

  • ... Posted by David Basson Posted 01/07/2013 02:04 PM

    Hi I'm getting the error Orchard.Core.Routable.Models is missing a reference. Could you possible let me know what I should do? I'm using build orchard 1.4. Thanks so much for a great tutorial!

  • ... Posted by Keighley Josiah Posted 01/29/2013 12:48 PM [http://www.peterdrake.me/]

    WOOHOO!jackpot...i was looking for the orchard module for some time now....

  • ... Posted by Rhys Lloyd Posted 02/05/2013 12:36 AM [http://rhyslloyd.me/]

    Hi mate, were you able to sort this issue? I have the exact same problem.

  • ... Posted by Dan Posted 07/25/2015 03:45 PM

    Hello

    So first off, I realise this series is a few years old and things evolve all the time. Thus far it has been a great tutorial. All has worked as expected (and is well explained) all the way to the end of this chapter... the submission. When I add in the Html.BeginFormAntiForgeryPost, I run into two issues...

    1. BeginFormAntiForgeryPost is not a recognized helper... I resolved this by adding a using statement for Orchard.Mvc.Html.
    2. Url (of the Url.Action) & T (of the @T..) both complain that they do not exist in the current context.

    I cannot seem to find any help on google and suspect that it is something really lame that I am missing. Any help would be great...

    I am using VS2015, .net 4.5.1 Orchard 1.9.1

    Thanks for the great series!!

    Dan

Leave a comment