Archive for the ‘Commerce Server’ Category

Adding A Relationship to the User Profile in Commerce Server 2009

For this post I am assuming you are familiar with Commerce Server 2009 and operation sequences, which in my opinion are the main reason to use the upgraded Commerce Server product. You can find some background on operation sequences on MSDN.

 

The problem we are trying to solve here is the addition of new properties to the Commerce Server Profile system, but we want a one-to-many relationship between the user profile and the new property. As an example, we will be adding a number of Tests to the user’s profile. In this case the user needs to participate in a number of tests that we can track, because these tests determine what products a user will be able to purchase. The more tests and test categories the user participates in, the more products they are eligible for.

 

The basic steps for this are:

  • Create the underlying SQL Server data store
  • Map the store to the user profile in Commerce Server manager
  • Create further mappings for Commerce Entities in the MetadataDefinitions.xml file
  • Create C# classes
  • Add the classes to the operation sequences in ChannelConfiguration.config

See How to Use the Profiles Schema Manager on MSDN for documentation on the Profiles editor in Commerce Server Manager.

  1. Create the underlying data table and fields in SQL Server in the profile database where the data will be stored. This is very important to do first, even though Commerce Server will let you create definitions before the underlying data store exists. (I’m assuming you are familiar with SQL Server). In my example I am going to add a number of tests per each person in the profile. The tests have and ID, name, and category.
  2. Assuming that we are adding our relationship to the User Profile, add a new field to the UserObject table that is nvarchar(255) and named after your custom profile object. Commerce Server uses this field to store the IDs (primary key) of the related items. You may need to make the field size larger if your key is large like a GUID or you expect to have many child records. 255 is the default number from the Commerce Server SDK examples.
  3. Save the database change scripts (for source control)
  4. Open Commerce Server Manager, expand the tree:
    Commerce Server Manager –> Global Resources –> Profiles –>Profile Catalog –> Data Sources –> SQL Data source (name varies) –> Data objects
  5. Create a new object: Right click the Data Objects node and choose New Data Object. Choose the database table name from the list box, the Display Name and Members will fill out automatically, even though the members are not propagated further.
  6. Add Members to your new object: For each field you want to add, right click the new node and choose New Data Member.
    1. Choose the Member Name by clicking the database field name in the list.
    2. Click the Add button.
    3. Repeat for other new members
    4. Click the Finish button.
  7. Move to the Profile Definitions node in Commerce Server Manager.
  8. Right-click the Profile Definitions node and choose New Profile Definition.
  9. Give the new profile definition a name and display name. Click the Finish button, ignoring the Custom Attributes section.
  10. Update the new node by first clicking the node and allowing the editor to load.
  11. Create a new Group by clicking the Add button and selecting Add a New Group.
    1. Name the first group “GeneralInfo” with a display name of “General Information”. This is a common Commerce Server pattern.
    2. Click the Apply button.
  12. For each property you want to add, click the Add button and choose to add a new property
  13. Give the property a name and display name. Case matters later, so pay attention to the details here.
  14. Choose the correct type
    1. If you are adding a relationship to a profile definition type:
      1. Choose Profile.
      2. Choose the definition type for the Type Reference field.
    2. If you are adding a new field
      1. Choose the appropriate data type
  15. Expand the advanced attributes node
    1. Choose the Map To Data and use the Data Source Picker to map the database field
    2. Click the Apply button, or the change won’t update the Properties panel.
  16. Be sure to define the primary key
  17. Save the Profile
  18. Now we will build the relationship between the User Object and the new Profile Item we created.
    Click the User Object node and allow the editor to load.
  19. Expand the General Information group
  20. Add a new property to the General Information group
    1. Name the property after your relationship
    2. Choose Profile as the Type
    3. In the Type Reference field navigate to your new Profile type
    4. In the Map To data field choose the database field you created in the UserObject table in step 2.
    5. Click the Apply button.
  21. Save the Profile
  22. Export the schema as an XML file (to put under source control)
  23. Now we need to map the changes we made in Commerce Server Manager into a CS2009 definition. In your web project in Visual Studio, edit MetadataDefinitions.xml
    1. Find he <CommerceEntities> node under the <DefaultChannel> node
    2. Create a new <CommerceEntity> node for the new item, Test in my example.

      <CommerceEntity name=Test>

          <DisplayName value=Test/>

          <EntityMappings>

              <EntityMapping

                csType=Microsoft.CommerceServer.Runtime.Profiles.Profile

                csAssembly=Microsoft.CommerceServer.Runtime, Version=6.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

                csDefinitionName=Test

                csArea=Profiles>

                  <PropertyMappings>

                      <PropertyMapping property=TestId csProperty=GeneralInfo.TestId />

                      <PropertyMapping property=TestName csProperty=GeneralInfo.TestName />

                      <PropertyMapping property=TestCategory csProperty=GeneralInfo.TestCategory />

                  </PropertyMappings>

              </EntityMapping>

          </EntityMappings>

          <Properties>

              <Property name=TestId dataType=String />

              <Property name=TestName dataType=String />

              <Property name=TestCategory dataType=String />

          </Properties>

          <Relationships/>

      </CommerceEntity>
       

    3. Find the <CommerceEntity> node named UserProfile.
    4. Add a new PropertyMapping node in the ProperyMappings section.
    5. Add a property attribute to name the property after the name defined in step 20.
    6. Add a csProperty attribute that is the name you chose in step 20a. Case matters. The format is PropertyGroup.PropertyName (ex: GeneralInfo.Gender)
    7. Find the <relationships> node under UserProfile.
      1. Add a relationship:
        ex:

        <Relationships>

            <Relationship name=Tests type=Relationship modelName=Test isMultipleItems=true>

                <DisplayName value=Tests />

                <Description value=User’s Tests />

            </Relationship>

        </Relationships>

  24. Create the following types of classes for Commerce Server:
    1. A class to hold your new data


      namespace
      CommerceDemo

      {

          [Serializable]

          public class Test

          {

              public string TestId { get; set; }

       

              public string TestName { get; set; }

       

              public string TestCategory { get; set; }

          }

      }

    2. A processor class that inherits RelatedProfileProcessorBase

      namespace CommerceDemo

      {

          public class UserProfileTestProcessor : RelatedProfileProcessorBase

          {

              // Methods

              protected override bool CanDeleteRelatedItem(string relatedItemId)

              {

                  return true;

              }

       

              // Properties

              protected override string CommercePreferredIdKeyName

              {

                  get

                  {

                      return “GeneralInfo.Test”;

                  }

              }

       

              protected override string CommerceRelatedIdsKeyName

              {

                  get

                  {

                      return “GeneralInfo.Test”;

                  }

              }

       

              protected override string ParentProfileModelName

              {

                  get

                  {

                      return “UserProfile”;

                  }

              }

       

              protected override string PreferredItemRelationshipName

              {

                  get

                  {

                      return “TestId”;

                  }

              }

       

              protected override string ProfileModelName

              {

                  get

                  {

                      return “Test”;

                  }

              }

       

              protected override string RelationshipName

              {

                  get

                  {

                      return “Tests”;

                  }

              }

       

          }

      }

    3. A committer class that inherits ProfileCommitterBase

      namespace CommerceDemo

      {

          class UserProfileTestCommitter: ProfileCommitterBase

          {

              protected override string ProfileModelName

              {

                  get { return “Test”; }

              }

          }

      }

    4. A response builder class that inherits RelatedTestResponseBuilderBase

      namespace CommerceDemo

      {

           class TestResponseBuilder : ProfileResponseBuilderBase

          {

       

              protected override string ProfileModelName

              {

                  get

                  {

                      return “Test”;

                  }

              }

          }

      }

  25. Open ChannelConfiguration.config
    1. Find the <MessageHandler> node for “CommerceQueryOperation_UserProfile”
      1. Add <Component> nodes for the processor and response builder classes created above:

        <MessageHandler name=CommerceQueryOperation_UserProfile responseType=Microsoft.Commerce.Contracts.Messages.CommerceQueryOperationResponse, Microsoft.Commerce.Contracts, Version=1.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35>

            <OperationSequence>

                <Component name=User Profile Loader type=Microsoft.Commerce.Providers.Components.UserProfileLoader, Microsoft.Commerce.Providers, Version=1.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35/>

                <Component name=User Profile Test Processor type=CommerceDemo.UserProfileTestProcessor, CommerceDemo, Version=1.0.0.0, Culture=neutral,PublicKeyToken=fbeb08efdb2d0b73/>

                <Component name=User Profile Response Builder type=Microsoft.Commerce.Providers.Components.UserProfileResponseBuilder, Microsoft.Commerce.Providers, Version=1.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35 />

                <Component name=User Profile Test Response Builder type=CommerceDemo.UserProfileTestResponseBuilder, CommerceDemo, Version=1.0.0.0, Culture=neutral,PublicKeyToken=fbeb08efdb2d0b73 />

            </OperationSequence>

        </MessageHandler>

    2. Find the <MessageHandler> node for “CommerceCreateOperation_UserProfile”
      1. Add <Component> nodes for the processor, Committer, and response builder classes created above, similar to the previous step.
    3. Find the <MessageHandler> node for “CommerceDeleteOperation_UserProfile”
      1. Add <Component> nodes for the processor, Committer, and response builder classes created above, similar to step 25a1.
  26. Create any converter classes which converts the collection of Commerce Server properties from a CommerceEntity into a typed C# object.

            public static Test ConvertTest(CommerceEntity testEntity)

            {

                Test result = new Test();

     

                result.Id = testEntity.Id;

                result.TestName = testEntity.Properties[“TestName”].ToString();

                result.TestCategory = testEntity.Properties[“TestCategory”].ToString();

     

                return result;

            }

  27. Redeploy the project
  28. Reset IIS so the Commerce Server cache is emptied.

