I have been working with the Commerce Server 2009 RTM (Mojave) codebase. While upgrading a site from the previous Mojave CTP I came across an interesting error. It appeared the .net reflection subsystem was not able to find the constructor on my CustomOrderForm object. This had always worked in the past, so I reflected on the Commerce Server 2009 API and found the offending method in the Microsoft.Commerce.Providers.Utility.CommerceServerClassFactory object:
public static TBaseCommerceServerClass CreateInstance
<TBaseCommerceServerClass>(string mojaveModelName, CommerceServerArea? commerceArea, object[] args)
where TBaseCommerceServerClass: class
{
ParameterChecker.CheckForNullOrEmpty(mojaveModelName, "mojaveModelName");
EntityMapping mapping = CommerceEntityMetadata.Get(mojaveModelName, commerceArea)
.EntityMappings.Items<EntityMapping>().Single<EntityMapping>();
Type type = Type.GetType(mapping.CommerceServerClass + ", " +
mapping.CommerceServerAssembly, true);
TBaseCommerceServerClass local = Activator.CreateInstance(type, args) as TBaseCommerceServerClass;
if (local == null)
{
throw new InvalidCastException(ProviderResources.ExceptionMessages
.GetMessage("InvalidClassToInstantiate", new object[] { type,
typeof(TBaseCommerceServerClass) }));
}
return local;
}
The args array contained a single “default” string member. This didn’t make sense because I knew that Commerce Server Orderform base class has no constructor which accepts a type string. After spending considerable time reviewing the code in the stack trace I thought I might have uncovered a bug. It turns out that this was implemented by design. So what this means is you now have to add a new constructor which accepts a string to all your types which inherit from Orderform. The really strange thing is that the string serves no purpose in the CustomOrderForm type.
This diagram is taken from the PDC 2008 session "Commerce Server - Mojave"
Overview
I included the above diagram because I think it help visualize where Operations and OperationSequences fit into the overall solution. Mojave provides CRUD type operations at the entity level through the implementation of operations. So, for example, when you want to retrieve products from Mojave you would call the "QueryOperation_Product" operation. You will inevitably find a need to create your own Operations based on constructs which don't directly relate to entities in Commerce Server. In some cases I am finding it useful to create operations in terms of Use Cases not simply Commerce Entities.
Business Problem
Information is stored in several disparate systems which is required by the web to provide a feature rich shopping experience. The decision was made not to create a series of night synchronizations to keep this information up to date. Instead, during the execute of a use case we need to get this information and store it in Commerce Server. Then as the customer continues to browse the site, calls for this information will go directly against Commerce Server.
Solution
There are a couple of ways this solution could be implemented with Mojave. The first would be to create a series of operationsequences to perform CRUD operations against each disparate system. I think this would be good if you were going to be CRUD heavy to those systems. The second option would be to create a single operation sequence called "QueryOperation_UseCase". Then inside the QueryOperation, create a component for each call to the disparate systems. More often than not in a SOA enabled environment, you are calling a web service to perform an operation against a system and those operations aren't tied to direct CRUD operations on an entity. So for this solution we will follow option 2.
1. First, you need to create or update an existing Channel in the MetadataDefinitions.xml. If you are using the SampleSite available on the Connect site, you can update the "TestChannel". Add a CommerceEntity node for your Use Case (EntityName).
<CommerceEntity name="UseCase"></CommerceEntity>
2. Add a Channel child node to the Channels node in the ChannelConfiguration.config. The channel name is set to "TestChannel", the sitename is set to "SampleSite" based on SampleSite.pup, and the language is set to english.
3. Add a "MessageHandlers" child node to the Channel node then add a "MessageHandler" child node to the "MessageHandlers" node. In the "MessageHandler" node, set the name to "OperationQuery_UseCase". If this were a different operation type, you would set this appropriately. Set the "ResponseType" to the standard "QueryOperationResponse".
4. Add a "OperationPipeline" child node to the "MessageHandler" node then add a "Component" child node to the "OperationPipeline" node. In a previous blog article I talk about creating custom components.
<Channels>
<Channel name="TestChannel" sitename="SampleSite" language="English">
<MessageHandlers>
<MessageHandler name="QueryOperation_UseCase" responseType="Microsoft.Commerce.Contracts.Messages.QueryOperationResponse, Microsoft.Commerce.Contracts, Version=1.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35">
<OperationPipeline>
<Component name="DisparateSystemProvider" type="DisparateSystemMojaveLib.GetCustomerInfo, DisparateSystemMojaveLib, Version=1.0.0.0, Culture=neutral,PublicKeyToken=404bf9ce77dd9381" />
</OperationPipeline>
</MessageHandler>
</MessageHandlers>
</Channel>
</Channels>
5. Create the Custom component defined in the Component node above.
6. Create a class which inherits from CommerceEntity. Assign the ModelName to the UseCase. The class needs to be visible in the code which is going to call into the QueryOperation.
public class UseCase: CatalogEntity
{
/// <summary>
/// Initializes a new instance of the <see cref="UseCase"/> class.
/// </summary>
public Login()
: base()
{
this._commerceEntity = new CommerceEntity("UseCase");
}
public Login(CommerceEntity commerceEntity)
: base(commerceEntity)
{
this._commerceEntity = commerceEntity;
}
}
7. Create the business logic to call the operation which executes the OperationalSequence.
// Create a Operation Service Agent
OperationServiceAgent _operationService = new OperationServiceAgent();
// Query for a UseCase
Query<UseCase> queryBuilder = new Query<UseCase>();
// Process the Request
Response response = _operationService.ProcessRequest(Utility.CreateRequestContext(), queryBuilder.ToRequest());
QueryOperationResponse queryBuilderResponse = (QueryOperationResponse)response.OperationResponses[0];
UseCase UC= new UseCase(queryBuilderResponse.CommerceEntities[0]);
At this point you should be ready to test the solution.
Background
In many enterprises product/catalog information is stored across many disparate systems. One of the big challenges was finding an effective way to aggregate that information on the web to facilitate the customers buying experience. This caused many Commerce Server engagements to be heavy on the integration side as you worked to get all the necessary information into the Commerce Server Catalog. For this example lets assume our enterprise stored custom product description information in an ERP system.
Mojave Solution
Mojave is implemented using Operational Sequences to service requests. An operational pipeline is implemented using a pipeline style pattern similar to ASP.NET. The pipeline represents a sequence of operations. Each component in the Operational Sequence is a discrete unit of work. Mojave gives you the ability to extend its pipelines through configuration files and custom component development.
For querying product information we would use the QueryOperation_Product (pipeline). In our scenario, we will create a custom OperationalSequence, ProductDescriptionQuery, which is a component to call out to our ERP system to grab additional product description information.
The following example assumes you have installed the Mojave October CTP and have downloaded the SampleSite.pup available on the Connect site. Also, this code could change based on the next release of Mojave.
1. Unpup the SampleSite. I typically do a custom unpup and set all the properties myself.
2. Using Commerce Server Catalog Schema Manager, extend the Commerce Server catalog schema and add the “AdditionalDescription” property definition.
a. Property Type : Text
b. Property Name: AdditionalDescription
c. Display On Site: Checked
d. Assign To All Product Types: Checked
3. Open SampleSite web site in Visual Studio.
4. Set a reference to the following libraries Mojave libraries located at \Program Files\Microsoft Commerce\Assemblies:
a. Microsoft.Commerce.Broker.dll
b. Microsoft.Commerce.Contracts.dll
c. Microsoft.Commerce.Providers.dll
6. Add a new Class Library project to the solution. I called mine Commerce.Providers.Components to match the naming structure provided by Mojave.
7. Add a class file called ProductDescriptionQuery
8. Add the following using statements:
a. Microsoft.Commerce.Providers
b. Microsoft.Commerce.Providers.Components
c. Microsoft.Commerce.Providers.Exceptions
d. Microsoft.Commerce.Providers.Utility
e. Microsoft.Commerce.Providers.Broker
f. Microsoft.Commerce.Contracts
g. Microsoft.Commerce.Contracts.Messages
h. Microsoft.Commerce.Contracts.CommerceEntities
9. Set the ProductDescriptionQuery class to inherit from PipelineComponent (Microsoft.Commerce.Providers.Components.PipelineComponent)
10. Override the ExecuteQuery Method:
Public class ProductDescriptionQuery : PipelineComponent
{
Public override void ExecuteQuery(QueryOperation queryOperation,
OperationCacheDictionary operationCache,
QueryOperationResponse response)
{
// The queryOperation provides the inputs to the method
CommerceEntity searchModel = queryOperation.GetSearchModel(“Product”);
If(!searchModel.Properties.ContainsProperty(“Id”))
Throw new OperationNotSupportedException(“This is invalid”);
// response is used to return values through the process
Response.CommerceEntities[0].Properties[“AdditionalDescription”]
= GetAdditionalDescription(searchModel.Id);
}
// Simulate a call to ERP
Private string GetAdditionalDescription(string productId)
{
Return “More Content….”);
}
}
11. Strong name the assembly and register in the GAC. All PipelineComponents need to be in the GAC because the Mojave assemblies are in the GAC.
12. Set a reference to the new assembly in the web project.
13. Open the ChannelConfiguration.config located in the web project. The ChannelConfiguration is responsible for maintaining the OperationalSequences and the mappings between Commece Server and Mojave Types. We need to make a couple additions here:
a. Search for the “QueryOperation_Product” MessageHandler. Add a child node at the end of the “Product Query Processor” component node which looks like this:
<Component name=”Addional Product Query” type=”Components.Providers.Components.ProductDescriptionQuery, Components.Providers.Components, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxx”>
b. In the “ToExternalEntities” node add the following child node:
<Translator sourceModelName=”Product” destinationType=”Microsoft.CommerceServer.Catalog.Product, Microsoft.CommerceServer.Catalog, Version=6.0.1.0, Culture=neutral, PublicKeyToken=31bf385ad364e35” type=”Microsoft.Commerce.Products.Translators.ProductTranslator, Microsoft.Commerce.Providers, Version=1.0.0.0, Culture=neutral,PublicKeyToken=31bf385ad364e35” />
The publictokenkey can be found on the Component in the GAC
14. The MetadataDefinitions.xml file manages the relationships between the Commerce Server types and Mojave types. We need to add a couple lines so the AdditionalDescription will be available to us.
a. Under the CommerceEntity[“product”]/EntityMappings/EntityMapping/PropertyMappings node add the following:
<PropertyMapping property=”AdditionalDescription” csProperty=”AdditionalDescription” />
15. The sample web solution has types similar to what would be created for a real project, just not deployed in the same manner. Open Product.cs in the App_Code folder of the web application. Create a new property to hold the AdditionalDescription.
Public string AdditionalDescription
{
Get{ return this._commerceEntity.GetPropertyValue(“AdditionalDescription”) as String; }
Set{ this._commerceEntity.SetPropertyValue(“AdditionalDescription”, value); }
}
16. Product.cs also contains a class called PropertyName which holds a list of properties exposed by the Product Class. The class already has a property name for InventoryCondition. Add the following life to the PropertyName class:
Public const string AdditionalDescription= “AdditionalDescription”;
17. Now we need to add the property to the queryBuilder.model so we will have access to it inside of our ExecuteQuery method.
queryBuilder.Model.Properties.Add(Product.PropertyName.AdditionalDescription);
18. Add the following line at the Page_Load method for Default.aspx.cs
Response.Write(product.AdditionalDescription);
19. Build the solution.
20. In the web application set a break point at:
Response response = _operationService.ProcessRequest(Utility………
This is the call which processes the QueryOperation_Product OperationalSequence (OpSec).
21. In the ProductDescriptionQuery class set a breakpoint at the top of the ExecuteQuery method.
22. Debug the Default.aspx page. At this point you should be able to step into the ProductDescriptionQuery.ExecuteQuery method.