Navigation


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.

Categories

On this page

Announcing CSUtilities library
Commerce Server as a Repository
REF: How to implement Custom Shipping Methods in Commerce Server
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
How to extend Commerce Server Payment Methods and Shipment Methods
Very cool Commerce Server training with Max Akbar
Refresh Catalog FullText Index causes search operations to fail
Commerce Server: Search exception on web-site when adding new property

Archive

Blogroll

Disclaimer
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
 Monday, 21 February 2011

As a contribution to the Commerce Server developer community I’ve decided to start working on a small library that will provide some useful functionality for developing Microsoft Commerce Server solutions.

The library was initially hosted on MSDN Code but due to recent restructuring of the way that hosting facility works, I’ve decided to move it to Google Code. From here you can follow the roadmap, change history, download the latest code base (using any SubVersion client) and read documentation on how to use the CSUtilities library.

More information on the CSUtilities library can be found at http://code.google.com/p/csutilities/

Roadmap of CSUtilities

  • Avoid magic strings with Code Generation based on MetadataDefinitions.xml (RELEASED – see below)
  • Get Connection Strings to Commerce Server databases
  • Custom Shipments (replace the built-in Shipping Methods)
  • Custom Payments (replace the built-in Payment Methods)
  • Custom Pricing (pricing outside Commerce Server - ERP, Custom Database, other)
  • LineItemRollup with Commerce Server 2009
  • Multi-currency pricing in a single catalog
  • Replace the Profiles System with a real ORM
  • ASP.NET Templates (.NET 3.5/4.0, MVC/WebForms/Razor)
  • Auto generate Order Schema (SQL/XSD)
  • Foundation API extension methods
  • Deploy pipelines
  • and hopefully a lore more :)

CSUtilities 0.0.0.1

The first release of the new CSUtilities library is now available for you to download on Google Code.

Read more about this release at http://code.google.com/p/csutilities/wiki/CSUtilities0001

Posted on Monday, 21 February 2011 21:13:33 (Romance Standard Time, UTC+01:00)
# | Comments [3]
 Wednesday, 12 January 2011

Working with Enterprise Applications like large e-commerce sites often requires interaction with different systems for different purposes, like:

  • Retrieving catalog data (products and categories)
  • Retrieving customer information
  • Retrieving inventory skus
  • Retrieving prices
  • Sending fullfilled orders
  • … and the list continues …

In Vertica most of our projects are based on the principles of Domain-Driven Design. In short DDD is all about keeping the focus on the domain – e.g. a B2B-driven e-commerce site. The way we structure Commerce Server is to look at Commerce Server as a number of repositories applying the Repository Pattern. That means that we have somewhat few classes that encapsulate all Commerce Server specific logic and return not Commerce Server data types (Microsoft.CommerceServer…*) but data types that we have defined in our own application.

This is illustrated in the image below.

CS-application-design

Cons

  • We need to map from Commerce Server types (ICommerceEntity in CS 2009)
    • Solution: We have created functionality that based on attributes and conventions can do the mappings automatically

Pros

  • All Commerce Server code/queries are encapsulated in few classes
    • Not all developers need to know about Commerce Server
    • Upgrading to newer versions becomes less painfull
  • Commerce Server repositories are mockable allowing us to unit-test functionality in caller classes
  • We cache only lightweight objects that we own and not the large DataSets returned by Commerce Server
    • We can use distributed caching

Properly there are more pros and cons than mentioned above. Please feel free to add your comment on this.

My point is that while Commerce Server is definitely a great platform and a good investment, and I’ll explain why I think so in the section below, it is reasonable to think outside the box when you develop your application. You don’t need to store anything in Commerce Server and you don’t need to spent that much time with their API’s rather than spending the time on the actual feature that you are implementing.

Commerce Server as a platform provides data storage, end-points and APIs for many of these purposes via the different subsystems (Catalog System, Orders System, Marketing System etc.). Most of these subsystems can be widely extended to fit business requirements, like having different product types, customized order schema and custom data entities. Also the product comes with out of the box client tools that that supports working on these different entities in Commerce Server, like creating products in Catalog Manager, viewing orders in Customer and Orders Manager and setting up campaigns in the Marketing Manager.