So how does this look when wired up? That’s a complex question to be sure, but here is a simplified example, assuming we have a User object class with simple common properties:

public User GetUser(string userId)

{

    User user = null;

    OperationServiceAgent svc= new OperationServiceAgent();

 

    CommerceQuery<CommerceEntity> query = new CommerceQuery<CommerceEntity>(“UserProfile”);

    query.SearchCriteria.Model.Properties[“Email”] = userId;

 

    CommercePropertyCollection properties = new CommercePropertyCollection();

    properties.Add(“Email”);

    properties.Add(“FirstName”);

    properties.Add(“LastName”);

    query.Model.Properties = properties;

 

    CommerceQueryRelatedItem<CommerceEntity> test= new CommerceQueryRelatedItem<CommerceEntity>(“Tests”, “Test”);

    test.Model.Properties.Add(“TestId”);

    test.Model.Properties.Add(“TestNumber”);

    test.Model.Properties.Add(“TestCategory”);

    query.RelatedOperations.Add(test);

 

 

    CommerceResponse response = svc.ProcessRequest(GetRequestContext(), query.ToRequest());

   

    CommerceQueryOperationResponse  queryResponse = response.OperationResponses[0] as ommerceQueryOperationResponse;

    if (queryResponse !=null && queryResponse.CommerceEntities.Count > 0)

    {

        Convert(queryResponse.CommerceEntities[0]);

    }

    return user;

}

 

