Upcoming courses:

  • Aarhus, Denmark, March 5 - 9, 2012
  • New York City, USA, March 26 - 30, 2012
Read more on our website

About me

Brian Holmgård Kristensen

Hi, I'm Brian. I'm a Danish guy primarily working with ASP.NET e-commerce solutions using Microsoft Commerce Server.

I'm co-founder and core-member of Aarhus .NET Usergroup (ANUG), which is a offline community for .NET developers in Denmark.

You can visit my View Brian Holmgård Kristensen's profile on LinkedIn or follow me on Twitter @brianh_dk. Also please feel free to contact me via e-mail Send me an e-mail.


On this page

How to implement Custom Shipping Methods in Commerce Server
Commerce Server Training in November 2010 – still spots left
Only one OrderForm in Commerce Server 2009
Commerce Server training videos
How to retrieve Category Rank in Commerce Server 2009
Midway impressions on Tech-Ed North America 2009
Installation af Umbraco - step 2
En aften i F#-tegnet



The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

RSS 2.0

Send mail to the author(s) E-mail

Total Posts: 36
This Year: 0
This Month: 0
This Week: 0
Comments: 10

Sign In

Follow me on Twitter @brianh_dk
 Friday, 15 October 2010

A couple of years back I wrote a blog-post on How to extend Commerce Server Payment Methods and Shipment Methods which described how you could use the Profiles System in Commerce Server to extend properties of Shipment- and Payment Methods and with very little effort customize the UI of the Customer And Orders Manager to allow easy management of these new properties. This technique is definitely still valid, but I’ve would like to explain another approach that overrides the overall of how Commerce Server handles Shipping Methods to allow custom business logic, custom pricing etc.