This is great stuff. If we were to implement the functionality that Commerce Server offers by ourselves most of us properly would not have gotten further than implementing a basic catalog system that could handle multiple products in multiple categories for the same budget than if we had bought a standard license to Commerce Server.

You can build a complete webshop by just utilizing the various components and subsystems in Commerce Server. Microsoft did it in Commerce Server 2007 with the ASP.NET StarterSite. In Commerce Server 2009 they did it again with the StoreFront/Contemporary Site in SharePoint/MOSS.

The problem is that in real world projects the business requirements to presenting data are often quite different that what can be expressed in e.g. a Catalog System. Pricing is one example. In Commerce Server a price is expressed as a single property on a product/variant in the Catalog System. If you add this product to your basket, the price will automatically be set on the Line Item. If you are building an international e-commerce site you properly need to express your price in different currencies. To do that, you would have to create different virtual catalogs, and your application logic would have to adapted to that making your design more complex and harder to maintain. Another scenario is when your price is dependent on the customer buying it – again you would need to have customer specific catalogs and maintain the prices there. In most of the B2B sites that I’ve seen prices are not even “static data” stored in some database, instead they are requested at runtime from the back-end ERP system.

To address the problem with custom pricing we need to tweak Commerce Server to override the price retrieved from the Product in the Catalog System with a price coming from ERP. This can be accomplished by creating a simple pipeline component.

As mentioned earlier there are many ways to extend Commerce Server to fit your business needs. The Profiles System allow you to create new data entities if you need to store custom data, like users, logins, roles, accounts, gift certificates, <insert data here> and the API on top supports all CRUD operations and returns .NET objects (like what you would expect from any ORM – object relational mapping software . While this might sound great: that you can extend and store any data that you would like in the Profiles System, it comes with a price. The price is bad performance (using non-parameterized OLEDB-queries, really bad support for one-to-many/many-to-many relationsships and cumbersome and ineffective extensibility/development model. And why would you even consider using anything remotely cumbersome when we have ORMs frameworks like Castle Active Record, NHibernate and Entity Framework to just name a few that very easily allows you to map data from a database and into your domain?

Besides using a ORM instead of the Profiles System we use and apply a number of different technologies and principles in our applications, like:

  • Model-View-Presenter/Model-View-Controller
    • Clean separation of UI and model
  • Dependency Injection pattern
    • With a Inversion of Control container in place
  • Unit-testing
    • With Mocking framework in place
  • Aspect Oriented Programming – DynamicProxy
    • Caching
    • Logging
  • Domain Events

I hope to see some comments on this blog-post as application design usually is very subjective :)

Posted on Wednesday, 12 January 2011 17:17:00 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Friday, 22 October 2010

For some reason my Feedburner feed is not showing my latest post on implementing custom shipping methods in Commerce Server, so I’m just publishing this post to make sure that people subscribing to my feed gets the latest information.

If you did get the latest post in your RSS-reader please ignore this post, and I’m sorry about the inconvenience.

Have a great weekend to all of you :-)

