Repository navigation
Pass an explicit context to the product attribute formatter - #936
Merged
KrzysztofPajak merged 2 commits intoOct 17, 2026
Merged
KrzysztofPajak merged 2 commits into
KrzysztofPajak merged 2 commits into
Conversation
ProductAttributeFormatter no longer reads IContextAccessor. FormatAttributes takes the CalculationContext the prices are rendered for, the language of the attribute names and the store host used for file download links. Callers pass the values they already hold: the storefront cart handlers their request (Host added to GetAddToCart, GetMiniShoppingCart and GetWishlist), the admin panel the request context with the customer whose cart is shown, PlaceOrder its command, and the message tokens the message's language and host.
- Shopping cart tokens: reset the attribute description and picture for each item and skip items whose product was deleted. - Customer out-of-stock subscriptions: fix the inverted attributes check. - PlaceOrder: render order item attribute descriptions in the working language again (PlaceOrderContainer.WorkingLanguage). - Shared wishlist: price attribute adjustments, unit price and subtotal for the viewing customer (GetWishlist.CurrentCustomer), as before. - Admin add product to order: format the stored description for the order's store, currency, language, tax display and primary host. - Admin cart and wishlist grids: price attribute adjustments for the cart item's store. - CampaignService: take currency, tax display and host from the caller instead of the ambient context. - GetMiniWishlist carries Host and TaxDisplayType; its handler no longer reads the ambient context. - ProductAttributeFormatter: require a context when rendering prices and render no download link without a host. - Cart requests share ICartCalculationRequest and one CalculationContext() extension instead of three copies.
KrzysztofPajak
deleted the
refactor/business-explicit-context-attribute-formatter
branch
October 17, 2026 13:07
This was referenced Oct 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Type: feature
Issue
ProductAttributeFormatter(Grand.Business.Catalog) read the customer, store, working currency, tax display, working language and request host fromWorkContext/StoreContext. The attribute descriptions it builds (cart, wishlist, order items, out-of-stock subscriptions, message tokens) therefore depended on who called it. Outside a request, for example a campaign or a stock notification sent from a scheduled task, it read whatever context happened to be there. Follows #931 (CalculationContext) and #932 (the same change forCheckoutAttributeFormatter).Solution
ProductAttributeFormatterno longer takesIContextAccessor. It now has the same shape asICheckoutAttributeFormattersince #932:IProductAttributeFormatter.FormatAttributes(product, attributes, CalculationContext context, Language language, DomainHost host, separator, …)contextis required whenrenderPricesis true (ArgumentNullExceptionotherwise), so the attribute-combination tokens (prices off) passnull.hostis read only for file-upload links; without a host the link is not rendered.FormatAttributes(product, attributes)is gone.GetAddToCart,GetShoppingCart,GetMiniShoppingCart,GetWishlist,GetMiniWishlist) implement the newICartCalculationRequest, and onerequest.CalculationContext()extension builds the context for all of them.Hostis added toGetAddToCart,GetMiniShoppingCart,GetWishlistandGetMiniWishlist(which also gainsTaxDisplayType);GetWishlistHandlerandGetMiniWishlistHandlerno longer takeIContextAccessor.GetWishlistgainsCurrentCustomer, the visitor. Attribute price adjustments, unit price, subtotal and discount are calculated for the visitor, as before;Customer(the owner) is still used for the name and the warnings.with { Customer = customer, Store = priceStore }), so the attribute adjustments match the unit price next to them.PlaceOrderCommandHandlerformats order items and checkout attributes with the order's context, the working language of the request (PlaceOrderContainer.WorkingLanguage, as before) and its host (PlaceOrderContainer.Host).GetShoppingCartTokensCommandcarriesContextandHost(in place ofCustomerandStore),GetAttributeCombinationTokensCommandcarriesLanguageandHost, andLiquidObjectBuilder.AddShoppingCartTokens/AddAttributeCombinationTokenstake them.CampaignServiceno longer takesIContextAccessor: bothSendCampaignoverloads take the currency, tax display and host, andCampaignControllerpasses the admin request's values, as before.MessageProviderServicepasses the values it already holds; its own ambient reads are left for the Marketing/Messages PR.Fixed on the way (found in review of this PR):
{{ShoppingCart}}tokens: an item without attributes repeated the previous item's attribute description and picture, and an item whose product was deleted failed the whole message. Each item now starts empty, and deleted products are skipped.Attributes == nullthrew.Deliberate behavior changes (off the normal storefront path, where the values are the same):
{{ShoppingCart}}and{{AttributeCombination}}, attribute names now use the message's language instead of the language of whoever triggered the message. Example: a campaign sent from an English admin panel to a Polish customer.LanguageIdfield when there was no working language is gone, because every caller now passes a language.Breaking changes
IProductAttributeFormatter:FormatAttributes(product, attributes)is removed, andFormatAttributes(product, attributes, Customer customer, …)becameFormatAttributes(product, attributes, CalculationContext, Language, DomainHost, …).ProductAttributeFormatterconstructor no longer takesIContextAccessor.ICampaignService.SendCampaign(campaign, emailAccount, subscriptions | email)gainsCurrency currency, TaxDisplayType taxDisplayType, DomainHost host;CampaignServiceconstructor no longer takesIContextAccessor.GetShoppingCartTokensCommand:CustomerandStorereplaced byContextandHost.GetAttributeCombinationTokensCommandgainsLanguageandHost.LiquidObjectBuilder.AddShoppingCartTokens(CalculationContext, DomainHost, Language)andAddAttributeCombinationTokens(product, combination, Language, DomainHost).OrderViewModelServiceconstructor gainsILanguageService.GetWishlistgainsCurrentCustomer;GetMiniWishlistgainsHostandTaxDisplayType. Senders must fill them.Plugins that call or implement these must be rebuilt.
Testing
dotnet build GrandNode.slnx: succeeds.dotnet testfor Catalog, Checkout, Messages, Marketing, Web, Web.Admin, Web.Common: all green.ProductAttributeFormatterTests: names in the given language, price adjustment priced and formatted for the given context, no context needed without prices, a context required with prices, file link to the given host, no link without a hostGetShoppingCartTokensCommandHandlerTests: forwards the message's context, language and host; an item without attributes does not repeat the previous item's description; a deleted product is skippedIntel Core i7 [+€142.50],32 GB [+€85.50]in EnglishIntel Core i9 [+€361.00]Customer/GetCartList,ShoppingCart/GetCartDetails,GetWishlistDetails) show the descriptions with adjustmentsnullattributes show nothing and do not fail{% for item in ShoppingCart.Items %}body: a deleted product is skipped and an item without attributes has an empty description