Repository navigation
Resolve plugin dependencies through the constructor instead of HttpContext - #926
Merged
Merged
Conversation
…ntext Payment, discount, shipping and widget plugins resolved services with HttpContext.RequestServices, so they threw outside a request (API module, scheduled tasks, background mediator). - Extract the cart subtotal into IShoppingCartSubTotalService; payment providers depend on it instead of IOrderCalculationService, which closed a cycle through IPaymentService. - ShoppingCartDiscountRule sends GetShoppingCartItemsSubTotalQuery instead of resolving IPricingService (cycle through the discount services), and reads the cart of request.Customer. - Percentage handling fee is no longer converted to the working currency twice. - Tests build the providers from DI without an HttpContext and check the real core services with the plugins for cycles.
…products - GetShoppingCartItemsSubTotalQuery carries Customer and Store; the handler prices each item for them in the primary store currency (the currency the spent-amount requirement is entered in) instead of the ambient customer. - OrderCalculationService.GetShoppingCartSubTotal is no longer virtual: payment providers read IShoppingCartSubTotalService, so an override there would make the order total and the payment fee disagree. Customize ShoppingCartSubTotalService instead. - Google Analytics ecommerce script no longer throws for an order item whose product was deleted. - Cycle test for shipping providers (ByWeight -> pricing -> discount rules).
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: bugfix
Issue
Payment, discount, shipping and widget plugins resolved services with
HttpContext!.RequestServices(CashOnDeliveryPaymentProvider,BrainTreePaymentProvider,ShoppingCartDiscountRule,ByWeightShippingCalcPlugin,WidgetsGoogleAnalyticsViewComponent). Outside an HTTP request, for example from the API module, scheduled tasks or a background mediator handler, they threwNullReferenceException, and their dependencies were hidden.Two of these were workarounds for real DI cycles:
OrderCalculationService → IPaymentService → IPaymentProvider → IOrderCalculationService. The percentage handling fee needs the cart subtotal.PricingService → DiscountHandlerService → DiscountValidationService → DiscountProviderLoader → DiscountProvider → ShoppingCartDiscountRule → IPricingService.Solution
IShoppingCartSubTotalService/ShoppingCartSubTotalServicein Grand.Business.Checkout. The subtotal calculation andGetOrderSubtotalDiscountmoved there unchanged, andOrderCalculationService.GetShoppingCartSubTotaldelegates to it. Payment providers depend on the new service, so the cycle is gone. TheIPaymentProvidercontract is unchanged.GetShoppingCartItemsSubTotalQuerywith a handler in Grand.Business.Catalog.ShoppingCartDiscountRulesends it throughIMediator, so the handler andIPricingServiceare resolved only at send time. The rule now reads the cart ofrequest.Customerinstead ofWorkContext.CurrentCustomer, and a request without a customer does not meet the requirement. The query carries the customer and store, and the handler prices each item for them (IPricingService.GetUnitPricewith an explicit customer, store and currency), so the cart and its prices always belong to the same customer. The sum is in the primary store currency, the currency the spent-amount requirement is entered in; before, a shopper paying in another currency was compared against the requirement in the wrong currency.ICurrencyService,IContextAccessor,IPaymentTransactionService,IShippingByWeightService,IPricingServicein ByWeight, and the GA widget's order, country, product and category services) are constructor-injected. They were never part of a cycle.IHttpContextAccessorbecause the card form is stored in the session. Computing the fee no longer needs it.Authentication.OpenIdConnectstill usescontext.RequestServicesin its endpoint delegate and event handler. There, that is the request's own container.Breaking changes
OrderCalculationServiceconstructor takesIShoppingCartSubTotalServiceinstead ofIPricingService,ICheckoutAttributeParserandIProductService.GetOrderSubtotalDiscount(protected virtual) moved toShoppingCartSubTotalService. A plugin that subclassesOrderCalculationServiceor constructs it by hand has to follow.OrderCalculationService.GetShoppingCartSubTotalis no longervirtual. Payment providers read the subtotal fromIShoppingCartSubTotalService, so an override inOrderCalculationServicewould make the order total and the payment fee disagree. A subclass that overrides it stops compiling; move the customization toShoppingCartSubTotalService, whoseGetShoppingCartSubTotalandGetOrderSubtotalDiscountare virtual.CashOnDeliveryPaymentProvider,BrainTreePaymentProvider,ShoppingCartDiscountRule,ByWeightShippingCalcPluginandWidgetsGoogleAnalyticsViewComponentchanged. This only matters to code that subclasses or constructs them.OrderCalculationServiceconstructor has to be rebuilt.Testing
dotnet build GrandNode.slnxdotnet test src/Tests/Grand.Business.Checkout.Tests: 245 passeddotnet test src/Tests/Grand.Business.Catalog.Tests: 576 passed, 1 skippeddotnet test src/Tests/Grand.Modules.Tests: 375 passed. New tests:CashOnDeliveryPaymentProviderTestsandBrainTreePaymentProviderTests: the provider is built from DI with no HttpContext; percentage fee without a second conversion; fixed fee converted;CancelPayment.ShoppingCartDiscountRuleTests: cart of the request customer and store, passed on to the query; no customer.PluginDependencyCycleTests: the real core services next to the real plugins, built withValidateOnBuild. PuttingIPricingServiceback intoShoppingCartDiscountRulemakes this test fail.ShippingProviders_DoNotDependBackOnShippingcoversShippingServicewith ByWeight, real pricing, discount services and the standard rules.GetShoppingCartItemsSubTotalQueryHandlerTestsin Grand.Business.Catalog.Tests: items priced for the request customer and store in the primary currency, missing products skipped.After the review follow-up (38beba0), re-run:
Grand.Modules.TestsPlugins folder (166 passed),GetShoppingCartItemsSubTotalQueryHandlerTests,OrderCalculationServicetests (10 passed), and a build ofWidgets.GoogleAnalytics. The full solution build and full test suites were not re-run.Not done: manual check of the storefront cart and checkout.