Exception with invalid dates using data annotations - c#

Have a Json API model with a date property defined as:
[Required]
[DataType(DataType.Date, ErrorMessage = "Invalid expiry date")]
public DateTime ExpiryDate { get; set; }
when posting an incorrect value for the ExpiryDate, examples:
"ExpiryDate":"2020-02-31T00:00:00",
"ExpiryDate":"2020-01-99T00:00:00",
"ExpiryDate":"abc",
the ModelState.Values.Errors[0].ErrorMessage is empty. Instead there is Model exception that I can not return to the API consumer, looks ugly.
ModelState.Values.Errors[0].Exception = {"Could not convert string to DateTime: 2020-02-31T00:00:00. Path 'CardDetails.ExpiryDate', line 13, position 39."}
My question are: how can I make the data annotation generate an error instead of an exception? Why is not the current data annotation giving an error, is not the job of [DataType(DataType.Date...] to do that?

The main issue here is that a DateTime value in JSON (at least according to the JSON parser) has a specific format, and not following that format is a parsing error, which is what's currently happening.
I think you'll have to take the value in as a string and do a conversion and validation on top of it. There's a couple of options. One is a custom ValidationAttribute like #erikscandola mentioned. Another is to implmenent IValidatableObject interface on your model.
You could also convert the model property to a string and simply do a check in the controller action:
DateTime expiryDate;
if (!DateTime.TryParse(model.ExpiryDate, out expiryDate))
{
ModelState.AddModelError("", string.Format(
"The given ExpiryDate '{0}' was not valid", model.ExpiryDate));
}
The approach depends upon how much reuse you need of the validation logic.

You should create a custom data annotation:
public class RequiredDateTimeAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// Here your code to check date in value
}
}

Related

Standard way of passing input to WebAPI for number data types?

