learn forever RSS 2.0
# Sunday, November 09, 2008

Why stop at just Images?

In this post, I will dig into a set of classes I created to help extend support for adding in any type of extension item support for Commerce Server 2007.  I will define an extension item as anything that can be related to Products or Categories in CS 2007 that does not come prepackaged with the install.  This includes images, videos and general file attachments.

I will assume you have read Max Akbar's 5 part series on adding Image Management support to CS 2007 before taking a look at this. Reading hs series isn't required, but much of what I'm implemented stemmed from his ideas and much of my data model matches what he recommended as a set up.

The Goal

Create fully functional tabs within Product and Category property settings in the Product Catalog Manager application that comes with Commerce Server 2007.  These tabs will give the user the ability to associate Images, Videos and Attachments to a Product or Category.  On the backend, these items will be saved to disk and their assocations maintained in the SQL Server database for the Product Catalog.

The Code

As I mentioned, this code basically follows what Max originally demonstrated.  It makes use of the Bridge Design pattern which I happen to like, however it does make compilation tricky when a Web Service is involved due to the quazi circular reference that is created by having a Class Library have a Web Reference to a Web Service that in turn directly references that same Class Library.  During coding of this, I found myself "stubbing" out methods just so I could get the code to compile. I would then fill in the details after the successful compilation.

Continuing on with that point, another thing that I did not want to have to work with was the DataTable class. I absolutely HATE DataTables and use them only when absolutely necessary which seems to be less and less frequently nowadays.  However, in this scenario, the DataTable must stay around because of the quazi circular reference. If you try and create a custom class in the Class Library project. The WebService will generate a proxy class (with another namespace) that will cause your code to never compile (without doing some hacking of the proxy class).   So I decided to work with the DataTables in favor of upholding the Bridge pattern and created some custom classes that inherited from DataTable and set their necessary columns upon instantiation.