Posted on Friday, 22 October 2010 07:08:00 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 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 (http://social.msdn.microsoft.com/Forums/en-US/commserver2009/thread/bca894c3-122a-4f11-8fe0-35e2b49de1b8/#5cad8631-ca9f-47bf-8bd9-5ad01d77ba8c) 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.

ShippingMethods

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.

PipelineComponents

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:

CustomizedPipeline

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");
   6:   
   7:      // Create a new OrderForm
   8:      if (basket.OrderForms.Count == 0)
   9:          basket.OrderForms.Add(new OrderForm());
  10:   
  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:      }
  16:   
  17:      // Add an address
  18:      var address = new OrderAddress("Default", String.Empty) { FirstName = "First Name" };
  19:      basket.Addresses.Add(address);
  20:   
  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:      });
  30:   
  31:      // Associate Shipping and Line Items
  32:      basket.OrderForms[0].Shipments[0].LineItemIndexes.Add(0);
  33:      basket.OrderForms[0].Shipments[0].LineItemIndexes.Add(1);
  34:   
  35:      // Execute the pipelines
  36:      using (var pipeline = new PipelineInfo("Basket", OrderPipelineType.Basket))
  37:      {
  38:          _writer.WriteLine(basket.RunPipeline(pipeline));
  39:      }
  40:   
  41:      using (var pipeline = new PipelineInfo("Total", OrderPipelineType.Total))
  42:      {
  43:          _writer.WriteLine(basket.RunPipeline(pipeline));
  44:      }
  45:   
  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");
   5:   
   6:      basketUpdateQuery.SearchCriteria.Model.SetPropertyValue("UserId", Guid.NewGuid().ToString());
   7:      basketUpdateQuery.SearchCriteria.Model.SetPropertyValue("BasketType", 0); // Basket
   8:      basketUpdateQuery.SearchCriteria.Model.SetPropertyValue("Name", "Default2009");
   9:   
  10:      // Must be set to "ReadyForCheckout" in order to have Total pipeline executed
  11:      basketUpdateQuery.Model.SetPropertyValue("Status", "ReadyForCheckout");
  12:   
  13:      basketUpdateQuery.Model.SetPropertyValue("BasketType", 0); // 0 = Basket
  14:   
  15:      // Decides whether to run pipelines or not
  16:      basketUpdateQuery.UpdateOptions.RefreshBasket = true;
  17:   
  18:      // Add an address
  19:      var address = new CommerceEntity("Address")
  20:      {
  21:          Id = Guid.NewGuid().ToString("B")
  22:      };
  23:   
  24:      address.SetPropertyValue("AddressName", "Home");
  25:      address.SetPropertyValue("FirstName", "First Name");
  26:      address.SetPropertyValue("ProfileAddressId", String.Empty);
  27:   
  28:      basketUpdateQuery.RelatedOperations.Add(
  29:          new CommerceCreateRelatedItem<CommerceEntity>("Addresses", "Address") { Model = address });
  30:   
  31:      // Add some line items
  32:      foreach (string productId in new[] { "2-1", "2-2" })
  33:      {
  34:          var lineItem = new CommerceEntity("LineItem");
  35:   
  36:          lineItem.SetPropertyValue("ProductId", productId);
  37:          lineItem.SetPropertyValue("Quantity", 1);
  38:          lineItem.SetPropertyValue("CatalogName", "TestCatalog");
  39:          lineItem.SetPropertyValue("ShippingAddressId", address.Id);
  40:   
  41:          // if using multiple shippings - use this to pair line item and shipping
  42:          lineItem.SetPropertyValue("ShippingMethodName", "MyShippingMethod");
  43:   
  44:          basketUpdateQuery.RelatedOperations.Add(
  45:              new CommerceCreateRelatedItem<CommerceEntity>("LineItems", "LineItem") { Model = lineItem });
  46:      }
  47:   
  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");
  55:   
  56:      basketUpdateQuery.RelatedOperations.Add(
  57:          new CommerceCreateRelatedItem<CommerceEntity>("Shipments") { Model = shipment });
  58:   
  59:      var operationServiceAgent = new OperationServiceAgent();
  60:   
  61:      var response =
  62:          operationServiceAgent
  63:              .ProcessRequest(Request.CreateCommerceRequestContext(), basketUpdateQuery.ToRequest())
  64:              .OperationResponses
  65:              .Single() as CommerceUpdateOperationResponse;
  66:   
  67:      foreach (var entity in response.CommerceEntities ?? Enumerable.Empty<CommerceEntity>())
  68:          entity.Output(_writer);
  69:   
  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);
   7:   
   8:          if (!result && 
   9:              value is String &&
  10:              String.Equals(commerceServerPropertyName, "ShippingMethodName", StringComparison.OrdinalIgnoreCase))
  11:          {
  12:              commerceServerObject.ShippingMethodName = value.ToString();
  13:              result = true;
  14:          }
  15:   
  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=6.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
   4:    type="Extensions.Providers.Translators.CustomShippingSupportingLineItemTranslator, Extensions, Version=1.0.0.0, 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;
   4:   
   5:      public ExternalEntitySupportingShipmentTranslator()
   6:      {
   7:          _propertyTranslator = 
   8:              new PropertyTranslator<Shipment>(
   9:                  null, 
  10:                  null,
  11:                  TranslateToStronglyTypedCommerceServerProperty,
  12:                  TranslateToWeaklyTypedCommerceServerProperty);
  13:      }
  14:   
  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");
  19:   
  20:          _propertyTranslator.TranslateToCommerceServer(sourceCommerceEntity, destination as Shipment, null);
  21:      }
  22:   
  23:      protected virtual bool TranslateToWeaklyTypedCommerceServerProperty(Shipment commerceServerObject, string commerceServerPropertyName, object value)
  24:      {
  25:          commerceServerObject[commerceServerPropertyName] = value;
  26:          return true;
  27:      }
  28:   
  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;
  37:   
  38:              case "ShippingAddressId":
  39:                  commerceServerObject.ShippingAddressId = value as string;
  40:                  break;
  41:   
  42:              case "ShippingMethodName":
  43:                  commerceServerObject.ShippingMethodName = value as string;
  44:                  break;
  45:   
  46:              case "ShippingMethodId":
  47:                  Guid methodId = (value is string) ? new Guid(value as string) : Guid.Empty;
  48:                  commerceServerObject.ShippingMethodId = methodId;
  49:                  break;
  50:   
  51:              case "Status":
  52:                  commerceServerObject.Status = value as string;
  53:                  break;
  54:   
  55:              case "ShipmentTrackingNumber":
  56:                  commerceServerObject.ShipmentTrackingNumber = value as string;
  57:                  break;
  58:   
  59:              default:
  60:                  return false;
  61:          }
  62:   
  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=6.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
   4:    type="Extensions.Providers.Translators.ExternalEntitySupportingShipmentTranslator, Extensions, Version=1.0.0.0, 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:      }
   7:   
   8:      protected override void CreateRelatedItem(CommerceCreateRelatedItem createRelatedItemOperation)
   9:      {
  10:          CommerceEntity shipmentEntity = createRelatedItemOperation.Model;
  11:   
  12:          var newShipment =
  13:              CommerceServerClassFactory.CreateInstance<Shipment>(
  14:                  shipmentEntity.ModelName, CommerceServerArea.Orders);
  15:   
  16:          shipmentEntity.Id = newShipment.ShipmentId.ToString("B");
  17:   
  18:          Translator.ToExternalEntity(shipmentEntity, newShipment);
  19:   
  20:          OrderForm orderForm = CachedOrderGroup.GetDefaultOrderForm();
  21:   
  22:          UpdateShippingLineItemsAssociation(newShipment, orderForm);
  23:   
  24:          orderForm.Shipments.Add(newShipment);
  25:      }
  26:   
  27:      public override void ExecuteUpdate(CommerceUpdateOperation updateOperation, Microsoft.Commerce.Broker.OperationCacheDictionary operationCache, CommerceUpdateOperationResponse response)
  28:      {
  29:          base.ExecuteUpdate(updateOperation, operationCache, response);
  30:   
  31:          EnsureLineItemsHasShippingMethod();
  32:      }
  33:   
  34:      private void EnsureLineItemsHasShippingMethod()
  35:      {
  36:          OrderForm orderForm = CachedOrderGroup.GetDefaultOrderForm();
  37:   
  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:      }
  46:   
  47:      protected override void DeleteRelatedItem(CommerceDeleteRelatedItem deleteRelatedItemOperation)
  48:      {
  49:          string shipmentId = GetSearchModelId(deleteRelatedItemOperation, "Shipment", true);
  50:   
  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:      }
  59:   
  60:      protected override void UpdateRelatedItem(CommerceUpdateRelatedItem updateRelatedItemOperation)
  61:      {
  62:          OrderForm orderForm = CachedOrderGroup.GetDefaultOrderForm();
  63:   
  64:          CommerceEntity model = updateRelatedItemOperation.GetModel("Shipment");
  65:          string shipmentId = GetSearchModelId(updateRelatedItemOperation, "Shipment", true);
  66:   
  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:      }
  79:   
  80:      protected virtual Shipment GetShipmentFromCachedOrderGroup(string shipmentId)
  81:      {
  82:          OrderForm orderForm = CachedOrderGroup.GetDefaultOrderForm();
  83:   
  84:          int index = orderForm.Shipments.IndexOf(new Guid(shipmentId));
  85:   
  86:          if (index < 0)
  87:              throw ItemDoesNotExist("Shipment", shipmentId);
  88:   
  89:          return orderForm.Shipments[index];
  90:      }
  91:   
  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:      }
 105:   
 106:      protected virtual FaultException<ItemDoesNotExistFault> ItemDoesNotExist(string entityName, string entityId)
 107:      {
 108:          string message = ProviderResources.ExceptionMessages.GetMessage("ItemDoesNotExist", new object[0]);
 109:   
 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:          }
 119:   
 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>
   8:            
   9:  <Component name="Custom Shipments processor" type="Extensions.Providers.Components.CustomShipmentsProcessor, Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4c1d6c3df403d290" />
  10:            
  11:  <Component name="Payments processor" type="Microsoft.Commerce.Providers.Components.PaymentsProcessor, Microsoft.Commerce.Providers, Version=1.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35" />
  12:  <Component name="Targeting Profiles" type="Microsoft.Commerce.Providers.Components.TargetingProfilesProcessor, Microsoft.Commerce.Providers, Version=1.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35" />
  13:  <Component name="Order Pipelines Processor" type="Microsoft.Commerce.Providers.Components.OrderPipelinesProcessor, Microsoft.Commerce.Providers, Version=1.0.0.0, 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