Can someone tell about passing input for WebApi for number data types i.e int, long etc.
public class Emp
{
public int Id { get; set; }
public long Volume { get; set; }
}
Input set 1:
{
"Id" : "1",
"Volume" : "200"
}
this is working without model validation error.
Input set 2:
{
"Id" : "1.2",
"Volume" : "200.5"
}
Model validation failing as not able to convert values.
Error : {"Error converting value \"200.5\" to type 'System.Int64'. Path 'Volume', line 2, position 14."}
Input set 3:
{
"Id" : 1.2,
"Volume" : 200.5
}
It not gives any model validation failure, but the values mapped to C# object is Id = 1, Volume = 200.
But here also, I want model validation error as I'm passing decimal input. It should allow only whole numbers.
So what is the right pattern to send api input?
What is the right way to get model validation error when passing decimal point values to int and long data type.
Using int & long will make those values rounded to nearest integer value
How to override this to give model validation errors?
You can use the JsonConverter attribute on properties to define a custom json converter for it.
public class Emp
{
[JsonConverter(typeof(MyCustomIntConverter))
public int Id { get; set; }
public long Volume { get; set; }
}
public class MyCustomIntConverter : JsonConverter<int>
{
//implement here
}
There is a way to archive what you want, but isn't a easy way.
#Saif answer is pointing to the right way.
You must change you model to avoid unwanted framework behaviors and get the raw values, for example, change the type to double or string.
Then you have to validade the arguments for yourself inside your method. If some argument is not right, you must create a model validation errro.
In mvc core, you can create a model validation error like this (in mvc should be something similar):
ModelState.AddModelError(nameof(emp.Volume), $"Error converting value {emp.Volume} to long");

Change default date serialization in WCF

Is there anyway to change the default JSON serialization/deserialization of DateTime in WCF?
Currently, DateTime are serialized into the /Date(1372252162657+0200)/ format, which should've been fine but I'm running into issues when my server is not in UTC (which I can't change).
All date/time data that is being processed by this service is in UTC format. Everything works when the server is in UTC. However, the staging/prod environments are set to GMT+1(Paris) and the serializer is assuming that the dates/times are in GMT+1, completely ignoring the attribute Kind. So as you'd expect calling DateTime.SetKind() and setting it to UTC will not work. In effect, the serialized times are delayed by an hour.
I can either do two-way date conversations (it also makes the same assumption when deserializing so its always GMT+1) conversation of dates: UTC to/from server time, but this is to tedious. So I thought maybe I could just override the default serialization behavior.
Just to expand on tdelepine's code snippet, here the code I've used:
In my WCF JSON Service, I had a (nullable) DateTime value, and wanted my service to return the date in a more readable format, so my iPhone app would be able to interpret it.
Here's what my JSON looked like, after applying a few changes:
Notice the UpdateDateOriginal field, which is the default way that WCF writes DateTimes, and the friendlier UpdateDate field, which I created using the code below.
My original lines looked like this:
[DataMember]
public DateTime? UpdateDateOriginal { get; set; }
... and here are the lines to create the new friendlier UpdateDate JSON value.
[IgnoreDataMember]
public DateTime? UpdateDate { get; set; }
[DataMember(Name = "UpdateDate")]
private string UpdateDateString { get; set; }
[OnSerializing]
void OnSerializing(StreamingContext context)
{
if (this.UpdateDate == null)
this.UpdateDateString = "";
else
this.UpdateDateString = this.UpdateDate.Value.ToString("MMM/dd/yyyy HH:mm", CultureInfo.InvariantCulture);
}
[OnDeserialized]
void OnDeserializing(StreamingContext context)
{
if (this.UpdateDateString == null)
this.UpdateDate = null;
else
this.UpdateDate = DateTime.ParseExact(this.UpdateDateString, "MMM/dd/yyyy HH:mm", CultureInfo.InvariantCulture);
}
Actually, you may find it more useful to return DateTime values in ISO8601 format. For example:
UpdateTime: "2014-08-24T13:02:32",
To do this, simply use my code above, but change the string "MMM/dd/yyyy HH:mm" to "s" in both places.
And, if your DateTime values are stored in UTC, but you wanted your WCF services to return the values in the user's local timezone, you can follow my tips here:
Get DateTime in users local timezone
Isn't life easier, with a few simple examples !
you can use this workaround, In your json object definition
[IgnoreDataMember]
public DateTime dateObject;
public string dateCustomSerialize
{
get {
//Custom get
}
set {
//Custom set
}
}
In assessor place your custom format serialisation
Yes, this can be done using the concept called "Message Formatters"
But Message Formatter would be tough and out of scope to explain here on stack overflow.
You can refere WCF Extensibility : Message Formatters
If you don't want mess up with this then an hack is available.
Set the return type of each method to Stream.
e.g.
public Stream GetStaticData()
{
var objTobeReturned = something;
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json; charset=utf-8";
return new MemoryStream(Encoding.UTF8.GetBytes(objTobeReturned.ToJson()));
}
here ToJson() is my own extension method which converts object into json string using NewtonSoft library.
WCF will skip the stream output for serializing and will pass it to your client as it is.
I hope you got your answer.
This does not solve your issue of timezones, but I'll post it here for others who are battling it out with WCF, ticks and DateTime.
If you don't want ticks, but human-readable time format, you can do it by introducing an additional string property. Then it's a matter of fiddling with the DateTime before turning the value into a string.
[IgnoreDataMember] // Ignore the original tick date.
public DateTime LastReminderDate { get { return _lastReminderDate; } set { _lastReminderDate = value; } }
[DataMember] // Make sure you have a public or private setter!
public string LastReminderDateText { get { return _lastReminderDate.ToString(); } set { _lastReminderDate = DateTime.Parse(value); } }
One way is to use a message formatter to change the default DataContractSerializer as described in WCF Extensibility – Message Formatters.
Another option is to write an extension method that loads your object into a stream and then you can apply whatever serializer you want to the object. See the accepted answer for Replace default JSON serializer in WCF 4 to JSON.NET for details on how to do this.

ASP.NET Web API decimal validation

We have the following
public class Model
{
[Required]
public decimal Cost { get;set; }
}
When we receive an invalid value, such as "dasd" or whatever else, I return a BadRequest:
public HttpResponseMessage Post(Model data)
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
But in the response, the json looks like:
{
"message": "The request is invalid.",
"modelState": {
"data.cost": ["Could not convert string to decimal: dasd. Path 'cost', line 1, position 181."],
}
}
First of all, how can I get rid of the data. prefix?
Also, I would like to implement a custom validator, something like MustBeDecimal attribute that could allow me to return more userfriendly error
message.
The Web API framework binds "dasd" to a decimal and it fails because it is not possible to convert "dasd" to decimal. Obviously, binding has to happen before validation. If you change decimal to string, binding will be okay and your regex validation will run and ModelState will be invalid but in this case also, the prefix that do you do not want to be present will be present.
The Validate method of DefaultBodyModelValidator has this keyPrefix parameter, which is used to create the prefix. Unfortunately, this method is not marked virtual for you to override. So, you will need to do something like this (see the first part).
You can set an errormessage on the RegularExpression data-annotation attribute. Is there any reason you can't use this?
public class Model
{
[Required]
[RegularExpression(#"^\d+.\d{0,2}$",ErrorMessage = "You didn't enter a decimal!")]
public decimal Cost { get;set; }
}

DataTypeAttribute is showing the wrong message

I create the following attribute:
public class SpecificDataTypeAttribute : DataTypeAttribute
{
public SpecificDataType(DataType dataType, string field)
: base(dataType)
{
this.ErrorMessage = string.Format("{0} {1}", field, Messages.SpecificDataTypeAttribute);
}
}
And use like:
[SpecificDataType(DataType.DateTime, "Initial date")]
public DateTime? InitialDate { get; set; }
So, the message that is in Messages.SpecificDataTypeAttribute is "is in a incorrect format.". When i input a wrong date in InitialDate, i got the default error: "The value '12' is not valid for InitialDate.". Why? I put the breakpoint and the code is calling the SpecificDataType ctor.
You are going in wrong direction - in asp.net mvc, DataTypeAttribute does not define validation rules. It is more or less like UIHintAttribute - helps to specify which template to use when rendering property in edit or display modes.
Take a look at this answer to learn about customizing validation messages for system types
The value for PropertyValueInvalid is formatted, with {0} replaced by invalid value, and {1} with property name. So you can define it as
{1} is in invalid format
In MVC 4 is possible to localize default error message. http://weblogs.asp.net/imranbaloch/archive/2013/03/31/localizing-default-error-messages-in-asp-net-mvc-and-web-form.aspx

DataAnnotation and optional DateTime values

I'm working with a HTML form that accepts 4 dates, two of which are optional. These dates are inserted into a MS SQL database, so I'm boundary checking the DateTime variables, which are passed from the form, against SqlDateTime.MinValue and SqlDateTime.MaxValue. Here's what my model looks like:
[Required]
[DisplayName("Planned Start Date")]
[CustomValidation(typeof(Goal), "ValidateGoalDate")]
public object planned_start_date { get; set; }
[DisplayName("Actual Start Date")]
[CustomValidation(typeof(Goal), "ValidateGoalDate")]
public object start_date { get; set; }
[Required]
[DisplayName("Planned End Date")]
[CustomValidation(typeof(Goal), "ValidateGoalDate")]
public object planned_end_date { get; set; }
[DisplayName("Actual Start Date")]
//[CustomValidation(typeof(Goal), "ValidateGoalDate")]
public object end_date { get; set; }
And my custom validator:
public static ValidationResult ValidateGoalDate(DateTime goalDate) {
//* this does not appear to work ever because the optional field does
//* not ever get validated.
if (goalDate == null || goalDate.Date == null)
return ValidationResult.Success;
if (goalDate.Date < (DateTime)SqlDateTime.MinValue)
return new ValidationResult("Date must be after " + SqlDateTime.MinValue.Value.ToShortDateString());
if (goalDate.Date > (DateTime)SqlDateTime.MaxValue)
return new ValidationResult("Date must be before " + SqlDateTime.MaxValue.Value.ToShortDateString() );
return ValidationResult.Success;
}
The problem occurs whenever you submit the form without the optional values. In my controller, my ModelState.IsValid returns false and I get a validation error message:
Could not convert the value of type 'null' to 'System.DateTime' as expected by method GoalManager.Models.Goal.ValidateGoalDate. Must enter a valid date.
Stepping though the code, I see that the custom validator does not run on the optional fields, but when I remove the DataAnnotation from those optional fields, I return no error. If the user does not insert a date into the field, I want to insert a NULL into the table. How do tell the Validator that I do not want to error check a blank (or null) date, to ignore it, and to insert a null into the database?
The DateTime that your custom validator takes as a param is not nullable in your example... If you make it a nullable DateTime, it should fix your problem.
Here's the implementation of what #Rikon said:
public static ValidationResult ValidateGoalDate(DateTime? goalDate) {
This will (probably) avoid the exception but I guess you have to follow it up with more code inside the method:
public static ValidationResult ValidateGoalDate(DateTime goalDate = new DateTime())
If you still have a problem with the ModelState.IsValid returning false, you can put something like this in the controller:
foreach (var state in ModelState) {
if (state.Key == "start_date") state.Value.Errors.Clear();
}
(I'm sure there are better ways to do this but nevermind, at least this is self-explanatory)
By the way, it is not wise to completely disable validation, as it would enable injection security exploits. For more info on validation, and how you can also disable it on a per-field basis on client-side, read this: http://msdn.microsoft.com/en-us/library/aa479045.aspx#aspplusvalid%5Fclientside

Categories

Resources