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,}")].
Related
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.
In my ASP.NET Core 2.1 MVC application, I want to expose a route like this:
/address/v1/postcode/{postcode}/street/{street}
I have defined my controller like this:
[Route("address/v1")]
[ApiController]
public class StreetController : ControllerBase
{
[HttpGet("postcode/{postcode}/street/{street}")]
public ActionResult<GetStreetDetailsResponse> GetStreetDetails([FromRoute] GetStreetDetailsRequest request)
{
throw new NotImplementedException();
}
}
public class GetStreetDetailsRequest
{
[Required]
[StringLength(4, MinimumLength = 4)]
[RegularExpression("^[\\d]+$")]
public string Postcode { get; set; }
[Required]
public string Street { get; set; }
}
public class GetStreetDetailsResponse
{
}
The route resolves as expected, however, the framework is not deserializing the Postcode and Street values, and those properties are not populated correctly in GetStreetDetailsRequest.
For example, if I call:
http://localhost/address/v1/postcode/0629/street/whatever
when it gets into the action method, the value of request.Postcode="{postcode}" and request.Street="{street}".
The issue appears to be due to the casing of my property names, because if I change GetStreetDetailsRequest to:
public class GetStreetDetailsRequest
{
[Required]
[StringLength(4, MinimumLength = 4)]
[RegularExpression("^[\\d]+$")]
public string postcode { get; set; }
[Required]
public string street { get; set; }
}
everything works correctly. However, I’m not happy with that solution as it doesn't follow conventional C# naming standards.
I’ve tried decorating the properties with [DataMember(Name="postcode")] or [JsonProperty("postcode")], but these seem to be ignored too.
For the record, in my Startup.ConfigureServices() method I am using the default serializer, which I understand supports camel case:
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Has anyone got a solution that will enable me to expose the route with camel case properties using Pascal case in the request object property names?
Well, you are correct in some way. This:
[HttpGet("postcode/{postcode}/street/{street}")]
Says you have a postcode and a street property, and you have none of them. If you want the default binding to work, the casing must match exactly:
[HttpGet("postcode/{Postcode}/street/{Street}")]
I am trying to compare two password fields with a Compare Attribute and in my project, I use an HTML helper to get the message in different languages depending on the language id and I can not use my method in the error message
[LocalizedRequired("PasswordRequired")]
public string Password { get; set; }
[LocalizedRequired("PasswordDoesntMatch")]
[System.ComponentModel.DataAnnotations.CompareAttribute("Password", ErrorMessage = "Password doesn't match.")]
public string ConfirmPassowrd { get; set; }
This is the model without the helper and it works fine, the problem is that if I use any method that returns a string error message it doesn't work at all.
[LocalizedRequired("PasswordRequired")]
public string Password { get; set; }
[LocalizedRequired("PasswordDoesntMatch")]
[System.ComponentModel.DataAnnotations.CompareAttribute("Password", ErrorMessage = Kuvendi.Infrastructure.MVC.HtmlHelperExtensions.DrawLabel("Password doesn't match")))]
public string ConfirmPassowrd { get; set; }
Draw label is an HTML helper method that returns strings for a specific language id, the helper returns strings successfully and it is tested.
I appreciate your time.
Parameters passed to attributes must be compile time constants, so you can not call any helper methods to generate them.
You can however provide the name of a string resource and the resource file. Language will be resolved by MVC and the translation from the correct resource file will be selected.
[LocalizedRequired("PasswordDoesntMatch")]
[System.ComponentModel.DataAnnotations.CompareAttribute("Password", ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "PasswordDoesntMatch")]
public string ConfirmPassowrd { get; set; }
[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; }
Hi I have following in my Asp.net MVc Model
TestModel.cs
public class TestModel
{
public double OpeningAmount { get; set; }
[Required(ErrorMessage="Required")]
[Display(Name = "amount")]
[Range(0 , double.MaxValue, ErrorMessage = "The value must be greater than 0")]
public string amount { get; set; }
}
Now from my controller "OpeningAmount " is assign .
Finaly when I submit form I want to check that "amount" must be greater than "OpeningAmonut" . so want to set Range dynamically like
[Range(minimum = OpeningAmount , double.MaxValue, ErrorMessage = "The value must be greater than 0")]
I do not want to use only Jquery or javascript because it will check only client side so possible I can set Range attribute minimum dynamically than it would be great for.
Recently there's been an amazing nuget that does just that: dynamic annotations and it's called ExpressiveAnnotations
It allows you to do things that weren't possible before such as
[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }
or even
public bool GoAbroad { get; set; }
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }
There's no built-in attribute which can work with dependence between properties.
So if you want to work with attributes, you'll have to write a custom one.
Se here for an example of what you need.
You can also take a look at dataannotationsextensions.org
Another solution would be to work with a validation library, like (the very nice) FluentValidation .