Finally, I might end up refactoring some of the code that I will paste below since the project I am using this code on is still in progress.  I will certainly make sure to update the revisions on Code Project.  I might not put all the code up here, but I will eventually have it all up on Code Project after my postings.  (Probably 3 total, this being #1)

Anyway, let's get to the code!

CommerceServerExtensions Project

This class library project will contain the base classes and interfaces for defining management support entities.  This project will also contain some built in implementations of these classes for images, videos and attachments.

Let's take a look at the base classes and interfaces:

ExtensionImplementor

    /// <summary>
    /// Defines the contract for accessing Extension items in Commerce Server.
    /// </summary>
    public interface ExtensionImplementor
    {
        /// <summary>
        /// Removes the extension items in the passed in records ExtensionDataTable.
        /// </summary>
        /// <param name="records">An ExtensionDataTable containing extension items to be removed.</param>
        void Remove(DataTable records);

        /// <summary>
        /// Saves the extension items in the passed in records ExtensionDataTable.
        /// </summary>
        /// <param name="records">An ExtensionDataTable containing extension items to be saved.</param>
        void Save(DataTable records);

        /// <summary>
        /// Resequences the extension items in the passed in records ExtensionDataTable.
        /// </summary>
        /// <param name="records">An ExtensionDataTable containing extension items to be saved.</param>
        void Resequence(DataTable records);

        /// <summary>
        /// Retrieves the extension items based on the passed in Commerce Server entity name and type.
        /// </summary>
        /// <param name="entityName">The Commerce Server entity name (ProductId or CategoryId)</param>
        /// <param name="type">The extension item type.</param>
        /// <returns>An ExtensionDataTable containing the retrieved extension items.</returns>
        DataTable Retrieve(string entityName, string type);

        /// <summary>
        /// Searches for extension items based on the extension item id, keyword and number of records.
        /// </summary>
        /// <param name="id">The extension item id</param>
        /// <param name="keyword">The keyword to search on</param>
        /// <param name="numRecords">The number of recrods to return</param>
        /// <returns>An ExtensionDataTable containing the search resulting extension items.</returns>
        DataTable Search(int id, string keyword, int numRecords);

        /// <summary>
        /// Retrieves all the available Extension item types.
        /// </summary>
        /// <returns>A String Array of all the available Extension item types.</returns>
        string[] RetrieveTypes();

        /// <summary>
        /// Retrieves the Extension Item contents based off the passed in extension item id and file name.
        /// </summary>
        /// <param name="id">The extension item id</param>
        /// <param name="fileName">The extension item file name</param>
        /// <returns>A Byte Array of the Extension Item contents</returns>
        byte[] GetBytes(int id, string fileName);

    }

ExtensionSiteAgent 

    /// <summary>
    /// Sets the information to connect to the Product Catalog database for extension item tables.
    /// </summary>
    public class ExtensionSiteAgent
    {
        /// <summary>
        /// Gets or sets the path to the Authorization Policy.
        /// </summary>
        public string AuthorizationPolicyPath { get; set; }

        /// <summary>
        /// Gets or sets the name of the Commerce Server Site.
        /// </summary>
        public string SiteName { get; set; }
    }

ExtensionServiceAgent 
    /// <summary>
    /// Sets the information to connect to the WebService for a Extension item objects.
    /// </summary>
    public class ExtensionServiceAgent
    {
        /// <summary>
        /// Gets or sets the credentials for accessing the WebService.
        /// </summary>
        public ICredentials Credentials { get; set; }

        /// <summary>
        /// Gets or sets the WebService Uri.
        /// </summary>
        public Uri ServiceUri { get; set; }
    }
 
ExtensionWebServiceAccess
 
    /// <summary>
    /// Controls action to WebServices implementing the ExtensionImplementor Interface.
    /// </summary>
    public abstract class ExtensionWebServiceAccess<T>:ExtensionImplementor where T : ExtensionImplementor
    {
        /// <summary>
        /// Returns an instance of a WebService proxy implementing the ExtensionImplementor Interface.
        /// </summary>
        protected abstract T WebService { get; }

        ///// <summary>
        ///// Calls the WebService object and removes the items represented by the passed in DataTable.
        ///// </summary>
        public virtual void Remove(DataTable records)
        {
            WebService.Remove(records);
        }

        ///// <summary>
        ///// Calls the WebService and saves the records in the passed in DataTable.
        ///// </summary>
        public virtual void Save(DataTable records)
        {
            WebService.Save(records);
        }

        ///// <summary>
        ///// Calls the WebService to resequence the records contained in the passed in DataTable.
        ///// </summary>
        /// <param name="records">An ExtensionDataTable containing rows of extension items to resequence</param>
        public virtual void Resequence(DataTable records)
        {
            WebService.Resequence(records);
        }

        /// <summary>
        /// Calls the WebService to retrieve the records represented by the passed in id and type.
        /// </summary>
        /// <param name="id">The Commerce Server id which can be a product or category id of the items to retrieve.</param>
        /// <param name="type">The Extension type of the items to retrieve.</param>
        /// <returns>An ExtensionDataTable containing rows of extension items retrieved</returns>
        public virtual DataTable Retrieve(string id, string type)
        {
            return WebService.Retrieve(id, type);
        }

        ///// <summary>
        ///// Calls the WebService to search items by the passed in id and keyword and number of records.
        ///// </summary>
        ///// <param name="id">The Extension item id</param>
        ///// <param name="keyword">The keyword to search on.</param>
        ///// <param name="numRecords">The number of recrods to return.</param>
        ///// <returns>An ExtensionDataTable containing the rows of extension items retrieved</returns>
        public virtual DataTable Search(int id, string keyword, int numRecords)
        {
            return WebService.Search(id, keyword, numRecords);
        }

        /// <summary>
        /// Returns all the available extension item types.
        /// </summary>
        /// <returns>A string array of all the available extension item types</returns>
        public virtual string[] RetrieveTypes()
        {
            return WebService.RetrieveTypes();
        }
       
        /// <summary>
        /// Returns a byte array of the extension item represented by the passed in id and file name.
        /// </summary>
        /// <param name="id">The Extension item id</param>
        /// <param name="fileName"></param>
        /// <returns>A byte array of the Extension item objects.</returns>
        public byte[] GetBytes(int id, string fileName)
        {
            return WebService.GetBytes(id,fileName);
        }

    }
 ExtensionContext
    /// <summary>
    /// Provides access to the ExtensionImplementor as well as security credentials and site name.
    /// </summary>
    /// <typeparam name="T">Any ExtensionImplementor class</typeparam>
    public class ExtensionContext<T> where T: ExtensionImplementor
    {
        /// <summary>
        /// Creates a new ExtensionContext instance using the passed in ExtensionSiteAgent class.
        /// </summary>
        /// <param name="siteAgent"></param>
        public ExtensionContext(ExtensionSiteAgent siteAgent)
        {
            this.SiteAgent = siteAgent;        
            
        }

        /// <summary>
        /// Creates a new ExtensionContext instance using the passed in ExtensionServiceAgent class.
        /// </summary>
        /// <param name="serviceAgent"></param>
        public ExtensionContext(ExtensionServiceAgent serviceAgent)
        {
            this.ServiceAgent = serviceAgent;         
        }

        /// <summary>
        /// Gets or sets the ExtensionImplementor class to be used with this ExtensionContext.
        /// </summary>
        public T Implementor { get; set; }

        /// <summary>
        /// Gets or sets the ExtensionServiceAgent class to be used with this ExtensionContext.
        /// </summary>
        public ExtensionServiceAgent ServiceAgent { get; private set; }

        /// <summary>
        /// Gets or sets the ExtensionSiteAgent class to be used with this ExtensionContext.
        /// </summary>
        public ExtensionSiteAgent SiteAgent { get; private set; }
    }
 ExtensionCommerceModule
    /// <summary>
    /// Provides access to the Extension Item Context for use in the Extension Item WebServices.
    /// </summary>
    /// <typeparam name="T">A class implementing a typed ExtensionImplementor</typeparam>
    public class ExtensionCommerceModule<T> : CommerceModule
        where T : ExtensionImplementor
      
    {
        private ExtensionContext<T> _context;
        private bool disposed = false;
        
        /// <summary>
        /// Returns a unique resource key based on the extension item type.
        /// </summary>
        /// <returns>The unique resource key</returns>
        public static string GetResourceKey()
        {
            return string.Format("{0}_{1}", typeof(T), "key");
        }
        
        /// <summary>
        /// Creates a new context based off the object type of T using the passed in ExtensionSiteAgent.
        /// </summary>
        /// <param name="agent"></param>
        /// <returns>A typed ExtensionContext providing access to typed Extension items.</returns>
        protected ExtensionContext<T> CreateContext(ExtensionSiteAgent agent)
        {
            return new ExtensionContext<T>(agent);
        }
      
        /// <summary>
        /// Gets the typed Extension Item Context.
        /// </summary>
        public ExtensionContext<T> Context
        {
            get
            {
                // Create the singleton instance of the resource
                if (_context == null)
                {
                    // Lock to prevent race conditions and ensure that exactly one
                    // instance of the resource is created
                    lock (typeof(ExtensionContext<T>))
                    {
                        //now it is safe to create a new image context
                        if (_context == null)
                        {
                            ExtensionSiteAgent agent = new ExtensionSiteAgent();
                            agent.SiteName = CommerceContext.Current.SiteName;
                            // TODO: See if we need to have authorization enabled?

                            _context = CreateContext(agent);
                        }
                    }
                }

                return _context;
            }
        }

        /// <param name="disposing">true if this was called from the Dispose method, false if called from the finalizer.</param>
        protected override void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                this.disposed = true;
                // If disposing equals true, dispose all managed 
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                }
                // Release unmanaged resources. If disposing is false, 
                // only the following code is executed.
                // ...

                // Now call the base class's dispose:
                base.Dispose(disposing);
            }
        }

        /// <param name="appInstance">The current Application instance (ASP.Net maintains a pool of these for every ASP.Net application</param>
        public override void Init(HttpApplication appInstance)
        {
            // First declare module and resource dependencies using the
            // CommerceModule protected helper methods.  These will throw
            // CommerceResourceDependencyExceptions or
            // CommerceModuleDependencyExceptions if the dependencies don’t exist

            // This module depends on the CatalogModule having been initialized
            // in the pipeline before this module. We need the Catalog Module 
            // to be intialized in order to get the connection string to access 
            // the image SQL objects.
            //DeclareModuleDependency(typeof(CommerceCatalogModule), this.GetType());

            // subscribe to BeginRequest events
            appInstance.BeginRequest += new EventHandler(OnBeginRequest);

            //make sure to call the base version
            base.Init(appInstance);
        }

        /// <summary>
        /// BeginRequest event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnBeginRequest(Object sender, EventArgs e)
        {
            HttpContext ctx = HttpContext.Current;
            if (ctx != null)
                ctx.Items[GetResourceKey()] = Context;
        }       
    } 