Denmark

Read more and sign-up on http://www.commerceservertraining.com/events.aspx 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);
   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 :-)

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 http://www.commerceservertraining.com/training-videos.aspx.

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:

image

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;
   4:   
   5:      var configuration = new CategoryConfiguration
   6:      {
   7:          LoadChildCategories = true,
   8:          ChildCategories =
   9:          {
  10:              SearchOptions =
  11:              {
  12:                  PropertiesToReturnArray = new[] { "DisplayName", "Rank" }
  13:              }
  14:          }
  15:      };
  16:   
  17:      var category =
  18:          catalogSystem.GetCategory(catalogName, categoryName, "da-DK", configuration);
  19:   
  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"]);
  25:   
  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" });
   5:   
   6:      categoryQuery.SearchCriteria.Model.SetPropertyValue("CatalogId", catalogName);
   7:      categoryQuery.SearchCriteria.Model.SetPropertyValue("Id", categoryName);
   8:   
   9:      var childCategoryQuery = new CommerceQueryRelatedItem<CommerceEntity>("ChildCategories", "Category");
  10:      childCategoryQuery.Model.Properties.Add(new[] { "Id", "DisplayName", "Rank" });
  11:      categoryQuery.RelatedOperations.Add(childCategoryQuery);
  12:   
  13:      var operationServiceAgent = new OperationServiceAgent();
  14:   
  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());
  25:   
  26:      var operationResponse = (CommerceQueryOperationResponse)response.OperationResponses.Single();
  27:   
  28:      var entity = operationResponse.CommerceEntities.SingleOrDefault();
  29:   
  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:          }
  40:   
  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=6.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
   5:   
   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]
 Sunday, 16 December 2007

