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.
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:
If you were able to modify ValidationResult
you could mark the ValidationResult(string?, IEnumerable<string>? memberNames)
constructor with [JsonConstructor]
to inform Json.NET to use that constructor.
For more complex types, you could consider creating a custom contract resolver that selects the appropriate constructor, such as the one from this answer by Zoltán Tamási that selects the parameterized constructor with the longest argument list.
Demo fiddle here.