public static User Convert(CommerceEntity userEntity)

{

    User result = new User();

    result.Id = userEntity.Properties[“Id”] as string;

    result.Email = userEntity.Properties[“Email”] as string;

    result.LastName = userEntity.Properties[“FirstName”] as string;

    result.FirstName = userEntity.Properties[“LastName”] as string;

 

    CommerceRelationshipList relationships = userEntity.Properties[“Tests”] as CommerceRelationshipList;

    result.Tests = ConvertTest(relationships); //Function above in post

 

     return output;

}

Posted on:
Posted in ASP.NET, Commerce Server | Comments Off on Adding A Relationship to the User Profile in Commerce Server 2009

Moving Commerce Server 2007 Databases

I needed to move the set of Commerce Server databases from one server to another, so I developed and tested this procedure.



  1. Script the database users to file. Our site is using domain users, so I performed this step first to keep the users in sync with a backup/restore of the databases.
  2. Backup each of the Commerce Server databases:

    • Marketing
    • Marketing_Lists
    • ProductCatalog
    • Profiles
    • TransactionConfig
    • Transactions
    • MSCS_Admin
    • MSCS_CatalogScratch

  3. Transfer the scripts and backup files to the new server.
  4. Run user scripts against the new server
  5. Restore each database on the new server
  6. On the web server, open Commerce Server Manager to change connection strings:

    1. Admin Database:

      1. Click the Commerce Server Manager node
      2. Click the Administration Database link in the right panel
      3. Change the Connection String, click OK

    2. Profiles


      1. Expand this node:
        Global Resources/Profiles

      2. Right-click and change the following 3 connection strings:
        s_BizDataStoreConnectionString
        s_CommerceProviderConnectionString
        s_ProfileServiceConnectionString

    3. Profile Database

      1. Expand this node:
        Global Resources/Profiles/ProfileCatalog/DataSources/ProfileService_SQLSource/Partitions
      2. Right-click SQLSource in the right pane and choose properties
      3. Change the Connection String, click OK

    4. Inventory Database

      1. Expand this node:
        Commerce Sites/CSharpSite/Site Resources/Inventory
      2. Right-click Inventory Database in the right pane and choose properties
      3. Change the Connection String, click OK

    5. Marketing Database

      1. Expand this node:
        Commerce Sites/CSharpSite/Site Resources/Marketing
      2. Right-click Mailing List Connection String in the right pane and choose properties
      3. Change the Connection String, click OK
      4. iv. Right-click Marketing Database Connection String in the right pane and choose properties
      5. Change the Connection String, click OK

    6. Product Catalog

      1. Expand this node:
        Commerce Sites/CSharpSite/Site Resources/Product Catalog
      2. Right-click Catalog Database in the right pane and choose properties
      3. Change the Connection String, click OK

    7. Transaction Config

      1. Expand this node:
        Commerce Sites/CSharpSite/Site Resources/Transaction Config
      2. Right-click Transactions Config Database in the right pane and choose properties
      3. Change the Connection String, click OK

    8. Transactions

      1. Expand this node:
        Commerce Sites/CSharpSite/Site Resources/Transaction Config
      2. Right-click Transactions Database in the right pane and choose properties
      3. Change the Connection String, click OK

  7. Change connection strings in the web.config for site (if any using API calls)
  8. Restart IIS

