-2

For using Patch in my Blazor application, we have used builder.Services.AddControllers().AddNewtonsoftJson(). But after adding it everything works fine if we dont return valdiationerror into response. When we return validation response, it throws an error as below,

blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
  Unhandled exception rendering component: Unable to find a constructor to use for type System.ComponentModel.DataAnnotations.ValidationResult. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'validationResult.validationResults[0].memberNames', line 1, position 493.
Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type System.ComponentModel.DataAnnotations.ValidationResult. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'validationResult.validationResults[0].memberNames', line 1, position 493.
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)

This is what I have done for grabbing response(please ignore the error at Mymodel, I changed it for screenshot only). It breaks at DeserializeObject only when res has validationresults.

response = await Http.PostAsJsonAsync(Constants.uri, editModel);
var res = response.Content.ReadAsStringAsync().Result;
var validationResponse = JsonConvert.DeserializeObject<Mymodel>(res);

And when I we will remove .AddNewtonsoftJson(), it returns proper validation message but Patch wont work!

Mymodel looks like this:

public class MymodelResponse : BaseResponse
{
    public int MymodelId { get; set; }
    public string? Phone { get; set; }
    public long CreatedBy { get; set; }
    public DateTime CreatedDate { get; set; }
    public long? ModifiedBy { get; set; }
    public DateTime? ModifiedDate { get; set; }
    public bool IsDeleted { get; set; }
    public string? Notes { get; set; }

    public MymodelResponse(List<ValidationResult> validationResults) : base(validationResults)
    {

    }
}

And BaseResponse has a property

public List<ValidationResult> ValidationResults  { get; set; }

Which is set in the constructor.

dbc
  • 104,963
  • 20
  • 228
  • 340
Mayur Saner
  • 444
  • 5
  • 10
  • What does `Mymodel` look like? Does it have a `System.ComponentModel.DataAnnotations.ValidationResult` property? – dbc Jun 27 '23 at 15:31
  • @dbc I have updated my question with model. And yes it have System.ComponentModel.DataAnnotations.ValidationResult property – Mayur Saner Jun 28 '23 at 06:15

1 Answers1

1

The problem is as described in the error message: Json.NET is unable to choose a constructor for ValidationResult because it has two public parameterized constructors and no default constructor, and Json.NET has no logic to prefer one parameterized constructor over another.

Since ValidationResult is a simple type with two properties, you could create a custom JsonConverter<ValidationResult> for it:

public class ValidationResultConverter : JsonConverter<ValidationResult>
{
    record DTO(string ErrorMessage, IEnumerable<string> MemberNames);       
    public override ValidationResult ReadJson(JsonReader reader, Type objectType, ValidationResult existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        serializer.Deserialize<DTO>(reader) is {} dto
            ? new ValidationResult(dto.ErrorMessage, dto.MemberNames)
            : null;
    public override void WriteJson(JsonWriter writer, ValidationResult value, JsonSerializer serializer) => 
        serializer.Serialize(writer, new DTO(value.ErrorMessage, value.MemberNames));
}

Then add the converter to JsonSerializerSettings.Converters and deserialize like so:

var settings = new JsonSerializerSettings
{
    Converters = { new ValidationResultConverter() },
};
var validationResponse = JsonConvert.DeserializeObject<Mymodel>(res, settings);

And/or add it in AddNewtonsoftJson() as follows

.AddNewtonsoftJson(opts =>
{
    opts.SerializerSettings.Converters.Add(new ValidationResultConverter());
});

Notes:

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340