Cart validation breaks site when all items in cart get expired
Vote:
Hi
I encountered a one-off but strange issue on one of the websites. A user created a cart with 13 items last month and this month when they tried logging into the site, the whole site broke for them. When we reviewed logs, we found this :
ERROR EPiServer.Global: Unhandled exception in ASP.NET System.NullReferenceException: Object reference not set to an instance of an object. at EPiServer.Commerce.Marketing.OrderFormPriceMatrix.CalculateAndCommit(AffectedEntries item, Func`3 apply) at EPiServer.Commerce.Marketing.RewardApplicatorBase`1.<>c_DisplayClass1_0.<Apply>b_1(TAffectedObject item) at EPiServer.Commerce.Marketing.RewardApplicatorBase`1.ForEach(Func`2 apply, IEnumerable`1 affectedItems) at EPiServer.Commerce.Marketing.RewardApplicatorBase`1.Apply(RewardDescription reward, Int32 remainingRedemptions, PromotionProcessorContext processorContext) at EPiServer.Commerce.Marketing.PromotionEngine.ApplyReward(RewardDescription rewardDescription, PromotionFilterContext promotionFilteringResult, PromotionExclusionHandler exclusionHandler, PromotionData promotion, PromotionProcessorContext processorContext) at EPiServer.Commerce.Marketing.PromotionEngine.Run(IOrderGroup orderGroup, PromotionEngineSettings settings) at EPiServer.Commerce.Order.IOrderGroupExtensions.ApplyDiscounts(IOrderGroup orderGroup, IPromotionEngine promotionEngine, PromotionEngineSettings settings)
Now, ApplyDiscounts is called from ValidateCart() method on CartService. However, before this, we validate the cart for other lineitem, inventory and pricing related issues. Here's the default epi implementation :
What we noticed now is that if all items on a cart are expired now, the first call on ValidateCart, i.e. ValidateOrRemoveLineItems(), actually figures it out and adds validation issue called RemovedDueToUnavailableItem for all lineitems. It also trasfers the line items from InnerList to DeletedList on cart.OrderForm[0].LineItems collection. DeletedList, however, is something I couldn't find a way to access. But it never really deletes the items from the cart itself, as i can still see them in the database.
Now, once it passes this step and moves over to the other validation calls, it eventually reaches ApplyDiscounts() and here in the internal implementation of Promotion engine, when it tries to access the Cart items, it breaks with Object reference errors.
I fixed this with a code workaround :
int lineitemcount = cart.GetAllLineItems().Count(); var validationIssues = new Dictionary<ILineItem, List<ValidationIssue>>();
// additional check here to see if all line items reported validation issue RemovedDueToUnavailableItem if (validationIssues.Values.All(v => v.Contains(ValidationIssue.RemovedDueToUnavailableItem)) && validationIssues.Count == lineitemcount) { DeleteCart(cart); validationIssues.Clear(); } else {
//continue with the rest of the validation calls....
}
I wanted to gather feedback on whether this should be considered an Epi bug that should be fixed in future releases, or we'll need to make this fix on every project, just to avoid this one-off situation that could essentially happen anytime on any project.
Hi
I encountered a one-off but strange issue on one of the websites. A user created a cart with 13 items last month and this month when they tried logging into the site, the whole site broke for them. When we reviewed logs, we found this :
ERROR EPiServer.Global: Unhandled exception in ASP.NET
System.NullReferenceException: Object reference not set to an instance of an object.
at EPiServer.Commerce.Marketing.OrderFormPriceMatrix.CalculateAndCommit(AffectedEntries item, Func`3 apply)
at EPiServer.Commerce.Marketing.RewardApplicatorBase`1.<>c_DisplayClass1_0.<Apply>b_1(TAffectedObject item)
at EPiServer.Commerce.Marketing.RewardApplicatorBase`1.ForEach(Func`2 apply, IEnumerable`1 affectedItems)
at EPiServer.Commerce.Marketing.RewardApplicatorBase`1.Apply(RewardDescription reward, Int32 remainingRedemptions, PromotionProcessorContext processorContext)
at EPiServer.Commerce.Marketing.PromotionEngine.ApplyReward(RewardDescription rewardDescription, PromotionFilterContext promotionFilteringResult, PromotionExclusionHandler exclusionHandler, PromotionData promotion, PromotionProcessorContext processorContext)
at EPiServer.Commerce.Marketing.PromotionEngine.Run(IOrderGroup orderGroup, PromotionEngineSettings settings)
at EPiServer.Commerce.Order.IOrderGroupExtensions.ApplyDiscounts(IOrderGroup orderGroup, IPromotionEngine promotionEngine, PromotionEngineSettings settings)
Now, ApplyDiscounts is called from ValidateCart() method on CartService. However, before this, we validate the cart for other lineitem, inventory and pricing related issues. Here's the default epi implementation :
orderGroup.ValidateOrRemoveLineItems((Action<ILineItem, ValidationIssue>) ((item, issue) => OrderValidationService.AddValidationIssues((IDictionary<ILineItem, IList<ValidationIssue>>) validationIssues, item, issue)), this._lineItemValidator);
orderGroup.UpdatePlacedPriceOrRemoveLineItems(this.GetContactById(orderGroup.CustomerId), (Action<ILineItem, ValidationIssue>) ((item, issue) => OrderValidationService.AddValidationIssues((IDictionary<ILineItem, IList<ValidationIssue>>) validationIssues, item, issue)), this._placedPriceProcessor);
orderGroup.UpdateInventoryOrRemoveLineItems((Action<ILineItem, ValidationIssue>) ((item, issue) => OrderValidationService.AddValidationIssues((IDictionary<ILineItem, IList<ValidationIssue>>) validationIssues, item, issue)), this._inventoryProcessor);
orderGroup.ApplyDiscounts(this._promotionEngine, new PromotionEngineSettings());
What we noticed now is that if all items on a cart are expired now, the first call on ValidateCart, i.e. ValidateOrRemoveLineItems(), actually figures it out and adds validation issue called RemovedDueToUnavailableItem for all lineitems. It also trasfers the line items from InnerList to DeletedList on cart.OrderForm[0].LineItems collection. DeletedList, however, is something I couldn't find a way to access. But it never really deletes the items from the cart itself, as i can still see them in the database.
Now, once it passes this step and moves over to the other validation calls, it eventually reaches ApplyDiscounts() and here in the internal implementation of Promotion engine, when it tries to access the Cart items, it breaks with Object reference errors.
I fixed this with a code workaround :
int lineitemcount = cart.GetAllLineItems().Count();
var validationIssues = new Dictionary<ILineItem, List<ValidationIssue>>();
cart.ValidateOrRemoveLineItems((item, issue) => validationIssues.AddValidationIssues(item, issue), ServiceLocator.Current.GetInstance<ILineItemValidator>());
// additional check here to see if all line items reported validation issue RemovedDueToUnavailableItem
if (validationIssues.Values.All(v => v.Contains(ValidationIssue.RemovedDueToUnavailableItem)) && validationIssues.Count == lineitemcount)
{
DeleteCart(cart);
validationIssues.Clear();
}
else
{
//continue with the rest of the validation calls....
}
I wanted to gather feedback on whether this should be considered an Epi bug that should be fixed in future releases, or we'll need to make this fix on every project, just to avoid this one-off situation that could essentially happen anytime on any project.
Please advise.
Regards
Ritu