Home > Mobile >  FromBody returns data as null after adding JsonSerializer converters
FromBody returns data as null after adding JsonSerializer converters

Time:01-27

I added the following converters in .Net 6 Startup like below:

services.AddControllersWithViews().AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.Converters.Add(new DateTimeConverterFactory());
            options.JsonSerializerOptions.Converters.Add(new DecimalConverterFactory());
            options.JsonSerializerOptions.Converters.Add(new IntConverterFactory());
        });

After adding these converters, whenever I pass data from ajax javascript like below. I am getting null in controller method. If I comment out these converters, the code works. Kindly suggest.

function PerformAction(url, data) {
    var model = {}; //pass all the data to an object
    model.ListOfIds = data;

    var testModel = JSON.stringify(model);
    $.ajax({
        url: url,
        type: "POST",
        Traditional: true,
        data: testModel,
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        success: function (ValidationErrors) {
            // some work here
        }
    });
}

The controller side:

[HttpPost]
public IActionResult AjaxReject([FromBody] ListModel testModel)
{
    var listOfIds = testModel?.ListOfIds; // testModel is null and ListOfIds is null
}

public class ListModel
{
    public List<int> ListOfIds { get; set; }
}

One of the convertors:

public class DecimalConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        return typeToConvert == typeof(decimal) ||
            typeToConvert == typeof(decimal?);
    }

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        //You may be tempted to cache these converter objects. 
        //Don't. JsonSerializer caches them already.
        if (typeToConvert == typeof(decimal))
        {
            return new DecimalConverter();
        }
        else if (typeToConvert == typeof(decimal?))
        {
            return new NullableDecimalConverter();
        }

        throw new NotSupportedException("CreateConverter got called on a type that this converter factory doesn't support");
    }
}

public class DecimalConverter : JsonConverter<decimal>
{
    public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var decimalToParse = reader.GetString();
        return DecimalExtensions.GetFormattedDecimal(decimalToParse);
    }

    public override void Write(
        Utf8JsonWriter writer,
        decimal objectToWrite,
        JsonSerializerOptions options)
    {
        writer.WriteRawValue(objectToWrite.ToString());
    }
}

public class NullableDecimalConverter : JsonConverter<decimal?>
{
    public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.GetString() is { } value)
        {
            return DecimalExtensions.GetFormattedDecimal(value);
        }

        return null;
    }

    public override void Write(
        Utf8JsonWriter writer,
        decimal? objectToWrite,
        JsonSerializerOptions options)
    {
        if (objectToWrite is { })
            writer.WriteRawValue(objectToWrite.ToString());
        else writer.WriteNullValue();
    }
}

IntConvertorFavtory:

public class IntConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        return typeToConvert == typeof(int) ||
            typeToConvert == typeof(int?);
    }

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        //You may be tempted to cache these converter objects. 
        //Don't. JsonSerializer caches them already.
        if (typeToConvert == typeof(int))
        {
            return new IntConverter();
        }
        else if (typeToConvert == typeof(int?))
        {
            return new NullableIntConverter();
        }

        throw new NotSupportedException("CreateConverter got called on a type that this converter factory doesn't support");
    }
}

public class IntConverter : JsonConverter<int>
{
    public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var stringValue = reader.GetString();
        return IntExtensions.GetFormattedInt(stringValue);
    }

    public override void Write(
        Utf8JsonWriter writer,
        int objectToWrite,
        JsonSerializerOptions options)
    {
        writer.WriteNumberValue(objectToWrite);
    }
}

public class NullableIntConverter : JsonConverter<int?>
{
    public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.GetString() is { } value)
        {
            return IntExtensions.GetFormattedInt(value);
        }

        return null;
    }

    public override void Write(
        Utf8JsonWriter writer,
        int? objectToWrite,
        JsonSerializerOptions options)
    {
        if (objectToWrite is { })
            writer.WriteNumberValue((int)objectToWrite);
        else writer.WriteNullValue();
    }
}

DecimalConvertorFactory:

public class DecimalConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        return typeToConvert == typeof(decimal) ||
            typeToConvert == typeof(decimal?);
    }

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        //You may be tempted to cache these converter objects. 
        //Don't. JsonSerializer caches them already.
        if (typeToConvert == typeof(decimal))
        {
            return new DecimalConverter();
        }
        else if (typeToConvert == typeof(decimal?))
        {
            return new NullableDecimalConverter();
        }

        throw new NotSupportedException("CreateConverter got called on a type that this converter factory doesn't support");
    }
}

public class DecimalConverter : JsonConverter<decimal>
{
    public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var decimalToParse = reader.GetString();
        return DecimalExtensions.GetFormattedDecimal(decimalToParse);
    }

    public override void Write(
        Utf8JsonWriter writer,
        decimal objectToWrite,
        JsonSerializerOptions options)
    {
        writer.WriteRawValue(objectToWrite.ToString());
    }
}

public class NullableDecimalConverter : JsonConverter<decimal?>
{
    public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.GetString() is { } value)
        {
            return DecimalExtensions.GetFormattedDecimal(value);
        }

        return null;
    }

    public override void Write(
        Utf8JsonWriter writer,
        decimal? objectToWrite,
        JsonSerializerOptions options)
    {
        if (objectToWrite is { })
            writer.WriteRawValue(objectToWrite.ToString());
        else writer.WriteNullValue();
    }
}

CodePudding user response:

After adding these converters, whenever I pass data from ajax javascript like below. I am getting null in Controller. If I comment these converters, the code works. Kindly suggest.

Well, you are getting null value because fo your incorrect model bindings. You have defined as model but in your controller parameter defined as testModel so when you have JSON.stringify(model) it already parsed into model not int testModel. Therefore, you are getting null.

Solution:

Based on your C# POCO you should bind your Javascript model as following:

           var ListOfIds = [1, 2, 3];
           
            var testModel = {};

            testModel.ListOfIds = ListOfIds;

            console.log(testModel);
            $.ajax({
                url: "/AjaxPostNull/AjaxReject",
                type: "POST",
                Traditional: true,
                data: JSON.stringify(testModel),
                dataType: "json",
                contentType: "application/json; charset=utf-8",
                success: function (ValidationErrors) {
                    // some work here
                }
            });

enter image description here

Note: Point to consider here var testModel = JSON.stringify(model) it's model key will still be treated as model which is certainly mismatched with controller parameter defined as testModel so you have to bind as testModel.ListOfIds

Output:

enter image description here

Update:

I have reeproduced your issue successfully, as per my investigation, your public override int Read extension method within public class IntConverter : JsonConverter<int> causing your null values on controller as its encountered an runtimee excption. As you can see following:

enter image description here

As you can see, Token Type is a type of Number but you are trying to unbox it by reader.GetString() which throws the exception. I am not quite sure what it does. Thus, based on the defination I have replaced it based on its data type as following:

public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        try
        {
          //  var stringValue = reader.GetString();
            var stringValue = reader.ValueSequence;
        }
        catch (Exception ex)
        {

            throw;
        }
       
        //return IntExtensions.GetFormattedInt(stringValue);
        return 0;
    }

Note: Above changes resolved throwing exception but I am not sure, what you were intended to return here. Now, your controller getting value and issue has been resolved. Furthermore, I would suggeest your to apply your necessary changes here. This is the scenario getting null value for your AddJsonOptions. Regardless of your implementation, I have observed, this costing a huge runtime performence issue on application memory.

  • Related