The correct way to validate JSON property - c#

What is the correct way to use advance property validation when deserializing JSON to Model? I am providing the MyClass as an example. I need to validate Name(required) and Email(e-mail address validation). I do find only [JsonProperty(Required = Required.Always)] to validate the required properties and nothing for e-mail validation. Unfortunately, Data Annotation validators can't be used from MVC for validation.
One idea which comes to my mind is to create custom ContractResolver and attach to deserializer where I could perform custom validation. Any other methods to consider?
public class MyClass
{
[JsonProperty(Required = Required.Always)]
public string Name { get; set; }
public string Email { get; set; }
}
_dto = JsonConvert.DeserializeObject<MyClass>(content);

So I've had some time to test this, and as I suspected, MailAddress is deserialized to a much more complex json object than a simple address - which means that if you want to keep your json as is, you can't change the property type to MailAddress.
However, this doesn't mean you can't take advantage of the built in format validation it has in it's constructor, just not with auto-implemented properties.
If you change your class to this -
public class MyClass
{
private string email;
[JsonProperty(Required = Required.Always)]
public string Name { get; set; }
[JsonProperty(Required = Required.Always)]
public string Email {
get
{
return email;
}
set
{
var address = new MailAddress(value);
email = value;
}
}
}
Whenever you try to deserialize a json that contains an invalid format email address (and in fact, whenever you try to set this property to a string that's not a valid email address), you'll get a FormatException:
System.FormatException: The specified string is not in the form required for an e-mail address.

public class MyClass
{
[JsonProperty("name", Required = Required.Always)]
public string Name { get; set; }
[JsonProperty("email", Required = Required.Always)]
public string Email { get; set; }
}
To validate the email you'll have to apply your own regex check after you deserialize.

Related

System.ComponentModel.DataAnnotations validation attributes not working as expected

I have checked for possible solutions to this and haven't been able to get any meaningful solution. I am working on an ASP.NET Web API project and I have a controller route with the below method signature:
[HttpPost("cimgAirtimeVending/{msisdn}")]
public async Task<ActionResult<ResponseObject>> CIMGAirtimeVending([FromBody] CIMGBillPaymentRequest _cimgAirtimeBillPaymentRequest, [Required, RegularExpression(#"\d{13}")] string msisdn)
CIMGBillPaymentRequest is a DTO with validations defined on each property as follows:
public class CIMGBillPaymentRequest
{
[Required, RegularExpression(#"\w+"), StringLength(30)]
public string RequestId { get; set; }
[Required, StringLength(10, MinimumLength = 10), RegularExpression(#"\d+")]
public string DebitAccount { get; set; }
/* [Required, RegularExpression(#"\w+")]
public string Narration { get; set; } */
[Required]
public bool IsFees { get; set; }
public List<Charge> Charges { get; set; }
[Required, RegularExpression(#"[\w-]+")]
public string ProductId { get; set; }
[Required, Range(1, 100, ErrorMessage = "Enter valid ChannelId")]
public int ChannelId { get; set; }
// [Required, Range(100, 100000, ErrorMessage = "Enter valid Amount (>=100)")]
public decimal Amount { get; set; }
[Required, RegularExpression(#"\w+")]
public string CustomerReference { get; set; }
}
public class Charge
{
// [StringLength(10, MinimumLength = 10), RegularExpression(#"\d{10,}")]
[RegularExpression(#"\w+")]
public string Account { get; set; }
public decimal Fee { get; set; }
}
The strange thing is, it appears not all the validation works. If isFees is removed from the DTO, it should return a Required validation error, but that does not happen. The validation is completely ignored. This does not happen with the ProductId property. A Required validation error is returned if it is not included in the DTO
Another issue I had was with the Account property in the Charge class. If you look at the line commented above it, you will see I am using a Regex validation RegularExpression(#"\d{10,}")] i.e. to ensure that it is a string of at least 10 digits. However, this validation is completely ignored and I had to use [RegularExpression(#"\w+")].
Any idea what the issue is and possible solution?
Take a look at the documentation here.
"The RequiredAttribute attribute specifies that when a field on a form is validated, the field must contain a value. A validation exception is raised if the property is null, contains an empty string (""), or contains only white-space characters."
"If the MVC data model or entity partial class contains a field that is annotated with the RequiredAttribute attribute, but the page does not contain the property, an error is not raised. Validation occurs only for fields that are submitted to the server."
If you leave out the property entirely then the required validation will not trigger. It will only trigger when explicitly called with null, empty string or string with only whitespace. Therefore I'm assuming the required check on some other fields namely the struct fields also don't work, however when you then check for a range, like with the ChannelId property it will validate the range. The issue here is that a struct can never be null. This means the RequiredAttribute won't work on bool, int, decimal, etc. fields.
In this case when not providing anything for IsFees will set the property with the value default(bool) the default value for a bool is false.
As for the Account field, look at the documentation here
"You apply the RegularExpressionAttribute attribute to a property when you need to validate values for the property against a regular expression. The regular expression enables you to specify very precisely the format of valid values. The Pattern property contains the regular expression. If the value of the property is null or an empty string (""), the value automatically passes validation for the RegularExpressionAttribute attribute. To validate that the value is not null or an empty string, use the RequiredAttribute attribute."
When providing null, or an empty string to a field with RegularExpressionAttribute it will pass the check. This means, for your Account field you need to use [Required, RegularExpression(#"\d{10,}")].

JSON.NET: How to deserialize from specific json object to a class with a different name

This is driving me kinda nuts, because we don't have time to ask for the API team to change their response object name.
We have a json results that reads:
"seqRing": {
"admStatus": "disabled",
"status": null,
"lines": null
},
And I need that the json deserialization map it to this class:
public class SequentialRing
{
public string admStatus { get; set; }
public string status { get; set; }
public List<SeqLinesAttr> SeqLines { get; set; } = new List<SeqLinesAttr>();
}
If this were a case of a difference in the property name, I could simply use the JsonPropertyAttribute at the top of the property, but I need something similar for the class.
If there's something I could use? Please.
Thank you!
The JSON that you've shown has "seqRing" as a property of a larger JSON object. In order to deserialize that, you can create a wrapper class (or using an existing class) where you can specify the JSON property name for the class:
public class SequentialRingWrapper
{
[JsonProperty("seqRing")]
public SequentialRing SequentialRing { get; set; }
}

WebAPI2 Json.Net Required property not adding ModelState Error properly

[DataContract(Namespace="")]
public class Value
{
[DataMember(IsRequired=true)]
public string Id { get; set; }
[DataMember(IsRequired=true)]
public int Num { get; set; }
[DataMember]
public string Name { get; set; }
}
public Value Post(Value value)
{
if(!ModelState.IsValid)
{
//Bad request
}
return value;
}
I am trying to enforce that all values are specified in a post request to a web api. In the Value model above, when the Num property is omitted:
{"Id": "abc", "Name":"John"}
it adds an error to the model state indicating its absence. However, when the Id property is omitted:
{"Num" : 3, "Name" : "John"}
unexpectedly, no model state error is added, and the model is considered valid.
When I manually Deserialize the model with JsonConvert.Deserialize it throws a serialization exception in both cases indicating that the property is missing. Why does it appear to add model state errors when a value type (int) is not present correctly, but not when a reference type (string) is missing from the request body? How can I include those in the model state errors?
Note: It is not enough to put a [Required] attribute on the Id property. I want to allow a null or empty string value to be posted, as long as it is included in the request.
Try this?
[DataMember]
[Newtonsoft.Json.JsonProperty(Required = Newtonsoft.Json.Required.AllowNull)]
public string Id { get; set; }

How can I refactor this Dictionary to a Class?

I feel this Dictionary is holding too much information: It holds information to build an
e-mail path and it holds extra parameters to get other data needed for e-mail templates. Here is a simplified version of my sample program:
void Main()
{
//Sample Path = Root/Action/TemplateX.txt
//Date used in other method
Dictionary<string,object> emailDict = new Dictionary<string,object>
{
{"Root","Email"},
{"Action", "Update"},
{"TemplateName", "TemplateX.txt"},
{"Date", DateTime.Now},
};
//Create email object
Email email = new Email();
//Send e-mail with email dictionary
email.SendEmail(emailDict);
}
// Define other methods and classes here
public class Email
{
public void SendEmail(Dictionary<string,object> emailDict)
{
//Build path from emailDict and use parameters from emailDict
//Send E-mail
}
}
Are there other re-factors I should consider?
You are certainly right - what you have needs to be refactored. Perhaps reading up on standard Object Orientated principals would help. I would have something more like this, though I would need to know more of how you plan to use it (public setters may be desirable):
enum EmailAction { Update } // add any other possible actions
public class Email
{
public string Email { get; private set; }
public EmailAction EmailAction { get; private set; }
public string TemlateName { get; private set; }
public DateTime DateTime { get; private set; }
public Email(string email, EmailAction action, string templateName, DateTime dateTime)
{
this.Email = email;
this.EmailAction = action;
this.TemlateName = templateName;
this.DateTime = dateTime;
}
public void Send()
{
//Build path from properties on this instance of Email
}
}
Then you can simply go:
Email newEmail = new Email("Email", EmailAction.Update, "TemplateX.txt", DateTime.Now);
newEmail.Send();
That is definitely abusing a Dictionary. You're losing all type safety having your value be an object which leaves you open to InvalidCast exceptions and a whole bunch of other issues. Just pull out all of your values into properties in a class:
public class EmailFields
{
public string Root {get;set;}
public string Action {get;set;}
public string TemplateName {get;set;}
public DateTime Date {get;set;}
public EmailHelper
{
Date = DateTime.Now;
}
}
Your SendEmail method would then take an EmailFields object as a parameter.
From this point, I'd also probably make enum's for Action and TemplateName.
public enum Action
{
Update,
}
public enum Template
{
TemplateX,
}
And your properties would then be
public Action EmailAction {get;set;}
public Template TemplateName {get;set;}

Adding the object name to its serialized string

Let's say I have this object:
[JsonObject]
public class User
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "birthday")]
public DateTime Birthday { get; set; }
}
This will produce JSON like this:
{
"id":null,
"name":"Bob",
"birthday":"/Date(374479200000-0600)/"
}
I'm looking for a way to add the classname around all this, for example:
{
"user":{
"id":null,
"name":"Bob",
"birthday":"/Date(374479200000-0600)/"
}
}
Does JSON.NET have a way to do this? Thanks!
Edit
My reason for doing this was to connect a .NET client to a Rails web service. Rails puts a model's attributes under a namespace, so I was looking for a way for .NET to conform to this.
However, after reading a bit about Backbone.js, it looks like a possible alternative would be to disable this behavior in Rails:
If you're working with a Rails backend, you'll notice that Rails'
default to_json implementation includes a model's attributes under a
namespace. To disable this behavior for seamless Backbone integration,
set:
ActiveRecord::Base.include_root_in_json = false
If you want to do this just for the root object, I think the simplest way is just to enclose the object in a way that it gets serialized the way you want:
static object AddTypeName(object o)
{
return new Dictionary<string, object>
{
{ o.GetType().Name.ToLowerInvariant(), o }
};
}
I achieved this personally using JavaScriptSerializer.Serialize(Object):
JavaScriptSerializer javaScriptSerializer = new JavascriptSerializer();
javaScriptSerializer.Serialize( new { user = userObject } );
That should automatically deserialize the following class:
class User
{
string id;
int age;
}
into this:
{
user : {
id = '12321345',
age = 32
}
}

Categories