By default Microsoft Commerce Server 2007 is highly extensible for example in regards to extending the object model in the Order System (e.g. creating new classes or extending existing ones). Also the Profile System is properly the most extensible subsystem in Commerce Server being a general purpose data repository that allows the developer to define and store any custom data in it. While this is all great and good news, not everything on the Commerce Server platform is possible to extend and this post will focus on one specific lack of extensibility in regards to extending Payment Methods and Shipment Methods.

If you want to read more about Commerce Server 2007 seen from a developer and architect's point of view, I recommend reading my colleague Søren's "Developing with Commerce Server 2007"-series on his blog publicvoid.dk.

Requirements

In Denmark we have a national wide postal service called Post Danmark which we use as our carrier for sending letters, packages etc. This is defined in Commerce Server as one of the Shipment Methods on the specific customer solution I'm currently working on. Post Danmark also offers a service allowing the end-customer to postpone the actual payment of the order until the physical arrival. By having this option, we created a Payment Method in the system called "Post Danmark Efterkrav" ("Post Danmark Cash On Delivery"), so the customer can choose this in the checkout process. To be able to use this payment method we demand that the customer has already chosen Post Danmark as the shipment method. If the customer has chosen another shipment method we will not allow the customer to choose the cash on delivery payment method from Post Danmark, thus having the payment method depending on the chosen shipment method.