Also this post serves as a (late, but promised) follow up answer to a question asked on the Commerce Server Forum on MSDN ( on how the best way to implement custom business logic for handling shipping in Commerce Server.

In this blog post I’ll show you can implement custom shipping methods in a Commerce Server 2007/2009 project. In order to better explain the “Why”-part, I’ll start by briefly explaining how Commerce Server works out of the box in regards to applying shipping on a basket. Last but not least I’ll explain the “How”-part, so you can implement this in your own project.

Shipping out of the box

Shipping Methods are configured in Customer And Orders Manager. Here you specify name and description for the different languages that you support, the cost of a given shipping method based on weight, subtotal or quantity and a Shipping Cost Calculator, which is a pipeline component that automatically gets executed from the Total-pipeline. As for prices you cannot specify multiple prices in multiple currencies – so you are stuck with only prices in one currency. Also important to mention is that there is no supported way to extend this schema, e.g. adding new properties, as you would also read about in my previously mentioned post.

This screenshot below shows the link between a Shipping Method created in Customer and Orders Manager and how and where this is persisted in the database.


In short Shipping Methods are added to a Basket by specifying a ShippingMethodId (Guid) to your Line Items as shown below for both 2007 and 2009 version of Commerce Server.

In Commerce Server 2007:

   1:  var lineItem = new LineItem("TestCatalog", "ProductID", null /* VariantID */, 1 /* Quantity */)
   2:  {
   3:      ShippingMethodId = new Guid("MethodId on Shipping Method")
   4:  };

In Commerce Server 2009:

   1:  lineItem.SetPropertyValue("CatalogName", "TestCatalog");
   2:  lineItem.SetPropertyValue("ProductId", "ProductID");
   3:  lineItem.SetPropertyValue("Quantity", 1 /* Quantity */);
   4:  lineItem.SetPropertyValue("ShippingMethodId", "GroupId on Shipping Method");


After adding a Line Item to the Basket you run the Total-pipeline in which there is a number of pipeline component responsible of requesting the database and actually create one or more instances of the Microsoft.CommerceServer.Runtime.Orders.Shipment class based on the ShippingMethodId on the Line Items. This new instance then gets added to the Shipments-collection on the OrderForm. Basically the same as I’m doing manually in the code example below:

   1:  var orderForm = basket.OrderForms[0];
   2:  var shipment = new Shipment();
   3:  orderForm.Shipments.Add(shipment);


The bottom-line is that you are not in control of the actual creation process of the Shipment instance, as this is taken care of in the Total-pipeline by the two components marked in red in the screenshot below.


If you do try to add it manually the Total-pipeline will throw an error because you did not specify a ShippingMethodId on your Line Items.

Why use custom shipping?

On most of the e-commerce projects I’ve been working on, pricing is a very complex thing – it heavily depends on a lot of context information like customer, country, currency, content of the basket, suppliers and sometimes more making it impossible to setup in Customer and Orders Manager. Sure you can try to create your own Shipping Cost Calculator pipeline component, but that requires you to do work and business logic in the Pipeline infrastructure of Commerce Server, which is cumbersome, because  you are dealing with COM, magic strings, and weak-types like the IDictionary and ISimpleList. Also it is difficult to interact with your existing infrastructure like services, repositories etc from your pipelines, and it is hard to automate tests for this. Pipeline components are just not the right place to put your business logic in my opinion.

So what we would really like is to be able to create the Shipping Methods from within our domain ourselves, being able to specify the price in that proces, e.g. having the price coming from a calculation made in the backend/ERP-system. And I’ll show you how to accomplish that in the next section.

How to implement custom shipping

First off we need to modify the Total-pipeline. The two pipeline components marked in red in the previous image needs to be removed thus making the Total-pipeline look like in the screenshot below:


The next steps really depend on whether you are working on a CS 2007 or CS 2009 project. I’ll start by explaining the CS 2007 approach:

CS 2007

   1:  public void CreateBasketInCs2007()
   2:  {
   3:      // Create a new basket
   4:      var basket =
   5:          CommerceContext.Current.OrderSystem.GetBasket(Guid.NewGuid(), "Default2007");
   7:      // Create a new OrderForm
   8:      if (basket.OrderForms.Count == 0)
   9:          basket.OrderForms.Add(new OrderForm());
  11:      // Add some line items
  12:      foreach (string productId in new[] { "2-1", "2-2" })
  13:      {
  14:          basket.OrderForms[0].LineItems.Add(new LineItem("TestCatalog", productId, null, 1));
  15:      }
  17:      // Add an address
  18:      var address = new OrderAddress("Default", String.Empty) { FirstName = "First Name" };
  19:      basket.Addresses.Add(address);
  21:      // Add your custom shipment with custom price (300)
  22:      basket.OrderForms[0].Shipments.Add(
  23:          new Shipment 
  24:      { 
  25:          ShippingMethodName = "MyShippingMethod",
  26:          ShipmentTrackingNumber = "My Tracking",
  27:          ShipmentTotal = 300m, 
  28:          ShippingAddressId = address.OrderAddressId 
  29:      });
  31:      // Associate Shipping and Line Items
  32:      basket.OrderForms[0].Shipments[0].LineItemIndexes.Add(0);
  33:      basket.OrderForms[0].Shipments[0].LineItemIndexes.Add(1);
  35:      // Execute the pipelines
  36:      using (var pipeline = new PipelineInfo("Basket", OrderPipelineType.Basket))
  37:      {
  38:          _writer.WriteLine(basket.RunPipeline(pipeline));
  39:      }
  41:      using (var pipeline = new PipelineInfo("Total", OrderPipelineType.Total))
  42:      {
  43:          _writer.WriteLine(basket.RunPipeline(pipeline));
  44:      }
  46:      basket.Save();
  47:  }

The example above creates a new Basket, adds some line items, and adds a Shipment instance with a custom price. The point is that this custom price could come from anywhere: web-service call to ERP, custom database, custom pricing calculation logic, etc. Here is the result of the basket shown in Customer and Orders Manager:

BasketCS2007-1 BasketCS2007-2

To illustrate the same in CS 2009 we need to do a bit more work as shown below.

CS 2009

Lets start by looking at the actual query code:

   1:  public void CreateBasketInCs2009()
   2:  {
   3:      var basketUpdateQuery =
   4:          new CommerceUpdate<CommerceEntity, CommerceModelSearch<CommerceEntity>, CommerceBasketUpdateOptionsBuilder>("Basket");
   6:      basketUpdateQuery.SearchCriteria.Model.SetPropertyValue("UserId", Guid.NewGuid().ToString());
   7:      basketUpdateQuery.SearchCriteria.Model.SetPropertyValue("BasketType", 0); // Basket
   8:      basketUpdateQuery.SearchCriteria.Model.SetPropertyValue("Name", "Default2009");
  10:      // Must be set to "ReadyForCheckout" in order to have Total pipeline executed
  11:      basketUpdateQuery.Model.SetPropertyValue("Status", "ReadyForCheckout");
  13:      basketUpdateQuery.Model.SetPropertyValue("BasketType", 0); // 0 = Basket
  15:      // Decides whether to run pipelines or not
  16:      basketUpdateQuery.UpdateOptions.RefreshBasket = true;
  18:      // Add an address
  19:      var address = new CommerceEntity("Address")
  20:      {
  21:          Id = Guid.NewGuid().ToString("B")
  22:      };
  24:      address.SetPropertyValue("AddressName", "Home");
  25:      address.SetPropertyValue("FirstName", "First Name");
  26:      address.SetPropertyValue("ProfileAddressId", String.Empty);
  28:      basketUpdateQuery.RelatedOperations.Add(
  29:          new CommerceCreateRelatedItem<CommerceEntity>("Addresses", "Address") { Model = address });
  31:      // Add some line items
  32:      foreach (string productId in new[] { "2-1", "2-2" })
  33:      {
  34:          var lineItem = new CommerceEntity("LineItem");
  36:          lineItem.SetPropertyValue("ProductId", productId);
  37:          lineItem.SetPropertyValue("Quantity", 1);
  38:          lineItem.SetPropertyValue("CatalogName", "TestCatalog");
  39:          lineItem.SetPropertyValue("ShippingAddressId", address.Id);
  41:          // if using multiple shippings - use this to pair line item and shipping
  42:          lineItem.SetPropertyValue("ShippingMethodName", "MyShippingMethod");
  44:          basketUpdateQuery.RelatedOperations.Add(
  45:              new CommerceCreateRelatedItem<CommerceEntity>("LineItems", "LineItem") { Model = lineItem });
  46:      }
  48:      // Add a custom shipment
  49:      var shipment = new CommerceEntity("Shipment");
  50:      shipment.SetPropertyValue("Total", 300m); // custom price right here!
  51:      shipment.SetPropertyValue("ShippingAddressId", address.Id);
  52:      shipment.SetPropertyValue("TrackingNumber", "some-tracking-code");
  53:      shipment.SetPropertyValue("StatusCode", "some-status");
  54:      shipment.SetPropertyValue("ShippingMethodName", "MyShippingMethod");
  56:      basketUpdateQuery.RelatedOperations.Add(
  57:          new CommerceCreateRelatedItem<CommerceEntity>("Shipments") { Model = shipment });
  59:      var operationServiceAgent = new OperationServiceAgent();
  61:      var response =
  62:          operationServiceAgent
  63:              .ProcessRequest(Request.CreateCommerceRequestContext(), basketUpdateQuery.ToRequest())
  64:              .OperationResponses
  65:              .Single() as CommerceUpdateOperationResponse;
  67:      foreach (var entity in response.CommerceEntities ?? Enumerable.Empty<CommerceEntity>())
  68:          entity.Output(_writer);
  70:      _writer.WriteLine("Count = {0}", response.Count);
  71:  }

Executing this query with the standard Operation Sequence Components and Translators in the Multi Channel Foundation API will fail. The reason why is that there is logic in these classes that validates whether Line Items have a valid ShippingMethodId in order to execute the Total-pipeline, and also there is simply no translator to translate from a CS 2009 Shipment-CommerceEntity to a Shipment-type in CS 2007.

More work needs to be put in this effort, lets start with the Translator part. First off we need to extend the current LineItemTranslator to have the ShippingMethodName translated:

   1:  public class CustomShippingSupportingLineItemTranslator : LineItemTranslator
   2:  {
   3:      protected override bool TranslateToStronglyTypedCommerceServerProperty(LineItem commerceServerObject, string commerceServerPropertyName, object value)
   4:      {
   5:          bool result =
   6:              base.TranslateToStronglyTypedCommerceServerProperty(commerceServerObject, commerceServerPropertyName, value);
   8:          if (!result && 
   9:              value is String &&
  10:              String.Equals(commerceServerPropertyName, "ShippingMethodName", StringComparison.OrdinalIgnoreCase))
  11:          {
  12:              commerceServerObject.ShippingMethodName = value.ToString();
  13:              result = true;
  14:          }
  16:          return result;
  17:      }
  18:  }

And register this in the ChannelConfiguration.config:

   1:  <Translator
   2:    sourceModelName="LineItem"
   3:    destinationType="Microsoft.CommerceServer.Runtime.Orders.LineItem, Microsoft.CommerceServer.Runtime, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
   4:    type="Extensions.Providers.Translators.CustomShippingSupportingLineItemTranslator, Extensions, Version=, Culture=neutral, PublicKeyToken=4c1d6c3df403d290"/>


Next up is extending the existing ShipmentTranslator to support External Entity translation, like this:

   1:  public class ExternalEntitySupportingShipmentTranslator : ShipmentTranslator, IToExternalEntityTranslator
   2:  {
   3:      private readonly PropertyTranslator<Shipment> _propertyTranslator;
   5:      public ExternalEntitySupportingShipmentTranslator()
   6:      {
   7:          _propertyTranslator = 
   8:              new PropertyTranslator<Shipment>(
   9:                  null, 
  10:                  null,
  11:                  TranslateToStronglyTypedCommerceServerProperty,
  12:                  TranslateToWeaklyTypedCommerceServerProperty);
  13:      }
  15:      public void Translate(CommerceEntity sourceCommerceEntity, object destination)
  16:      {
  17:          if (sourceCommerceEntity == null) throw new ArgumentNullException("sourceCommerceEntity");
  18:          if (destination == null) throw new ArgumentNullException("destination");
  20:          _propertyTranslator.TranslateToCommerceServer(sourceCommerceEntity, destination as Shipment, null);
  21:      }
  23:      protected virtual bool TranslateToWeaklyTypedCommerceServerProperty(Shipment commerceServerObject, string commerceServerPropertyName, object value)
  24:      {
  25:          commerceServerObject[commerceServerPropertyName] = value;
  26:          return true;
  27:      }
  29:      protected virtual bool TranslateToStronglyTypedCommerceServerProperty(Shipment commerceServerObject, string commerceServerPropertyName, object value)
  30:      {
  31:          switch (commerceServerPropertyName)
  32:          {
  33:              case "ShipmentTotal":
  34:                  if (value != null)
  35:                      commerceServerObject.ShipmentTotal = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
  36:                  break;
  38:              case "ShippingAddressId":
  39:                  commerceServerObject.ShippingAddressId = value as string;
  40:                  break;
  42:              case "ShippingMethodName":
  43:                  commerceServerObject.ShippingMethodName = value as string;
  44:                  break;
  46:              case "ShippingMethodId":
  47:                  Guid methodId = (value is string) ? new Guid(value as string) : Guid.Empty;
  48:                  commerceServerObject.ShippingMethodId = methodId;
  49:                  break;
  51:              case "Status":
  52:                  commerceServerObject.Status = value as string;
  53:                  break;
  55:              case "ShipmentTrackingNumber":
  56:                  commerceServerObject.ShipmentTrackingNumber = value as string;
  57:                  break;
  59:              default:
  60:                  return false;
  61:          }
  63:          return true;
  64:      }
  65:  }

And again register this in the ChannelConfiguration.config:

   1:  <Translator
   2:    sourceModelName="Shipment"
   3:    destinationType="Microsoft.CommerceServer.Runtime.Orders.Shipment, Microsoft.CommerceServer.Runtime, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
   4:    type="Extensions.Providers.Translators.ExternalEntitySupportingShipmentTranslator, Extensions, Version=, Culture=neutral, PublicKeyToken=4c1d6c3df403d290" />


This covers the translation part, so we can map/translate from a CS 2009 entity to a CS 2007 entity.

The next part is where it gets a little tricky. We need to create a custom Operation Sequence Component, that will take care of the actual creation of the CS 2007 Shipment-instance, and add this to the Shipments collection of the Basket that we are executing a query against.

To create a custom Operation Sequence Component, we create the following class:

   1:  public class CustomShipmentsProcessor : BasketRelatedItemOperationSequenceComponent
   2:  {
   3:      protected override string RelationshipName
   4:      {
   5:          get { return "Shipments"; }
   6:      }
   8:      protected override void CreateRelatedItem(CommerceCreateRelatedItem createRelatedItemOperation)
   9:      {
  10:          CommerceEntity shipmentEntity = createRelatedItemOperation.Model;
  12:          var newShipment =
  13:              CommerceServerClassFactory.CreateInstance<Shipment>(
  14:                  shipmentEntity.ModelName, CommerceServerArea.Orders);
  16:          shipmentEntity.Id = newShipment.ShipmentId.ToString("B");
  18:          Translator.ToExternalEntity(shipmentEntity, newShipment);
  20:          OrderForm orderForm = CachedOrderGroup.GetDefaultOrderForm();
  22:          UpdateShippingLineItemsAssociation(newShipment, orderForm);
  24:          orderForm.Shipments.Add(newShipment);
  25:      }
  27:      public override void ExecuteUpdate(CommerceUpdateOperation updateOperation, Microsoft.Commerce.Broker.OperationCacheDictionary operationCache, CommerceUpdateOperationResponse response)
  28:      {
  29:          base.ExecuteUpdate(updateOperation, operationCache, response);
  31:          EnsureLineItemsHasShippingMethod();
  32:      }
  34:      private void EnsureLineItemsHasShippingMethod()
  35:      {
  36:          OrderForm orderForm = CachedOrderGroup.GetDefaultOrderForm();
  38:          foreach (LineItem lineItem in
  39:              orderForm.LineItems
  40:                  .OfType<LineItem>()
  41:                  .Where(x => x.ShippingMethodId == Guid.Empty))
  42:          {
  43:              lineItem.ShippingMethodId = Guid.NewGuid();
  44:          }
  45:      }
  47:      protected override void DeleteRelatedItem(CommerceDeleteRelatedItem deleteRelatedItemOperation)
  48:      {
  49:          string shipmentId = GetSearchModelId(deleteRelatedItemOperation, "Shipment", true);
  51:          if (!String.IsNullOrEmpty(shipmentId))
  52:          {
  53:              Shipment deletingShipment = GetShipmentFromCachedOrderGroup(shipmentId);
  54:              CachedOrderGroup.GetDefaultOrderForm().Shipments.Remove(deletingShipment);
  55:          }
  56:          else
  57:              CachedOrderGroup.GetDefaultOrderForm().Shipments.Clear();
  58:      }
  60:      protected override void UpdateRelatedItem(CommerceUpdateRelatedItem updateRelatedItemOperation)
  61:      {
  62:          OrderForm orderForm = CachedOrderGroup.GetDefaultOrderForm();
  64:          CommerceEntity model = updateRelatedItemOperation.GetModel("Shipment");
  65:          string shipmentId = GetSearchModelId(updateRelatedItemOperation, "Shipment", true);
  67:          if (!String.IsNullOrEmpty(shipmentId))
  68:          {
  69:              Shipment updatingShipment = GetShipmentFromCachedOrderGroup(shipmentId);
  70:              Translator.ToExternalEntity(model, updatingShipment);
  71:              UpdateShippingLineItemsAssociation(updatingShipment, orderForm);
  72:          }
  73:          else
  74:          {
  75:              foreach (Shipment shipment in orderForm.Shipments)
  76:                  Translator.ToExternalEntity(model, shipment);
  77:          }
  78:      }
  80:      protected virtual Shipment GetShipmentFromCachedOrderGroup(string shipmentId)
  81:      {
  82:          OrderForm orderForm = CachedOrderGroup.GetDefaultOrderForm();
  84:          int index = orderForm.Shipments.IndexOf(new Guid(shipmentId));
  86:          if (index < 0)
  87:              throw ItemDoesNotExist("Shipment", shipmentId);
  89:          return orderForm.Shipments[index];
  90:      }
  92:      protected virtual void UpdateShippingLineItemsAssociation(Shipment shipment, OrderForm orderForm)
  93:      {
  94:          foreach (LineItem lineItem in
  95:              orderForm.LineItems
  96:                  .OfType<LineItem>()
  97:                  .Where(x => 
  98:                      String.IsNullOrEmpty(x.ShippingMethodName) ||
  99:                      String.Equals(x.ShippingMethodName, shipment.ShippingMethodName, StringComparison.OrdinalIgnoreCase)))
 100:          {
 101:              lineItem.ShippingMethodId = shipment.ShipmentId;
 102:              shipment.LineItemIndexes.Add(lineItem.Index);
 103:          }
 104:      }
 106:      protected virtual FaultException<ItemDoesNotExistFault> ItemDoesNotExist(string entityName, string entityId)
 107:      {
 108:          string message = ProviderResources.ExceptionMessages.GetMessage("ItemDoesNotExist", new object[0]);
 110:          var detail = new ItemDoesNotExistFault(message);
 111:          if (!String.IsNullOrEmpty(entityName))
 112:          {
 113:              detail.CommerceEntityName = entityName;
 114:          }
 115:          if (!String.IsNullOrEmpty(entityId))
 116:          {
 117:              detail.CommerceEntityId = entityId;
 118:          }
 120:          return new FaultException<ItemDoesNotExistFault>(detail, message);
 121:      }
 122:  }


This Operation Sequence Component will make sure that all the prerequisites required for the Total-pipeline component to be executed by the standard OrderPipelineProcessor are met, and it will make sure that Line Items and the newly created Shipments are associated to each other. This is particular necessary if you need multiple shipment – having different line items using different shipment methods.

To register the new Operation Sequence Component we again have to do some work in the ChannelConfiguration.config:

   1:  <Component name="Requested Promo Codes processor" type="Microsoft.Commerce.Prov…">
   2:    <Configuration
   3:      customElementName="RequestedPromoCodesProcessorConfiguration"
   4:      customElementType="Microsoft.Commerce.Providers.Components.RequestedPromoCodesPr…">
   5:      <RequestedPromoCodesProcessorConfiguration promoUserIdentityProperty="Email"/>
   6:    </Configuration>
   7:  </Component>
   9:  <Component name="Custom Shipments processor" type="Extensions.Providers.Components.CustomShipmentsProcessor, Extensions, Version=, Culture=neutral, PublicKeyToken=4c1d6c3df403d290" />
  11:  <Component name="Payments processor" type="Microsoft.Commerce.Providers.Components.PaymentsProcessor, Microsoft.Commerce.Providers, Version=, Culture=neutral,PublicKeyToken=31bf3856ad364e35" />
  12:  <Component name="Targeting Profiles" type="Microsoft.Commerce.Providers.Components.TargetingProfilesProcessor, Microsoft.Commerce.Providers, Version=, Culture=neutral,PublicKeyToken=31bf3856ad364e35" />
  13:  <Component name="Order Pipelines Processor" type="Microsoft.Commerce.Providers.Components.OrderPipelinesProcessor, Microsoft.Commerce.Providers, Version=, Culture=neutral,PublicKeyToken=31bf3856ad364e35">

The component needs to be added before the Order Pipelines Processor.

Thats it. Execute the query, and the result will look like the following in Customer and Orders Manager:


BasketCS2009-1 BasketCS2009-2


I hope this was useful. If you have any questions or want me to elaborate more on this subject please don't hesitate to contact me in any way.

Posted on Friday, 15 October 2010 09:36:37 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Tuesday, 05 October 2010

Interested in taking your Commerce Server skills to the next level?

Do you want to know how you can apply custom pricing, custom shipping and custom payment?

These are just some of the subjects that we will be covering in our upcoming Commerce Server Developer Week course held in New York and Europe this November.

November 1st – 5th 2010
Hilton Times Square
234 West, 42nd Street
New York

November 22nd – 26th 2010
Quality Airport Hotel Dan
Kastruplundgade 15
2770 Kastrup, Copenhagen

Read more and sign-up on where you will also be able to read reviews from previous courses held.

Hope to see you there :-)

