Riy
Riy

Reputation: 489

passing decimals to REST API from multilingual frontend

I have a REST API in .Net Core 3 and front end in Angular 8. My front is a multilingual admin panel where I need to configure price for a product. The issue is I am not able receive price with decimal values.

My default culture of .NET Core API is "en-US" but my client is using "nl-NL" from front end. As you know Netherland they use "," instead of "." for decimal points therefore I am not getting price in my submitted model. Here are code snippets;

REST API

Entity

public class Product{
  public Guid Id {get;set;}
  public string Name {get;set;}
  public decimal Price {get;set;}
}

Controller Method

[HttpPost]
public Task<IActionResult> SaveProduct([FromForm]Product model){
  ....code to save the product....
}

Stratup.cs

services.Configure<RequestLocalizationOptions>(options =>
        {
            var supportedCultures = new List<CultureInfo>
            {
                new CultureInfo("en"),
                new CultureInfo("de"),
                new CultureInfo("fr"),
                new CultureInfo("sv")
            };
            options.RequestCultureProviders = new List<IRequestCultureProvider>()
            {
                new AcceptLanguageHeaderRequestCultureProvider()
            };
            options.FallBackToParentCultures = true;
            options.SupportedCultures = supportedCultures;
            options.SupportedUICultures = supportedCultures;
        });

I tried setting Requestdefault culture to "nl" but then "en" values doesn't work. Can any one please help me how to pass decimal points from multilingual frontend to a REST API.

Thanks.

Upvotes: 0

Views: 5158

Answers (3)

Christopher
Christopher

Reputation: 9804

Getting Decimal values for a price from the client is a terrible idea. I remember stories where a haphazard developer had put the shopping cart into the cookie, including prices. It took not that long for this mistake to be found and exploited. The company had the bill, because they had made their shop faulty.

Never trust input from the user. Especially if that user is on the Internet.

As for the specific problem: You basically have the issue that frontend and backend culture varies. I got 3 items of advice for transmitting numbers between processes:

  • never transmit them as String. String is arguably the 2nd worst format for processing. The only thing worse is raw binary
  • if you have to transmit them as string, make certain you pick fixed culture and encoding at both endpoints. Stuff like XML and JSON tends to take care of that for you. But you may have to pick something like the Invariant Culture.
  • If you are transmitting, storing or retrieving a DateTime, always do so as UTC. And hope you do not have that rare application that has to store the original Timezone (like a calendar).

Upvotes: 0

Ryan
Ryan

Reputation: 20116

If you use [FromForm],you could create your own custom mobel binder for the Price property:

1.Create a DecimalModelBinder

public class DecimalModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {

        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (valueProviderResult == null)
        {
            return Task.CompletedTask;
        }

        var value = valueProviderResult.FirstValue;

        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        // Replace commas and remove spaces
        value = value.Replace(",", ".").Trim();

        decimal myValue = 0;
        if (!decimal.TryParse(value, out myValue))
        {
            // Error
            bindingContext.ModelState.TryAddModelError(
                                    bindingContext.ModelName,
                                    "Could not parse MyValue.");
            return Task.CompletedTask;
        }

        bindingContext.Result = ModelBindingResult.Success(myValue);
        return Task.CompletedTask;
    }
}

2.Use it on Price property

public class Product
{
    public Guid Id { get; set; }
    public string Name { get; set; }

    [BindProperty(BinderType = typeof(DecimalModelBinder))]
    public decimal Price { get; set; }
}

3.Action

[HttpPost]
public Task<IActionResult> SaveProduct([FromForm]Product model){
  ....code to save the product....
}

Upvotes: 1

Dan
Dan

Reputation: 10538

Based on your question, it appears that your problem is that your front-end code is submitting the price with a comma on it. This can only be possible if you are sending a string representation of the price to the server, rather than a numeric one.

You should modify your front end code to always store and transmit the price value as a number. You can control how a price is displayed to a user in strings using Intl.NumberFormat().

As the user has to enter a price, you're probably using a <input type=text /> tag to capture the cost of the product. Since we want users to be able to enter commas here, we can't use <input type=number /> - instead, we can simply modify the value we get from this input tag on the client side by replacing occurences of commas in the string with a period - amount.replace(',', '.') - and then attempting to parse the number from the string - parseInt(amount, 10).

If parseInt returns NaN, you should display a validation error to the user.

Upvotes: 0

Related Questions