Management of Commerce Server Payment Methods and Shipment Methods takes place in the Customer and Orders Manager application. When creating a Payment Method it is possible to specify e.g. Payment Type (Credit Card Payment, Gift Certificate Payment, etc.). It is also possible to specify Name and Description of the Payment Method in the languages you want to support, plus a few more options. Unfortunately this is one of the areas in Commerce Server where you seemed to be locked by this model or behavior and you cannot do any obvious extending. Nonetheless we needed some way to do it, because we wanted to be able to specify a relation or dependency between Payment Methods and Shipment Methods. We wanted to make it possible to specify that a specific Payment Method should only be available when a specific Shipment Method was selected in the checkout process. And of course we wanted to accomplish this using as much Commerce Server standards as possible, so future service pack upgrades would not cause problems among other obvious reasons :-)

The solution

As mentioned earlier the Profile System in Commerce Server is our general purpose data repository where we can model almost anything and leverage from Commerce Server API's to do data access operations and provide security to our data store. The solution we came up with was to create two new profiles in the Profile System; one for payment method extended properties (PaymentMethodExtensionProfile) and another for shipment method extended properties (ShipmentMethodExtensionProfile) and link these profiles to the Shipment Method and the Payment Method types.

Shipped with Commerce Server comes a Software Development Kit, which among other very nice things contains the complete source code to all the business applications (except the Catalog and Inventory Schema Manager). There are two classes in the Customer and Orders Manager project responsible of displaying the dialogs for management of Payment Methods (PaymentMethodEditView) and Shipment Methods (ShippingMethodEditView) respectively. In each of these Windows Forms dialog classes, we added a new button in the toolbar, gave it an icon and named it "Extended Properties". When the user clicks on this button, we execute some code, that depending on whether it is executed from a Payment Method or Shipment Method basically just performs a search for a specific profile (our extended properties profile) specified by the unique id of for instance the Payment Method, and depending on whether the profile exist or not we open the standard profile management dialog in the right state (edit or new). If the profile does not exist, we initialize it with the unique id (our relation-key value). This is all done in a relatively few lines of code, where all we do is basically the same steps as a business user would manually do to either create a new profile or search for an existing one. All the code pieces for this is of course already in the solution, the hard part was just to find these exact pieces and assemble them to suit our requirements.

I have attached some screenshots below that shows how our feature is implemented in the Customer and Orders Manager application.

payment_method_list

The figure above shows the list of payment methods in the system.

payment_method_dialog

The figure above shows the new button we have added to the dialog, that, when the user clicks on the button, executes our custom logic that opens up the profile management dialog (either a new one, or an existing one), related to the payment method.

payment_method_extension_profile_dialog   

The figure above shows the actual profile which we use for extending the properties for a specific payment method in the system.

payment_related_shipping_method 

The figure above shows the actual relation between the payment method and the shipment method. The logical relation is established between the two extension profiles of the "Post Danmark" shipment method and the "Post Danmark Efterkrav" payment method.

Conclusion

The solution I have described here for extending Payment Method and Shipment Methods in Commerce Server 2007 works really good for us and we have added a lot of extended properties to both the Payment Method type (see screenshots supplied) and the Shipment Method type. Some of the functionality this provides is the ability to filter a specific shipment method to a specific country as well as filter a payment method based on the items added to the basket. Really cool and useful features and the best part of it: we are using standard Commerce Server all the way! :-)

Another very cool thing to mention is that when we had Max Akbar at Vertica to provide us some educational days of concentrated Commerce Server training, I gave him a demo of the feature, and his responses to it were really good.

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 Sunday, 16 December 2007 20:12:41 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Thursday, 06 September 2007

My first experience with Microsoft Commerce Server was in May 2007 when I started working at Vertica. At that point I had absolutely no clue of what Commerce Server could leverage as a standard eCommerce platform. The first few weeks basically went by doing training based on some internal training material and the documentation on MSDN for Commerce Server. Soon after that I became team member on a Commerce Server project for one of our customers. Since then my work has been more or less exclusively to this project which have been a incredible educational experience for me.