Posted on Tuesday, 05 October 2010 08:07:12 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Monday, 06 September 2010

There is a built-in restriction in the new Commerce Server 2009 Foundation API (MCCF - Multi Channel Commerce Foundation) to only expect one OrderForm instance on the aggregate root, being the OrderGroup. An OrderGroup is either a Basket or PurchaseOrder (or OrderTemplate if you are using that).

Below is a simplified diagram of the key classes in the Object Model for the Orders System. Please notice that we have the OrderForms collection on the OrderGroup class, where each instance of an OrderForm can be retrieved either by index or by a name (string).

Orders API - class hierachy

This allows us to create multiple OrderForms, each with their own data like Line Items, Payments, Shipments etc, and share other data like Addresses, TrackingNumber and a few more properties on the OrderGroup class.

In all projects I’ve been working on we don’t actually use the ability to have multiple OrderForms. We only have one single OrderForm at all times.

The Commerce Server 2007 code would then look something like:

   1:  Basket basket = OrderContext.Current.GetBasket(userId, basketName);
   3:  if (basket.OrderForms.Count == 0)
   4:      basket.OrderForms.Add(new OrderForm());


This retrieves a named Basket from the Orders System and if the basket is new, it will create an instance of OrderForm and add this to the OrderForms collection.

   1:  basket.OrderForms[0].LineItems.Add(
   2:      new LineItem("ProductCatalog", "ProductID", "VariantID", 1m));