Posted on:
Posted in Commerce Server, SQL Server | Comments Off on Moving Commerce Server 2007 Databases

Commerce Server 2007 Customer and Orders Payment Method Error

Right off I just want to say it sucks when the error message is correct but you don’t understand why.

We needed to add a new payment method to a Commerce Server 2007 implementation. The Customer and Orders UI threw an error immediately when going to manage Payment methods:

The underlying table was full of data. The site has been in production for over a year, collecting credit card payments the whole time without issues. Obviously the payment methods weren’t missing.

Of course, Googling was no help whatsoever. I played with the Commerce API, wrote some code, ran SQL traces, all to no avail.

So on the a non-production version of the site (with the same problem luckily), I decided to work on the database table directly. I scripted off the original data, and truncated the table. Now the UI doesn’t throw the error. So I re-added the data manually, and again the UI is fine, no errors.

So I compared the newly entered data with the old data, and there was a difference (outside of the obvious differences like keys). There is a bit column in the table called IsDefault. In the old data, all rows except one were set to False. In the new data, all rows were set to True. To test that this was the problem, I truncated the table again, and re-inserted the original data. Then I changed the IsDefault values to True on all the rows. Bingo. The UI now works again, and I could add the new data. So apparently someone did modify the data in the past, and the error message was correct about the database being tampered with. Luckily this modification had not affected Commerce Server’s ability to process credit cards.

I think the IsDefault column refers to the language setting for the row in the table, as you can have multiple language variants for a credit card and choose which one is the default language. So for the UI to function properly, at least one row for each language needs to have IsDefault set to True.

Posted on:
Posted in Commerce Server, Strange Problems | Comments Off on Commerce Server 2007 Customer and Orders Payment Method Error

Web Services Authentication Gotcha

We had code in an ASP.NET page trying to call the Commerce Server Profiles web service that resides on the same physical box. The credentials we used were appropriately configured for Commerce Server using AzMan. For some reason, the code was failing with a 401: Unauthorized error. No matter what credentials we used, no luck. But if you ran the code from another box, it worked fine. Same credentials pointing to the service on that box, no errors.

Turns out the hosts file had an entry for the DNS name we were using, and mapped that name to 127.0.0.1, the loopback address. This was the gotcha. Apparently there is a loopback security feature that causes this behavior. There is a support article describing the effect. Essentially it is a security check to keep certain kinds of attacks at bay. The article suggests registry changes to disable it, but we took a different route.  In the short term, if the calling code accessed the web service via IP address (NOT 127.0.0.1) instead of DNS name the problem was circumvented. Meanwhile the network guru is working to get the actual DNS resolution to work.