Moving on to my point it had been planned for some months that we should have Max Akbar, former Program Manager for the Microsoft Commerce Server Team, come to our company location in Aarhus and provide us some technical training on Commerce Server. At that time I had only read some of Max' posts about Commerce Server but honestly I did not knew much about who he was or what he had do with the Commerce Server product. But as I got to learn more about Commerce Server and began digging more and more into the technical aspects of the product, in purpose of extending and modifying it to our customer needs, Max' name and posts appeared more frequently on the web-sites I came along. Also he had posted some very useful articles on his blog that really helped me a lot doing the development. So it was very great news that he would come to our company, and I was really looking forward to meet him and none the less learn from him.

So this week we had Max coming all the way from California, USA to Aarhus, Denmark to spend a couple of days with us giving in-house training on the Commerce Server 2007 product. Although Max suffered a bit jetlag from his flight there were no way to tell it because he really pulled off some very intensive training and the level of expertise was just stunning! The experience and in-depth knowledge with the Commerce Server product was absolutely remarkable. I really enjoyed attending the course with Max and it fully lived up to my expectations - and then some.

This blog post doesn't cover all the topics that we went through the training with Max, but hopefully it gives a resume of what went on. The first day we got around talking about the installation, architecture and security stuff of Commerce Server as well as giving us some insight on some of the work he had been doing at Microsoft and some of the large scale enterprise solutions he have been a part of. It was very exciting to hear about all that. The second day we learned a lot about the CSS - Commerce Server Staging on how to easily deploy Commerce Server business data and content between different environments. It was very cool to learn about because the CSS part of Commerce Server is one thing that I have none on-hands experience with nor much knowledge about. We also got through the Catalog-, Profiles- and Marketing systems which gave me some very good insight on how this is implemented in the product and how to best interact with the systems. After the second day of training most of us from Vertica went out with Max to grap a beer on Kafé Komma and later we all went to eat on a really good restaurant. The third and last day of the training was primarily focused on integration, extending the Orders system and building custom pipelines.

For me it was really cool to show Max some of the stuff that I have been working on. Especially on how I have extended the Payment- and Shipping Methods using the standard Profile System and how I integrated this into the Customer and Orders Manager application with pretty much no modification to the application at all. I'm proud to say that Max found the stuff I did very cool. I might write a post about it some day.

Also pretty cool was that Max gave us some of the custom tools that he has developed for his various Commerce Server solutions and I'm absolutely sure that they will come in pretty handy in our solutions as well. Max is a really nice guy and besides all the training he did we also had a lot of fun during the training days. I'm really glad to met Max and hopefully I'll come across him again some day.

I'm sorry for any bad English, bad grammar or misspelling. The clock is getting pretty late and I'm not even done packing all my stuff yet. The thing is that we are all leaving very early in the morning (4.15 AM to be exact) as we are going with the train from Aarhus to the Copenhagen Airport from where we are flying to Edinburgh. Vertica has arranged a company trip throughout the weekend to Scotland which I'm really looking forward to.

Posted on Thursday, 06 September 2007 21:30:35 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Tuesday, 28 August 2007

The other day I wrote a blog post about a search problem in the Commerce Server Starter Site. I had added a new property to the propertiesToReturn element of web.config and that caused all search operations on the web-site to fail with the following exception from the database: 'Invalid column name 'Show_Variants_As_Products'.

In my efforts to solve the problem I stumbled across another issue when I tried to execute the "Refresh Catalog FullText Index" command on one of my catalogs in the Catalog Manager (screenshot provided).

This command executed without errors but when I tried to perform a new search on the web-site the database threw another exception with the following error message:

Full-text table or indexed view has more than one LCID among its full-text indexed columns.

This post helped me to the solution.

The solution

In SQL Server Management Studio I opened the Product Catalog database and found the table holding the products for the specific catalog I initially performed the Refresh command on. I changed  the "Language for Word Breaker" setting for the CategoryName column from 'English' back to 'Neutral'  in the properties Full Text Indexing (screenshot provided below).