ExtensionDataTable 

    /// <summary>
    /// A DataTable containing columns specific to an Extension item type.
    /// </summary>
    public abstract class ExtensionDataTable : DataTable
    {
        /// <summary>
        /// Gets the name of this ExtensionDataTable.
        /// </summary>
        protected abstract string Name { get; }

         /// <summary>
        /// Creates a new instance of this class setting the appropriate columns for Extension Management.
        /// </summary>
        public ExtensionDataTable()
        {
            base.TableName = this.Name;
            this.Columns.Add(Constants.Type);
            this.Columns.Add(Constants.FileId, typeof(int));
            this.Columns.Add(Constants.HierarchyId, typeof(int));
            this.Columns.Add(Constants.FeatureId, typeof(int));
            this.Columns.Add(Constants.Sequence, typeof(int));
            this.Columns.Add(Constants.ProductId);
            this.Columns.Add(Constants.VariantId);
            this.Columns.Add(Constants.CategoryName);
            this.Columns.Add(Constants.CatalogName);
            this.Columns.Add(Constants.DisplayName);
            this.Columns.Add(Constants.Name);
            this.Columns.Add(Constants.ClientFileName);
            this.Columns.Add(Constants.Height, typeof(int));
            this.Columns.Add(Constants.Width, typeof(int));
            this.Columns.Add(Constants.Bytes, typeof(byte[]));
        }

        /// <summary>
        /// Provides the look up values for the different columns represented in the parent class.
        /// </summary>
        public static class Constants
        {
            public const string Type = "Type";
            public const string FileId = "FileId";
            public const string FeatureId = "FeatureId";
            public const string Sequence = "Sequence";
            public const string ProductId = "ProductId";
            public const string VariantId = "VariantId";
            public const string CategoryName = "CategoryName";
            public const string CatalogName = "CatalogName";
            public const string DisplayName = "DisplayName";
            public const string HierarchyId = "HierarchyId";
            public const string Name = "Name";
            public const string ClientFileName = "ClientFileName";
            public const string Height = "Height";
            public const string Width = "Width";
            public const string Bytes = "Bytes";
        }
    } 