At some point we would then like to add a Line Item to our basket as illustrated above where we hardcode the index-value to 0 to retrieve the one and only OrderForm we have.

In Commerce Server 2009 though, we never deal with the actual types, instead we work with the ICommerceEntity type. The Commerce Server 2009 code for retrieving a basket would be something like:

   1:  var query = new CommerceQuery<CommerceEntity>("Basket");
   3:  query.SearchCriteria.Model.SetPropertyValue("UserId", userId);
   4:  query.SearchCriteria.Model.SetPropertyValue("BasketType", 0); // 0 = Basket, 1 = Purchase Order
   5:  query.SearchCriteria.Model.SetPropertyValue("Name", basketName);
   7:  var operationServiceAgent = new OperationServiceAgent();
   9:  var response =
  10:      operationServiceAgent
  11:          .ProcessRequest(
  12:              new CommerceRequestContext
  13:              {
  14:                  Channel = "DefaultChannel",
  15:                  UserId = Guid.NewGuid().ToString("b"),
  16:                  UserLocale = CultureInfo.CurrentCulture.ToString(),
  17:                  UserUILocale = CultureInfo.CurrentUICulture.ToString(),
  18:                  RequestId = Guid.NewGuid().ToString()
  19:              }, 
  20:              query.ToRequest())
  21:          .OperationResponses
  22:          .Single() as CommerceQueryOperationResponse;
  24:  var basketEntity = response.CommerceEntities.Single();


