Skip to content

Split ProductViewModelService into one service per product tab - #975

Merged
KrzysztofPajak merged 30 commits into
developfrom
refactor/split-product-view-model-service
Oct 22, 2026
Merged

KrzysztofPajak merged 30 commits into
developfrom
refactor/split-product-view-model-service

Conversation

@KrzysztofPajak

@KrzysztofPajak KrzysztofPajak commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Part of the architecture backlog (item S5: god classes, ProductViewModelService)
Type: feature

Stacked on #974. Its base is refactor/split-base-product-controller, so the diff shows only this split; it is retargeted to develop once #974 is merged.

Issue

Grand.Web.AdminShared/Services/ProductViewModelService.cs was 2712 lines behind an 87-member IProductViewModelService, with 39 constructor dependencies. After #974 split the product edit tabs into their own controllers, each of those controllers still depended on the whole service and on everything it injects.

Solution

  • One IProduct<Tab>ViewModelService / Product<Tab>ViewModelService per tab controller, registered as scoped next to IProductViewModelService, the same lifetime as before. Each takes only the services its members use:

    Service Members Dependencies
    ProductSpecification 4 2
    ProductPicture 5 2
    ProductCategory 4 3
    ProductCollection 4 3
    ProductBid 1 5
    ProductPrice 2 (PrepareTierPriceModel overloads) 6
    ProductRelated 10 (related, similar, cross-sell, recommended) 2
    ProductBundle 3 2
    ProductAssociated 2 1
    ProductAttributeMapping 16 (mappings, validation rules, conditions, values) 9
    ProductAttributeCombination 11 (incl. the combination overload of OutOfStockNotifications) 12
    ProductAddPopup 9: the "add product" popup family (PrepareAddRequiredProductModel, Prepare{Related,Similar,Bundle,CrossSell,Recommended,Associated}ProductModel, PrepareAssociateProductToAttributeValueModel) and its product search PrepareProductModel(AddProductModel, ...) 7
  • Members moved unchanged, with their comments, except for the combination stock fix below.

  • ProductViewModelService keeps the product form (prepare, insert, update, delete, warehouse inventory, tags, picture SEO names, out-of-stock notification for the product), the list and search, reviews and bulk edit.

  • The tab controllers take their new service and no longer depend on IProductViewModelService; the popup actions take IProductAddPopupViewModelService.

  • Attribute combinations (ProductAttributeCombinationViewModelService): the product's stock total is now computed on the product already loaded, so it includes a combination added in the same request (it did not before); a multi-warehouse update now sums the reserved quantity as the insert already did; saving a combination that no longer exists returns a warning instead of throwing.

  • Tests: the controller tests set up the new mocks for the moved members, and nothing else in them changed. The service tests of moved members are in ProductPriceViewModelServiceTests, ProductRelatedViewModelServiceTests, ProductBundleViewModelServiceTests and ProductAddPopupViewModelServiceTests, each with a fixture that builds only that service; ProductAttributeCombinationViewModelServiceTests covers the stock fix. ProductViewModelServiceTests lost the arguments the trimmed constructor no longer takes.

Breaking changes

  • 71 members moved from IProductViewModelService to the new interfaces. Code outside this repository that calls one of them on IProductViewModelService must inject the matching IProduct<Tab>ViewModelService or IProductAddPopupViewModelService.
  • A class that implements IProductViewModelService, or subclasses ProductViewModelService and overrides a moved virtual, must move that code to the new service. The ProductViewModelService constructor lost the parameters only the moved members used.
  • The tab controllers from Split the product edit tabs out of BaseProductController #974 take the new services in their constructors.
  • Behavior: with "Track inventory by product attributes", adding an attribute combination now updates the product's stock to include it, and editing a multi-warehouse combination now updates its reserved quantity.
  • No route, view, permission, setting or localization key changes.

Testing

  1. dotnet build GrandNode.slnx: no errors.
  2. dotnet test for Grand.Web.Admin.Tests (1596), Grand.Web.Store.Tests (181) and Grand.Web.Vendor.Tests (53) pass. No assertion was removed or weakened.
  3. Checked on a fresh install with sample data, as admin, as a store manager and as a vendor, which also exercises the DI registrations. Every product edit tab loads and every grid and popup listed in Split the product edit tabs out of BaseProductController #974 returns 200 with no console error.
  4. "Add product" popups: required (list and friendly names), related, similar, cross-sell, recommended, bundle, associated and associate-product-to-attribute-value open, search and save in Admin. The Store search lists the store's products and silently skips a selected product the store does not own. The Vendor search lists only the vendor's products.
  5. Combination stock on a product set to "Track inventory by product attributes":
    • add combinations 12/2 and 8/1: the product shows 20/3;
    • edit the first to 15: 23/3;
    • "Generate all possible combinations": one combination added, still 23/3;
    • on a multi-warehouse product, add a combination with EU 10/3 and US 5/1: 15/4;
    • change the EU reserved quantity to 5: the combination and the product show reserved 6.

support added 28 commits October 21, 2026 22:25
The stock total now includes a newly added combination, a multi-warehouse update sums reserved quantity as the insert does, and saving a combination that no longer exists returns a warning.
The product tab controllers no longer depend on the product view-model service. Also corrects the scope comments left behind by the split.
InsertAsync stamps CreatedOnUtc with the current time, so the dates the test gave were dropped and posts inserted in the same millisecond came back in any order.
Base automatically changed from refactor/split-base-product-controller to develop October 22, 2026 17:38
Copilot AI balanced review requested due to automatic review settings October 22, 2026 17:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread src/Web/Grand.Web.AdminShared/Services/ProductAttributeMappingViewModelService.cs Dismissed
Comment thread src/Web/Grand.Web.AdminShared/Services/ProductPriceViewModelService.cs Dismissed
Comment thread src/Web/Grand.Web.AdminShared/Services/ProductAttributeCombinationViewModelService.cs Dismissed
Comment thread src/Web/Grand.Web.AdminShared/Services/ProductAttributeCombinationViewModelService.cs Dismissed
Comment thread src/Web/Grand.Web.AdminShared/Services/ProductBundleViewModelService.cs Dismissed
Comment thread src/Web/Grand.Web.AdminShared/Services/ProductRelatedViewModelService.cs Dismissed
Comment thread src/Web/Grand.Web.AdminShared/Services/ProductRelatedViewModelService.cs Dismissed
Comment thread src/Web/Grand.Web.AdminShared/Services/ProductRelatedViewModelService.cs Dismissed
Comment thread src/Web/Grand.Web.AdminShared/Services/ProductRelatedViewModelService.cs Dismissed
Comment thread src/Web/Grand.Web.AdminShared/Services/ProductAttributeCombinationViewModelService.cs Dismissed
@KrzysztofPajak
KrzysztofPajak merged commit 6adf9c2 into develop Oct 22, 2026
8 of 9 checks passed
@KrzysztofPajak
KrzysztofPajak deleted the refactor/split-product-view-model-service branch October 22, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants