I'm on a project with DataAnnotations and ASP.NET MVC3. We use this solution to test the validations of "Date Annotions": http://gcbyjm.blogspot.com.br/2011/02/how-to-unit-test-dataannotations.html
I have problems to test a property "DateTime" of "ViewModel".
public class AchievementVM
{
...
[Required(ErrorMessage = "The date field is required.")]
[DataType(DataType.DateTime, ErrorMessage = "Invalid date.")]
public DateTime Date { get; set; }
...
}
[TestMethod]
public void AchievementVMValidator_ShouldHaveErrorWhenDateIsInvalid()
{
// Arrange
var achievementVM = new AchievementVM() { Date = ???? };
// Act
var errors = ValidationBuddy.GetErrors(achievementVM) as List<ErrorInfo>;
// Assert
ErrorInfo error = errors.Find(delegate(ErrorInfo e) { return e.ErrorMessage == "The date field is required."; });
Assert.IsTrue(error != null);
}
My question is how to pass the value of this property to simulate the ModelBind ERROR. In both situations, textbox empty and invalid data.
Thanks a lot!
If I understand your question correctly I think you should be testing action method that gets called by the view where the field is located. Like that you can pass invalid or empty values to the action method replicating empty or invalid values.
Remember that your view-model should be a representation of your view. So if your view allows a DateTime to not be entered (empty text box) then I think your view-model should have a nullable DateTime.
public DateTime? MyProperty { get; set; }
That way, in your unit test you can test for a null DateTime.
On your domain model this should be different as you dont want your database to receive a null DateTime so the domain model property should not be nullable. you view-model validation should stop the null date getting passed to the domain model or something converts it to a DateTime the domain model can handle. Maybe, for example, a null DateTime gets converted to DateTime.Now if your business requirements match this.
I hope this helps.
Related
I have ASP.Net WebAPI based application. Below is my DTO.
public class CustomerTO
{
[Required(ErrorMessage="Name required")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Name invalid")]
public string Name { get; set; }
[Required(ErrorMessage="CountryId required")]
[Range(1,250,ErrorMessage="CountryId invalid")]
public int Country { get; set; }
}
My API Controller.
[HttpPost]
[AllowAnonymous]
public HttpResponseMessage Post([FromBody]CustomerTO model)
{
if (ModelState.IsValid)
{
//my stuff
}
else
{
var msg = ModelState.SelectMany(s => s.Value.Errors).FirstOrDefault().ErrorMessage;
}
}
If user passed any of the required field as Null, it returns the right Error message mentioned in the Data Annotations while if I pass string for CountryId, it enters into else condition(*ModelState.IsValid = false*)
But the ErrorMessage is empty.
While If I debug & put the below statement in quick watch.
msg = ModelState.SelectMany(s => s.Value.Errors).FirstOrDefault().Exception.Message;
It returns - Could not convert string to integer: en. Path 'Country', line 6, position 14.
Why in this scenario, I am not getting the Error message as CountryId Invalid
How do I get this?
Using a RegularExpressionAttribute does not prevent RangeAttribute from throwing an "Could not convert string to integer" exception. So just filter the right one:
var msg = ModelState.SelectMany(s => s.Value.Errors)
.FirstOrDefault(_ => _.Exception == null)
.ErrorMessage;
As far as I know, it is a common problem: SO question 1, SO question 2.
According to code, from any validation attribute there is creating a wrapper, derived from RequestFieldValidatorBase. Each wrapper calls IsValid method of ValidationAttribute. In the method Validate of RequestFieldValidatorBase passing form value for validation.
So, RequiredAttribute does not fails, because form value is not empty and is not null, and RangeAttribute does not fails, because it has problems converting this value to int.
To achieve your desired behaviour it is recommend to create your own validation attribute or use a RegularExpressionAttribute. You can take a look at this answer.
I believe the range validators dont cater for string entry, i.e. they only fire if it's a valid integer. It doesn't enforce the type passed in.
Try changing your annotation to a regex.
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Country code invalid")]
public string Country { get; set; }
With reference to this link Integer validation against non-required attributes in MVC
As dirty patch, I modified my property from int to string & decorated it with Regular Expression.
I have been having headaches with this function so I need a little help.
Describing a little bit there is one class which has this attribute:
public class SessionDate
{
...
[DisplayFormat(DataFormatString = "{0:d}")]
public DateTime? searchValue2 { get; set; }
...
}
and there is a interface that has this function
public interface ISessionDate
{
IEnumerable<SessionDate> Search (string searchName, Datetime? searchDate)
}
which is declared in another class
public class SessionDateHelper
{
IEnumerable<SessionDate> Search (string searchName, Datetime? searchDate)
{
...
}
}
The problem is that the searchvalue2 is invoked in the View (inside the MVC 4 logic), which is set to dd/M/yyyy date format, but when I invoke the search in the Controller, somehow it goes rearranged to M/dd/yyyy, making the input change completely. For example I introduce 09/03/2014 (March 9th, 2014), but when it reaches the service it gets 03/09/2014 (Sept 03rd, 2014), how can I set that the function in the Interface and Helper automatically sets the date to {0:d}
The problem was that using GET method doesn't consider uiCulture, therefor it's set as default. To solve that you need a Model Binder, like this
Model Binder
I have a view model that looks vaguely like:
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
I have a custom ModelBinder to parse the fields in the form, and assign the values to them. Which works.
However, when an error occurs, my ModelState ends up with two errors in it. The first from my ModelBinder, and the second from (I guess) the default validation rules:
- Invalid start date selected <-- My custom error message.
- The value 'fgfdg' is not valid for Start Date. <-- I want this to go away
How do I turn the default validation off for a specific field, on the server side?
Edit: Before you ask, yes my ModelBinder is extending DefaultModelBinder, but obviously I need the other default model binding behaviour; it's just these fields I want a custom behavior for.
(Why don't I just use standard validation rules? Because this is a search form, and depending on if the 'custom date range' is selected, we either ignore the StartDate and EndDate, or parse and perform various checks on them. Specifically, it is an absolute requirement that if the date ranges are invalid (eg. 'fdafsfsf' for start date, but 'search by XXX' instead of 'search by date range' is selected, the form must submit successfully without error)
Code fragment:
[ModelBinderType(typeof(MyViewModel))]
public class MyViewModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext cc, ModelBindingContext bc) {
var model = new MyViewModel();
var searchType = cc.HttpContext.Request["SearchType"];
if (searchType == "CustomDateRange") {
// Do checks here, etc.
// ONLY if searchType == "CustomDateRange" should there be ANY validation on StartDate
bc.ModelState.AddModelError("StartDate", "Invalid start date; outside of invoice range");
}
// bc.ModelState["StartDate"].Errors.Clear(); <--- Clears my error, not the default one.
bc.ModelMetadata.Model = model;
return base.BindModel(cc, bc);
}
}
You should be able to call ModelState.Errors.Clear on the specific property. E.g:
if (someCondition) {
bindingContext.ModelState["StartDate"].Errors.Clear();
return base.BindModel(......
}
Clear the ModelState then call the DefaultModelBinder implementation..
I have this code:
#Html.TextBoxFor(m => Model.MyDateTime)
MyDateTime - is DateTime object.
It shows correct date and time inside textbox: 09/10/2010 05:19:56 PM
But when I try to click submit button it shows that it is incorrect value. I use jquery.validate.unobtrusive.js file for validation.
The gist of the solution I pointed to in my comment is that you can use a specialized model for the view which contains a string representation instead of the DateTime type, which allows you to easily validate the value with RegularExpressionAttribute. When you receive this model on the server (as posted from the client), simply convert it to a corresponding database model.
public class ViewModel
{
[Required]
[RegularExpression("\d{2}-\d{2}-\d{4}\s\d{2}:\d{2}:\d{2}")]
public string MyDateTime { get; set; }
public Model ToPoco()
{
return new Model {
MyDateTime = DateTime.Parse(this.MyDateTime, "MM-dd-yyyy H:mm:ss")
};
}
}
public class Model
{
DateTime MyDateTime { get; set; }
}
data annotation will work for you!
You could use dataannotaion for validate yor model field properly. Using such annatation you could manualy prvide format of date in your annotation passing string pattern to it. And in that case it will perefectly working with default mvc validation.
I am using ASP.NET C# MVC2 and I have the following field within a model with the following data annotation validation attributes:
[DisplayName("My Custom Field")]
[Range(long.MinValue, long.MaxValue, ErrorMessage = "The stated My Custom Field value is invalid!")]
public long? MyCustomField{ get; set; }
In the form this field should allow the user to leave it blank and display a validation message should the user attempt to enter a value that cannot be expressed as a number. From a validation point of view, this is working as intended and displaying the following error messages:
The stated My Custom Field value is invalid!
The field My Custom Field must be a number.
The first validation message is the custom validation message that I wrote and the second validation message is the one the MVC2 automatically generates. I need to get rid of the second one since its redundant. How do I do this? In my view I have the following markup
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm())
{ %>
<%:Html.ValidationSummary(false)%>
<% Html.ValidateFor(m => m.MyCustomField); %>
This issue you have here is because the property being bound is a numeric, and model binding is automatically handling the fact that the string could not be converted to a number. It's not the RangeAttribute's doing.
You might instead consider having a new property as a string and deriving your own RangeAttribute which works at the string level, parsing the number first.
Then you have your existing property wrap that string:
[DisplayName("My Custom Field")]
[MyCustomRangeAttribute(/* blah */)] //<-- the new range attribute you write
public string MyCustomFieldString
{
get; set;
}
public int? MyCustomField
{
get
{
if(string.IsNullOrWhiteSpace(MyCustomField))
return null;
int result;
if(int.TryParse(MyCustomField, out result))
return result;
return null;
}
set
{
MyCustomFieldString = value != null ? value.Value.ToString() : null;
}
}
Your code can continue to work on the int? property quite happily, but - all model binding is done on the string property.
You will also ideally add [Bind(Exclude"MyCustomField")] to the model type - to ensure that MVC doesn't try and bind the int? field. Or you can just make it internal. If it's in the web project and you only need to reference it in the web project.
You could also consider the really hacky approach - and finding that error in your controller method via ModelState.Errors and removing it before you return your view result...