dbo.[catalogname]_CatalogProducts > Full Text index > Properties > Columns > CategoryName=Neutral (from English)

Posted on Tuesday, 28 August 2007 19:30:22 (Romance Standard Time, UTC+01:00)
# | Comments [0]
 Saturday, 25 August 2007

Short explanation

When creating a new property definition which has the 'Assign to all products' attribute set to true and when this new property is added to the propertiesToReturn element in web.config (configuration/commerceSite/propertiesToReturn) search operations on the web-site can cause the following exception: Invalid column name '[property-name]'. This occurs if one or more of the catalogs in Commerce Server are out of sync with the catalog schema (defined in the Catalog And Inventory Schema Manager) e.g. if the catalog is empty.

Solution: Create a new dummy product in each catalog to ensure synchronization is done. You can safely delete the dummy product after creation.


Complete explanation

I had added the property 'Show_Variants_As_Products' in the Catalog And Inventory Schema Manager. The 'Assign to all products' attribute was set to true to ensure that all products in Commerce Server include this property.

Afterwards I added the new property to the propertiesToReturn string list in web.config:

<commerceSite
 requireSSL="false"
 enableExpressCheckout="false"
 persistAnonymousBaskets="false"
 rewriteProductUrls="true"
 assetLocation="~/"
 defaultShippingMethodId="87c0e40f-3661-4197-8f88-fa3f101ae1ad"
 propertiesToReturn="CategoryName, ProductID, CatalogName, i_ClassType, DisplayName, cy_list_price, VariantID,  Show_Variants_As_Products">

This setting ensures that the property is available in the returned DataSet when e.g. performing search operations on the web-site.

However when I tried to perform a search, the web-site crashed with the following exception coming from the SQL Server:

Invalid column name 'Show_Variants_As_Products'.

In the Catalog Manager I opened a random product in one of my catalogs to verify that the new property was actual present - and it was. So a bit confused I opened SQL Server Profiler to create a new trace to check the queries which was actually sent to the database from the web-site when performing a search.

The query sent to the database was:

exec dbo.ctlg_GetResults
 @Catalogs=N'MyCatalog1,MyCatalog2,MyCatalog3,',
 @Language=N'da-DK',
 @PropertiesToReturn=N
'[CategoryName], [ProductID], [CatalogName], [i_ClassType], [DisplayName], [cy_list_price], [VariantID], [Show_Variants_As_Products], [BaseCatalogName],
 [oid], [OrigProductId], [OrigVariantId], [OrigCategoryName], [DefinitionName], [PrimaryParentCategory], [UseCategoryPricing]'
,
 @SQLClause=NULL,
 @FTSPhrase=N'perfume',
 @AdvancedFTSPhrase=NULL,
 @OrderBy=NULL,
 @StartingRec=1,
 @NumRecords=10,
 @SortAscending=1,
 @ClassType=10,
 @eJoinType=-1,
 @TargetTableName=N'',
 @SourceJoinKey=N'',
 @TargetJoinKey=N'',
 @RecordCount=@p16 output,
 @EnableInventory=1,
 @InventoryServerName=NULL,
 @InventoryDatabaseName=NULL,
 @FilterOutOfStockProducts=0,
 @FilterBackOrderableProducts=0,
 @FilterPreOrderableProducts=0,
 @UseThresholdAsFloor=1,
 @TreatmissingAsOutOfStock=0,
 @StockHandling=0,
 @InventoryPropertiesToReturn=N'*',
 @CategoryClause=NULL,
 @Recursive=0

When executing this query directly on database I got the same error message. I opened each of the catalog tables to verify that the new column 'Show_Variants_As_Products' was present. The column was present in the first two catalogs (MyCatalog1 and MyCatalog2) but in the last catalog (MyCatalog3) the column was not present indicating that this specific table was out of sync with the Catalog schema. The catalog was also in fact empty. So creating a new dummy product in MyCatalog3 using the Catalog Manager did the trick of getting the MyCatalog3 catalog table back in sync with the Catalog schema. I tested it with a new search operation on the web-site and everything returned successfully.

Posted on Saturday, 25 August 2007 11:24:16 (Romance Standard Time, UTC+01:00)
# | Comments [0]