[Bug]: BodySerializationMethod.UrlEncoded with Complex view model class
Describe the bug 🐞
I have a very complex class called CheckoutViewModel: using OrchardCore.Commerce.MoneyDataType; using System.Collections.Generic;
namespace OrchardCore.Commerce.Payment.ViewModels;
public class CheckoutViewModel : PaymentViewModel //, ICheckoutViewModel
{
public string? ShoppingCartId { get; set; }
public Amount GrossTotal { get; set; } = new();
// [BindNever]
// public IEnumerable<OrchardCore.Commerce.Abstraction.ViewModels.Region> Regions { get; set; } = Array.Empty<OrchardCore.Commerce.Abstraction.ViewModels.Region>();
// [BindNever]
public IDictionary<string, IDictionary<string, string>> Provinces { get; set; } =
new Dictionary<string, IDictionary<string, string>>();
public string? UserEmail { get; set; }
public bool IsInvalid { get; set; }
// public IEnumerable<IShape> CheckoutShapes { get; set; } = Array.Empty<IShape>();
//public CheckoutViewModel(OrderPart orderPart, Amount singleCurrencyTotal, Amount netTotal)
// : base(orderPart, singleCurrencyTotal, netTotal) =>
// Metadata.Type = "Checkout";
//public CheckoutViewModel()
// : base() =>
// Metadata.Type = "Checkout";
}
//using Microsoft.AspNetCore.Mvc.ModelBinding;
using OrchardCore.Commerce.Abstractions.Abstractions;
using OrchardCore.Commerce.Abstractions.Models;
using OrchardCore.Commerce.MoneyDataType;
using OrchardCore.Commerce.Payment.Abstractions;
//using OrchardCore.DisplayManagement.Views;
using System.Collections.Generic;
using System.Threading.Tasks;
using BlazingOrchard.DisplayManagement.Services;
using BlazingOrchard.DisplayManagement.Shapes;
using BlazingOrchard.DisplayManagement.Zones;
using System.Linq;
namespace OrchardCore.Commerce.Payment.ViewModels;
public class PaymentViewModel : IPaymentViewModel //ShapeViewModel ,
{
public Amount SingleCurrencyTotal { get; set; } = new();
public Amount NetTotal { get; set; } = new();
public OrderPart OrderPart { get; set; } = new();
//[BindNever]
public IDictionary<string, object> PaymentProviderData { get; set; } = new Dictionary<string, object>();
public PaymentViewModel(OrderPart orderPart, Amount singleCurrencyTotal, Amount netTotal)
{
OrderPart = orderPart;
SingleCurrencyTotal = singleCurrencyTotal;
NetTotal = netTotal;
}
public PaymentViewModel()
{
}
//public async Task WithProviderDataAsync(IEnumerable<IPaymentProvider> paymentProviders)
//{
// foreach (var provider in paymentProviders)
// {
// if (await provider.CreatePaymentProviderDataAsync(this) is { } data)
// {
// PaymentProviderData[provider.Name] = data;
// }
// }
//}
//private ShapeMetadata _metadata;
//public ShapeMetadata Metadata
//{
// get
// {
// return _metadata ??= new ShapeMetadata();
// }
// set
// {
// _metadata = value;
// }
//}
//public string Position
//{
// get
// {
// return Metadata.Position;
// }
// set
// {
// Metadata.Position = value;
// }
//}
//public string Id { get; set; }
//public string TagName { get; set; }
//private List<string> _classes;
//public IList<string> Classes
//{
// get { return _classes ??= new List<string>(); }
// set { _classes = (List<string>)value; }
//}
//private Dictionary<string, string> _attributes;
//public IDictionary<string, string> Attributes
//{
// get { return _attributes ??= new Dictionary<string, string>(); }
// set { _attributes = (Dictionary<string, string>)value; }
//}
//private Dictionary<string, object> _properties;
//public IDictionary<string, object> Properties
//{
// get { return _properties ??= new Dictionary<string, object>(); }
// set { _properties = (Dictionary<string, object>)value; }
//}
//private bool _sorted = false;
//private List<IPositioned> _items;
//public IReadOnlyList<IPositioned> Items
//{
// get
// {
// _items ??= new List<IPositioned>();
// if (!_sorted)
// {
// _items = _items.OrderBy(x => x, FlatPositionComparer.Instance).ToList();
// _sorted = true;
// }
// return _items;
// }
//}
}
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OrchardCore.Commerce.Abstractions.Abstractions;
using OrchardCore.Commerce.Abstractions.Fields;
using OrchardCore.ContentFields.Fields;
using OrchardCore.ContentManagement;
using System.Collections.Generic;
namespace OrchardCore.Commerce.Abstractions.Models;
public class OrderPart : ContentPart
{
public TextField OrderId { get; set; } = new();
public TextField Status { get; set; } = new();
/// <summary>
/// Gets the order's line items.
/// </summary>
public IList<OrderLineItem> LineItems { get; set; } = new List<OrderLineItem>();
/// <summary>
/// Gets additional costs that don't belong to an <see cref="OrderLineItem"/>, such as taxes and shipping.
/// </summary>
public IList<OrderAdditionalCost> AdditionalCosts { get; set; } = new List<OrderAdditionalCost>();
/// <summary>
/// Gets the amounts charged for this order. Typically a single credit card charge.
/// </summary>
// This is a temporary solution, it needs to be reworked in the future!
#pragma warning disable CA2326 // Do not use TypeNameHandling values other than None
#pragma warning disable SCS0028 // TypeNameHandling is set to the other value than 'None'. It may lead to deserialization vulnerability.
[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]
#pragma warning restore SCS0028 // TypeNameHandling is set to the other value than 'None'. It may lead to deserialization vulnerability.
#pragma warning restore CA2326 // Do not use TypeNameHandling values other than None
public IList<IPayment> Charges { get; set; } = new List<IPayment>();
public TextField Email { get; set; } = new();
public TextField Phone { get; set; } = new();
public TextField VatNumber { get; set; } = new();
public AddressField BillingAddress { get; set; } = new();
public AddressField ShippingAddress { get; set; } = new();
public BooleanField BillingAndShippingAddressesMatch { get; set; } = new();
public BooleanField IsCorporation { get; set; } = new();
public IDictionary<string, JToken> AdditionalData { get; set; } = new Dictionary<string, JToken>();
}
Step to reproduce
[Post("/api/checkout/confirm/{providerName}/{paymentIntentId}/{shoppingCartId}?middleUrl={middleUrl}")] Task<PaymentOperationStatusViewModelVM> ConfirmPaymentAsync( string providerName, string paymentIntentId, string shoppingCartId, string middleUrl, [Body(BodySerializationMethod.UrlEncoded)] CheckoutViewModel checkoutViewModel, CancellationToken cancellationToken = default); 2. Click on submit to have a http post aync calling to api server 3. Scroll down to it hit the api server which is ok . 4. See error but I got this 👍 POST /api/checkout/confirm/Stripe/pi_3PsrfnRpyWVeDn6H0MxBWbGo_secret_r7Q8wEngIEPH0JeVjLzfuCECs/4hap81e272txkszm6jrryh52xj?middleUrl=guangmauiauth HTTP/1.1 Host: localhost:62354 Content-Type: application/x-www-form-urlencoded Content-Length: 544
RegionList=System.Collections.Generic.List%601%5BOrchardCore.Commerce.Abstraction.ViewModels.Region%5D&ShoppingCartId=4hap81e272txkszm6jrryh52xj&GrossTotal=%240.00&Provinces=System.Collections.Generic.Dictionary%602%5BSystem.String%2CSystem.Collections.Generic.IDictionary%602%5BSystem.String%2CSystem.String%5D%5D&UserEmail=&IsInvalid=False&SingleCurrencyTotal=%243.00&NetTotal=%243.00&OrderPart=OrchardCore.Commerce.Abstractions.Models.OrderPart&PaymentProviderData=System.Collections.Generic.Dictionary%602%5BSystem.String%2CSystem.Object%5D
UserEmail=&IsInvalid=False&SingleCurrencyTotal=%243.00&NetTotal=%243.00 are ok, but OrderPart=OrchardCore.Commerce.Abstractions.Models.OrderPart&PaymentProviderData=System.Collections.Generic.Dictionary%602%5BSystem.String%2CSystem.Object%5D just gave out the name of the another class , not the data.
Reproduction repository
https://github.com/reactiveui/refit
Expected behavior
OrderPart=OrchardCore.Commerce.Abstractions.Models.OrderPart&PaymentProviderData=System.Collections.Generic.Dictionary%602%5BSystem.String%2CSystem.Object%5D should show the data like [email protected]&....
Screenshots 🖼️
No response
IDE
No response
Operating system
No response
Version
No response
Device
No response
Refit Version
No response
Additional information ℹ️
No response