Question

I am posting JSON to a controller method, and it appears as if the JsonValueProvider included with ASP.Net MVC 3 is broken when it tries to cast JSON numbers into the byte type.

Basically what is happening is that any non-null value that needs to be turned into a byte is null and my model state has errors with no error messages. As seen in the issue list in the return JSON. dnbscore and region have errors, but rating does not. The returned data has dnbscore and region as null even though when the JSON was passed, those fields had values. I can take care of this with a custom model binder, but I wonder if there is a different approach to make the JsonValueProider work as I expect it to.

This is the object that I am trying to build:

public class APICustomerDetailsDTO
{
    public int customerid { get; set; }

    [StringLength(30)]
    public string customername { get; set; }
    public decimal? globalcredit { get; set; }
    [Range(0,5)]
    public byte? rating { get; set; }
    [Range(0, 100)]
    public byte? dnbscore { get; set; }
    [Range(0, 8)]
    public byte? region { get; set; }
    [Required]
    public bool isnotactive { get; set; }
    public string salesperson { get; set; }
    [StringLength(30)]
    public string newmill_venderid { get; set; }
    [StringLength(30)]
    public string newmill_supplierid { get; set; }
    [StringLength(30)]
    public string edi_receiverid { get; set; }
    [Required]
    public bool edi_invoice { get; set; }
    [StringLength(15)]
    public string bill_code { get; set; }
}

This is the JSON that I am sending in the post request:

{"bill_code":"good","customerid":50,"customername":"Ted","dnbscore":80,"edi_invoice":false,"edi_receiverid":null,"globalcredit":null,"isnotactive":false,"newmill_supplierid":null,"newmill_venderid":null,"rating":null,"region":0,"salesperson":null}

Part of my Method that checks model state:

        if (!ModelState.IsValid)
        {
            var issues = ModelState.Where(m => m.Value.Errors.Any())
                                   .Select((m)=> new {field = m.Key, error = m.Value.Errors.FirstOrDefault().ErrorMessage})
                                   .ToArray();

            var result = new
            {
                result = "Failure",
                message = "Invalid data received. See issues for details.",
                issues = issues,
                data = cust
            };

Return JSON:

{"result":"Failure","message":"Invalid data received. See issues for details.","issues":[{"field":"dnbscore","error":""},{"field":"region","error":""}],"data":{"customerid":50,"customername":"Ted","globalcredit":null,"rating":null,"dnbscore":null,"region":null,"isnotactive":false,"salesperson":null,"newmill_venderid":null,"newmill_supplierid":null,"edi_receiverid":null,"edi_invoice":false,"bill_code":"good"}}

For the sake of completeness here is what I did to get around this issue:

With the information provided by Darin, I realized this is a bigger issue than just byte? to int? conversion. It would be an issue for any non-int conversions provided by the default binder. Because of this, I made a custom binder which should handle byte, decimal, etc numbers fine. See below:

public class APICustomerDetailsDTOBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        APICustomerDetailsDTO model = (APICustomerDetailsDTO)bindingContext.Model ??
            (APICustomerDetailsDTO)DependencyResolver.Current.GetService(typeof(APICustomerDetailsDTO));

        bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);
        string searchPrefix = (hasPrefix) ? bindingContext.ModelName + "." : "";

        int customerid = 0;
        int.TryParse(GetValue(bindingContext, searchPrefix, "customerid"), out customerid);
        model.customerid = customerid;

        string customername = GetValue(bindingContext, searchPrefix, "customername");
        if (!String.IsNullOrEmpty(customername))
        { model.customername = customername; }
        else
        { model.customername = null; }

        decimal globalcredit;
        if (decimal.TryParse(GetValue(bindingContext, searchPrefix, "globalcredit"), out globalcredit))
        { model.globalcredit = globalcredit; }
        else
        { model.globalcredit = null; }

        byte rating;
        if (byte.TryParse(GetValue(bindingContext, searchPrefix, "rating"), out rating))
        { model.rating = rating; }
        else
        { model.rating = null; }

        byte dnbscore;
        if (byte.TryParse(GetValue(bindingContext, searchPrefix, "dnbscore"), out dnbscore))
        { model.dnbscore = dnbscore; }
        else
        { model.dnbscore = null; }

        byte region;
        if (byte.TryParse(GetValue(bindingContext, searchPrefix, "region"), out region))
        { model.region = region; }
        else
        { model.region = null; }

        bool isnotactive;
        if (bool.TryParse(GetValue(bindingContext, searchPrefix, "isnotactive"), out isnotactive))
        { model.isnotactive = isnotactive; }
        else
        { model.isnotactive = false; }

        string salesperson = GetValue(bindingContext, searchPrefix, "salesperson");
        if (!String.IsNullOrEmpty(salesperson))
        { model.salesperson = salesperson; }
        else
        { model.salesperson = null; }

        string newmill_venderid = GetValue(bindingContext, searchPrefix, "newmill_venderid");
        if (!String.IsNullOrEmpty(newmill_venderid))
        { model.newmill_venderid = newmill_venderid; }
        else
        { model.newmill_venderid = null; }

        string newmill_supplierid = GetValue(bindingContext, searchPrefix, "newmill_supplierid");
        if (!String.IsNullOrEmpty(newmill_supplierid))
        { model.newmill_supplierid = newmill_supplierid; }
        else
        { model.newmill_supplierid = null; }

        string edi_receiverid = GetValue(bindingContext, searchPrefix, "edi_receiverid");
        if (!String.IsNullOrEmpty(edi_receiverid))
        { model.edi_receiverid = edi_receiverid; }
        else
        { model.edi_receiverid = null; }


        bool edi_invoice;
        if (bool.TryParse(GetValue(bindingContext, searchPrefix, "edi_invoice"), out edi_invoice))
        { model.edi_invoice = edi_invoice; }
        else
        { model.edi_invoice = false; }

        model.bill_code = GetValue(bindingContext, searchPrefix, "bill_code");

        return model;
    }

    private string GetValue(ModelBindingContext context, string prefix, string key)
    {
        ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key);
        return vpr == null ? null : vpr.AttemptedValue;
    }

    private bool GetCheckedValue(ModelBindingContext context, string prefix, string key)
    {
        bool result = false;
        ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key);
        if (vpr != null)
        {
            result = (bool)vpr.ConvertTo(typeof(bool));
        }
        return result;
    }
}
Was it helpful?

Solution

You could send them as strings:

{ 
    "bill_code": "good", 
    "customerid": 50, 
    "customername": "Ted", 
    "dnbscore": "80", 
    "edi_invoice": false, 
    "edi_receiverid": null, 
    "globalcredit": null, 
    "isnotactive": false, 
    "newmill_supplierid": null, 
    "newmill_venderid": null, 
    "rating": null, 
    "region": "0", 
    "salesperson": null 
}

If you are interested in more details you may checkout the following post.

OTHER TIPS

My homebrew solutuion to this is to create a javascript function that ensures that all numeric values get serialized with quotes around them, like the following

$.toJson = function (data) {
    return JSON.stringify(param, function (k, v) { return typeof v === 'number' ? v.toString() : v; });
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top