Effectively this will execute the CommerceQueryOperation_Basket Operation Sequence defined in ChannelConfiguration.config which in short will load the Basket from the Orders System and map this to the generic ICommerceEntity type.

In the process of retrieving and mapping a Basket to a ICommerceEntity the following extension method from Microsoft.Commerce.Providers.Utility.OrderGroupExtensions is being called on the OrderGroup:

   1:  public static void EnsureDefaultOrderForm(this OrderGroup orderGroup, string modelName)
   2:  {
   3:      ParameterChecker.CheckForNull(orderGroup, "orderGroup");
   4:      if (orderGroup.OrderForms["Default"] == null)
   5:      {
   6:          if (string.IsNullOrEmpty(modelName))
   7:          {
   8:              modelName = "Basket";
   9:          }
  10:          OrderForm orderForm = CommerceServerClassFactory.CreateInstance<OrderForm>(modelName, 2, new object[] { "Default" });
  11:          orderGroup.OrderForms.Add(orderForm);
  12:      }
  13:  }

This method is responsible of making sure that a single OrderForm with the name “Default” is always available in the OrderForms collection of the OrderGroup instance.

Also there is an extension method to always retrieve this single OrderForm called Microsoft.Commerce.Providers.Utility.OrderGroupExtensions.GetDefaultOrderForm.

   1:  public static OrderForm GetDefaultOrderForm(this OrderGroup orderGroup, string modelName)
   2:  {
   3:      ParameterChecker.CheckForNull(orderGroup, "orderGroup");
   4:      if (orderGroup.OrderForms["Default"] == null)
   5:      {
   6:          if (string.IsNullOrEmpty(modelName))
   7:          {
   8:              modelName = "Basket";
   9:          }
  10:          OrderForm orderForm = CommerceServerClassFactory.CreateInstance<OrderForm>(modelName, 2, new object[] { "Default" });
  11:          orderGroup.OrderForms.Add(orderForm);
  12:      }
  13:      return orderGroup.OrderForms["Default"];
  14:  }

This method is used throughout the many Operation Sequence Components in the Foundation API and if you are developing your own custom Operation Sequence Components that works on the Orders System this method is properly something you would like to use.

As mentioned earlier we’ve just been using one OrderForm in all our Commerce Server projects, so this new limitation is not a problem for us, but on one of my Commerce Server Training courses I had a guy telling me that they where actually using multiple OrderForms on a CS 2007 project, so this new limitation would be a problem for them if they were to upgrade or create a similar solution on a new CS 2009 project.

What if we need more than OrderForm on our project, what to do?

In short: rethink. A solution with multiple OrderForms in CS 2009 will be too difficult to implement because of the many dependencies in the standard Operation Sequence Components that expecting there to be only one – remember they are all calling the GetDefaultOrderForm() extension method. Consider using multiple OrderGroups instead.


If you are reading this and have come up with a better solution to having multiple OrderForms in CS 2009 or have any comments at all I would really appreciate hearing from you :-)

Posted on Monday, 06 September 2010 07:54:00 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Friday, 03 September 2010

Max Akbar’s Commerce Server 2007 training videos have yet again become available for purchase. More than 19 hours of material can now be found on

We are working on producing and releasing new training videos targeting Commerce Server 2009.

Posted on Friday, 03 September 2010 13:27:37 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Tuesday, 27 April 2010

In Commerce Server the Rank property is used as a sorting discriminator when retrieving categories, products and variants. This property is usually managed using Catalog Manager as shown in the screenshot below:


