I'm making a form to work with a table that had some fields set as decimal(18, 4) to store financial datas.
When I try to populate the fields with my form, the model is considered valid if the values are integers, but it isn't if the values have decimals. To work around the globalization issues, I have a set of regular text fields where people can type in the numbers and parse them into hidden fields with some javascript:
<div class="row text-center">
<label class="col-sm-2 col-form-label font-weight-bold">Budget A</label>
<div class="col">
<input type="text" class="form-control form-control-sm BudgetField text-right" data-type="A" data-family="1" name="BudgetTextA1" value="0" required />
</div>
<div class="col">
<input type="text" class="form-control form-control-sm BudgetField text-right" data-type="A" data-family="2" name="BudgetTextA2" value="0" required />
</div>
<div class="col">
<input type="text" class="form-control form-control-sm BudgetField text-right" data-type="A" data-family="3" name="BudgetTextA3" value="0" required />
</div>
<div class="col">
<input type="text" class="form-control form-control-sm BudgetField text-right" data-type="A" data-family="4" name="BudgetTextA4" value="0" required />
</div>
</div>
@Html.HiddenFor(m => m.BudgetA1)
@Html.HiddenFor(m => m.BudgetA2)
@Html.HiddenFor(m => m.BudgetA3)
@Html.HiddenFor(m => m.BudgetA4)
<script>
// https://stackoverflow.com/questions/469357/html-text-input-allow-only-numeric-input -> https://jsfiddle.net/emkey08/zgvtjc51
function setInputFilter(textbox, inputFilter) {
["input", "keydown", "keyup", "mousedown", "mouseup", "select", "contextmenu", "drop"].forEach(function (event) {
textbox.addEventListener(event, function () {
if (inputFilter(this.value)) {
this.oldValue = this.value;
this.oldSelectionStart = this.selectionStart;
this.oldSelectionEnd = this.selectionEnd;
} else if (this.hasOwnProperty("oldValue")) {
this.value = this.oldValue;
this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
} else {
this.value = "";
}
});
});
}
$(document).ready(function () {
$('.BudgetField').each(function () {
var lBudgetCode = $(this).data('type') + $(this).data('family');
var lBudgetField = $('#Budget' + lBudgetCode).val();
if (lBudgetField != 0) {
$('input[name=BudgetText' + lBudgetCode + ']').val(parseFloat(lBudgetField).toFixed(2));
}
});
$('.BudgetField').on('change', function () {
setInputFilter(this, function (value) {
return /^-?\d*[.,]?\d*$/.test(value);
});
lBudget = 0;
if (this.value == '') {
this.value = 0;
}
$('.BudgetField').each(function () {
lBudget += Number(this.value);
$('#Budget' + $(this).data('type') + $(this).data('famille')).val(parseFloat(this.value));
});
});
});
</script>
My controller does some rather simple stuff:
public ActionResult BudgetForm(SomeObject pFormObject)
{
if (ModelState.IsValid)
{
db.Entry(pFormObject).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbEntityValidationException e)
{
/* do some exception handling */
}
return RedirectToAction("Index", "Identification");
}
return View(pFormObject);
}
The model is generated from the database, I merely added some metadata in a separate file for display formatting:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace WebApp.Models
{
using System;
using System.Collections.Generic;
public partial class SomeObject
{
public System.Guid ID { get; set; }
(...)
public Nullable<decimal> BudgetA1 { get; set; }
public Nullable<decimal> BudgetA2 { get; set; }
public Nullable<decimal> BudgetA3 { get; set; }
public Nullable<decimal> BudgetA4 { get; set; }
}
}
Metadata file
using System;
using System.ComponentModel.DataAnnotations;
namespace WebApp.Models
{
[MetadataType(typeof(SomeObjectMetadata))]
public partial class SomeObject
{
}
public class SomeObjectMetadata
{
(...)
[DisplayFormat(DataFormatString = "{0:C}")]
public decimal BudgetA1;
[DisplayFormat(DataFormatString = "{0:C}")]
public decimal BudgetA2;
[DisplayFormat(DataFormatString = "{0:C}")]
public decimal BudgetA3;
[DisplayFormat(DataFormatString = "{0:C}")]
public decimal BudgetA4;
}
}
If the user types in 8400 in one of the fields, I get 8400M as value in the object the controller receives.
If the user types in 8400.04 in one of the fields, I see in that value in Chrome's DevTools for both the text field's value, and the hidden field value as data sent in the POST request, but I get null
value for if I inspect the object properties the controller receives in the debugger and the controller rightfully returns me the form with the data in the fields.
What am I missing?