Sunday, July 22, 2012

Including Product Tags in nopCommerce Import/Export

Product tags are not included in Excel import/export by default in nopCommerce solution. Product tags are many-to-many relation to products. Following code illustrates how to import/export product tags in nopCommerce (added to Nop.Services.ExportImport in service layer):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Nop.Core.Domain.Catalog;
using Nop.Services.Catalog;
using OfficeOpenXml;

namespace Nop.Services.ExportImport
{
    /// <summary>
    /// Import/Export helper class
    /// </summary>
    public class ImportExportHelper
    {
        public static string[] AddCustomColumns(string[] properties)
        {
            string[] newProperties = new string[] 
            {       
                "ProductTags",      
            };
            string[] returnProperties = properties.Union(newProperties).ToArray();
            return returnProperties;
        }

        public static string GetProductTagsString(Product product)
        {
            var result = new StringBuilder();
            for (int i = 0; i < product.ProductTags.Count; i++)
            {
                var pt = product.ProductTags.ToList()[i];
                result.Append(pt.Name);
                if (i != product.ProductTags.Count - 1)
                    result.Append(", ");
            }
            return result.ToString();
        }

        public static string[] ParseProductTags(string productTags)
        {
            var result = new List<string>();
            if (!String.IsNullOrWhiteSpace(productTags))
            {
                string[] values = productTags.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (string val1 in values)
                    if (!String.IsNullOrEmpty(val1.Trim()))
                        result.Add(val1.Trim());
            }
            return result.ToArray();
        }

        public static void SaveProductTags(IProductService _productService, 
            IProductTagService _productTagService, Product product, string[] productTags)
        {
            if (product == null)
                throw new ArgumentNullException("product");

            //product tags
            var existingProductTags = product.ProductTags.OrderByDescending(pt => pt.ProductCount).ToList();
            var productTagsToDelete = new List<ProductTag>();
            foreach (var existingProductTag in existingProductTags)
            {
                bool found = false;
                foreach (string newProductTag in productTags)
                {
                    if (existingProductTag.Name.Equals(newProductTag, StringComparison.InvariantCultureIgnoreCase))
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    productTagsToDelete.Add(existingProductTag);
                }
            }
            foreach (var productTag in productTagsToDelete)
            {
                product.ProductTags.Remove(productTag);
                //ensure product is saved before updating totals
                _productService.UpdateProduct(product);
                _productTagService.UpdateProductTagTotals(productTag);
            }
            foreach (string productTagName in productTags)
            {
                ProductTag productTag = null;
                var productTag2 = _productTagService.GetProductTagByName(productTagName);
                if (productTag2 == null)
                {
                    //add new product tag
                    productTag = new ProductTag()
                    {
                        Name = productTagName,
                        ProductCount = 0
                    };
                    _productTagService.InsertProductTag(productTag);
                }
                else
                {
                    productTag = productTag2;
                }
                if (!product.ProductTagExists(productTag.Id))
                {
                    product.ProductTags.Add(productTag);
                    //ensure product is saved before updating totals
                    _productService.UpdateProduct(product);
                }
                //update product tag totals 
                _productTagService.UpdateProductTagTotals(productTag);
            }
        }

    }

    /// <summary>
    /// Partial class of ExportManager
    /// </summary>
    public partial class ExportManager : IExportManager
    {
        public void UpdateCustomProductProperties(ExcelWorksheet worksheet, int row, ref int column, Product product)
        {
            string productTags = ImportExportHelper.GetProductTagsString(product);
            worksheet.Cells[row, column].Value = productTags;
            column++;
        }

    }

    /// <summary>
    /// Partial class of ImportManager
    /// </summary>
    public partial class ImportManager : IImportManager
    {
        public void UpdateCustomProductProperties(ExcelWorksheet worksheet, string[] properties, int row, Product product)
        {
            string productTags = Convert.ToString(worksheet.Cells[row, GetColumnIndex(properties, "ProductTags")].Value);
            ImportExportHelper.SaveProductTags(_productService, _productTagService, product, ImportExportHelper.ParseProductTags(productTags));
        }
    }
}
In order to use above code, the original ExportManager and ImportManager need to be modified a bit. Go to Nop.Services.ExportImport/ExportManger.cs, inside the ExportProductsToXlsx method:

1. Update property definition and add following code after line 532:
properties = ImportExportHelper.AddCustomColumns(properties);
2. Export product tags using custom function and add following code after line 797:
UpdateCustomProductProperties(worksheet, row, ref col, p);

Update Nop.Services.ExportImport/ExportManger.cs constructor and inject IProductTagService:
 public partial class ImportManager : IImportManager
    {
        private readonly IProductService _productService;
        private readonly ICategoryService _categoryService;
        private readonly IManufacturerService _manufacturerService;
        private readonly IPictureService _pictureService;
        private readonly IProductTagService _productTagService;

        public ImportManager(IProductService productService, ICategoryService categoryService,
            IManufacturerService manufacturerService, IPictureService pictureService, 
            IProductTagService productTagService)
        {
            this._productService = productService;
            this._categoryService = categoryService;
            this._manufacturerService = manufacturerService;
            this._pictureService = pictureService;
            this._productTagService = productTagService;
        }
Then update ImportProductsFromXlsx method inside ExportManger.cs:

1. Update property definition; add following code after line 142:
properties = ImportExportHelper.AddCustomColumns(properties);
2. Import product tags using custom function add following code after line 318 AND line 402:
UpdateCustomProductProperties(worksheet, properties, iRow, product);

Rebuild the solution and test out.