The Rank property could also be managed in your own application which will be the topic of a later blog-post. This blog-post will focus on how to get the actual Rank value when retrieving categories in Commerce Server.

How did we do this in in Commerce Server 2007?

Pretty easy! We just include “Rank” as an item in the PropertiesToReturnArray for child categories (line 12) when requesting a specific category and later retrieve the value from the property-bag of the Category instance (line 24) as shown in the example below:

   1:  public void RequestUsing2007(string catalogName, string categoryName)
   2:  {
   3:      var catalogSystem = CommerceContext.Current.CatalogSystem;
   5:      var configuration = new CategoryConfiguration
   6:      {
   7:          LoadChildCategories = true,
   8:          ChildCategories =
   9:          {
  10:              SearchOptions =
  11:              {
  12:                  PropertiesToReturnArray = new[] { "DisplayName", "Rank" }
  13:              }
  14:          }
  15:      };
  17:      var category =
  18:          catalogSystem.GetCategory(catalogName, categoryName, "da-DK", configuration);
  20:      foreach (var childCategory in category.ChildCategories)
  21:      {
  22:          _writer.WriteLine("Id={0}", childCategory.Name);
  23:          _writer.WriteLine("DisplayName={0}", childCategory.DisplayName);
  24:          _writer.WriteLine("Rank={0}", childCategory["rank"]);
  26:          _writer.WriteLine();
  27:      }
  28:  }

And how can I do it in Commerce Server 2009?

In Commerce Server 2009 with the new Foundation API the overall way of retrieving entities in Commerce Server has changed a lot. In the example below I’ve added “Rank” as part of the properties to retrieve when requesting child categories for a specific category (line 10):

   1:  public void RequestUsing2009(string catalogName, string categoryName)
   2:  {
   3:      var categoryQuery = new CommerceQuery<CommerceEntity>("Category");
   4:      categoryQuery.Model.Properties.Add(new[] { "Id", "DisplayName" });
   6:      categoryQuery.SearchCriteria.Model.SetPropertyValue("CatalogId", catalogName);
   7:      categoryQuery.SearchCriteria.Model.SetPropertyValue("Id", categoryName);
   9:      var childCategoryQuery = new CommerceQueryRelatedItem<CommerceEntity>("ChildCategories", "Category");
  10:      childCategoryQuery.Model.Properties.Add(new[] { "Id", "DisplayName", "Rank" });
  11:      categoryQuery.RelatedOperations.Add(childCategoryQuery);
  13:      var operationServiceAgent = new OperationServiceAgent();
  15:      var response = 
  16:          operationServiceAgent.ProcessRequest(
  17:              new CommerceRequestContext
  18:              {
  19:                  Channel = "MyChannel",
  20:                  UserLocale = "da-DK",
  21:                  UserUILocale = "da-DK",
  22:                  RequestId = Guid.NewGuid().ToString()
  23:              }, 
  24:              categoryQuery.ToRequest());
  26:      var operationResponse = (CommerceQueryOperationResponse)response.OperationResponses.Single();
  28:      var entity = operationResponse.CommerceEntities.SingleOrDefault();
  30:      foreach (var childCategory in entity.Properties["ChildCategories"] as CommerceRelationshipList)
  31:      {
  32:          foreach (var categoryProperty in childCategory.Target.Properties)
  33:          {
  34:              _writer.WriteLine
  35:                  ("{0}={1} ({2})", 
  36:                  categoryProperty.Key, 
  37:                  categoryProperty.Value, 
  38:                  categoryProperty.Value.GetType().Name);
  39:          }
  41:          _writer.WriteLine();
  42:      }
  43:  }


One of the Operation Sequence components being executed in this operation is responsible for creating the CategoryConfiguration instance, that we manually created in the Commerce Server 2007 example (CS 2007 example line 5). This Operation Sequence component is named “CategoryConfiguration_Prepare”.

The actual logic for building the CategoryConfiguration instance is implemented in a class named CatalogConfigurationBuilder. This class will look in the MetadataDefinitions.xml file to match up the properties requested by the developer (in our case “Id, DisplayName, Rank”) with the properties registered for the actual entity being requested (“Category”).

If Rank is not registred in the MetadataDefinitions.xml file, it will be ignored by the CatalogConfigurationBuilder (method doing this is called TranslatePropertyName) and will therefore not be part of the “PropertiesToReturnArray” that in the end decides which columns to retrieve from the catalog table in SQL Server.

To make sure that Rank is registred in MetadataDefinitions.xml verify it by follow these steps:

  1. Open MetadataDefinitions.xml
  2. Locate <CommerceEntity name=”Category”>
  3. Make sure that there is a <PropertyMapping> element for property=”Rank” (<PropertyMapping property=”Rank” csProperty=”rank” />)
  4. Make sure that there is a <Property> element for name=”Rank” (<Property name=”Rank” dataType=”String” />)

Step 3 explained:

   1:  <EntityMappings>
   2:    <EntityMapping
   3:          csType="Microsoft.CommerceServer.Catalog.Category"
   4:          csAssembly="Microsoft.CommerceServer.Catalog, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
   6:      <PropertyMappings>
   7:        <!-- 
   8:              The following mappings are required here to ensure that all
   9:              the product common properties are represented in the Microsoft Multi-Channel Commerce Foundation 
  10:              metadata. These are not returned by the Commerce Server
  11:              metadata automatically as the common properties.
  12:              -->
  13:        <PropertyMapping property="BaseCatalogName" csProperty="BaseCatalogName" />
  14:        <PropertyMapping property="Rank" csProperty="rank" />


Step 4 explained:

   1:  <Properties>
   2:    <!-- 
   3:      The following property definitions are required here to ensure that all
   4:      the product common properties are represented in the 
   5:      metadata. These are not returned by the Commerce Server
   6:      metadata automatically as the common properties.
   7:      -->
   8:    <Property name="BaseCatalogName"        dataType="String" >
   9:      <DisplayName value="Base Catalog Name"/>
  10:    </Property>
  11:    <Property name="Rank" dataType="String" />


That’s it! The response returned by the Foundation API should now have the actual Rank value returned for each child category entity:

