Posts
12
Comments
5
Trackbacks
0
A Better Product Provider

I wrote the code for Building Product Business Entities From Commerce Server 2007 Products on my flight this morning.  I was sitting next to one of my teammates, we got to talking about the approach, and I wound up writing the ProductProvider class in the post to demonstrate how the mapping factory would work.  It's a trivial implementation.  Here's a better one.

First, we define the provider interface for getting a product from the catalog:

    1 using MyCommerceApplication.Entities;

    2 

    3 namespace MyCommerceApplication

    4 {

    5 

    6     /// <summary>

    7     /// Provider used to get products from the catalog.

    8     /// </summary>

    9     public interface IProductProvider

   10     {

   11         #region Methods

   12 

   13         /// <summary>

   14         /// Gets a single product from a Commerce Server catalog.

   15         /// </summary>

   16         /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog that

   17         /// the product is to be retrieved from.</param>

   18         /// <param name="productId">The product identifier <see cref="String"/> of the product that

   19         /// is to be retrieved.</param>

   20         /// <param name="cultureName">The name of the culture to use to retrieve culture-specific

   21         /// resources.</param>

   22         /// <returns>A <see cref="ProductEntityBase"/> if the specified product exists and could be

   23         /// retrieved, or <see langword="null"/> if the product doesn't exist or couldn't be

   24         /// retrieved.</returns>

   25         ProductEntityBase GetProduct(string catalogId, string productId, string cultureName);

   26 

   27         #endregion // Methods

   28     }

   29 

   30 }

Second, we implement the provider interface in terms of ICatalogContextFactory and IProductMappingFactory:

    1 using System;

    2 using System.Globalization;

    3 using Microsoft.CommerceServer;

    4 using Microsoft.CommerceServer.Catalog;

    5 using Microsoft.Practices.Unity;

    6 using MyCommerceApplication.Entities;

    7 using MyCommerceApplication.Mappings;

    8 

    9 namespace MyCommerceApplication

   10 {

   11 

   12     /// <summary>

   13     /// An <see cref="IProductProvider"/> implementation that gets products from the Commerce

   14     /// Server catalog.

   15     /// </summary>

   16     public class ProductProvider : IProductProvider

   17     {

   18         #region Properties

   19 

   20         /// <summary>

   21         /// Gets or sets the <see cref="ICatalogContextFactory"/> to use to create the catalog

   22         /// context.

   23         /// </summary>

   24         [Dependency]

   25         public ICatalogContextFactory CatalogContextFactory { get; set; }

   26 

   27         /// <summary>

   28         /// Gets or sets the <see cref="ProductMappingFactory"/> to use to create product mappings.

   29         /// </summary>

   30         [Dependency]

   31         public IProductMappingFactory ProductMappingFactory { get; set; }

   32 

   33         #endregion // Properties

   34 

   35         #region Methods

   36 

   37         /// <summary>

   38         /// Gets the specified product from the specified catalog.

   39         /// </summary>

   40         /// <param name="catalogId">The catalog identifier.</param>

   41         /// <param name="productId">The product identifier.</param>

   42         /// <returns>A <see cref="ProductEntityBase"/> representing the product.</returns>

   43         #region IProductProvider Methods

   44 

   45         /// <summary>

   46         /// Gets a single product from a Commerce Server catalog.

   47         /// </summary>

   48         /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog that

   49         /// the product is to be retrieved from.</param>

   50         /// <param name="productId">The product identifier <see cref="String"/> of the product that

   51         /// is to be retrieved.</param>

   52         /// <param name="cultureName">The name of the culture to use to retrieve culture-specific

   53         /// resources.</param>

   54         /// <returns>A <see cref="ProductEntityBase"/> if the specified product exists and could be

   55         /// retrieved, or <see langword="null"/> if the product doesn't exist or couldn't be

   56         /// retrieved.</returns>

   57         public ProductEntityBase GetProduct(string catalogId, string productId, string cultureName)

   58         {

   59             CatalogContext catalogContext;

   60             Product product;

   61             IProductMapping mapping;

   62             ProductEntityBase entity;

   63 

   64             catalogContext = CatalogContextFactory.CreateCatalogContext();

   65             try

   66             {

   67                 product = catalogContext.GetProduct(catalogId, productId, cultureName);

   68             }

   69             catch (EntityDoesNotExistException)

   70             {

   71                 // If the catalog doesn't exist, throw an exception.

   72                 try

   73                 {

   74                     catalogContext.GetCatalog(catalogId, cultureName);

   75                 }

   76                 catch (EntityDoesNotExistException ex)

   77                 {

   78                     throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Catalog '{0}' does not exist.", catalogId), ex);

   79                 }

   80                 // If the product doesn't exist, return null.

   81                 return null;

   82             }

   83             // If the product's active language isn't the requested culture, throw an exception.

   84             if (!product.ActiveLanguage.Equals(cultureName, StringComparison.OrdinalIgnoreCase))

   85             {

   86                 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Catalog '{0}' does not support culture '{1}', returning culture '{2}' instead.", catalogId, cultureName, product.ActiveLanguage));

   87             }

   88             try

   89             {

   90                 mapping = ProductMappingFactory.CreateProductMapping(product.DefinitionName);

   91                 entity = mapping.Map(product);

   92             }

   93             catch (Exception ex)

   94             {

   95                 // If the mapping fails, either because we couldn't get a mapping for the specified

   96                 // definition name or because the mapping threw an exception when trying to perform

   97                 // the map, throw an exception.

   98                 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Product '{0}' in catalog '{1}' is malformed.", productId, catalogId), ex);

   99             }

  100             return entity;

  101         }

  102 

  103         #endregion // IProductProvider Methods

  104 

  105         #endregion // Methods

  106     }

  107 

  108 }

This implementation has a few key improvements over the one in the original post:

1. It takes the culture name as a parameter.

2. It returns null if the product doesn't exist.   (It could just as easily throw a different exception or return a special type of product entity indicating that the product doesn't exist.)

3. It throws a meaningful exception if the catalog doesn't exist.  (This is nice to have, but it could hurt performance depending on your caching strategy and on how many requests you get for products that don't exist.)

4. It throws a meaningful exception if the product isn't returned with the requested culture.

5. It throws a meaningful exception if the product can't be mapped to a business entity.

It's still a very simple implementation (e.g. we normally put caching here, we normally throw specific exceptions for each error condition, etc), but it's better than the original.

Cheers,

Colin

posted on Monday, August 24, 2009 9:53 PM Print
Comments
No comments posted yet.

Post Comment

Title *
Name *
Email
Url
Comment *  
Please add 8 and 5 and type the answer here: