0

In my ASP.NET MVC Core Code First project, I have a model with a property SaleAmount of type float that I'm successfully displaying in a currency format but when I save data using this property I get this field as null in SQL Server Database. In debug mode, when I place a breakpoint on this line oAnnualSales.SaleAmount = item.SaleAmount; in controller (shown below) I can see item.SaleAmount is null.

View:

public class AnnualSale
{    @model IList<MyProj.Models.SaleViewModel>
<div class="row">
    <div class="col-md-9">
        <form asp-controller="DbRelated" asp-action="UpdateSales" asp-route-returnurl="@ViewData[" ReturnUrl"]" method="post">
            <table class="table">
                <thead>
                    <tr>
                        <th></th>
                        <th>
                            State Name
                        </th>
                        <th>
                            Sale Amount
                        </th>
                    </tr>
                </thead>
                <tbody>
                    @for (int i = 0; i < Model.Count(); i++)
                    {
                    <tr>
                        <td>@Html.HiddenFor(r => r[i].StateId)</td>
                        <td>
                            @Html.EditorFor(r => r[i].StateName)
                        </td>
                        <td>
                            @Html.EditorFor(r => r[i].SaleAmount)
                        </td>
                    </tr>
                    }
                </tbody>
            </table>
            <button type="submit" class="btn btn-default">Save</button>
        </form>
    </div>
</div>
    [Key]
    public int Sale_Id { get; set; }
    public int? FiscalYear { get; set; }

    [DisplayFormat(DataFormatString = "{0:C0}", ApplyFormatInEditMode = true)]
    public float? SaleAmount { get; set; }

    public int StateId { get; set; }
    public StateName StateName { get; set; }
}

Model:

public class AnnualGrant
{
    [Key]
    public int Sale_Id { get; set; }

    public int? FiscalYear { get; set; }

    [DisplayFormat(DataFormatString = "{0:C0}", ApplyFormatInEditMode = true)]
    public float? SaleAmount { get; set; }

    public int StateId { get; set; }
    public StateName StateName { get; set; }
}

Controller:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UpdateSales(List<SaleViewModel> model, string returnUrl = null)
{
    if (ModelState.IsValid)
    {
        foreach (var item in model)
        {
            var oAnnualSales = _context.AnnualSales.Where(r => r.StateId.Equals(item.StateId)).FirstOrDefault();
            if (oAnnualSales != null)
            {
                oAnnualSales.FiscalYear = item.FY;
                oAnnualSales.SaleAmount = item.SaleAmount;
            }
            else
            {
                AnnualSale oAnnualSaleNew = new AnnualSale { StateId = item.StateId, FiscalYear = item.FY, SaleAmount = item.SaleAmount};
                _context.Add(oAnnualSaleNew);
            }
        }
        await _context.SaveChangesAsync();
        return View(model);
    }

    // If we got this far, something failed, redisplay form
    return View();
}
nam
  • 21,967
  • 37
  • 158
  • 332
  • I already explained that in your [previous question](http://stackoverflow.com/questions/39600688/data-annotation-for-currency-format-not-working). If you post a value that is (say) `"$1,000.00"` it cannot be bound to `float` by the `DefaultModelBinder` (only a value of ``"1000.00"` would be bound). You would need to create a custom `ModelBinder` –  Sep 23 '16 at 22:16

2 Answers2

0

As mentioned in the comment to your question, posting characters such as currency symbols with the value will not parse to a float?, So will revert to null. You can test that by writing a quick unit test or console app which uses float.TryParse, which will return 0. You would need to either (not recommended) change the amount to a string, or (recommended) have the currency type and amount as different properties. Posting the currency symbol with the amount also relies on the user entering the currency symbol, which is unlikely to happen

Steven Brookes
  • 834
  • 6
  • 27
  • The formatting I'm using is like `$15,481`. So just creating a separate property for $ may not work. – nam Sep 23 '16 at 22:51
  • How are you storing the value, once posted to the server? If it's in a databaae, what data type is it? – Steven Brookes Sep 24 '16 at 06:53
  • The value is null when I check it at a breakpoint on `item.SaleAmount` in the controller before `_context.SaveChangesAsync();` line. And hence it's null in the Db as well. But it displays fine as, say, $15,481 if you enter it as 15481 and then post and then open the page (GET method) to display it. DataType in Db is `real`. SQL Server is 2014 EXPRESS. – nam Sep 24 '16 at 16:53
  • If you are only ever entering dollars, can you not have the dollar display outside of the text box and just have the user enter without needing the dollar symbol? You would need a custom ModelBinder to do what you are asking at the moment, but I wouldn't recommend that as I feel it is a long-winded workaround for a design choice which could be tweaked for better results in the long term – Steven Brookes Sep 24 '16 at 17:03
  • I agree. Using custom ModelBinder for my purpose is unnecessarily a long-winded workaround as I don't need it for many places. So, using $ sign outside the saleAmount control will suffice it. But, how about getting rid of commas before the POST? I tried it in `View` at `EditorFor(r => r[i].SaleAmount` but I got a runtime error stating you can't do it in `EditorFor` – nam Sep 24 '16 at 17:21
  • This is where I become somewhat conflicting with my advice! If you must have commas, they still won't bind when you post, so you have multiple options. 1. Custom ModelBinder (not normally recommended, but widely used with decimals/floats for the use case you have), 2. Use JavaScript to strip them before posting or 3. Create a new property which is a string which will be what is bound to the editor, and have the setter parse the float and set your float property using the required culture setting – Steven Brookes Sep 24 '16 at 20:06
0

This is not supported by default in ASP.NET MVC, but you can work around it with a custom model binder.

You first need to create a new class for the custom model binding:

public class FloatModelBinder : IModelBinder
{
  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
    ModelState modelState = new ModelState { Value = valueResult };
    object actualValue = null;
    try
    {
      if (bindingContext.ModelMetadata.EditFormatString.StartsWith("{0:c", StringComparison.InvariantCultureIgnoreCase))
      {
        actualValue = float.Parse(valueResult.AttemptedValue, NumberStyles.Currency);
      }
      else
      {
        actualValue = float.Parse(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
      }
    }
    catch (FormatException e)
    {
      modelState.Errors.Add(e);
    }
    bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
    return actualValue;
  }
}

Then you need to add this to the MVC binders by adding it to the Application_Start method:

protected void Application_Start()
{
  ModelBinders.Binders.Add(typeof(float?), new FloatModelBinder());    
}
Cat_Clan
  • 352
  • 2
  • 9
  • Your class does not work in ASP.NET Core MVC. I'm using latest version of ASP.NET Core 1.0 and Visual Studio 2015. Some of the errors are: `The type or namespace name 'ModelState' could not be found`, `'StringComparison' does not contain a definition for 'InvariantCultureIgnoreCase'` and `'ValueProviderResult' does not contain a definition for 'AttemptedValue'` – nam Sep 24 '16 at 04:05
  • My bad, I didn't read it was ASP.NET Core MVC. For reference you can use a similar technique using the custom Model Binding for Core: http://intellitect.com/custom-model-binding-in-asp-net-core-1-0/ – Cat_Clan Sep 26 '16 at 16:22