Id=ThirdRankingCategory (String)
Rank=1 (Int32)
DisplayName=ThirdRankingCategory (String)

Id=AnotherRankingCategory (String)
Rank=2 (Int32)
DisplayName=AnotherRankingCategory (String)

Id=SomeRankingCategory (String)
Rank=3 (Int32)
DisplayName=SomeRankingCategory (String)
Posted on Tuesday, 27 April 2010 12:00:00 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Wednesday, 13 May 2009

This is a brief story of my impressions and experiences with the first couple of days attending TechEd 2009 North America.

So after more than 24 hours of travelling from source (Århus, Denmark) to destination (Los Angeles, CA, USA)  I finally arrived at the hotel Saturday evening. I’m staying at a very nice hotel called Hollywood Roosevelt Hotel, just across Kodak Theatre (the place where the Oscar Academy Award takes place every year) on Hollywood Boulevard.


I was signed up to participate in the Pre-Conference part of TechEd, joining in on a whole-day seminar with the topic “The ASP.NET Performance Tuning Cycle”. Stuff I was really looking forward hearing about since we are focusing a lot on performance improvements at Vertica on our projects. The seminar was held by Richard Campbell and Kent Alstad and man those two Canadian guys were really on fire that day. We came around lots of different areas including how to do load-testing with Visual Studio 2008 Team System (Test Edition), load-balancing, how to analyze the response time of a web-site and much more. Generally a whole lot of good advices on how to look at the whole picture of optimizing a given web-site – where to put the energy basically. On advice I would like to highlight here is to use as an external tool to analyze your web-site.

After a great and educational day in company with Richard and Kent I was all psyched about the rest of the conference. I went to see the movie “Wolverine” after getting back to the hotel.

Day 1 – Keynote, ASP.NET 4.0, SharePoint

First day of TechEd started out with the Keynote speech as one would expect. I must admit I wasn’t that enthusiastic about this particular Keynote and as it turned out I wasn’t proved really wrong on that hunch. The Keynote was primarily held by Bill Veghte, Microsoft Senior Vice President on Windows Business. The topics were all about the IT-stuff going on right know with focus on the soon to come operating systems; Windows 7 and Windows Server 2008 R2. Not that I’m not interested in those topics at all, I just wanted to see some developer stuff too – unfortunately there was none.

The first actual session I attended was about ASP.NET 4.0 and what’s coming in that release. It was a pretty good one. Jeff King showed some examples of how you are now able to have more fine-grained control over the markup rendered by the ASP.NET WebForms Framework, some better support for SEO-stuff including new properties on the System.Web.Page class for meta-keywords and –description and generally some new controls to do LINQ-based operations (e.g. Filtering and Sorting) on your LINQ-enabled backend datasource.

The last two sessions I attended that day were both about SharePoint development. The first was focusing on tooling for SharePoint development, especially with focus on using the tool VSeWSS 1.3 (Visual Studio extensions for Windows SharePoint Services). The last session was about how to develop Windows Workflow Foundations to use inside WSS/MOSS applications. This session was held by Ted Pattison, SharePoint MVP and book-writer, and now from my experience a really good presenter. I didn’t get much out of the course technically-wise though since we’ve already been doing a lot of the stuff that he presented.

Next up after first-day sessions was the Partner Expo Reception event which I attended. It can best be described as a circus where all the audience gets magically drawn by all the stuff and weird contests offered by the different vendors. But they had food and beer, and I got a few good talks with some of the different vendors – including a nice demo by a Red Gate Software guy on the soon-to-be-released new version of ANTS Memory Profiler. And yeah of course I got a lot of swag too :-)

Day 2 – More SharePoint, C#, Commerce Server 2009, Parallels, Web Deployment

The second day started out with a morning session by Todd Bleeker on SharePoint Web Part Development Best Practices. Todd had a lot of energy on the stage and that was just really awesome! Even though the session was about Best Practices over half of the audience was rather new to Web Part development which had the impact of Todd having to explain a lot more of the basics taking time from the really interesting stuff. Pretty unfortunate also was that time completely ran out for Todd so he didn’t come around all of his points. All in all I think it was a great session mostly because of Todd’s enthusiasm. And I did get some good advices to take me back home.

I also attended Anders Hejlsberg’s session on “Future of C#”, which I enjoyed a lot – mostly because I think he rocks. To be more specific and professional about it I believe that the future “Compiler As A Service”-feature can really leverage some interesting possibilities to the table, e.g. for an easy way stick-in additional behavior to your code (logging, validation, pre-/post conditions, etc.) to allow cross-cutting concerns to be addressed as secondary concerns.

After having a brief chat with one of the guys at the Commerce Server booth he had me convinced that I should go to their interactive session on Commerce Server 2009 Foundation Services. So I did (later to discover that I thereby missed a Scott Hanselmann session!). Although they had a whole lot of technical problems at this session, I got a pretty good understanding of the upcoming Commerce Server 2009 R2 version including the support of having Commerce Server configured as a true 3-tier environment with Commerce Server as an application server in the middle of this. I’m soon to begin on a new Commerce Server 2009 project which I look really forward to, but seeing that new Foundation API kind of shocks me a little – to me it is extremely verbose compared to the CS 2007 API, and the amount of XML configuration doesn’t seem to have become any less I’m afraid. Perhaps wrapping the new very generic API in a nice fluent and slightly more specific API might just do the trick for me. We’ll see about that.

Next in line was a interesting session on Parallel Computing APIs with .NET 4.0. It gave me a good overview and examples of the possibilities with parallelism that you can get using the new framework that will be baked into .NET 4.0.

Last but not least this I attended a session on Web Application Deployment Packaging and Migration – a look into some new possibilities in IIS about importing and exporting applications and on how to package your solution to allow smooth GUI-enabled installation on IIS. At the session, Faith Allington showed the new Web Platform Installer from Microsoft which I personally think is a awesome tool. Both BlogEngine.NET and Umbraco got some visual attention at this session being on the list of web-applications available on the Web Platform Installer. Faith did really well on this presentation clearly having a lot of knowledge of what she was talking about.

All in all a great day with good sessions by excellent speakers.

