1

When I am calling my azure function with HTTP POST and a json body the deserialization is not working for a decimal as I expected.

I am hosting the azure function locally and in the request body I am transferring a json object with a decimal in it.

{
    "Receiver": {
        "Name1": "Mr. Homer Simpson",
        "AddressLine1": "742 Evergreen Terrace",
        "ZipCode": "AEED",
        "City": "Springfield",
        "CountryCode": "US"
    },
    "ReferenceNumber": "US1939383",
    "Weight": 4.2
}
    public class LabelInformation
    {
        public ParcelAddress? Receiver { get; set; }

        /// <summary>
        /// Invoice number. TODO Should be renamed.
        /// </summary>
        public string? ReferenceNumber { get; set; }

        /// <summary>
        /// Total weight of the parcel.
        /// </summary>
        public decimal? Weight { get; set; }
    }

    public class ParcelAddress
    {
        public string? Name1 { get; set; }
        public string? AddressLine1 { get; set; }
        public string? ZipCode { get; set; }
        public string? City { get; set; }
        /// <summary>
        /// Country 2 letter ISO code.
        /// </summary>
        public string? CountryCode { get; set; }
    }
        [FunctionName("GenerateLabelGLSFunctionHttpTrigger")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "label/gls")]
            LabelInformation info)
        {
             ...
        }

Changing the type of info to string and then manually deserialize the string works as expected.

        [FunctionName("GenerateLabelGLSFunctionHttpTrigger")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "label/gls")]
            string info)
        {
            var labelInformation = JsonConvert.DeserializeObject<LabelInformation>(info);

            _logger.LogInformation("create the label.");
            GlsSoap.ShipmentRequestData payload = _labelService.CreateShipmentRequestData(labelInformation);

The error I receive is

[09.10.2019 10:28:38] Executed 'GenerateLabelGLSFunctionHttpTrigger' (Failed, Id=90330456-1ac2-43f3-9285-ab2284b6c31f)
[09.10.2019 10:28:38] System.Private.CoreLib: Exception while executing function: GenerateLabelGLSFunctionHttpTrigger. Microsoft.Azure.WebJobs.Host: Exception binding parameter 'info'. System.Private.CoreLib: Input string was not in a correct format.
[09.10.2019 10:28:38] fail: Host.Results[0]
Microsoft.Azure.WebJobs.Host.FunctionInvocationException: Exception while executing function: GenerateLabelGLSFunctionHttpTrigger ---> System.InvalidOperationException: Exception binding parameter 'info' ---> System.FormatException: Input string was not in a correct format.
   at System.Number.ParseSingle(ReadOnlySpan`1 value, NumberStyles options, NumberFormatInfo numfmt)
   at System.String.System.IConvertible.ToSingle(IFormatProvider provider)
   at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at Microsoft.Azure.WebJobs.Extensions.Http.HttpTriggerAttributeBindingProvider.HttpTriggerBinding.ConvertValueIfNecessary(Object value, Type targetType) in C:\azure-webjobs-sdk-extensions\src\WebJobs.Extensions.Http\HttpTriggerAttributeBindingProvider.cs:line 415
...

I expect the automatic deserialization not to use locale information (on my OS it is German - if I change to English everything is working as expected) for deserializing a decimal.

Or please explain me, why this should be good, as functions can be hosted on different locales and a caller to that function would need to know where the function is deployed to take into account the correct decimal seperator.

2 Answers2

0

I see a lot of declaration issues in your class definition:

Here is my code which worked perfectly:

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace FunctionAppPostComplexObject
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] LabelInformation info,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            LabelInformation labelInformation = info;

            //_logger.LogInformation("create the label.");
            string name = "";// req.Query["name"];

            //string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
          //  dynamic data = JsonConvert.DeserializeObject(requestBody);
          //  name = name ?? data?.name;

            return name != null
                ? (ActionResult)new OkObjectResult($"Hello, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }

        public class LabelInformation
        {
            public ParcelAddress Receiver { get; set; }

            /// <summary>
            /// Invoice number. TODO Should be renamed.
            /// </summary>
            public string ReferenceNumber { get; set; }

            /// <summary>
            /// Total weight of the parcel.
            /// </summary>
            public decimal? Weight { get; set; }
        }

        public class ParcelAddress
        {
            public string Name1 { get; set; }
            public string AddressLine1 { get; set; }
            public string ZipCode { get; set; }
            public string City { get; set; }
            /// <summary>
            /// Country 2 letter ISO code.
            /// </summary>
            public string CountryCode { get; set; }
        }
    }
}

and here is the value for info:

enter image description here

Check this and see if it helps.

Mohit Verma
  • 5,140
  • 2
  • 12
  • 27
  • I see on your system time that you are using english locale. That is my problem. If I change from German locale to English locale everything is working as expected. – Christoph Knafl Oct 10 '19 at 06:57
  • i see what you are saying , its more of a localisation issue, let me check and get back. – Mohit Verma Oct 10 '19 at 07:43
  • In european server the decimal seperator is "," Try passing like "Weight": "4,2" , it should work. – Mohit Verma Oct 10 '19 at 07:49
  • Yes, I know. But this is my issue. For me this is not working as expected. A client that is sending the request would have to know which locale is defined on the server-side. – Christoph Knafl Oct 11 '19 at 08:57
  • simple approach for that would be to use string instead of decimal, would that work for you. In that case you don't need to worry. – Mohit Verma Oct 11 '19 at 08:59
  • No, because than it does not work the other way round. Sending "weight": "4,2" on an English locale leads to "42" after unmarshalling. – Christoph Knafl Oct 14 '19 at 08:45
0

Adapting from this answer in order to override the serializer settings, you might try adding something like this to your function app project to tell the serializer to use the invariant culture:

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Converters;

[assembly: FunctionsStartup(typeof(Configs.Startup))]

namespace Configs
{
    class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddMvcCore().AddNewtonsoftJson(x =>
            {
                x.SerializerSettings.Culture = System.Globalization.CultureInfo.InvariantCulture;
            });
        }
    }
}

If that doesn't work, it's probably because of this custom Decimal converter, but the fact that it adapter to your locale makes me think the above will work.

Update: the above may have been supplanted in the .NET 5 era. See this post.

N8allan
  • 2,138
  • 19
  • 32