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).
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);
2:
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");
2:
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);
6:
7: var operationServiceAgent = new OperationServiceAgent();
8:
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;
23:
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 