Posted on Wednesday, 13 May 2009 05:25:37 (Romance Standard Time, UTC+01:00)
# | Comments [2]
 Monday, 29 September 2008

Jeg fik desværre ikke chancen for at arbejde videre med Umbraco sidst på ugen, forrige uge, pga. F#-arrangement i vores .NET brugergruppe i Århus, samt egen forberedelse til en præsentation og demo af et lille samspil mellem SharePoint 2007, InfoPath Forms og Windows Workflow Foundation.

Derudover kan jeg nævne at jeg i går var til TechTalk hos Microsoft med Anders Hejlsberg, chefarkitekt hos Microsoft på bl.a. C#, samt Steve Ballmer, CEO hos Microsoft. Det var hele køreturen fra Århus til Vedbæk værd, selvom arrangementet kun varede et par timer.

Tilbage til Umbraco. Som nævnt i min forrige post, ramte jeg en lille "mur" i forbindelse med installationen, da jeg ikke havde konfigureret File Permissions for mit Umbraco-website. Jeg Googlede mig vej til en post, som beskriver de foldere og filer som kræver opsætning. Desværre var det ikke helt nok for mig. Jeg måtte sætte Full Control på hele Umbraco-mappen for at få Umbraco til at "æde den". Jeg skal nok vende tilbage med en mere "sikker" løsning, når jeg får tid til at smide lidt tid efter det. Da det her installeres lokalt uden mulighed for at tilgå sitet udefra, kan jeg godt leve med risikoen.


Som det kan ses havde jeg problemer med ACL på mit Umbraco-website. En hurtig quick-fix var at sætte "Full Control" på hele mappen til kontoen "Network Service", som er den konto som afvikler websitet i IIS'en. Dette anbefales ikke til enhver form for produktionsmiljø.

Videre i installationen kom jeg, men allerede ved næste step kaster Umbraco en Exception i hovedet på mig. Se nedenstående skærmbilleder.


Ved klik på "Install", kaster Umbraco følgende Exception:


Kedeligt. Hvad kan man gøre ved det? Jeg vælger at kigge lidt på den kode som smider den Exception, med anvendelse af mit yndlingstool til decompilering af .NET kode: Lutz Roeders Reflector. Reflector er i øvrigt nu overtaget af softwarehuset Red Gate, som har en væld af yderst interessante tools til .NET og SQL i deres produktportefølje, herunder Ants Profiler og SQL Compare. Et hurtigt kig i koden hjælper imidlertid ingenting - så jeg må overveje andre alternativer. Èt af dem er at prøve igen. Jeg klikker på "Tilbage"-knappen i browseren, og klikker på Install-knappen endnu en gang. Det hjalp :-)


Videre i installationen - lækkert. Umbraco tjekker opsætningen af mine filrettigheder, og fortæller mig at det er perfekt sat op, hvilket også er fair nok, på trods af at jeg har sat Full Control på hele mappen. Så helt perfekt er det ikke - men det må jeg kigge på senere.

Igen fortsætter installationen, og nu står jeg på step 5/5 - hvor jeg skal vælge om jeg vil installere Boost. Boost er et nyt begreb i Umbraco 4, og er i følge screencastet af Niels Hartvig, et helt skrabet web-site som overholder en række Umbraco-konventioner (som jeg stadig ikke kender), og som lukker op for en anden feature: Nitros. Igen kan jeg kun trygt læne mig op af Niels Hartvigs definition af Nitros, som dækker over en afgrænset specifik funktionalitet, som f.eks. TopNavigation, standard SiteMap. Jeg har erfaring med WSS/MOSS (SharePoint) udvikling, og umiddelbart minder det rigtig meget om det vi her kender som "Features". Det lyder meget lovende - og mon ikke jeg burde prøve at lave min egen lille Nitro i forbindelse med det her projekt. Jeg følger Niels' guidelines, og installerer Boost med de Nitros som installationen finder.


Et klik på "Install Boost"-knappen bekræfter at jeg har brugt for meget tid på at skrive den her post (og i øvrigt lave en masse andet), end at rent faktisk gennemføre selve installationen af Umbraco, for jeg får straks smidt følgende Exception i hovedet:


Min bruger har simpelthen fået et timeout af Umbraco/ASP.NET. Helt forståeligt. Løsningen var ret simpel; jeg besøgte "http://localhost/umbraco" som er adressen til selve administrationsdelen af Umbraco. Her loggede jeg ind med den bruger som blev oprettet i forbindelse med installationen.

Tilbage til installationen, og et klik på "Install Boost", får mig videre til næste side (se nedenstående skærmbillede), hvor jeg kan vælge de Nitros som skal installeres med sitet. Jeg vælger at installere dem alle.



Hermed er Umbraco 4 installeret på Windows Server 2008 med SQL Server 2008. I Rock!

Posted on Monday, 29 September 2008 18:37:57 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Wednesday, 24 September 2008

I aften var det sidste onsdag i måneden, ergo afholdte vi vores faste månedlige møde i Århus .NET UserGroup (ANUG). Denne gang havde vi inviteret Christian Holm Nielsen til at udgøre den som hovedtaler. Der var lagt på bordet til en gang snak om F#. Et .NET baseret sprog som har været under udvikling i Microsoft Research afdeling, og som nu er udkommet i en CTP-release, og som bl.a. har lagt grund for nogle af de sprog features som vi har fået i C# 3.0 (herunder type inference, og flere eksempler fra denne blog-post om "The Roots of C# 3.0: F# and C-Omega").

Christian gennemgik en langt række eksempler på sprog-features i F#, herunder tupler, sequences, smileys (intern joke), immutable types o.m.a.

Det var super flot at Christian sammen med sin kollega var interesseret i at køre hele vejen fra Odense for at give en præsentation i vores brugergruppe her i Århus. Thumbs up for præsentationen. Klaus Hebsgaard var endda så heldig at vinde en F#-bog for at have stillet aftenens spørgsmål omkring type inference.

Dvs. dagen er 100 % gået med arbejde, og direkte til Usergrup, så jeg har desværre ikke fået arbejdet videre med mit Umbraco-projekt. To be continued...

Posted on Wednesday, 24 September 2008 20:08:25 (Romance Standard Time, UTC+01:00)
# | Comments [0]