diff --git a/samples/VirtoCommerce.CatalogModule2.Web/Module.cs b/samples/VirtoCommerce.CatalogModule2.Web/Module.cs index 23d828a7b..df078ce16 100644 --- a/samples/VirtoCommerce.CatalogModule2.Web/Module.cs +++ b/samples/VirtoCommerce.CatalogModule2.Web/Module.cs @@ -50,7 +50,7 @@ public void Initialize(IServiceCollection serviceCollection) serviceCollection.AddTransient(); serviceCollection.AddTransient(); - serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); diff --git a/samples/VirtoCommerce.CatalogModule2.Web/Search/Indexing/ProductDocumentBuilder.cs b/samples/VirtoCommerce.CatalogModule2.Web/Search/Indexing/ProductDocumentBuilder.cs index a26c28c1d..dd64f9a40 100644 --- a/samples/VirtoCommerce.CatalogModule2.Web/Search/Indexing/ProductDocumentBuilder.cs +++ b/samples/VirtoCommerce.CatalogModule2.Web/Search/Indexing/ProductDocumentBuilder.cs @@ -12,8 +12,8 @@ namespace VirtoCommerce.CatalogModule2.Data.Search.Indexing { public class ProductDocumentBuilder2 : ProductDocumentBuilder { - public ProductDocumentBuilder2(ISettingsManager settingsManager, IPropertySearchService propertySearchService, IItemService itemService, IProductSearchService productsSearchService) - : base(settingsManager, propertySearchService, itemService, productsSearchService) + public ProductDocumentBuilder2(ISettingsManager settingsManager, IPropertySearchService propertySearchService, IProductService productService, IProductSearchService productsSearchService) + : base(settingsManager, propertySearchService, productService, productsSearchService) { } protected override IndexDocument CreateDocument(CatalogProduct product) diff --git a/samples/VirtoCommerce.CatalogModule2.Web/Search/Indexing/ProductIndexedSearchService.cs b/samples/VirtoCommerce.CatalogModule2.Web/Search/Indexing/ProductIndexedSearchService.cs index 10eae7741..7237a6e88 100644 --- a/samples/VirtoCommerce.CatalogModule2.Web/Search/Indexing/ProductIndexedSearchService.cs +++ b/samples/VirtoCommerce.CatalogModule2.Web/Search/Indexing/ProductIndexedSearchService.cs @@ -14,11 +14,11 @@ namespace VirtoCommerce.CatalogModule2.Data.Search.Indexing { public class ProductIndexedSearchService2 : ProductIndexedSearchService { - public ProductIndexedSearchService2(ISearchRequestBuilderRegistrar searchRequestBuilderRegistrar, ISearchProvider searchProvider, ISettingsManager settingsManager, IItemService itemService, IBlobUrlResolver blobUrlResolver, IAggregationConverter aggregationConverter) : base( + public ProductIndexedSearchService2(ISearchRequestBuilderRegistrar searchRequestBuilderRegistrar, ISearchProvider searchProvider, ISettingsManager settingsManager, IProductService productService, IBlobUrlResolver blobUrlResolver, IAggregationConverter aggregationConverter) : base( searchRequestBuilderRegistrar, searchProvider, settingsManager, - itemService, + productService, blobUrlResolver, aggregationConverter ) diff --git a/samples/VirtoCommerce.CatalogModule2.Web/Search/ListEntrySearchService.cs b/samples/VirtoCommerce.CatalogModule2.Web/Search/ListEntrySearchService.cs index 4ca832d7c..df2aabd3b 100644 --- a/samples/VirtoCommerce.CatalogModule2.Web/Search/ListEntrySearchService.cs +++ b/samples/VirtoCommerce.CatalogModule2.Web/Search/ListEntrySearchService.cs @@ -14,7 +14,7 @@ namespace VirtoCommerce.CatalogModule2.Data.Search { public class ListEntrySearchService2 : ListEntrySearchService { - public ListEntrySearchService2(Func catalogRepositoryFactory, IItemService itemService, ICategoryService categoryService) : base(catalogRepositoryFactory, itemService, categoryService) + public ListEntrySearchService2(Func catalogRepositoryFactory, IProductService productService, ICategoryService categoryService) : base(catalogRepositoryFactory, productService, categoryService) { } protected override IQueryable BuildQuery(IQueryable query, CatalogListEntrySearchCriteria criteria) diff --git a/samples/VirtoCommerce.CatalogModule2.Web/Search/ProductSearchService.cs b/samples/VirtoCommerce.CatalogModule2.Web/Search/ProductSearchService.cs index 2615900ce..b9ba2c707 100644 --- a/samples/VirtoCommerce.CatalogModule2.Web/Search/ProductSearchService.cs +++ b/samples/VirtoCommerce.CatalogModule2.Web/Search/ProductSearchService.cs @@ -18,7 +18,7 @@ public class ProductSearchService2 : ProductSearchService public ProductSearchService2( Func repositoryFactory, IPlatformMemoryCache platformMemoryCache, - IItemService crudService, + IProductService crudService, IOptions crudOptions) : base(repositoryFactory, platformMemoryCache, crudService, crudOptions) { diff --git a/samples/VirtoCommerce.CatalogModule2.Web/Services/CatalogSeoDuplicatesDetector.cs b/samples/VirtoCommerce.CatalogModule2.Web/Services/CatalogSeoDuplicatesDetector.cs index 60f593edb..c5a110c16 100644 --- a/samples/VirtoCommerce.CatalogModule2.Web/Services/CatalogSeoDuplicatesDetector.cs +++ b/samples/VirtoCommerce.CatalogModule2.Web/Services/CatalogSeoDuplicatesDetector.cs @@ -13,7 +13,7 @@ namespace VirtoCommerce.CatalogModule2.Web.Services public class CatalogSeoDuplicatesDetector2 : CatalogSeoDuplicatesDetector { public CatalogSeoDuplicatesDetector2( - IItemService productService, + IProductService productService, ICategoryService categoryService, IStoreService storeService, Func repositoryFactory, diff --git a/samples/VirtoCommerce.CatalogModule2.Web/Services/ProductMover.cs b/samples/VirtoCommerce.CatalogModule2.Web/Services/ProductMover.cs index 2ee23ad6c..2a70badff 100644 --- a/samples/VirtoCommerce.CatalogModule2.Web/Services/ProductMover.cs +++ b/samples/VirtoCommerce.CatalogModule2.Web/Services/ProductMover.cs @@ -8,7 +8,7 @@ namespace VirtoCommerce.CatalogModule2.Data.Services { public class ProductMover2 : ProductMover { - public ProductMover2(IItemService itemService) : base(itemService) + public ProductMover2(IProductService productService) : base(productService) { } public override Task ConfirmMoveAsync(IEnumerable entities) diff --git a/samples/VirtoCommerce.CatalogModule2.Web/Services/ItemService.cs b/samples/VirtoCommerce.CatalogModule2.Web/Services/ProductService.cs similarity index 94% rename from samples/VirtoCommerce.CatalogModule2.Web/Services/ItemService.cs rename to samples/VirtoCommerce.CatalogModule2.Web/Services/ProductService.cs index e5c53bf98..a7ed888c2 100644 --- a/samples/VirtoCommerce.CatalogModule2.Web/Services/ItemService.cs +++ b/samples/VirtoCommerce.CatalogModule2.Web/Services/ProductService.cs @@ -1,87 +1,87 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using FluentValidation; -using VirtoCommerce.AssetsModule.Core.Assets; -using VirtoCommerce.CatalogModule.Core.Model; -using VirtoCommerce.CatalogModule.Core.Services; -using VirtoCommerce.CatalogModule.Data.Repositories; -using VirtoCommerce.CatalogModule.Data.Services; -using VirtoCommerce.Platform.Core.Caching; -using VirtoCommerce.Platform.Core.Events; - -namespace VirtoCommerce.CatalogModule2.Web.Services -{ - public class ItemService2 : ItemService - { - public ItemService2( - Func repositoryFactory, - IPlatformMemoryCache platformMemoryCache, - IEventPublisher eventPublisher, - AbstractValidator hasPropertyValidator, - ICatalogService catalogService, - ICategoryService categoryService, - IOutlineService outlineService, - IBlobUrlResolver blobUrlResolver, - ISkuGenerator skuGenerator, - AbstractValidator productValidator) - : base( - repositoryFactory, - platformMemoryCache, - eventPublisher, - hasPropertyValidator, - catalogService, - categoryService, - outlineService, - blobUrlResolver, - skuGenerator, - productValidator) - { - } - - protected override void ApplyInheritanceRules(IList products) - { - base.ApplyInheritanceRules(products); - } - - protected override void ClearCache(IList models) - { - base.ClearCache(models); - } - - public override Task DeleteAsync(IList ids, bool softDelete = false) - { - return base.DeleteAsync(ids, softDelete); - } - - public override Task GetByIdAsync(string itemId, string responseGroup, string catalogId) - { - return base.GetByIdAsync(itemId, responseGroup, catalogId); - } - - public override Task> GetByIdsAsync(IList ids, string responseGroup, string catalogId) - { - return base.GetByIdsAsync(ids, responseGroup, catalogId); - } - - protected override Task LoadDependencies(IList products) - { - return base.LoadDependencies(products); - } - - public override Task SaveChangesAsync(IList models) - { - return base.SaveChangesAsync(models); - } - - protected override void SetProductDependencies(CatalogProduct product, IDictionary catalogsByIdDict, IDictionary categoriesByIdDict) - { - base.SetProductDependencies(product, catalogsByIdDict, categoriesByIdDict); - } - - protected override Task ValidateProductsAsync(IList products) - { - return base.ValidateProductsAsync(products); - } - } -} +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentValidation; +using VirtoCommerce.AssetsModule.Core.Assets; +using VirtoCommerce.CatalogModule.Core.Model; +using VirtoCommerce.CatalogModule.Core.Services; +using VirtoCommerce.CatalogModule.Data.Repositories; +using VirtoCommerce.CatalogModule.Data.Services; +using VirtoCommerce.Platform.Core.Caching; +using VirtoCommerce.Platform.Core.Events; + +namespace VirtoCommerce.CatalogModule2.Web.Services +{ + public class ProductService2 : ProductService + { + public ProductService2( + Func repositoryFactory, + IPlatformMemoryCache platformMemoryCache, + IEventPublisher eventPublisher, + AbstractValidator hasPropertyValidator, + ICatalogService catalogService, + ICategoryService categoryService, + IOutlineService outlineService, + IBlobUrlResolver blobUrlResolver, + ISkuGenerator skuGenerator, + AbstractValidator productValidator) + : base( + repositoryFactory, + platformMemoryCache, + eventPublisher, + hasPropertyValidator, + catalogService, + categoryService, + outlineService, + blobUrlResolver, + skuGenerator, + productValidator) + { + } + + protected override void ApplyInheritanceRules(IList products) + { + base.ApplyInheritanceRules(products); + } + + protected override void ClearCache(IList models) + { + base.ClearCache(models); + } + + public override Task DeleteAsync(IList ids, bool softDelete = false) + { + return base.DeleteAsync(ids, softDelete); + } + + public override Task GetByIdAsync(string itemId, string responseGroup, string catalogId) + { + return base.GetByIdAsync(itemId, responseGroup, catalogId); + } + + public override Task> GetByIdsAsync(IList ids, string responseGroup, string catalogId) + { + return base.GetByIdsAsync(ids, responseGroup, catalogId); + } + + protected override Task LoadDependencies(IList products) + { + return base.LoadDependencies(products); + } + + public override Task SaveChangesAsync(IList models) + { + return base.SaveChangesAsync(models); + } + + protected override void SetProductDependencies(CatalogProduct product, IDictionary catalogsByIdDict, IDictionary categoriesByIdDict) + { + base.SetProductDependencies(product, catalogsByIdDict, categoriesByIdDict); + } + + protected override Task ValidateProductsAsync(IList products) + { + return base.ValidateProductsAsync(products); + } + } +} diff --git a/src/VirtoCommerce.CatalogModule.BulkActions/Actions/PropertiesUpdate/PropertiesUpdateBulkAction.cs b/src/VirtoCommerce.CatalogModule.BulkActions/Actions/PropertiesUpdate/PropertiesUpdateBulkAction.cs index 88dbb2aee..135783b26 100644 --- a/src/VirtoCommerce.CatalogModule.BulkActions/Actions/PropertiesUpdate/PropertiesUpdateBulkAction.cs +++ b/src/VirtoCommerce.CatalogModule.BulkActions/Actions/PropertiesUpdate/PropertiesUpdateBulkAction.cs @@ -16,7 +16,7 @@ namespace VirtoCommerce.CatalogModule.BulkActions.Actions.PropertiesUpdate public class PropertiesUpdateBulkAction : IBulkAction { private readonly PropertiesUpdateBulkActionContext _context; - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly IBulkPropertyUpdateManager _bulkPropertyUpdateManager; /// @@ -28,13 +28,25 @@ public class PropertiesUpdateBulkAction : IBulkAction /// /// The context. /// - public PropertiesUpdateBulkAction(PropertiesUpdateBulkActionContext context, IItemService itemService, IBulkPropertyUpdateManager bulkPropertyUpdateManager) + public PropertiesUpdateBulkAction(PropertiesUpdateBulkActionContext context, IProductService productService, IBulkPropertyUpdateManager bulkPropertyUpdateManager) { _context = context ?? throw new ArgumentNullException(nameof(context)); - _itemService = itemService; + _productService = productService; _bulkPropertyUpdateManager = bulkPropertyUpdateManager; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public PropertiesUpdateBulkAction(PropertiesUpdateBulkActionContext context, IItemService itemService, IBulkPropertyUpdateManager bulkPropertyUpdateManager) + : this(context, (IProductService)itemService, bulkPropertyUpdateManager) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public PropertiesUpdateBulkAction(PropertiesUpdateBulkActionContext context, IProductService productService, /* ReSharper disable once UnusedParameter.Local */ IItemService itemService, IBulkPropertyUpdateManager bulkPropertyUpdateManager) + : this(context, productService, bulkPropertyUpdateManager) + { + } + public BulkActionContext Context => _context; public async Task ExecuteAsync(IEnumerable entities) @@ -48,7 +60,7 @@ public async Task ExecuteAsync(IEnumerable entities) var productQuery = entries.Where(entry => entry.Type.EqualsInvariant(ProductListEntry.TypeName)); var productIds = productQuery.Select(entry => entry.Id).ToList(); - var products = await _itemService.GetAsync(productIds, (ItemResponseGroup.ItemInfo | ItemResponseGroup.ItemProperties).ToString()); + var products = await _productService.GetAsync(productIds, (ItemResponseGroup.ItemInfo | ItemResponseGroup.ItemProperties).ToString()); return await _bulkPropertyUpdateManager.UpdatePropertiesAsync(products?.ToArray(), _context.Properties); } diff --git a/src/VirtoCommerce.CatalogModule.BulkActions/Services/BulkPropertyUpdateManager.cs b/src/VirtoCommerce.CatalogModule.BulkActions/Services/BulkPropertyUpdateManager.cs index c7ecbc4fb..b2ec70958 100644 --- a/src/VirtoCommerce.CatalogModule.BulkActions/Services/BulkPropertyUpdateManager.cs +++ b/src/VirtoCommerce.CatalogModule.BulkActions/Services/BulkPropertyUpdateManager.cs @@ -15,7 +15,7 @@ public class BulkPropertyUpdateManager : IBulkPropertyUpdateManager private readonly IDataSourceFactory _dataSourceFactory; private readonly IPropertyUpdateManager _propertyUpdateManager; - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly ICategoryService _categoryService; private readonly ICatalogService _catalogService; @@ -27,21 +27,33 @@ public class BulkPropertyUpdateManager : IBulkPropertyUpdateManager /// /// The data source factory. /// - /// + /// /// The item service. /// /// /// /// - public BulkPropertyUpdateManager(IDataSourceFactory dataSourceFactory, IItemService itemService, ICategoryService categoryService, ICatalogService catalogService, IPropertyUpdateManager propertyUpdateManager) + public BulkPropertyUpdateManager(IDataSourceFactory dataSourceFactory, IProductService productService, ICategoryService categoryService, ICatalogService catalogService, IPropertyUpdateManager propertyUpdateManager) { _dataSourceFactory = dataSourceFactory; - _itemService = itemService; + _productService = productService; _categoryService = categoryService; _catalogService = catalogService; _propertyUpdateManager = propertyUpdateManager; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public BulkPropertyUpdateManager(IDataSourceFactory dataSourceFactory, IItemService itemService, ICategoryService categoryService, ICatalogService catalogService, IPropertyUpdateManager propertyUpdateManager) + : this(dataSourceFactory, (IProductService)itemService, categoryService, catalogService, propertyUpdateManager) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public BulkPropertyUpdateManager(IDataSourceFactory dataSourceFactory, IProductService productService, /* ReSharper disable once UnusedParameter.Local */ IItemService itemService, ICategoryService categoryService, ICatalogService catalogService, IPropertyUpdateManager propertyUpdateManager) + : this(dataSourceFactory, productService, categoryService, catalogService, propertyUpdateManager) + { + } + public async Task GetPropertiesAsync(BulkActionContext context) { var result = new List(); @@ -52,7 +64,7 @@ public async Task GetPropertiesAsync(BulkActionContext context) while (await dataSource.FetchAsync()) { var productIds = dataSource.Items.Select(item => item.Id).ToList(); - var products = await _itemService.GetAsync(productIds, (ItemResponseGroup.ItemInfo | ItemResponseGroup.ItemProperties).ToString()); + var products = await _productService.GetAsync(productIds, (ItemResponseGroup.ItemInfo | ItemResponseGroup.ItemProperties).ToString()); // using only product inherited properties from categories, // own product props (only from PropertyValues) are not set via bulk update action @@ -88,7 +100,7 @@ public async Task UpdatePropertiesAsync(CatalogProduct[] produ if (hasChanges) { - await _itemService.SaveChangesAsync(products); + await _productService.SaveChangesAsync(products); } return result; diff --git a/src/VirtoCommerce.CatalogModule.BulkActions/Services/CatalogBulkActionFactory.cs b/src/VirtoCommerce.CatalogModule.BulkActions/Services/CatalogBulkActionFactory.cs index cf4a7cd83..b0d237406 100644 --- a/src/VirtoCommerce.CatalogModule.BulkActions/Services/CatalogBulkActionFactory.cs +++ b/src/VirtoCommerce.CatalogModule.BulkActions/Services/CatalogBulkActionFactory.cs @@ -13,7 +13,7 @@ namespace VirtoCommerce.CatalogModule.BulkActions.Services public class CatalogBulkActionFactory : IBulkActionFactory { private readonly ICatalogService _catalogService; - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly IBulkPropertyUpdateManager _bulkPropertyUpdateManager; private readonly ListEntryMover _categoryListEntryMover; private readonly ListEntryMover _productListEntryMover; @@ -22,18 +22,40 @@ public class CatalogBulkActionFactory : IBulkActionFactory /// Initializes a new instance of the class. /// public CatalogBulkActionFactory(ICatalogService catalogService, - IItemService itemService, + IProductService productService, IBulkPropertyUpdateManager bulkPropertyUpdateManager, ListEntryMover categoryListEntryMover, ListEntryMover productListEntryMover) { _catalogService = catalogService; - _itemService = itemService; + _productService = productService; _bulkPropertyUpdateManager = bulkPropertyUpdateManager; _categoryListEntryMover = categoryListEntryMover; _productListEntryMover = productListEntryMover; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public CatalogBulkActionFactory(ICatalogService catalogService, + IItemService itemService, + IBulkPropertyUpdateManager bulkPropertyUpdateManager, + ListEntryMover categoryListEntryMover, + ListEntryMover productListEntryMover) + : this(catalogService, (IProductService)itemService, bulkPropertyUpdateManager, categoryListEntryMover, productListEntryMover) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public CatalogBulkActionFactory(ICatalogService catalogService, + IProductService productService, + // ReSharper disable once UnusedParameter.Local + IItemService itemService, + IBulkPropertyUpdateManager bulkPropertyUpdateManager, + ListEntryMover categoryListEntryMover, + ListEntryMover productListEntryMover) + : this(catalogService, productService, bulkPropertyUpdateManager, categoryListEntryMover, productListEntryMover) + { + } + public IBulkAction Create(BulkActionContext context) { IBulkAction result = null; @@ -45,7 +67,7 @@ public IBulkAction Create(BulkActionContext context) break; case PropertiesUpdateBulkActionContext updatePropertiesActionContext: - result = new PropertiesUpdateBulkAction(updatePropertiesActionContext, _itemService, _bulkPropertyUpdateManager); + result = new PropertiesUpdateBulkAction(updatePropertiesActionContext, _productService, _bulkPropertyUpdateManager); break; } diff --git a/src/VirtoCommerce.CatalogModule.Core/Services/IItemService.cs b/src/VirtoCommerce.CatalogModule.Core/Services/IProductService.cs similarity index 59% rename from src/VirtoCommerce.CatalogModule.Core/Services/IItemService.cs rename to src/VirtoCommerce.CatalogModule.Core/Services/IProductService.cs index 287411ac4..706ac6044 100644 --- a/src/VirtoCommerce.CatalogModule.Core/Services/IItemService.cs +++ b/src/VirtoCommerce.CatalogModule.Core/Services/IProductService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using VirtoCommerce.CatalogModule.Core.Model; @@ -5,11 +6,16 @@ namespace VirtoCommerce.CatalogModule.Core.Services { - public interface IItemService : ICrudService + public interface IProductService : ICrudService { Task> GetByCodes(string catalogId, IList codes, string responseGroup); Task> GetIdsByCodes(string catalogId, IList codes); Task GetByIdAsync(string itemId, string responseGroup, string catalogId); Task> GetByIdsAsync(IList ids, string responseGroup, string catalogId); } + + // Left for backward compatibility only - new code should use IProductService which has consistent naming scheme + // (compare IProductSearchService, ICategoryService/ICategorySearchService and ICatalogService/ICatalogSearchService) + [Obsolete($"Use {nameof(IProductService)} instead")] + public interface IItemService : IProductService; } diff --git a/src/VirtoCommerce.CatalogModule.Data/ExportImport/CatalogExportImport.cs b/src/VirtoCommerce.CatalogModule.Data/ExportImport/CatalogExportImport.cs index c92a8c5a2..20f268fc0 100644 --- a/src/VirtoCommerce.CatalogModule.Data/ExportImport/CatalogExportImport.cs +++ b/src/VirtoCommerce.CatalogModule.Data/ExportImport/CatalogExportImport.cs @@ -21,7 +21,7 @@ public class CatalogExportImport private readonly IProductSearchService _productSearchService; private readonly ICategorySearchService _categorySearchService; private readonly ICategoryService _categoryService; - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly IPropertyService _propertyService; private readonly IPropertySearchService _propertySearchService; private readonly IPropertyDictionaryItemSearchService _propertyDictionarySearchService; @@ -33,14 +33,14 @@ public class CatalogExportImport private readonly int _batchSize = 50; public CatalogExportImport(ICatalogService catalogService, ICatalogSearchService catalogSearchService, IProductSearchService productSearchService, ICategorySearchService categorySearchService, ICategoryService categoryService, - IItemService itemService, IPropertyService propertyService, IPropertySearchService propertySearchService, IPropertyDictionaryItemSearchService propertyDictionarySearchService, + IProductService productService, IPropertyService propertyService, IPropertySearchService propertySearchService, IPropertyDictionaryItemSearchService propertyDictionarySearchService, IPropertyDictionaryItemService propertyDictionaryService, JsonSerializer jsonSerializer, IBlobStorageProvider blobStorageProvider, IAssociationService associationService) { _catalogService = catalogService; _productSearchService = productSearchService; _categorySearchService = categorySearchService; _categoryService = categoryService; - _itemService = itemService; + _productService = productService; _propertyService = propertyService; _propertySearchService = propertySearchService; _propertyDictionarySearchService = propertyDictionarySearchService; @@ -51,6 +51,22 @@ public CatalogExportImport(ICatalogService catalogService, ICatalogSearchService _catalogSearchService = catalogSearchService; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public CatalogExportImport(ICatalogService catalogService, ICatalogSearchService catalogSearchService, IProductSearchService productSearchService, ICategorySearchService categorySearchService, ICategoryService categoryService, + IItemService itemService, IPropertyService propertyService, IPropertySearchService propertySearchService, IPropertyDictionaryItemSearchService propertyDictionarySearchService, + IPropertyDictionaryItemService propertyDictionaryService, JsonSerializer jsonSerializer, IBlobStorageProvider blobStorageProvider, IAssociationService associationService) + : this(catalogService, catalogSearchService, productSearchService, categorySearchService, categoryService, (IProductService)itemService, propertyService, propertySearchService, propertyDictionarySearchService, propertyDictionaryService, jsonSerializer, blobStorageProvider, associationService) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public CatalogExportImport(ICatalogService catalogService, ICatalogSearchService catalogSearchService, IProductSearchService productSearchService, ICategorySearchService categorySearchService, ICategoryService categoryService, + IProductService productService, /* ReSharper disable once UnusedParameter.Local */ IItemService itemService, IPropertyService propertyService, IPropertySearchService propertySearchService, IPropertyDictionaryItemSearchService propertyDictionarySearchService, + IPropertyDictionaryItemService propertyDictionaryService, JsonSerializer jsonSerializer, IBlobStorageProvider blobStorageProvider, IAssociationService associationService) + : this(catalogService, catalogSearchService, productSearchService, categorySearchService, categoryService, productService, propertyService, propertySearchService, propertyDictionarySearchService, propertyDictionaryService, jsonSerializer, blobStorageProvider, associationService) + { + } + public async Task DoExportAsync(Stream outStream, ExportImportOptions options, Action progressCallback, ICancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -386,7 +402,7 @@ await reader.DeserializeArrayWithPagingAsync(_jsonSerializer, _b return product; }).ToArray(); - await _itemService.SaveChangesAsync(products); + await _productService.SaveChangesAsync(products); if (options != null && options.HandleBinaryData) { diff --git a/src/VirtoCommerce.CatalogModule.Data/ExportImport2/CatalogExportPagedDataSourceFactory.cs b/src/VirtoCommerce.CatalogModule.Data/ExportImport2/CatalogExportPagedDataSourceFactory.cs index 136da6450..15698e99c 100644 --- a/src/VirtoCommerce.CatalogModule.Data/ExportImport2/CatalogExportPagedDataSourceFactory.cs +++ b/src/VirtoCommerce.CatalogModule.Data/ExportImport2/CatalogExportPagedDataSourceFactory.cs @@ -14,7 +14,7 @@ public class CatalogExportPagedDataSourceFactory : ICatalogExportPagedDataSource private readonly IPropertyDictionaryItemSearchService _propertyDictionaryItemSearchService; private readonly IBlobStorageProvider _blobStorageProvider; private readonly IProductSearchService _productSearchService; - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly ICategorySearchService _categorySearchService; private readonly ICatalogSearchService _catalogSearchService; @@ -23,7 +23,7 @@ IPropertySearchService propertySearchService , IPropertyDictionaryItemSearchService propertyDictionaryItemSearchService , IBlobStorageProvider blobStorageProvider , IProductSearchService productSearchService - , IItemService itemService + , IProductService productService , ICategorySearchService categorySearchService , ICatalogSearchService catalogSearchService) { @@ -31,11 +31,39 @@ IPropertySearchService propertySearchService _propertyDictionaryItemSearchService = propertyDictionaryItemSearchService; _blobStorageProvider = blobStorageProvider; _productSearchService = productSearchService; - _itemService = itemService; + _productService = productService; _categorySearchService = categorySearchService; _catalogSearchService = catalogSearchService; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public CatalogExportPagedDataSourceFactory( + IPropertySearchService propertySearchService + , IPropertyDictionaryItemSearchService propertyDictionaryItemSearchService + , IBlobStorageProvider blobStorageProvider + , IProductSearchService productSearchService + , IItemService itemService + , ICategorySearchService categorySearchService + , ICatalogSearchService catalogSearchService) + : this(propertySearchService, propertyDictionaryItemSearchService, blobStorageProvider, productSearchService, (IProductService)itemService, categorySearchService, catalogSearchService) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public CatalogExportPagedDataSourceFactory( + IPropertySearchService propertySearchService + , IPropertyDictionaryItemSearchService propertyDictionaryItemSearchService + , IBlobStorageProvider blobStorageProvider + , IProductSearchService productSearchService + , IProductService productService + // ReSharper disable once UnusedParameter.Local + , IItemService itemService + , ICategorySearchService categorySearchService + , ICatalogSearchService catalogSearchService) + : this(propertySearchService, propertyDictionaryItemSearchService, blobStorageProvider, productSearchService, productService, categorySearchService, catalogSearchService) + { + } + public virtual IPagedDataSource Create(ExportDataQuery dataQuery) { IPagedDataSource result = null; @@ -50,7 +78,7 @@ public virtual IPagedDataSource Create(ExportDataQuery dataQuery) } else if (dataQuery is ProductFullExportDataQuery productFullExportQuery) { - result = new ProductExportPagedDataSource(_blobStorageProvider, _itemService, _productSearchService, productFullExportQuery.ToProductExportDataQuery()); + result = new ProductExportPagedDataSource(_blobStorageProvider, _productService, _productSearchService, productFullExportQuery.ToProductExportDataQuery()); } else if (dataQuery is CategoryExportDataQuery categoryExportQuery) { @@ -62,7 +90,7 @@ public virtual IPagedDataSource Create(ExportDataQuery dataQuery) } else if (dataQuery is ProductExportDataQuery productExportQuery) { - result = new ProductExportPagedDataSource(_blobStorageProvider, _itemService, _productSearchService, productExportQuery); + result = new ProductExportPagedDataSource(_blobStorageProvider, _productService, _productSearchService, productExportQuery); } else if (dataQuery is CatalogFullExportDataQuery catalogFullExportQuery) { diff --git a/src/VirtoCommerce.CatalogModule.Data/ExportImport2/ProductExportPagedDataSource.cs b/src/VirtoCommerce.CatalogModule.Data/ExportImport2/ProductExportPagedDataSource.cs index a0140a3a0..830c83a3d 100644 --- a/src/VirtoCommerce.CatalogModule.Data/ExportImport2/ProductExportPagedDataSource.cs +++ b/src/VirtoCommerce.CatalogModule.Data/ExportImport2/ProductExportPagedDataSource.cs @@ -16,16 +16,27 @@ namespace VirtoCommerce.CatalogModule.Data.ExportImport public class ProductExportPagedDataSource : ExportPagedDataSource { private readonly IBlobStorageProvider _blobStorageProvider; - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly IProductSearchService _productSearchService; - public ProductExportPagedDataSource(IBlobStorageProvider blobStorageProvider, IItemService itemService, IProductSearchService productSearchService, ProductExportDataQuery dataQuery) : base(dataQuery) + public ProductExportPagedDataSource(IBlobStorageProvider blobStorageProvider, IProductService productService, IProductSearchService productSearchService, ProductExportDataQuery dataQuery) : base(dataQuery) { _blobStorageProvider = blobStorageProvider; - _itemService = itemService; + _productService = productService; _productSearchService = productSearchService; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public ProductExportPagedDataSource(IBlobStorageProvider blobStorageProvider, IItemService itemService, IProductSearchService productSearchService, ProductExportDataQuery dataQuery) + : this(blobStorageProvider, (IProductService)itemService, productSearchService, dataQuery) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public ProductExportPagedDataSource(IBlobStorageProvider blobStorageProvider, IProductService productService, /* ReSharper disable once UnusedParameter.Local */ IItemService itemService, IProductSearchService productSearchService, ProductExportDataQuery dataQuery) + : this(blobStorageProvider, productService, productSearchService, dataQuery) + { + } protected override ExportableSearchResult FetchData(ProductSearchCriteria searchCriteria) { @@ -36,7 +47,7 @@ protected override ExportableSearchResult FetchData(ProductSearchCriteria search if (searchCriteria.ObjectIds.Any(x => !string.IsNullOrWhiteSpace(x))) { - result = _itemService.GetAsync(searchCriteria.ObjectIds.ToList(), responseGroup).GetAwaiter().GetResult().ToArray(); + result = _productService.GetAsync(searchCriteria.ObjectIds.ToList(), responseGroup).GetAwaiter().GetResult().ToArray(); totalCount = result.Length; } else diff --git a/src/VirtoCommerce.CatalogModule.Data/Handlers/TrackSpecialChangesEventHandler.cs b/src/VirtoCommerce.CatalogModule.Data/Handlers/TrackSpecialChangesEventHandler.cs index 8b5ceff8c..1fc609a32 100644 --- a/src/VirtoCommerce.CatalogModule.Data/Handlers/TrackSpecialChangesEventHandler.cs +++ b/src/VirtoCommerce.CatalogModule.Data/Handlers/TrackSpecialChangesEventHandler.cs @@ -16,12 +16,24 @@ namespace VirtoCommerce.CatalogModule.Data.Handlers public sealed class TrackSpecialChangesEventHandler : IEventHandler { private readonly Func _catalogRepositoryFactory; - private readonly IItemService _itemService; + private readonly IProductService _productService; - public TrackSpecialChangesEventHandler(Func catalogRepositoryFactory, IItemService itemService) + public TrackSpecialChangesEventHandler(Func catalogRepositoryFactory, IProductService productService) { _catalogRepositoryFactory = catalogRepositoryFactory; - _itemService = itemService; + _productService = productService; + } + + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public TrackSpecialChangesEventHandler(Func catalogRepositoryFactory, IItemService itemService) + : this(catalogRepositoryFactory, (IProductService)itemService) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public TrackSpecialChangesEventHandler(Func catalogRepositoryFactory, IProductService productService, /* ReSharper disable once UnusedParameter.Local */ IItemService itemService) + : this(catalogRepositoryFactory, productService) + { } public Task Handle(CategoryChangedEvent message) @@ -56,8 +68,8 @@ public async Task UpdateProductsAsync(List categoryIds) categoryIds.AddRange(childrenCategoryIds); var childrenProductIds = await repository.Items.Where(x => categoryIds.Contains(x.CategoryId)).Select(x => x.Id).ToListAsync(); - var products = await _itemService.GetAsync(childrenProductIds.ToList(), ItemResponseGroup.ItemInfo.ToString()); - await _itemService.SaveChangesAsync(products); + var products = await _productService.GetAsync(childrenProductIds.ToList(), ItemResponseGroup.ItemInfo.ToString()); + await _productService.SaveChangesAsync(products); } } } diff --git a/src/VirtoCommerce.CatalogModule.Data/Search/Indexing/ProductDocumentBuilder.cs b/src/VirtoCommerce.CatalogModule.Data/Search/Indexing/ProductDocumentBuilder.cs index 429657547..928b8687e 100644 --- a/src/VirtoCommerce.CatalogModule.Data/Search/Indexing/ProductDocumentBuilder.cs +++ b/src/VirtoCommerce.CatalogModule.Data/Search/Indexing/ProductDocumentBuilder.cs @@ -17,16 +17,28 @@ namespace VirtoCommerce.CatalogModule.Data.Search.Indexing { public class ProductDocumentBuilder : CatalogDocumentBuilder, IIndexSchemaBuilder, IIndexDocumentBuilder { - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly IProductSearchService _productsSearchService; - public ProductDocumentBuilder(ISettingsManager settingsManager, IPropertySearchService propertySearchService, IItemService itemService, IProductSearchService productsSearchService) + public ProductDocumentBuilder(ISettingsManager settingsManager, IPropertySearchService propertySearchService, IProductService productService, IProductSearchService productsSearchService) : base(settingsManager, propertySearchService) { - _itemService = itemService; + _productService = productService; _productsSearchService = productsSearchService; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public ProductDocumentBuilder(ISettingsManager settingsManager, IPropertySearchService propertySearchService, IItemService itemService, IProductSearchService productsSearchService) + : this(settingsManager, propertySearchService, (IProductService)itemService, productsSearchService) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public ProductDocumentBuilder(ISettingsManager settingsManager, IPropertySearchService propertySearchService, IProductService productService, /* ReSharper disable once UnusedParameter.Local */ IItemService itemService, IProductSearchService productsSearchService) + : this(settingsManager, propertySearchService, productService, productsSearchService) + { + } + public virtual async Task BuildSchemaAsync(IndexDocument schema) { schema.AddFilterableString("__type"); @@ -111,7 +123,7 @@ public virtual async Task> GetDocumentsAsync(IList protected virtual async Task GetProducts(IList productIds) { #pragma warning disable CS0618 // Variations can be used here - var products = await _itemService.GetNoCloneAsync(productIds.ToList(), (ItemResponseGroup.Full & ~ItemResponseGroup.Variations).ToString()); + var products = await _productService.GetNoCloneAsync(productIds.ToList(), (ItemResponseGroup.Full & ~ItemResponseGroup.Variations).ToString()); #pragma warning restore CS0618 return products.ToArray(); diff --git a/src/VirtoCommerce.CatalogModule.Data/Search/Indexing/ProductIndexedSearchService.cs b/src/VirtoCommerce.CatalogModule.Data/Search/Indexing/ProductIndexedSearchService.cs index 6ab13870e..851f99f69 100644 --- a/src/VirtoCommerce.CatalogModule.Data/Search/Indexing/ProductIndexedSearchService.cs +++ b/src/VirtoCommerce.CatalogModule.Data/Search/Indexing/ProductIndexedSearchService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -15,24 +16,35 @@ namespace VirtoCommerce.CatalogModule.Data.Search.Indexing { public class ProductIndexedSearchService : CatalogIndexedSearchService, IProductIndexedSearchService { - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly IBlobUrlResolver _blobUrlResolver; private readonly IAggregationConverter _aggregationConverter; - public ProductIndexedSearchService(ISearchRequestBuilderRegistrar searchRequestBuilderRegistrar, ISearchProvider searchProvider, ISettingsManager settingsManager, IItemService itemService, IBlobUrlResolver blobUrlResolver, IAggregationConverter aggregationConverter) + public ProductIndexedSearchService(ISearchRequestBuilderRegistrar searchRequestBuilderRegistrar, ISearchProvider searchProvider, ISettingsManager settingsManager, IProductService productService, IBlobUrlResolver blobUrlResolver, IAggregationConverter aggregationConverter) : base(searchRequestBuilderRegistrar, searchProvider, settingsManager) { - _itemService = itemService; + _productService = productService; _blobUrlResolver = blobUrlResolver; _aggregationConverter = aggregationConverter; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public ProductIndexedSearchService(ISearchRequestBuilderRegistrar searchRequestBuilderRegistrar, ISearchProvider searchProvider, ISettingsManager settingsManager, IItemService itemService, IBlobUrlResolver blobUrlResolver, IAggregationConverter aggregationConverter) + : this(searchRequestBuilderRegistrar, searchProvider, settingsManager, (IProductService)itemService, blobUrlResolver, aggregationConverter) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public ProductIndexedSearchService(ISearchRequestBuilderRegistrar searchRequestBuilderRegistrar, IProductService productService, /* ReSharper disable once UnusedParameter.Local */ ISearchProvider searchProvider, ISettingsManager settingsManager, IItemService itemService, IBlobUrlResolver blobUrlResolver, IAggregationConverter aggregationConverter) + : this(searchRequestBuilderRegistrar, searchProvider, settingsManager, productService, blobUrlResolver, aggregationConverter) + { + } protected override async Task> LoadMissingItems(string[] missingItemIds, ProductIndexedSearchCriteria criteria) { var catalog = criteria.CatalogId; var responseGroup = GetResponseGroup(criteria); - var products = await _itemService.GetByIdsAsync(missingItemIds, responseGroup.ToString(), catalog); + var products = await _productService.GetByIdsAsync(missingItemIds, responseGroup.ToString(), catalog); //var result = products.Select(p => p.ToWebModel(_blobUrlResolver)).ToArray(); return products; } diff --git a/src/VirtoCommerce.CatalogModule.Data/Search/ListEntrySearchService.cs b/src/VirtoCommerce.CatalogModule.Data/Search/ListEntrySearchService.cs index 66eff5e5d..cd9d89957 100644 --- a/src/VirtoCommerce.CatalogModule.Data/Search/ListEntrySearchService.cs +++ b/src/VirtoCommerce.CatalogModule.Data/Search/ListEntrySearchService.cs @@ -18,20 +18,32 @@ namespace VirtoCommerce.CatalogModule.Data.Search public class ListEntrySearchService : IListEntrySearchService { private readonly Func _catalogRepositoryFactory; - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly ICategoryService _categoryService; private readonly Dictionary _productSortingAliases = new Dictionary(); private readonly Dictionary _categorySortingAliases = new Dictionary(); - public ListEntrySearchService(Func catalogRepositoryFactory, IItemService itemService, ICategoryService categoryService) + public ListEntrySearchService(Func catalogRepositoryFactory, IProductService productService, ICategoryService categoryService) { _catalogRepositoryFactory = catalogRepositoryFactory; - _itemService = itemService; + _productService = productService; _categoryService = categoryService; _productSortingAliases["sku"] = nameof(CatalogProduct.Code); _categorySortingAliases["sku"] = nameof(Category.Code); } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public ListEntrySearchService(Func catalogRepositoryFactory, IItemService itemService, ICategoryService categoryService) + : this(catalogRepositoryFactory, (IProductService)itemService, categoryService) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public ListEntrySearchService(Func catalogRepositoryFactory, IProductService productService, /* ReSharper disable once UnusedParameter.Local */ IItemService itemService, ICategoryService categoryService) + : this(catalogRepositoryFactory, productService, categoryService) + { + } + public async Task SearchAsync(CatalogListEntrySearchCriteria criteria) { if (criteria == null) @@ -52,7 +64,7 @@ public async Task SearchAsync(CatalogListEntrySearchCrite var categorySkip = 0; var categoryTake = 0; - //Because products and categories represent in search result as two separated collections for handle paging request + //Because products and categories represent in search result as two separated collections for handle paging request //we should join two resulting collection artificially //search categories if (criteria.ObjectTypes.IsNullOrEmpty() || criteria.ObjectTypes.Contains(nameof(Category))) @@ -118,7 +130,7 @@ protected virtual async Task> SearchCategoriesAsyn { if (criteria.SearchInChildren) { - //need search in all catalog linked and children categories + //need search in all catalog linked and children categories //First need load all categories belong to searched catalogs searchCategoryIds = repository.Categories.Where(x => criteria.CatalogIds.Contains(x.CatalogId)).Select(x => x.Id).ToArray(); //Then load all physical categories linked to catalog @@ -146,7 +158,7 @@ protected virtual async Task> SearchCategoriesAsyn { query = query.Where(x => x.Code == criteria.Code); } - //Extension point + //Extension point query = BuildQuery(query, criteria); var sortInfos = BuildSortExpression(criteria); //Try to replace sorting columns names @@ -195,7 +207,7 @@ protected virtual async Task> SearchItemsAsy var hasVirtualCatalog = repository.Catalogs.Where(x => criteria.CatalogIds.Contains(x.Id)).Any(x => x.Virtual); if (!hasVirtualCatalog) { - // When searching from the root level of a catalog and all searched catalogs are not virtual then 'categoryIds' condition can safely cut off + // When searching from the root level of a catalog and all searched catalogs are not virtual then 'categoryIds' condition can safely cut off searchCategoryIds = null; } else @@ -226,7 +238,7 @@ protected virtual async Task> SearchItemsAsy var essentialResponseGroup = ItemResponseGroup.ItemInfo | ItemResponseGroup.ItemAssets | ItemResponseGroup.Links | ItemResponseGroup.Seo | ItemResponseGroup.Outlines; var responseGroup = string.Concat(criteria.ResponseGroup, ",", essentialResponseGroup.ToString()); - result.Results = (await _itemService.GetByIdsAsync(itemIds.ToArray(), responseGroup, criteria.CatalogId)).OrderBy(x => itemIds.IndexOf(x.Id)).ToList(); + result.Results = (await _productService.GetByIdsAsync(itemIds.ToArray(), responseGroup, criteria.CatalogId)).OrderBy(x => itemIds.IndexOf(x.Id)).ToList(); } } diff --git a/src/VirtoCommerce.CatalogModule.Data/Search/ProductSearchService.cs b/src/VirtoCommerce.CatalogModule.Data/Search/ProductSearchService.cs index c64e6f10b..d7f2a6b21 100644 --- a/src/VirtoCommerce.CatalogModule.Data/Search/ProductSearchService.cs +++ b/src/VirtoCommerce.CatalogModule.Data/Search/ProductSearchService.cs @@ -20,12 +20,33 @@ public class ProductSearchService : SearchService repositoryFactory, IPlatformMemoryCache platformMemoryCache, - IItemService crudService, + IProductService crudService, IOptions crudOptions) : base(repositoryFactory, platformMemoryCache, crudService, crudOptions) { } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public ProductSearchService( + Func repositoryFactory, + IPlatformMemoryCache platformMemoryCache, + IItemService itemService, + IOptions crudOptions) + : this(repositoryFactory, platformMemoryCache, (IProductService)itemService, crudOptions) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public ProductSearchService( + Func repositoryFactory, + IPlatformMemoryCache platformMemoryCache, + IProductService productService, + // ReSharper disable once UnusedParameter.Local + IItemService itemService, + IOptions crudOptions) + : this(repositoryFactory, platformMemoryCache, productService, crudOptions) + { + } protected override IQueryable BuildQuery(IRepository repository, ProductSearchCriteria criteria) { diff --git a/src/VirtoCommerce.CatalogModule.Data/Services/CatalogSeoDuplicatesDetector.cs b/src/VirtoCommerce.CatalogModule.Data/Services/CatalogSeoDuplicatesDetector.cs index c45462330..bb74cc292 100644 --- a/src/VirtoCommerce.CatalogModule.Data/Services/CatalogSeoDuplicatesDetector.cs +++ b/src/VirtoCommerce.CatalogModule.Data/Services/CatalogSeoDuplicatesDetector.cs @@ -20,14 +20,14 @@ namespace VirtoCommerce.CatalogModule.Data.Services /// public class CatalogSeoDuplicatesDetector : ISeoDuplicatesDetector { - private readonly IItemService _productService; + private readonly IProductService _productService; private readonly ICategoryService _categoryService; private readonly IStoreService _storeService; private readonly Func _repositoryFactory; private readonly ISettingsManager _settingsManager; public CatalogSeoDuplicatesDetector( - IItemService productService, + IProductService productService, ICategoryService categoryService, IStoreService storeService, Func repositoryFactory, @@ -40,6 +40,30 @@ public CatalogSeoDuplicatesDetector( _settingsManager = settingsManager; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public CatalogSeoDuplicatesDetector( + IItemService itemService, + ICategoryService categoryService, + IStoreService storeService, + Func repositoryFactory, + ISettingsManager settingsManager) + : this((IProductService)itemService, categoryService, storeService, repositoryFactory, settingsManager) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public CatalogSeoDuplicatesDetector( + IProductService productService, + // ReSharper disable once UnusedParameter.Local + IItemService itemService, + ICategoryService categoryService, + IStoreService storeService, + Func repositoryFactory, + ISettingsManager settingsManager) + : this(productService, categoryService, storeService, repositoryFactory, settingsManager) + { + } + #region ISeoConflictDetector Members public async Task> DetectSeoDuplicatesAsync(TenantIdentity tenantIdentity) diff --git a/src/VirtoCommerce.CatalogModule.Data/Services/ProductMover.cs b/src/VirtoCommerce.CatalogModule.Data/Services/ProductMover.cs index 3179255b3..dbc38e250 100644 --- a/src/VirtoCommerce.CatalogModule.Data/Services/ProductMover.cs +++ b/src/VirtoCommerce.CatalogModule.Data/Services/ProductMover.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -10,22 +11,34 @@ namespace VirtoCommerce.CatalogModule.Data.Services { public class ProductMover : ListEntryMover { - private readonly IItemService _itemService; + private readonly IProductService _productService; /// /// Initializes a new instance of the class. /// - /// + /// /// The item service. /// + public ProductMover(IProductService productService) + { + _productService = productService; + } + + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] public ProductMover(IItemService itemService) + : this((IProductService)itemService) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public ProductMover(IProductService productService, /* ReSharper disable once UnusedParameter.Local */ IItemService itemService) + : this(productService) { - _itemService = itemService; } public override Task ConfirmMoveAsync(IEnumerable entities) { - return _itemService.SaveChangesAsync(entities.ToArray()); + return _productService.SaveChangesAsync(entities.ToArray()); } public override async Task> PrepareMoveAsync(ListEntriesMoveRequest moveInfo) @@ -35,7 +48,7 @@ public override async Task> PrepareMoveAsync(ListEntriesMov foreach (var listEntryProduct in moveInfo.ListEntries.Where( listEntry => listEntry.Type.EqualsInvariant(ProductListEntry.TypeName))) { - var product = await _itemService.GetByIdAsync(listEntryProduct.Id, ItemResponseGroup.ItemLarge.ToString()); + var product = await _productService.GetByIdAsync(listEntryProduct.Id, ItemResponseGroup.ItemLarge.ToString()); if (product.CatalogId == moveInfo.Catalog) { // idle diff --git a/src/VirtoCommerce.CatalogModule.Data/Services/ItemService.cs b/src/VirtoCommerce.CatalogModule.Data/Services/ProductService.cs similarity index 86% rename from src/VirtoCommerce.CatalogModule.Data/Services/ItemService.cs rename to src/VirtoCommerce.CatalogModule.Data/Services/ProductService.cs index 7245fd73c..c30597f3d 100644 --- a/src/VirtoCommerce.CatalogModule.Data/Services/ItemService.cs +++ b/src/VirtoCommerce.CatalogModule.Data/Services/ProductService.cs @@ -1,346 +1,375 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Web; -using FluentValidation; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; -using VirtoCommerce.AssetsModule.Core.Assets; -using VirtoCommerce.CatalogModule.Core.Events; -using VirtoCommerce.CatalogModule.Core.Model; -using VirtoCommerce.CatalogModule.Core.Services; -using VirtoCommerce.CatalogModule.Data.Caching; -using VirtoCommerce.CatalogModule.Data.Model; -using VirtoCommerce.CatalogModule.Data.Repositories; -using VirtoCommerce.Platform.Caching; -using VirtoCommerce.Platform.Core.Caching; -using VirtoCommerce.Platform.Core.Common; -using VirtoCommerce.Platform.Core.Events; -using VirtoCommerce.Platform.Data.GenericCrud; - -namespace VirtoCommerce.CatalogModule.Data.Services -{ - public class ItemService : CrudService, IItemService - { - private readonly Func _repositoryFactory; - private readonly IPlatformMemoryCache _platformMemoryCache; - private readonly IEventPublisher _eventPublisher; - private readonly AbstractValidator _hasPropertyValidator; - private readonly ICatalogService _catalogService; - private readonly ICategoryService _categoryService; - private readonly IOutlineService _outlineService; - private readonly IBlobUrlResolver _blobUrlResolver; - private readonly ISkuGenerator _skuGenerator; - private readonly AbstractValidator _productValidator; - - public ItemService( - Func repositoryFactory, - IPlatformMemoryCache platformMemoryCache, - IEventPublisher eventPublisher, - AbstractValidator hasPropertyValidator, - ICatalogService catalogService, - ICategoryService categoryService, - IOutlineService outlineService, - IBlobUrlResolver blobUrlResolver, - ISkuGenerator skuGenerator, - AbstractValidator productValidator) - : base(repositoryFactory, platformMemoryCache, eventPublisher) - { - _repositoryFactory = repositoryFactory; - _platformMemoryCache = platformMemoryCache; - _eventPublisher = eventPublisher; - _hasPropertyValidator = hasPropertyValidator; - _catalogService = catalogService; - _categoryService = categoryService; - _outlineService = outlineService; - _blobUrlResolver = blobUrlResolver; - _skuGenerator = skuGenerator; - _productValidator = productValidator; - } - - public virtual async Task> GetByCodes(string catalogId, IList codes, string responseGroup) - { - var idsByCodes = await GetIdsByCodes(catalogId, codes); - - return idsByCodes.Any() - ? await GetByIdsAsync(idsByCodes.Values.ToList(), responseGroup, catalogId) - : Array.Empty(); - } - - public virtual async Task> GetIdsByCodes(string catalogId, IList codes) - { - var cacheKeyPrefix = CacheKey.With(GetType(), nameof(GetIdsByCodes), catalogId); - - var models = await _platformMemoryCache.GetOrLoadByIdsAsync(cacheKeyPrefix, codes, - missingCodes => GetIdsByCodesNoCache(catalogId, missingCodes), - ConfigureCache); - - return models.ToDictionary(x => x.Id, x => x.ProductId, StringComparer.OrdinalIgnoreCase); - } - - public virtual async Task GetByIdAsync(string itemId, string responseGroup, string catalogId) - { - var products = await GetByIdsAsync(new[] { itemId }, responseGroup, catalogId); - - return products.FirstOrDefault(); - } - - public virtual async Task> GetByIdsAsync(IList ids, string responseGroup, string catalogId) - { - var products = await GetAsync(ids, responseGroup); - - // Remove outlines that don't belong to the requested catalog - if (products.Any() && catalogId != null && HasFlag(responseGroup, ItemResponseGroup.Outlines)) - { - products - .Where(x => x.Variations != null) - .SelectMany(x => x.Variations) - .Concat(products) - .Apply(product => - { - product.Outlines = product.Outlines - .Where(outline => outline.Items.Any(item => - item.Id.EqualsInvariant(catalogId) && - item.SeoObjectType.EqualsInvariant("catalog"))) - .ToList(); - }); - } - - return products; - } - - - public override async Task DeleteAsync(IList ids, bool softDelete = false) - { - var items = await GetAsync(ids, ItemResponseGroup.ItemInfo.ToString()); - - if (items.Any()) - { - var changedEntries = items - .Select(x => new GenericChangedEntry(x, EntryState.Deleted)) - .ToList(); - - await _eventPublisher.Publish(new ProductChangingEvent(changedEntries)); - - using (var repository = _repositoryFactory()) - { - // TODO: Implement soft delete - await repository.RemoveItemsAsync(ids); - await repository.UnitOfWork.CommitAsync(); - } - - ClearCache(items); - - await _eventPublisher.Publish(new ProductChangedEvent(changedEntries)); - } - } - - - protected override Task> LoadEntities(IRepository repository, IList ids, string responseGroup) - { - return ((ICatalogRepository)repository).GetItemByIdsAsync(ids, responseGroup); - } - - protected override IList ProcessModels(IList entities, string responseGroup) - { - var products = base.ProcessModels(entities, responseGroup); - - if (products != null && products.Any()) - { - LoadDependencies(products).GetAwaiter().GetResult(); - ApplyInheritanceRules(products); - - var productsAndVariations = products - .Where(x => x.Variations != null) - .SelectMany(x => x.Variations) - .Concat(products) - .ToList(); - - if (HasFlag(responseGroup, ItemResponseGroup.Outlines)) - { - _outlineService.FillOutlinesForObjects(productsAndVariations, catalogId: null); - } - - foreach (var product in productsAndVariations) - { - product.ReduceDetails(responseGroup); - } - } - - return products; - } - - protected virtual bool HasFlag(string responseGroup, ItemResponseGroup flag) - { - var itemResponseGroup = EnumUtility.SafeParseFlags(responseGroup, ItemResponseGroup.ItemLarge); - - return itemResponseGroup.HasFlag(flag); - } - - protected override async Task BeforeSaveChanges(IList models) - { - await base.BeforeSaveChanges(models); - await ValidateProductsAsync(models); - } - - protected virtual async Task> GetIdsByCodesNoCache(string catalogId, IList codes) - { - using var repository = _repositoryFactory(); - var query = repository.Items.Where(x => x.CatalogId == catalogId); - - query = codes.Count == 1 - ? query.Where(x => x.Code == codes.First()) - : query.Where(x => codes.Contains(x.Code)); - - var items = await query - .Select(x => new ProductCodeCacheItem { Id = x.Code, ProductId = x.Id }) - .ToListAsync(); - - return items; - } - - protected virtual void ConfigureCache(MemoryCacheEntryOptions cacheOptions, string id, ProductCodeCacheItem model) - { - cacheOptions.AddExpirationToken(CatalogCacheRegion.CreateChangeToken()); - cacheOptions.AddExpirationToken(ItemCacheRegion.CreateChangeTokenForKey(id)); - } - - protected override void ConfigureCache(MemoryCacheEntryOptions cacheOptions, string id, CatalogProduct model) - { - cacheOptions.AddExpirationToken(CatalogCacheRegion.CreateChangeToken()); - cacheOptions.AddExpirationToken(ItemCacheRegion.CreateChangeTokenForKey(id)); - - if (model?.Variations?.Any() == true) - { - cacheOptions.AddExpirationToken(ItemCacheRegion.CreateChangeToken(model.Variations)); - } - } - - protected override void ClearCache(IList models) - { - GenericSearchCachingRegion.ExpireRegion(); - - AssociationSearchCacheRegion.ExpireRegion(); - SeoInfoCacheRegion.ExpireRegion(); - - foreach (var model in models) - { - ItemCacheRegion.ExpireEntity(model); - } - } - - protected virtual async Task LoadDependencies(IList products) - { - // TODO: Refactor to do this by one call and iteration - var catalogsIds = new { products }.GetFlatObjectsListWithInterface().Select(x => x.CatalogId).Where(x => x != null).Distinct().ToList(); - var catalogsByIdDict = (await _catalogService.GetNoCloneAsync(catalogsIds)).ToDictionary(x => x.Id, StringComparer.OrdinalIgnoreCase); - - var categoriesIds = new { products }.GetFlatObjectsListWithInterface().Select(x => x.CategoryId).Where(x => x != null).Distinct().ToList(); - var categoriesByIdDict = (await _categoryService.GetNoCloneAsync(categoriesIds)).ToDictionary(x => x.Id, StringComparer.OrdinalIgnoreCase); - - var allImages = new { products }.GetFlatObjectsListWithInterface().Where(x => x.Images != null).SelectMany(x => x.Images); - foreach (var image in allImages.Where(x => !string.IsNullOrEmpty(x.Url))) - { - image.RelativeUrl = !string.IsNullOrEmpty(image.RelativeUrl) ? image.RelativeUrl : image.Url; - image.Url = _blobUrlResolver.GetAbsoluteUrl(HttpUtility.UrlDecode(image.Url)); - } - - var allAssets = new { products }.GetFlatObjectsListWithInterface().Where(x => x.Assets != null).SelectMany(x => x.Assets); - foreach (var asset in allAssets.Where(x => !string.IsNullOrEmpty(x.Url))) - { - asset.RelativeUrl = !string.IsNullOrEmpty(asset.RelativeUrl) ? asset.RelativeUrl : asset.Url; - asset.Url = _blobUrlResolver.GetAbsoluteUrl(HttpUtility.UrlDecode(asset.Url)); - } - - foreach (var product in products) - { - SetProductDependencies(product, catalogsByIdDict, categoriesByIdDict); - - if (product.MainProduct != null) - { - SetProductDependencies(product.MainProduct, catalogsByIdDict, categoriesByIdDict); - } - if (!product.Variations.IsNullOrEmpty()) - { - foreach (var variation in product.Variations) - { - SetProductDependencies(variation, catalogsByIdDict, categoriesByIdDict); - } - } - } - } - - protected virtual void SetProductDependencies(CatalogProduct product, IDictionary catalogsByIdDict, IDictionary categoriesByIdDict) - { - // TODO: Refactor after covering by the unit tests - if (string.IsNullOrEmpty(product.Code)) - { - product.Code = _skuGenerator.GenerateSku(product); - } - - product.Catalog = catalogsByIdDict.GetValueOrThrow(product.CatalogId, $"catalog with key {product.CatalogId} doesn't exist"); - - if (product.CategoryId != null) - { - product.Category = categoriesByIdDict.GetValueOrThrow(product.CategoryId, $"category with key {product.CategoryId} doesn't exist"); - } - - foreach (var link in product.Links ?? Array.Empty()) - { - link.Catalog = catalogsByIdDict.GetValueOrThrow(link.CatalogId, $"link catalog with key {link.CatalogId} doesn't exist"); - - if (!string.IsNullOrEmpty(link.CategoryId)) - { - link.Category = categoriesByIdDict.GetValueOrThrow(link.CategoryId, $"link category with key {link.CategoryId} doesn't exist"); - } - } - } - - protected virtual void ApplyInheritanceRules(IList products) - { - foreach (var product in products) - { - if (product.MainProduct != null) - { - // Need to apply inheritance rules for main product first. - product.MainProduct.TryInheritFrom(product.Category ?? (IEntity)product.Catalog); - product.TryInheritFrom(product.MainProduct); - } - else - { - product.TryInheritFrom(product.Category ?? (IEntity)product.Catalog); - } - } - } - - protected virtual async Task ValidateProductsAsync(IList products) - { - if (products == null) - { - throw new ArgumentNullException(nameof(products)); - } - - // Validate products - foreach (var product in products) - { - await _productValidator.ValidateAndThrowAsync(product); - } - - // Validate properties - foreach (var product in products) - { - var validationResult = await _hasPropertyValidator.ValidateAsync(product); - if (!validationResult.IsValid) - { - throw new ValidationException($"Product properties has validation error: {string.Join(Environment.NewLine, validationResult.Errors.Select(x => x.ToString()))}"); - } - } - } - - protected class ProductCodeCacheItem : Entity - { - public string ProductId { get; set; } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web; +using FluentValidation; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using VirtoCommerce.AssetsModule.Core.Assets; +using VirtoCommerce.CatalogModule.Core.Events; +using VirtoCommerce.CatalogModule.Core.Model; +using VirtoCommerce.CatalogModule.Core.Services; +using VirtoCommerce.CatalogModule.Data.Caching; +using VirtoCommerce.CatalogModule.Data.Model; +using VirtoCommerce.CatalogModule.Data.Repositories; +using VirtoCommerce.Platform.Caching; +using VirtoCommerce.Platform.Core.Caching; +using VirtoCommerce.Platform.Core.Common; +using VirtoCommerce.Platform.Core.Events; +using VirtoCommerce.Platform.Data.GenericCrud; + +namespace VirtoCommerce.CatalogModule.Data.Services +{ + public class ProductService : CrudService, IProductService + { + private readonly Func _repositoryFactory; + private readonly IPlatformMemoryCache _platformMemoryCache; + private readonly IEventPublisher _eventPublisher; + private readonly AbstractValidator _hasPropertyValidator; + private readonly ICatalogService _catalogService; + private readonly ICategoryService _categoryService; + private readonly IOutlineService _outlineService; + private readonly IBlobUrlResolver _blobUrlResolver; + private readonly ISkuGenerator _skuGenerator; + private readonly AbstractValidator _productValidator; + + public ProductService( + Func repositoryFactory, + IPlatformMemoryCache platformMemoryCache, + IEventPublisher eventPublisher, + AbstractValidator hasPropertyValidator, + ICatalogService catalogService, + ICategoryService categoryService, + IOutlineService outlineService, + IBlobUrlResolver blobUrlResolver, + ISkuGenerator skuGenerator, + AbstractValidator productValidator) + : base(repositoryFactory, platformMemoryCache, eventPublisher) + { + _repositoryFactory = repositoryFactory; + _platformMemoryCache = platformMemoryCache; + _eventPublisher = eventPublisher; + _hasPropertyValidator = hasPropertyValidator; + _catalogService = catalogService; + _categoryService = categoryService; + _outlineService = outlineService; + _blobUrlResolver = blobUrlResolver; + _skuGenerator = skuGenerator; + _productValidator = productValidator; + } + + public virtual async Task> GetByCodes(string catalogId, IList codes, string responseGroup) + { + var idsByCodes = await GetIdsByCodes(catalogId, codes); + + return idsByCodes.Any() + ? await GetByIdsAsync(idsByCodes.Values.ToList(), responseGroup, catalogId) + : Array.Empty(); + } + + public virtual async Task> GetIdsByCodes(string catalogId, IList codes) + { + var cacheKeyPrefix = CacheKey.With(GetType(), nameof(GetIdsByCodes), catalogId); + + var models = await _platformMemoryCache.GetOrLoadByIdsAsync(cacheKeyPrefix, codes, + missingCodes => GetIdsByCodesNoCache(catalogId, missingCodes), + ConfigureCache); + + return models.ToDictionary(x => x.Id, x => x.ProductId, StringComparer.OrdinalIgnoreCase); + } + + public virtual async Task GetByIdAsync(string itemId, string responseGroup, string catalogId) + { + var products = await GetByIdsAsync(new[] { itemId }, responseGroup, catalogId); + + return products.FirstOrDefault(); + } + + public virtual async Task> GetByIdsAsync(IList ids, string responseGroup, string catalogId) + { + var products = await GetAsync(ids, responseGroup); + + // Remove outlines that don't belong to the requested catalog + if (products.Any() && catalogId != null && HasFlag(responseGroup, ItemResponseGroup.Outlines)) + { + products + .Where(x => x.Variations != null) + .SelectMany(x => x.Variations) + .Concat(products) + .Apply(product => + { + product.Outlines = product.Outlines + .Where(outline => outline.Items.Any(item => + item.Id.EqualsInvariant(catalogId) && + item.SeoObjectType.EqualsInvariant("catalog"))) + .ToList(); + }); + } + + return products; + } + + + public override async Task DeleteAsync(IList ids, bool softDelete = false) + { + var items = await GetAsync(ids, ItemResponseGroup.ItemInfo.ToString()); + + if (items.Any()) + { + var changedEntries = items + .Select(x => new GenericChangedEntry(x, EntryState.Deleted)) + .ToList(); + + await _eventPublisher.Publish(new ProductChangingEvent(changedEntries)); + + using (var repository = _repositoryFactory()) + { + // TODO: Implement soft delete + await repository.RemoveItemsAsync(ids); + await repository.UnitOfWork.CommitAsync(); + } + + ClearCache(items); + + await _eventPublisher.Publish(new ProductChangedEvent(changedEntries)); + } + } + + + protected override Task> LoadEntities(IRepository repository, IList ids, string responseGroup) + { + return ((ICatalogRepository)repository).GetItemByIdsAsync(ids, responseGroup); + } + + protected override IList ProcessModels(IList entities, string responseGroup) + { + var products = base.ProcessModels(entities, responseGroup); + + if (products != null && products.Any()) + { + LoadDependencies(products).GetAwaiter().GetResult(); + ApplyInheritanceRules(products); + + var productsAndVariations = products + .Where(x => x.Variations != null) + .SelectMany(x => x.Variations) + .Concat(products) + .ToList(); + + if (HasFlag(responseGroup, ItemResponseGroup.Outlines)) + { + _outlineService.FillOutlinesForObjects(productsAndVariations, catalogId: null); + } + + foreach (var product in productsAndVariations) + { + product.ReduceDetails(responseGroup); + } + } + + return products; + } + + protected virtual bool HasFlag(string responseGroup, ItemResponseGroup flag) + { + var itemResponseGroup = EnumUtility.SafeParseFlags(responseGroup, ItemResponseGroup.ItemLarge); + + return itemResponseGroup.HasFlag(flag); + } + + protected override async Task BeforeSaveChanges(IList models) + { + await base.BeforeSaveChanges(models); + await ValidateProductsAsync(models); + } + + protected virtual async Task> GetIdsByCodesNoCache(string catalogId, IList codes) + { + using var repository = _repositoryFactory(); + var query = repository.Items.Where(x => x.CatalogId == catalogId); + + query = codes.Count == 1 + ? query.Where(x => x.Code == codes.First()) + : query.Where(x => codes.Contains(x.Code)); + + var items = await query + .Select(x => new ProductCodeCacheItem { Id = x.Code, ProductId = x.Id }) + .ToListAsync(); + + return items; + } + + protected virtual void ConfigureCache(MemoryCacheEntryOptions cacheOptions, string id, ProductCodeCacheItem model) + { + cacheOptions.AddExpirationToken(CatalogCacheRegion.CreateChangeToken()); + cacheOptions.AddExpirationToken(ItemCacheRegion.CreateChangeTokenForKey(id)); + } + + protected override void ConfigureCache(MemoryCacheEntryOptions cacheOptions, string id, CatalogProduct model) + { + cacheOptions.AddExpirationToken(CatalogCacheRegion.CreateChangeToken()); + cacheOptions.AddExpirationToken(ItemCacheRegion.CreateChangeTokenForKey(id)); + + if (model?.Variations?.Any() == true) + { + cacheOptions.AddExpirationToken(ItemCacheRegion.CreateChangeToken(model.Variations)); + } + } + + protected override void ClearCache(IList models) + { + GenericSearchCachingRegion.ExpireRegion(); + + AssociationSearchCacheRegion.ExpireRegion(); + SeoInfoCacheRegion.ExpireRegion(); + + foreach (var model in models) + { + ItemCacheRegion.ExpireEntity(model); + } + } + + protected virtual async Task LoadDependencies(IList products) + { + // TODO: Refactor to do this by one call and iteration + var catalogsIds = new { products }.GetFlatObjectsListWithInterface().Select(x => x.CatalogId).Where(x => x != null).Distinct().ToList(); + var catalogsByIdDict = (await _catalogService.GetNoCloneAsync(catalogsIds)).ToDictionary(x => x.Id, StringComparer.OrdinalIgnoreCase); + + var categoriesIds = new { products }.GetFlatObjectsListWithInterface().Select(x => x.CategoryId).Where(x => x != null).Distinct().ToList(); + var categoriesByIdDict = (await _categoryService.GetNoCloneAsync(categoriesIds)).ToDictionary(x => x.Id, StringComparer.OrdinalIgnoreCase); + + var allImages = new { products }.GetFlatObjectsListWithInterface().Where(x => x.Images != null).SelectMany(x => x.Images); + foreach (var image in allImages.Where(x => !string.IsNullOrEmpty(x.Url))) + { + image.RelativeUrl = !string.IsNullOrEmpty(image.RelativeUrl) ? image.RelativeUrl : image.Url; + image.Url = _blobUrlResolver.GetAbsoluteUrl(HttpUtility.UrlDecode(image.Url)); + } + + var allAssets = new { products }.GetFlatObjectsListWithInterface().Where(x => x.Assets != null).SelectMany(x => x.Assets); + foreach (var asset in allAssets.Where(x => !string.IsNullOrEmpty(x.Url))) + { + asset.RelativeUrl = !string.IsNullOrEmpty(asset.RelativeUrl) ? asset.RelativeUrl : asset.Url; + asset.Url = _blobUrlResolver.GetAbsoluteUrl(HttpUtility.UrlDecode(asset.Url)); + } + + foreach (var product in products) + { + SetProductDependencies(product, catalogsByIdDict, categoriesByIdDict); + + if (product.MainProduct != null) + { + SetProductDependencies(product.MainProduct, catalogsByIdDict, categoriesByIdDict); + } + if (!product.Variations.IsNullOrEmpty()) + { + foreach (var variation in product.Variations) + { + SetProductDependencies(variation, catalogsByIdDict, categoriesByIdDict); + } + } + } + } + + protected virtual void SetProductDependencies(CatalogProduct product, IDictionary catalogsByIdDict, IDictionary categoriesByIdDict) + { + // TODO: Refactor after covering by the unit tests + if (string.IsNullOrEmpty(product.Code)) + { + product.Code = _skuGenerator.GenerateSku(product); + } + + product.Catalog = catalogsByIdDict.GetValueOrThrow(product.CatalogId, $"catalog with key {product.CatalogId} doesn't exist"); + + if (product.CategoryId != null) + { + product.Category = categoriesByIdDict.GetValueOrThrow(product.CategoryId, $"category with key {product.CategoryId} doesn't exist"); + } + + foreach (var link in product.Links ?? Array.Empty()) + { + link.Catalog = catalogsByIdDict.GetValueOrThrow(link.CatalogId, $"link catalog with key {link.CatalogId} doesn't exist"); + + if (!string.IsNullOrEmpty(link.CategoryId)) + { + link.Category = categoriesByIdDict.GetValueOrThrow(link.CategoryId, $"link category with key {link.CategoryId} doesn't exist"); + } + } + } + + protected virtual void ApplyInheritanceRules(IList products) + { + foreach (var product in products) + { + if (product.MainProduct != null) + { + // Need to apply inheritance rules for main product first. + product.MainProduct.TryInheritFrom(product.Category ?? (IEntity)product.Catalog); + product.TryInheritFrom(product.MainProduct); + } + else + { + product.TryInheritFrom(product.Category ?? (IEntity)product.Catalog); + } + } + } + + protected virtual async Task ValidateProductsAsync(IList products) + { + if (products == null) + { + throw new ArgumentNullException(nameof(products)); + } + + // Validate products + foreach (var product in products) + { + await _productValidator.ValidateAndThrowAsync(product); + } + + // Validate properties + foreach (var product in products) + { + var validationResult = await _hasPropertyValidator.ValidateAsync(product); + if (!validationResult.IsValid) + { + throw new ValidationException($"Product properties has validation error: {string.Join(Environment.NewLine, validationResult.Errors.Select(x => x.ToString()))}"); + } + } + } + + protected class ProductCodeCacheItem : Entity + { + public string ProductId { get; set; } + } + } + + [Obsolete($"Use {nameof(ProductService)} instead")] + public class ItemService( + Func repositoryFactory, + IPlatformMemoryCache platformMemoryCache, + IEventPublisher eventPublisher, + AbstractValidator hasPropertyValidator, + ICatalogService catalogService, + ICategoryService categoryService, + IOutlineService outlineService, + IBlobUrlResolver blobUrlResolver, + ISkuGenerator skuGenerator, + AbstractValidator productValidator) + : + ProductService(repositoryFactory, platformMemoryCache, eventPublisher, hasPropertyValidator, catalogService, + categoryService, outlineService, blobUrlResolver, skuGenerator, productValidator), + IItemService; + + [Obsolete($"Use only when legacy code requires {nameof(IItemService)}")] + public sealed class ProductServiceAdapter(IProductService productService) : IItemService + { + public Task> GetAsync(IList ids, string responseGroup = null, bool clone = true) => productService.GetAsync(ids, responseGroup, clone); + public Task SaveChangesAsync(IList models) => productService.SaveChangesAsync(models); + public Task DeleteAsync(IList ids, bool softDelete = false) => productService.DeleteAsync(ids, softDelete); + public Task> GetByCodes(string catalogId, IList codes, string responseGroup) => productService.GetByCodes(catalogId, codes, responseGroup); + public Task> GetIdsByCodes(string catalogId, IList codes) => productService.GetIdsByCodes(catalogId, codes); + public Task GetByIdAsync(string itemId, string responseGroup, string catalogId) => productService.GetByIdAsync(itemId, responseGroup, catalogId); + public Task> GetByIdsAsync(IList ids, string responseGroup, string catalogId) => productService.GetByIdsAsync(ids, responseGroup, catalogId); + } +} diff --git a/src/VirtoCommerce.CatalogModule.Data/Validation/PropertyNameValidator.cs b/src/VirtoCommerce.CatalogModule.Data/Validation/PropertyNameValidator.cs index de91b3682..bac69e14b 100644 --- a/src/VirtoCommerce.CatalogModule.Data/Validation/PropertyNameValidator.cs +++ b/src/VirtoCommerce.CatalogModule.Data/Validation/PropertyNameValidator.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -13,15 +14,27 @@ namespace VirtoCommerce.CatalogModule.Data.Validation { public class PropertyNameValidator : AbstractValidator { - private readonly IItemService _itemService; + private readonly IProductService _productService; private readonly IProductSearchService _productSearchService; - public PropertyNameValidator(IItemService itemService, IProductSearchService productSearchService) + public PropertyNameValidator(IProductService productService, IProductSearchService productSearchService) { - _itemService = itemService; + _productService = productService; _productSearchService = productSearchService; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public PropertyNameValidator(IItemService itemService, IProductSearchService productSearchService) + : this((IProductService)itemService, productSearchService) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public PropertyNameValidator(IProductService productService, /* ReSharper disable once UnusedParameter.Local */ IItemService itemService, IProductSearchService productSearchService) + : this(productService, productSearchService) + { + } + public override async Task ValidateAsync(ValidationContext context, CancellationToken cancellation = default) { var result = new ValidationResult(); @@ -32,7 +45,7 @@ public override async Task ValidateAsync(ValidationContext _categoryMover; private readonly ListEntryMover _productMover; - public CatalogModuleListEntryController( - IInternalListEntrySearchService internalListEntrySearchService, + public CatalogModuleListEntryController(IInternalListEntrySearchService internalListEntrySearchService, IListEntrySearchService listEntrySearchService, ILinkSearchService linkSearchService, ICategoryService categoryService, - IItemService itemService, + IProductService productService, ICatalogService catalogService, IAuthorizationService authorizationService, ListEntryMover categoryMover, @@ -47,13 +46,45 @@ public CatalogModuleListEntryController( _categoryService = categoryService; _linkSearchService = linkSearchService; _authorizationService = authorizationService; - _itemService = itemService; + _productService = productService; _catalogService = catalogService; _listEntrySearchService = listEntrySearchService; _categoryMover = categoryMover; _productMover = productMover; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public CatalogModuleListEntryController( + IInternalListEntrySearchService internalListEntrySearchService, + IListEntrySearchService listEntrySearchService, + ILinkSearchService linkSearchService, + ICategoryService categoryService, + IItemService itemService, + ICatalogService catalogService, + IAuthorizationService authorizationService, + ListEntryMover categoryMover, + ListEntryMover productMover) + : this(internalListEntrySearchService, listEntrySearchService, linkSearchService, categoryService, (IProductService)itemService, catalogService, authorizationService, categoryMover, productMover) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public CatalogModuleListEntryController( + IInternalListEntrySearchService internalListEntrySearchService, + IListEntrySearchService listEntrySearchService, + ILinkSearchService linkSearchService, + ICategoryService categoryService, + IProductService productService, + // ReSharper disable once UnusedParameter.Local + IItemService itemService, + ICatalogService catalogService, + IAuthorizationService authorizationService, + ListEntryMover categoryMover, + ListEntryMover productMover) + : this(internalListEntrySearchService, listEntrySearchService, linkSearchService, categoryService, productService, catalogService, authorizationService, categoryMover, productMover) + { + } + /// /// Searches for the items by complex criteria. /// @@ -276,8 +307,8 @@ public async Task Delete([FromBody] CatalogListEntrySearchCriteria { var commonIds = idsToDelete.Skip(i).Take(deleteBatchSize).ToList(); - var searchProductResult = await _itemService.GetNoCloneAsync(commonIds, ItemResponseGroup.None.ToString()); - await _itemService.DeleteAsync(searchProductResult.Select(x => x.Id).ToArray()); + var searchProductResult = await _productService.GetNoCloneAsync(commonIds, ItemResponseGroup.None.ToString()); + await _productService.DeleteAsync(searchProductResult.Select(x => x.Id).ToArray()); var searchCategoryResult = await _categoryService.GetNoCloneAsync(commonIds, CategoryResponseGroup.None.ToString()); await _categoryService.DeleteAsync(searchCategoryResult.Select(x => x.Id).ToArray()); @@ -293,7 +324,7 @@ private async Task SaveListCatalogEntitiesAsync(IEntity[] entities) var products = entities.OfType().ToArray(); if (!products.IsNullOrEmpty()) { - await _itemService.SaveChangesAsync(products); + await _productService.SaveChangesAsync(products); } var categories = entities.OfType().ToArray(); @@ -307,7 +338,7 @@ private async Task SaveListCatalogEntitiesAsync(IEntity[] entities) private async Task> LoadCatalogEntriesAsync(string[] ids) { #pragma warning disable CS0618 // Variations can be used here - var products = await _itemService.GetAsync(ids, (ItemResponseGroup.Links | ItemResponseGroup.ItemProperties | ItemResponseGroup.Variations).ToString()); + var products = await _productService.GetAsync(ids, (ItemResponseGroup.Links | ItemResponseGroup.ItemProperties | ItemResponseGroup.Variations).ToString()); #pragma warning restore CS0618 var categories = await _categoryService.GetAsync(ids.Except(products.Select(x => x.Id)).ToList(), (CategoryResponseGroup.WithLinks).ToString()); return products.OfType().Concat(categories.OfType()).ToList(); diff --git a/src/VirtoCommerce.CatalogModule.Web/Controllers/Api/CatalogModuleProductsController.cs b/src/VirtoCommerce.CatalogModule.Web/Controllers/Api/CatalogModuleProductsController.cs index ad7c8f15d..fc8f2189e 100644 --- a/src/VirtoCommerce.CatalogModule.Web/Controllers/Api/CatalogModuleProductsController.cs +++ b/src/VirtoCommerce.CatalogModule.Web/Controllers/Api/CatalogModuleProductsController.cs @@ -21,7 +21,7 @@ namespace VirtoCommerce.CatalogModule.Web.Controllers.Api [Authorize] public class CatalogModuleProductsController : Controller { - private readonly IItemService _itemsService; + private readonly IProductService _productService; private readonly ICatalogService _catalogService; private readonly ICategoryService _categoryService; private readonly ISkuGenerator _skuGenerator; @@ -30,7 +30,7 @@ public class CatalogModuleProductsController : Controller private readonly MvcNewtonsoftJsonOptions _jsonOptions; public CatalogModuleProductsController( - IItemService itemsService, + IProductService productService, ICategoryService categoryService, ICatalogService catalogService, ISkuGenerator skuGenerator, @@ -38,7 +38,7 @@ public CatalogModuleProductsController( IPropertyUpdateManager updateManager, IOptions jsonOptions) { - _itemsService = itemsService; + _productService = productService; _categoryService = categoryService; _catalogService = catalogService; _skuGenerator = skuGenerator; @@ -47,6 +47,34 @@ public CatalogModuleProductsController( _jsonOptions = jsonOptions.Value; } + [Obsolete($"Use the overload that accepts {nameof(IProductService)}")] + public CatalogModuleProductsController( + IItemService itemService, + ICategoryService categoryService, + ICatalogService catalogService, + ISkuGenerator skuGenerator, + IAuthorizationService authorizationService, + IPropertyUpdateManager updateManager, + IOptions jsonOptions) + : this((IProductService)itemService, categoryService, catalogService, skuGenerator, authorizationService, updateManager, jsonOptions) + { + } + + [Obsolete($"This constructor is intended to be used by a DI container only")] + public CatalogModuleProductsController( + IProductService productService, + // ReSharper disable once UnusedParameter.Local + IItemService itemService, + ICategoryService categoryService, + ICatalogService catalogService, + ISkuGenerator skuGenerator, + IAuthorizationService authorizationService, + IPropertyUpdateManager updateManager, + IOptions jsonOptions) + : this(productService, categoryService, catalogService, skuGenerator, authorizationService, updateManager, jsonOptions) + { + } + /// /// Gets product by id. @@ -58,7 +86,7 @@ public CatalogModuleProductsController( public async Task> GetProductById(string id, [FromQuery] string respGroup = null) { - var product = await _itemsService.GetNoCloneAsync(id, respGroup); + var product = await _productService.GetNoCloneAsync(id, respGroup); if (product == null) { return NotFound(); @@ -74,7 +102,7 @@ public async Task> GetProductById(string id, [FromQ [HttpPost("~/api/catalog/{catalogId}/products-by-codes")] public async Task> GetByCodes([FromRoute] string catalogId, [FromBody] List codes, [FromQuery] string responseGroup) { - var idsByCodes = await _itemsService.GetIdsByCodes(catalogId, codes); + var idsByCodes = await _productService.GetIdsByCodes(catalogId, codes); return await GetProductByIds(idsByCodes.Values.ToList(), responseGroup); } @@ -88,7 +116,7 @@ public async Task> GetByCodes([FromRoute] string [Route("")] public async Task> GetProductByIds([FromQuery] List ids, [FromQuery] string respGroup = null) { - var items = await _itemsService.GetNoCloneAsync(ids, respGroup); + var items = await _productService.GetNoCloneAsync(ids, respGroup); if (items == null) { return NotFound(); @@ -98,7 +126,7 @@ public async Task> GetProductByIds([FromQuery] Li { return Forbid(); } - //It is a important to return serialized data by such way. Instead you have a slow response time for large outputs + //It is a important to return serialized data by such way. Instead you have a slow response time for large outputs //https://github.com/dotnet/aspnetcore/issues/19646 var result = JsonConvert.SerializeObject(items, _jsonOptions.SerializerSettings); @@ -106,7 +134,7 @@ public async Task> GetProductByIds([FromQuery] Li } /// - /// Gets products by plenty ids + /// Gets products by plenty ids /// /// Item ids /// Response group. @@ -184,7 +212,7 @@ public async Task> GetNewProductByCatalogAndCategor [Route("{productId}/getnewvariation")] public async Task> GetNewVariation(string productId) { - var product = await _itemsService.GetByIdAsync(productId); + var product = await _productService.GetByIdAsync(productId); if (product == null) { return NotFound(); @@ -215,7 +243,7 @@ public async Task> GetNewVariation(string productId [Route("{productId}/clone")] public async Task> CloneProduct(string productId) { - var product = await _itemsService.GetByIdAsync(productId); + var product = await _productService.GetByIdAsync(productId); if (product == null) { return NotFound(); @@ -223,7 +251,7 @@ public async Task> CloneProduct(string productId) var copyProduct = (CatalogProduct)product.GetCopy(); - // Reset + // Reset copyProduct.Id = null; copyProduct.CreatedDate = DateTime.UtcNow; copyProduct.CreatedBy = null; @@ -259,7 +287,7 @@ public async Task> CloneProduct(string productId) [Route("{productId}/{language}")] public async Task> ProductPartialUpdate(string productId, string language, [FromBody] JObject productPatch) { - var product = await _itemsService.GetByIdAsync(productId); + var product = await _productService.GetByIdAsync(productId); if (product == null) { return NotFound(); @@ -346,13 +374,13 @@ public async Task SaveProducts([FromBody] CatalogProduct[] product [Route("")] public async Task DeleteProduct([FromQuery] List ids) { - var products = await _itemsService.GetNoCloneAsync(ids, ItemResponseGroup.ItemInfo.ToString()); + var products = await _productService.GetNoCloneAsync(ids, ItemResponseGroup.ItemInfo.ToString()); var authorizationResult = await _authorizationService.AuthorizeAsync(User, products, new CatalogAuthorizationRequirement(ModuleConstants.Security.Permissions.Delete)); if (!authorizationResult.Succeeded) { return Forbid(); } - await _itemsService.DeleteAsync(ids); + await _productService.DeleteAsync(ids); return Ok(); } @@ -381,7 +409,7 @@ private async Task InnerSaveProducts(CatalogProduct[] products if (!toSaveList.IsNullOrEmpty()) { - await _itemsService.SaveChangesAsync(toSaveList.ToArray()); + await _productService.SaveChangesAsync(toSaveList.ToArray()); } return toSaveList.ToArray(); diff --git a/src/VirtoCommerce.CatalogModule.Web/Module.cs b/src/VirtoCommerce.CatalogModule.Web/Module.cs index bb518e017..630ade857 100644 --- a/src/VirtoCommerce.CatalogModule.Web/Module.cs +++ b/src/VirtoCommerce.CatalogModule.Web/Module.cs @@ -118,7 +118,16 @@ public void Initialize(IServiceCollection serviceCollection) serviceCollection.AddTransient(); serviceCollection.AddTransient(); - serviceCollection.AddTransient(); +#pragma warning disable CS0618 // Type or member is obsolete + serviceCollection.AddTransient(); + serviceCollection.AddTransient(ResolveItemService); + serviceCollection.AddTransient(provider => provider.GetRequiredService()); +#pragma warning restore CS0618 // Type or member is obsolete + + serviceCollection.AddSingleton(); + serviceCollection.AddTransient(provider => + provider.GetRequiredService().Resolve(provider)); + serviceCollection.AddTransient(); serviceCollection.AddSingleton(); serviceCollection.AddTransient(); @@ -261,6 +270,69 @@ public void Initialize(IServiceCollection serviceCollection) serviceCollection.AddTransient(); } + [ThreadStatic] private static bool _resolvingItemService; // Guards against stack overflow when resolving IItemService or IProductService + +#pragma warning disable CS0618 // Type or member is obsolete + private static IItemService ResolveItemService(IServiceProvider provider) + { + // The idea here is to try to resolve IItemService by resolving IProductService first and casting or + // adapting it to IItemService. This way, if there's a custom registration for IProductService somewhere, + // it will be used. Conversely, the standard registration for IProductService tries to resolve this + // registration first, for same reasons (there might be custom IItemService registered somewhere). + // If neither IItemService nor IProductService is overridden, this would cause a circular dependency and + // a stack overflow - that's why we need _resolvingItemService to guard the stack and cut the loop at + // first detected reentrancy. As a result, without custom IItemService or IProductService, ItemService + // is resolved by default. + + if (_resolvingItemService) // Reentrant? Fall back to the default implementation: + { + return provider.GetRequiredService(); + } + + _resolvingItemService = true; + try + { + var productService = provider.GetRequiredService(); + + if (productService is IItemService itemService) // First see, if we can get away with a simple cast + { + return itemService; + } + + // Adapt the resolved IProductService to IItemService: + return new ProductServiceAdapter(productService); + } + finally + { + _resolvingItemService = false; // Clean up + } + } +#pragma warning restore CS0618 // Type or member is obsolete + + private sealed class ProductServiceResolver + { + private volatile bool _tryResolveToLegacyItemService = true; + + public IProductService Resolve(IServiceProvider serviceProvider) + { + if (_tryResolveToLegacyItemService) + { +#pragma warning disable CS0618 // Type or member is obsolete + var itemService = serviceProvider.GetService(); + + if (itemService is not null and not ProductServiceAdapter) + { + return itemService; + } +#pragma warning restore CS0618 // Type or member is obsolete + } + + _tryResolveToLegacyItemService = false; + + return serviceProvider.GetRequiredService(); + } + } + public void PostInitialize(IApplicationBuilder appBuilder) { _appBuilder = appBuilder; diff --git a/tests/VirtoCommerce.CatalogModule.Tests/BulkPropertyUpdateManagerTests.cs b/tests/VirtoCommerce.CatalogModule.Tests/BulkPropertyUpdateManagerTests.cs index 6e4c37370..46f9a7a73 100644 --- a/tests/VirtoCommerce.CatalogModule.Tests/BulkPropertyUpdateManagerTests.cs +++ b/tests/VirtoCommerce.CatalogModule.Tests/BulkPropertyUpdateManagerTests.cs @@ -33,13 +33,13 @@ public async Task GetProperties_DataSource_InvokeFetch() } [Fact] - public async Task GetProperties_ItemService_InvokeGetAsync() + public async Task GetProperties_ProductService_InvokeGetAsync() { // arrange var context = new PropertiesUpdateBulkActionContext(); var dataSourceFactory = new Mock(); - var itemService = new Mock(); - var manager = BuildManager(dataSourceFactory, itemService); + var productService = new Mock(); + var manager = BuildManager(dataSourceFactory, productService); var dataSource = new Mock(); var productId = "fakeProductId"; var group = ItemResponseGroup.ItemInfo | ItemResponseGroup.ItemProperties; @@ -52,13 +52,13 @@ public async Task GetProperties_ItemService_InvokeGetAsync() dataSourceFactory.Setup(t => t.Create(context)).Returns(dataSource.Object); dataSource.SetupSequence(t => t.FetchAsync()).ReturnsAsync(true).ReturnsAsync(false); dataSource.Setup(t => t.Items).Returns(products); - itemService.Setup(t => t.GetAsync(productIds, group.ToString(), It.IsAny())).ReturnsAsync(products.ToArray()); + productService.Setup(t => t.GetAsync(productIds, group.ToString(), It.IsAny())).ReturnsAsync(products.ToArray()); // act await manager.GetPropertiesAsync(context); // assert - itemService.Verify(t => t.GetAsync(productIds, group.ToString(), It.IsAny())); + productService.Verify(t => t.GetAsync(productIds, group.ToString(), It.IsAny())); } [Theory] @@ -82,36 +82,36 @@ public async Task GetProperties_Should_HaveCountGreaterThan(int count) private IBulkPropertyUpdateManager BuildManager() { var dataSourceFactory = new Mock(); - var itemService = new Mock(); + var productService = new Mock(); var categoryService = new Mock(); var catalogService = new Mock(); var dictService = new Mock(); var singleProductUpdateManager = new PropertyUpdateManager(dictService.Object); - var manager = new BulkPropertyUpdateManager(dataSourceFactory.Object, itemService.Object, categoryService.Object, catalogService.Object, singleProductUpdateManager); + var manager = new BulkPropertyUpdateManager(dataSourceFactory.Object, productService.Object, categoryService.Object, catalogService.Object, singleProductUpdateManager); return manager; } private IBulkPropertyUpdateManager BuildManager(IMock dataSourceFactory) { - var itemService = new Mock(); + var productService = new Mock(); var categoryService = new Mock(); var catalogService = new Mock(); var dictService = new Mock(); var singleProductUpdateManager = new PropertyUpdateManager(dictService.Object); - var manager = new BulkPropertyUpdateManager(dataSourceFactory.Object, itemService.Object, categoryService.Object, catalogService.Object, singleProductUpdateManager); + var manager = new BulkPropertyUpdateManager(dataSourceFactory.Object, productService.Object, categoryService.Object, catalogService.Object, singleProductUpdateManager); return manager; } - private IBulkPropertyUpdateManager BuildManager(IMock dataSourceFactory, IMock itemService) + private IBulkPropertyUpdateManager BuildManager(IMock dataSourceFactory, IMock productService) { var categoryService = new Mock(); var catalogService = new Mock(); var dictService = new Mock(); var singleProductUpdateManager = new PropertyUpdateManager(dictService.Object); - var manager = new BulkPropertyUpdateManager(dataSourceFactory.Object, itemService.Object, categoryService.Object, catalogService.Object, singleProductUpdateManager); + var manager = new BulkPropertyUpdateManager(dataSourceFactory.Object, productService.Object, categoryService.Object, catalogService.Object, singleProductUpdateManager); return manager; } } diff --git a/tests/VirtoCommerce.CatalogModule.Tests/CatalogPropertyTest.cs b/tests/VirtoCommerce.CatalogModule.Tests/CatalogPropertyTest.cs index 1f2257b3f..0e1e7ac33 100644 --- a/tests/VirtoCommerce.CatalogModule.Tests/CatalogPropertyTest.cs +++ b/tests/VirtoCommerce.CatalogModule.Tests/CatalogPropertyTest.cs @@ -43,9 +43,9 @@ // newProperty.DictionaryValues.Add(colorDictValue); // newProperty = propertyService.Create(newProperty); -// var productService = GetItemService(); +// var productService = GetProductService(); // var product = productService.GetById("93beb4e92dba4a08a173aa0a0cf0cffb", ItemResponseGroup.ItemInfo); -// //Set value +// //Set value // product.PropertyValues.Add(new PropertyValue { PropertyId = newProperty.Id, PropertyName = newProperty.Name, ValueId = colorDictValue.Id, Value = colorDictValue.Value, ValueType = newProperty.ValueType }); // productService.Update(new[] { product }); @@ -57,10 +57,10 @@ // return new CatalogServiceImpl(GetCatalogRepository, new Mock>().Object, new Mock>().Object, GetEventPublisher().Object); // } -// private static IItemService GetItemService() +// private static IProductService GetProductService() // { -// return new ItemServiceImpl( +// return new ProductServiceImpl( // GetCatalogRepository, // GetCommerceService(), // new Mock().Object, diff --git a/tests/VirtoCommerce.CatalogModule.Tests/ClassCtorTests.cs b/tests/VirtoCommerce.CatalogModule.Tests/ClassCtorTests.cs index e98387363..628bf629a 100644 --- a/tests/VirtoCommerce.CatalogModule.Tests/ClassCtorTests.cs +++ b/tests/VirtoCommerce.CatalogModule.Tests/ClassCtorTests.cs @@ -5,6 +5,7 @@ using VirtoCommerce.CatalogModule.BulkActions.Actions.PropertiesUpdate; using VirtoCommerce.CatalogModule.BulkActions.DataSources; using VirtoCommerce.CatalogModule.Core.Search; +using VirtoCommerce.CatalogModule.Core.Services; using Xunit; namespace VirtoCommerce.CatalogModule.Tests @@ -20,7 +21,7 @@ public void PropertiesUpdateBulkAction_NullArgs_ThrowArgumentException() var action = new Action( () => { - new PropertiesUpdateBulkAction(null, null, null); + new PropertiesUpdateBulkAction(null, (IProductService)null, null); }); // assert diff --git a/tests/VirtoCommerce.CatalogModule.Tests/ProductAssociationSearchServiceTests.cs b/tests/VirtoCommerce.CatalogModule.Tests/ProductAssociationSearchServiceTests.cs index 7c4519277..38f0c63fd 100644 --- a/tests/VirtoCommerce.CatalogModule.Tests/ProductAssociationSearchServiceTests.cs +++ b/tests/VirtoCommerce.CatalogModule.Tests/ProductAssociationSearchServiceTests.cs @@ -24,10 +24,10 @@ public class ProductAssociationSearchServiceTests // catalogRepository.Setup(x => x.Items).Returns(TestItemEntities.AsQueryable()); // catalogRepository.Setup(x => x.GetAllChildrenCategoriesIds(It.Is(ids => ids.SequenceEqual(new[] { "cat-1" })))).Returns(TestChildCategories); - // var itemService = new Mock(); - // itemService.Setup(x => x.GetByIds(It.Is(ids => ids.SequenceEqual(verifySet.Select(p => p.Id))), It.IsIn(domainModel.ItemResponseGroup.ItemInfo), It.IsAny())) + // var productService = new Mock(); + // productService.Setup(x => x.GetByIds(It.Is(ids => ids.SequenceEqual(verifySet.Select(p => p.Id))), It.IsIn(domainModel.ItemResponseGroup.ItemInfo), It.IsAny())) // .Returns(verifySet); - // var sut = new ProductAssociationSearchService(() => catalogRepository.Object, itemService.Object); + // var sut = new ProductAssociationSearchService(() => catalogRepository.Object, productService.Object); // // Act // var result = sut.SearchProductAssociations(criteria); // // Assert @@ -43,8 +43,8 @@ public class ProductAssociationSearchServiceTests // ObjectIds = new string[] { }.ToList() // }; // var catalogRepository = new Mock(); - // var itemService = new Mock(); - // var sut = new ProductAssociationSearchService(() => catalogRepository.Object, itemService.Object); + // var productService = new Mock(); + // var sut = new ProductAssociationSearchService(() => catalogRepository.Object, productService.Object); // Assert.Throws(() => sut.SearchProductAssociations(criteria)); //} diff --git a/tests/VirtoCommerce.CatalogModule.Tests/ItemServiceUnitTests.cs b/tests/VirtoCommerce.CatalogModule.Tests/ProductServiceUnitTests.cs similarity index 88% rename from tests/VirtoCommerce.CatalogModule.Tests/ItemServiceUnitTests.cs rename to tests/VirtoCommerce.CatalogModule.Tests/ProductServiceUnitTests.cs index 2b4cd8b9c..44474882d 100644 --- a/tests/VirtoCommerce.CatalogModule.Tests/ItemServiceUnitTests.cs +++ b/tests/VirtoCommerce.CatalogModule.Tests/ProductServiceUnitTests.cs @@ -1,118 +1,118 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using FluentValidation; -using FluentValidation.Results; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Moq; -using VirtoCommerce.AssetsModule.Core.Assets; -using VirtoCommerce.CatalogModule.Core.Model; -using VirtoCommerce.CatalogModule.Core.Services; -using VirtoCommerce.CatalogModule.Data.Model; -using VirtoCommerce.CatalogModule.Data.Repositories; -using VirtoCommerce.CatalogModule.Data.Services; -using VirtoCommerce.CatalogModule.Data.Validation; -using VirtoCommerce.Platform.Caching; -using VirtoCommerce.Platform.Core.Caching; -using VirtoCommerce.Platform.Core.Common; -using VirtoCommerce.Platform.Core.Domain; -using VirtoCommerce.Platform.Core.Events; -using Xunit; - -namespace VirtoCommerce.CatalogModule.Tests -{ - public class ItemServiceUnitTests - { - private readonly Mock _unitOfWorkMock; - private readonly Mock _repositoryMock; - private readonly Mock _eventPublisherMock; - private readonly Mock> _hasPropertyValidatorMock; - private readonly Mock _catalogServiceMock; - private readonly Mock _categoryServiceMock; - private readonly Mock _outlineServiceMock; - private readonly Mock _blobUrlResolverMock; - private readonly Mock _skuGeneratorMock; - - public ItemServiceUnitTests() - { - _unitOfWorkMock = new Mock(); - _repositoryMock = new Mock(); - _eventPublisherMock = new Mock(); - _hasPropertyValidatorMock = new Mock>(); - _catalogServiceMock = new Mock(); - _categoryServiceMock = new Mock(); - _outlineServiceMock = new Mock(); - _blobUrlResolverMock = new Mock(); - _skuGeneratorMock = new Mock(); - } - - [Fact] - public async Task GetByIdsAsync_GetThenSaveItem_ReturnCachedItem() - { - //Arrange - var id = Guid.NewGuid().ToString(); - var newItem = new CatalogProduct - { - Id = id, - CatalogId = Guid.NewGuid().ToString(), - CategoryId = Guid.NewGuid().ToString(), - Name = "some product", - Code = "some code" - }; - var newItemEntity = AbstractTypeFactory.TryCreateInstance().FromModel(newItem, new PrimaryKeyResolvingMap()); - var service = GetItemServiceWithPlatformMemoryCache(); - _repositoryMock.Setup(x => x.Add(newItemEntity)) - .Callback(() => - { - _repositoryMock.Setup(o => o.GetItemByIdsAsync(new[] { id }, null)) - .ReturnsAsync(new[] { newItemEntity }); - }); - - _catalogServiceMock - .Setup(x => x.GetAsync(It.IsAny>(), It.IsAny(), false)) - .ReturnsAsync(new[] { new Catalog { Id = newItem.CatalogId } }); - - _categoryServiceMock - .Setup(x => x.GetAsync(It.IsAny>(), It.IsAny(), false)) - .ReturnsAsync(new[] { new Category { Id = newItem.CategoryId } }); - - //Act - var nullItem = await service.GetByIdAsync(id); - await service.SaveChangesAsync(new[] { newItem }); - var item = await service.GetByIdAsync(id); - - //Assert - Assert.NotEqual(nullItem, item); - } - - private ItemService GetItemServiceWithPlatformMemoryCache() - { - var memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); - var platformMemoryCache = new PlatformMemoryCache(memoryCache, Options.Create(new CachingOptions()), new Mock>().Object); - - _repositoryMock.Setup(ss => ss.UnitOfWork).Returns(_unitOfWorkMock.Object); - - return GetItemService(platformMemoryCache, _repositoryMock.Object); - } - - private ItemService GetItemService(IPlatformMemoryCache platformMemoryCache, ICatalogRepository catalogRepository) - { - _hasPropertyValidatorMock - .Setup(x => x.ValidateAsync(It.IsAny>(), default)) - .ReturnsAsync(new ValidationResult()); - - return new ItemService(() => catalogRepository, - platformMemoryCache, - _eventPublisherMock.Object, - _hasPropertyValidatorMock.Object, - _catalogServiceMock.Object, - _categoryServiceMock.Object, - _outlineServiceMock.Object, - _blobUrlResolverMock.Object, - _skuGeneratorMock.Object, - new ProductValidator(new PropertyValidator())); - } - } -} +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentValidation; +using FluentValidation.Results; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using VirtoCommerce.AssetsModule.Core.Assets; +using VirtoCommerce.CatalogModule.Core.Model; +using VirtoCommerce.CatalogModule.Core.Services; +using VirtoCommerce.CatalogModule.Data.Model; +using VirtoCommerce.CatalogModule.Data.Repositories; +using VirtoCommerce.CatalogModule.Data.Services; +using VirtoCommerce.CatalogModule.Data.Validation; +using VirtoCommerce.Platform.Caching; +using VirtoCommerce.Platform.Core.Caching; +using VirtoCommerce.Platform.Core.Common; +using VirtoCommerce.Platform.Core.Domain; +using VirtoCommerce.Platform.Core.Events; +using Xunit; + +namespace VirtoCommerce.CatalogModule.Tests +{ + public class ProductServiceUnitTests + { + private readonly Mock _unitOfWorkMock; + private readonly Mock _repositoryMock; + private readonly Mock _eventPublisherMock; + private readonly Mock> _hasPropertyValidatorMock; + private readonly Mock _catalogServiceMock; + private readonly Mock _categoryServiceMock; + private readonly Mock _outlineServiceMock; + private readonly Mock _blobUrlResolverMock; + private readonly Mock _skuGeneratorMock; + + public ProductServiceUnitTests() + { + _unitOfWorkMock = new Mock(); + _repositoryMock = new Mock(); + _eventPublisherMock = new Mock(); + _hasPropertyValidatorMock = new Mock>(); + _catalogServiceMock = new Mock(); + _categoryServiceMock = new Mock(); + _outlineServiceMock = new Mock(); + _blobUrlResolverMock = new Mock(); + _skuGeneratorMock = new Mock(); + } + + [Fact] + public async Task GetByIdsAsync_GetThenSaveItem_ReturnCachedItem() + { + //Arrange + var id = Guid.NewGuid().ToString(); + var newItem = new CatalogProduct + { + Id = id, + CatalogId = Guid.NewGuid().ToString(), + CategoryId = Guid.NewGuid().ToString(), + Name = "some product", + Code = "some code" + }; + var newItemEntity = AbstractTypeFactory.TryCreateInstance().FromModel(newItem, new PrimaryKeyResolvingMap()); + var service = GetProductServiceWithPlatformMemoryCache(); + _repositoryMock.Setup(x => x.Add(newItemEntity)) + .Callback(() => + { + _repositoryMock.Setup(o => o.GetItemByIdsAsync(new[] { id }, null)) + .ReturnsAsync(new[] { newItemEntity }); + }); + + _catalogServiceMock + .Setup(x => x.GetAsync(It.IsAny>(), It.IsAny(), false)) + .ReturnsAsync(new[] { new Catalog { Id = newItem.CatalogId } }); + + _categoryServiceMock + .Setup(x => x.GetAsync(It.IsAny>(), It.IsAny(), false)) + .ReturnsAsync(new[] { new Category { Id = newItem.CategoryId } }); + + //Act + var nullItem = await service.GetByIdAsync(id); + await service.SaveChangesAsync(new[] { newItem }); + var item = await service.GetByIdAsync(id); + + //Assert + Assert.NotEqual(nullItem, item); + } + + private ProductService GetProductServiceWithPlatformMemoryCache() + { + var memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); + var platformMemoryCache = new PlatformMemoryCache(memoryCache, Options.Create(new CachingOptions()), new Mock>().Object); + + _repositoryMock.Setup(ss => ss.UnitOfWork).Returns(_unitOfWorkMock.Object); + + return GetProductService(platformMemoryCache, _repositoryMock.Object); + } + + private ProductService GetProductService(IPlatformMemoryCache platformMemoryCache, ICatalogRepository catalogRepository) + { + _hasPropertyValidatorMock + .Setup(x => x.ValidateAsync(It.IsAny>(), default)) + .ReturnsAsync(new ValidationResult()); + + return new ProductService(() => catalogRepository, + platformMemoryCache, + _eventPublisherMock.Object, + _hasPropertyValidatorMock.Object, + _catalogServiceMock.Object, + _categoryServiceMock.Object, + _outlineServiceMock.Object, + _blobUrlResolverMock.Object, + _skuGeneratorMock.Object, + new ProductValidator(new PropertyValidator())); + } + } +} diff --git a/tests/VirtoCommerce.CatalogModule.Tests/PropertiesUpdateBulkActionTests.cs b/tests/VirtoCommerce.CatalogModule.Tests/PropertiesUpdateBulkActionTests.cs index b62f16672..8c5421777 100644 --- a/tests/VirtoCommerce.CatalogModule.Tests/PropertiesUpdateBulkActionTests.cs +++ b/tests/VirtoCommerce.CatalogModule.Tests/PropertiesUpdateBulkActionTests.cs @@ -18,11 +18,11 @@ namespace VirtoCommerce.CatalogModule.Tests { public class PropertiesUpdateBulkActionTests { - private readonly Mock _itemServiceMock; + private readonly Mock _productServiceMock; public PropertiesUpdateBulkActionTests() { - _itemServiceMock = new Mock(); + _productServiceMock = new Mock(); } [Fact] @@ -56,19 +56,19 @@ public async Task Execute_BulkPropertyUpdateManager_InvokeUpdateProperties() } [Fact] - public async Task Execute_ItemService_InvokeGetByIds() + public async Task Execute_ProductService_InvokeGetByIds() { // arrange var context = new PropertiesUpdateBulkActionContext(); - var itemService = new Mock { DefaultValueProvider = DefaultValueProvider.Mock }; + var productService = new Mock { DefaultValueProvider = DefaultValueProvider.Mock }; context.Properties = new Property[] { }; - var bulkAction = BuildBulkAction(context, itemService); + var bulkAction = BuildBulkAction(context, productService); // act await bulkAction.ExecuteAsync(Enumerable.Empty()); // assert - itemService.Verify( + productService.Verify( t => t.GetAsync( It.IsAny>(), (ItemResponseGroup.ItemInfo | ItemResponseGroup.ItemProperties).ToString(), @@ -172,26 +172,26 @@ private IBulkAction BuildBulkAction( PropertiesUpdateBulkActionContext context, IMock manager) { - return BuildBulkAction(context, _itemServiceMock, manager); + return BuildBulkAction(context, _productServiceMock, manager); } private IBulkAction BuildBulkAction( PropertiesUpdateBulkActionContext context, - IMock itemServiceMock) + IMock productServiceMock) { var manager = new Mock(); manager.Setup(t => t.GetPropertiesAsync(It.IsAny())) .ReturnsAsync(new List().ToArray()); - return BuildBulkAction(context, itemServiceMock, manager); + return BuildBulkAction(context, productServiceMock, manager); } private IBulkAction BuildBulkAction( PropertiesUpdateBulkActionContext context, - IMock itemServiceMock, + IMock productServiceMock, IMock manager) { - return new PropertiesUpdateBulkAction(context, itemServiceMock.Object, manager.Object); + return new PropertiesUpdateBulkAction(context, productServiceMock.Object, manager.Object); } } } diff --git a/tests/VirtoCommerce.CatalogModule.Tests/PropertyDictionaryItemTest.cs b/tests/VirtoCommerce.CatalogModule.Tests/PropertyDictionaryItemTest.cs index eb2165803..291c03ab2 100644 --- a/tests/VirtoCommerce.CatalogModule.Tests/PropertyDictionaryItemTest.cs +++ b/tests/VirtoCommerce.CatalogModule.Tests/PropertyDictionaryItemTest.cs @@ -19,7 +19,7 @@ public async Task Add_New_DictionaryItems_To_Property_And_Then_Choose_DictItem_F { var propDictionaryService = new Mock().Object; var propDictionarySearchService = new Mock(); - var productService = new Mock(); + var productService = new Mock(); var colorProperty = new Property {