What was all that? 

Well that was a lot of code. The good news is that most of this code does the work of hooking up the CatalogManager to the CatalogWebService to the Data Layer. Let's now look through how we can now implement Image support code on the Web Service side.

ImageImplementor 
    /// <summary>
    /// Defines the contract for accessing Image items in Commerce Server.
    /// </summary>
    public interface ImageImplementor:ExtensionImplementor
    {      
    } 
ImageWebServiceAccess 
    /// <summary>
    /// Controls action to WebServices implementing the ImageImplementor Interface.
    /// </summary>
    public class ImageWebServiceAccess : ExtensionWebServiceAccess<ImageWebService.ImageWebService>, ImageImplementor
    {    
        ImageWebService.ImageWebService _imageWebService = new ImageWebService.ImageWebService();

        /// <summary>
        /// Returns the instance of the Image Web Service
        /// </summary>
        protected override ImageWebService.ImageWebService WebService
        {
            get { return _imageWebService; }
        }
    } 
ImageDataTable 
    /// <summary>
    /// A DataTable containing columns specific to a Image item type.
    /// </summary>
    public class ImageDataTable : ExtensionDataTable
    {

        protected override string Name
        {
            get { return "Images";   }
        }
    } 

Very little code so far for Images, even though we're not adding any other additional properties, it still makes sense to inherit from the base classes to not have to make changes down the road.

Let's take a look at the ImageWebService in the CatalogWebService project. 

CatalogWebService Project 

ImageWebService 
/// <summary>
/// Implements the ImageImplementor interface to provide external access to image extension items in Commerce Server.
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ImageWebService : System.Web.Services.WebService,ImageImplementor
{
    ExtensionContext<ImageImplementor> _context = null;

    /// <summary>
    /// Default constructor retrieves and sets the ImageImplementor from the ExtensionCommerceModule.
    /// </summary>
    public ImageWebService()
    {
        _context = ((ExtensionContext<ImageImplementor>)HttpContext.Current.Items[ExtensionCommerceModule<ImageImplementor>.GetResourceKey()]);
        _context.Implementor = new ImageDirector(_context.SiteAgent);
    }
 
    /// <summary>
    /// Retrieves all Image records related to the passed in ProductID.
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [WebMethod]
    public DataTable Retrieve(string entityName, string imageType)
    {
        return _context.Implementor.Retrieve(entityName, imageType);
    }

    /// <summary>
    /// Removes all the images represented by the passed in DataTable.
    /// </summary>
    /// <param name="imageDataTable"></param>
    [WebMethod]
    public void Remove(DataTable imageDataTable)
    {       
        _context.Implementor.Remove(imageDataTable);
        
    }

    /// <summary>
    /// Changes the sequence order of the images represented by the passed in DataTable.
    /// </summary>
    /// <param name="imageDataTable"></param>
    [WebMethod]
    public void Resequence(DataTable imageDataTable)
    {
        _context.Implementor.Resequence(imageDataTable);        
    }

    /// <summary>
    /// Creates or Updates images represented by the passed in DataTable.
    /// </summary>
    /// <param name="imageDataTable"></param>
    [WebMethod]
    public void Save(DataTable imageDataTable)
    {
        _context.Implementor.Save(imageDataTable);
    }

    /// <summary>
    /// Returns the images based on the imageFileId and keyword passed in.
    /// </summary>
    /// <param name="imageFileId"></param>
    /// <param name="keyword"></param>
    /// <param name="numRecords"></param>
    /// <returns></returns>
    [WebMethod]
    public DataTable Search(int id, string keyword, int numRecords)
    {
        return _context.Implementor.Search(id, keyword, numRecords);
    }

    /// <summary>
    /// Returns the byte array of the passed in imageFileId and filename.
    /// </summary>
    /// <param name="imageFileId"></param>
    /// <param name="fileName"></param>
    /// <returns></returns>
    [WebMethod]
    public byte[] GetBytes(int imageFileId, string fileName)
    {
        return _context.Implementor.GetBytes(imageFileId, fileName);
        
    }
    
    [WebMethod]
    public string[] RetrieveTypes()
    {
        return _context.Implementor.RetrieveTypes();
    } 

Here's the business logic layer class in the CatalogWebService project for Image support: 

    /// <summary>
    /// Image extension item business logic layer.
    /// </summary>
    public class ImageDirector:ImageImplementor
    {
        private ImageDatabaseAccess _imageDatabaseAccess;
        
        /// <summary>
        /// Creates a new ImageDirector instance and sets appropriate Commerce Server connection information passed in by the ExtensionSiteAgent.
        /// </summary>
        /// <param name="siteAgent">The ExtensionSiteAgent containing Commerce Server connection information.</param>
        public ImageDirector(ExtensionSiteAgent siteAgent)
        {
            _imageDatabaseAccess = new ImageDatabaseAccess(siteAgent);
        }

        #region ExtensionImplementor Members

        /// <summary>
        /// Removes the passed in image item records in the database and file system.
        /// </summary>
        /// <param name="records">The ImageDataTable of image records</param>
        public void Remove(System.Data.DataTable records)
        {
            _imageDatabaseAccess.RemoveImageRecords(records);

            foreach (DataRow dr in records.Rows)
            {
                string fullPath = HttpContext.Current.Server.MapPath(string.Format("~/Images/{0}_{1}", dr[ImageDataTable.Constants.FileId], dr[ImageDataTable.Constants.Name]));

                if (File.Exists(fullPath))
                    File.Delete(fullPath);
            }
        }

        /// <summary>
        /// Saves the passed in image item records into the database and file system.
        /// </summary>
        /// <param name="records">The ImageDataTable of video records</param>
        public void Save(System.Data.DataTable records)
        {
            _imageDatabaseAccess.SaveImageRecords(records);

            foreach (DataRow record in records.Rows)
            {
                if (record[ImageDataTable.Constants.Bytes] != DBNull.Value)
                    File.WriteAllBytes(HttpContext.Current.Server.MapPath(string.Format("~/Images/{0}_{1}", record[ImageDataTable.Constants.FileId], record[ImageDataTable.Constants.Name])), (byte[])record[ImageDataTable.Constants.Bytes]);
            }
        }

        /// <summary>
        /// Resequences the passed in image item records in the database.
        /// </summary>
        /// <param name="records">The ImageDataTable of image records</param>
        public void Resequence(System.Data.DataTable records)
        {
            _imageDatabaseAccess.ResequenceImageRecords(records);
        }

        /// <summary>
        /// Retrieves the image item records based off the passed in Commerce Server entity and image item type.
        /// </summary>
        /// <param name="entityName">The Commerce Server entity name</param>
        /// <param name="type">The image item type</param>
        /// <returns>The ImageDataTable of image records</returns>
        public System.Data.DataTable Retrieve(string entityName, string type)
        {
            return _imageDatabaseAccess.RetrieveImageRecords(entityName, type);
        }

        /// <summary>
        /// Retrieves the image item records for the passed in image item id, keyword and number of records.
        /// </summary>
        /// <param name="id">The image item id</param>
        /// <param name="keyword">The keyword to search on</param>
        /// <param name="numRecords">The number of rows to return</param>
        /// <returns>The ImageDataTable of image records</returns>
        public System.Data.DataTable Search(int id, string keyword, int numRecords)
        {
            return _imageDatabaseAccess.SearchImageRecords(id, keyword, numRecords);
        }

        /// <summary>
        /// Retrieves the image item record types.
        /// </summary>
        /// <returns>A string array of the different image item types</returns>
        public string[] RetrieveTypes()
        {
            return new string[] { };
        }

        /// <summary>
        /// Retrieves the image item contents from the passed in image item id and file name.
        /// </summary>
        /// <param name="id">The image item id</param>
        /// <param name="fileName">The image file name</param>
        /// <returns>The byte array of the image contents</returns>
        public byte[] GetBytes(int imageFileId, string fileName)
        {
            string fullPath = HttpContext.Current.Server.MapPath(string.Format("~/Images/{0}_{1}", imageFileId, fileName));

            if (!File.Exists(fullPath))
                return null;
            try
            {
                return File.ReadAllBytes(fullPath);
            }
            catch
            {
                return null;
            }
        }

        #endregion

    }

Here is the data access layer class I created for Image support:

ImageDatabaseAccess

   /// <summary>
    /// Controls all database access for correlating Images to Commerce Server 2007 entities.
    /// </summary>
    public class ImageDatabaseAccess
    {
        Database db = null;

        /// <summary>
        /// Creates an instance of this class and sets site information based on the passed in ImageSiteAgent instance.
        /// </summary>
        /// <param name="imageSiteAgent"></param>
        public ImageDatabaseAccess(ExtensionSiteAgent imageSiteAgent)
        {
            // TODO: Cache the Connection string

            // get the connection string for the database
            string siteName = imageSiteAgent.SiteName;
            CommerceResourceCollection cs = new CommerceResourceCollection(siteName);
            OleDbConnectionStringBuilder builder1 = new OleDbConnectionStringBuilder(cs["Product Catalog"]["connstr_db_Catalog"].ToString());

            builder1.Remove("Provider");
            db = new SqlDatabase(builder1.ConnectionString);
        }

        /// <summary>
        /// Resequences the images in the passed in ImageDataTable in the database.
        /// </summary>
        /// <param name="imageDataTable"></param>
        public void ResequenceImageRecords(DataTable imageDataTable)
        {
            DbCommand dbCommand = db.GetStoredProcCommand("img_Resequence");

            foreach (DataRow record in imageDataTable.Rows)
            {
                db.AddInParameter(dbCommand, "@ImageHierarchyID", DbType.Int32, record[ImageDataTable.Constants.HierarchyId]);
                db.AddInParameter(dbCommand, "@ImageFileID", DbType.Int32, record[ImageDataTable.Constants.FileId]);
                db.AddInParameter(dbCommand, "@ProductID", DbType.String, record[ImageDataTable.Constants.ProductId]);
                db.AddInParameter(dbCommand, "@Sequence", DbType.Int32, record[ImageDataTable.Constants.Sequence]);
                db.ExecuteNonQuery(dbCommand);
            }
        }

        /// <summary>
        /// Saves the images in the passed in ImageDataTable to the database.
        /// </summary>
        /// <param name="imageDataTable"></param>
        public void SaveImageRecords(DataTable imageDataTable)
        {
            DbCommand dbCommand = db.GetStoredProcCommand("img_CreateImage");
            db.DiscoverParameters(dbCommand);
            foreach (DataRow record in imageDataTable.Rows)
            {
                dbCommand.Parameters["@FeatureID"].Value = record[ImageDataTable.Constants.FeatureId];
                dbCommand.Parameters["@Sequence"].Value = record[ImageDataTable.Constants.Sequence];
                dbCommand.Parameters["@ProductID"].Value = record[ImageDataTable.Constants.ProductId];
                dbCommand.Parameters["@VariantID"].Value = record[ImageDataTable.Constants.VariantId];
                dbCommand.Parameters["@CategoryName"].Value = record[ImageDataTable.Constants.CategoryName];
                dbCommand.Parameters["@CatalogName"].Value = record[ImageDataTable.Constants.CatalogName];
                dbCommand.Parameters["@DisplayName"].Value = record[ImageDataTable.Constants.DisplayName];
                dbCommand.Parameters["@ImageName"].Value = record[ImageDataTable.Constants.Name];
                dbCommand.Parameters["@Height"].Value = record[ImageDataTable.Constants.Height];
                dbCommand.Parameters["@Width"].Value = record[ImageDataTable.Constants.Width];

                if (record[ImageDataTable.Constants.FileId] != DBNull.Value)
                    dbCommand.Parameters["@ImageFileID"].Value = record[ImageDataTable.Constants.FileId];

                record[ImageDataTable.Constants.FileId] = (int)db.ExecuteScalar(dbCommand);
            }
        }

        /// <summary>
        /// Returns the images from the database based on the passed in ImageFileId and keyword.
        /// </summary>
        /// <param name="imageFileId"></param>
        /// <param name="keyword"></param>
        /// <param name="numRecords"></param>
        /// <returns></returns>
        public DataTable SearchImageRecords(int imageFileId, string keyword, int numRecords)
        {
            DbCommand dbCommand = db.GetStoredProcCommand("img_Search");
            db.DiscoverParameters(dbCommand);
            dbCommand.Parameters["@ImageFileId"].Value = imageFileId;
            if (keyword != null)
                dbCommand.Parameters["@keyword"].Value = keyword;
            dbCommand.Parameters["@RecordsReturned"].Value = numRecords;

            return PopulateProductImageDataTableFromDbCommand(db, dbCommand);
        }

        /// <summary>
        /// Retrieves the image records from the database for the passed in ProductID.
        /// </summary>
        /// <param name="entityName"></param>
        /// <param name="imageType"></param>
        /// <returns></returns>
        public DataTable RetrieveImageRecords(string entityName, string imageType)
        {
            string spName = imageType == "ProductImage" ? "img_GetImageProduct" : "img_GetImageCategory";
            DbCommand dbCommand = db.GetStoredProcCommand(spName);

            db.DiscoverParameters(dbCommand);

            if (imageType == "ProductImage")
                dbCommand.Parameters["@ProductID"].Value = entityName;
            else
                dbCommand.Parameters["@CategoryName"].Value = entityName;

            return PopulateProductImageDataTableFromDbCommand(db, dbCommand);
        }

        /// <summary>
        /// Removes all image records in the database contained in the passed in ImageDataTable.
        /// </summary>
        /// <param name="imageDataTable"></param>
        public void RemoveImageRecords(DataTable imageDataTable)
        {
            if (imageDataTable.Rows.Count > 0)
            {
                string imageType = (string)imageDataTable.Rows[0][ImageDataTable.Constants.Type];

                string spName = imageType == "ProductImage" ? "img_DeleteProduct" : "img_DeleteCategory";

                DbCommand dbCommand = db.GetStoredProcCommand(spName);

                db.DiscoverParameters(dbCommand);

                foreach (DataRow dr in imageDataTable.Rows)
                {
                    dbCommand.Parameters["@ImageFileId"].Value = (int)dr[ImageDataTable.Constants.FileId];

                    if (imageType == "ProductImage")
                        dbCommand.Parameters["@ProductId"].Value = (string)dr[ImageDataTable.Constants.ProductId];
                    else
                        dbCommand.Parameters["@CategoryName"].Value = (string)dr[ImageDataTable.Constants.CategoryName];

                    db.ExecuteNonQuery(dbCommand);
                }
            }
        }

        /// <summary>
        /// Populates an ImageDataTable with Image information obtained from executing the passed in DdCommand and Database objects.
        /// </summary>
        /// <param name="db"></param>
        /// <param name="dbCommand"></param>
        /// <returns></returns>
        private DataTable PopulateProductImageDataTableFromDbCommand(Database db, DbCommand dbCommand)
        {
            DataTable imageDataTable = new ImageDataTable();
            
            using (IDataReader reader = db.ExecuteReader(dbCommand))
            {
                while (reader.Read())
                {
                    DataRow record = imageDataTable.NewRow();
                    record[ImageDataTable.Constants.Sequence] = reader["Sequence"];
                    record[ImageDataTable.Constants.DisplayName] = reader["DisplayName"];
                    record[ImageDataTable.Constants.Name] = reader["ImageName"];
                    record[ImageDataTable.Constants.Height] = reader["Height"];
                    record[ImageDataTable.Constants.Width] = reader["Width"];
                    record[ImageDataTable.Constants.ProductId] = reader["ProductID"];
                    record[ImageDataTable.Constants.VariantId] = reader["VariantID"];
                    record[ImageDataTable.Constants.FileId] = reader["ImageFileID"];
                    imageDataTable.Rows.Add(record);
                }
            }

            imageDataTable.AcceptChanges();

            return imageDataTable;

        }
    } 

Finally I had to create a Partial class on the ImageWebService proxy class so it could implement the ImageImplementor interface. This code lives in the CommerceServerExtensions project.

    
        
     
    
           
            
                /// <summary>
            
            
                /// Extends the ImageWebService web service proxy so it implements the ImageImplementor interface.
            
            
                /// </summary>
            
            
                public
             
            partial 
            
                class
             
            ImageWebService:ImageImplementor
            {

            }
        
    

That's all the backend code for images. The data model is nearly identical to what was in Max Akbar's blog posting with only a few adjustments. Again, I will post this along with all the code on Code Project after my final post regarding this topic.

What's Next

In my next posting, I will show the code for Videos and Attachments and then get into the CatalogManager WinForm code.

Sunday, November 09, 2008 3:19:30 AM (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
Microsoft Commerce Server 2007
Dave Arlin
Archive
<September 2010>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
Dave Arlin
Sign In
Statistics
Total Posts: 9
This Year: 2
This Month: 0
This Week: 0
Comments: 0
All Content © 2010, Dave Arlin
DasBlog theme 'Business' created by Christoph De Baene (delarou)