Mvvm model validation with INotifyDataErrorInfo - c#

my model implements the INotifyDataErrorInfo interface to validate it's properties, and it works fine, but the probleme is, the property HasErrors is by default false, so when i run my app at the first time and click save (form is empty) the view raise no errors, and the data is saved.
here is a snipet of my viewmodel
public LoggingViewModel()
{
_loggingCommand = new RelayCommand(checkCredentials, canExecuteLogginForm);
_logingModel = new LoggingModel();
// I raise this event in the 'OnErrorsChanged' method in the model,
// so my ViewModel can subscribe and check the 'HasErrors' property.
_logingModel.FormIsValid += (o, e) => _loggingCommand.RaiseCanExecuteChanged();
}
private bool canExecuteLogginForm()
{
return !_logingModel.HasErrors;
}
how do you handle this situation in your app?
for more info i created this github repos.

As the LogginModel is actually in an invalid state originally you should call the ValidateForm() method in its constructor to actually set it into this state and populate the _errors dictionary in order for the HasErrors property to return true as it should:
public class LoggingModel : PocoBase
{
public LoggingModel()
{
ValidateForm();
}
[Display(Name = "Name")]
[MaxLength(32), MinLength(4)]
public string UserName
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
[Required]
public string Password
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
}

ViewModel logic is correct.
Problem is in your validation logic inside the model which is returning HasErrors = False when HasErrors = true.
Take a look at how you are setting/returning/evaluating HasErrors.
Are you validating the Model on property get?
public bool HasErrors
{
get
{
bool hasErrors = false; // Default true here?
// Validation logic ...
return hasErrors;
}
}
Are you storing the HasError value in a property and setting it somewhere else?
public LoggingModel()
{
HasErrors = true; // Default true here?
}
public bool HasErrors { get; set; } // Gets set via validation logic
Just some ideas, like I said if you can show the structure on how you handle INotifyDataErrorInfo validation I can give a better answer.

Related

Extending dynamic dispatch to call functions in the view model?

I'm using MVVM in a Xamarin application, I have an interface to navigate between pages:
public interface INavigate
{
INavigate Next();
INavigate Previous();
string ViewTitle { get; }
}
In the implementing views:
public partial class V2Upload : ContentView, INavigate
{
public string ViewTitle => "Upload photos";
public INavigate Next()
=> new V3AdDetail();
public INavigate Previous()
=> new V1Agreement();
}
and in the view model
I have a property of type INavigate:
public INavigate CurrentAddItemStep
{
get { return _currentAddItemStep; }
set { Set(ref _currentAddItemStep, value); }
}
and the Content property of the parent view is bound to this property:
when next button is clicked I execute this code:
CurrentAddItemStep = CurrentAddItemStep.Next();
ViewTitle = CurrentAddItemStep.ViewTitle;
now a validation method is required before navigating to the next page for all the Content views..
I want to keep the MVVM pattern as clean as possible by not writing business code in the view, for example in the V2Upload view the File1 and File2 properties of the view model shouldn't be null:
private bool ValidateFiles(){
return (File1 ?? File2) != null;
}
but since the navigating is done dynamically in run-time, I can't know which view is the current view.
I'm thinking to use reflection , to know what is the name of the view (but this will break the whole design)
Another option is to provide a function parameter to the Next method, but also how to provide it in the design time from the view model?
This is what I'm doing now:
public INavigate Next()
{
if (((ViewModel.AddItemViewModel)BindingContext).ValidateFiles())
return new V3AdDetail();
else
return this;
}
but again, I'm accessing the view model from the view (and had to change the ValidateFiles method from private to public), which I want to avoid

Custom DataAnnotation Binding Wrong Value

I have a C# app with a custom RequiredIf DataAnnotation implementation as follows:
public class RequiredIf : RequiredAttribute {
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIf (String property_name, Object desired_value) {
PropertyName = property_name;
DesiredValue = desired_value;
}
protected override ValidationResult IsValid (object value, ValidationContext context) {
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object prop = type.GetProperty(PropertyName);
Object property_value = type.GetProperty(PropertyName).GetValue(instance, null);
if ( property_value.ToString() == DesiredValue.ToString() ) {
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
This appears to work in nearly every case.
I have a ViewModel that I am trying to validate. The ViewModel has a child class. The basic implementation is:
public class ViewModel {
.........
public class ChildObject {
[RequiredIf("FieldToCheck", false)] // note that it's only required if the value is FALSE!
DateTime? ConditionalField { get; set; } // could be NULL
Boolean FieldToCheck { get; set; }
}
..........
}
When I submit the form with FieldToCheck set to true, my ViewModel validation still fails on this RequiredIf. I can view the value in the ViewModel variable in the controller and verify that it is true. However, I can set a breakpoint in the DataAnnotation's IsValid method and see in the debugger that the FieldToCheck is false - NOT what I submitted!
Is the Model Binder binding wrong? If so, why? And why is the bound value incorrect in the validator, but correct in the controller?
This is causing my ViewModel validation to fail whenever that DateTime field is left blank (NULL gets submitted). But if that Boolean is false, I don't even show the DateTime field in my form -- I don't want the user to submit anything in that field.
EDIT: This seems to happen only with Booleans, but it happens consistently regardless of where the Boolean/ConditionalField are created.
Does the Model Binder not bind Boolean members prior to validation?
EDIT2:
In the view, I have a standard form that I use to post the data:
#Model ViewModel
#using ( Html.BeginForm("Create", "Controller", FormMethod.Post ) {
.........
#Html.DropDownListFor(m => m.FieldToCheck, Model.FieldToCheckList)
.........
<button type="submit">Submit</button>
}
The Controller:
public class Controller : Controller {
[HttpPost]
public ActionResult Create (ViewModel view_model) {
// I set a breakpoint here, and if I submit FieldToCheck = true, the debugger shows that FieldToCheck is, indeed, true.
if ( ModelState.IsValid ) {
// save to db
return View();
}
return View();
}
}
I have a method that initializes fields in the ViewModel before rendering the form, and in it, I set FieldToCheckList as follows:
view_model.FieldToCheckList = new List<SelectListItem> {
new SelectListItem { Text = "Yes", Value = Boolean.TrueString },
new SelectListItem { Text = "No", Value = Boolean.FalseString }
}
So I am using a select to populate the value. However, I have tried multiple other form elements (including statically setting a HiddenFor to true) and it still results in the same problem. If I set a breakpoint in the IsValid method in the RequiredIf validator, all of my boolean values are false, regardless of submitted data. If I set a breakpoint in the Create method in Controller, my boolean values are set correctly.
I have also tested both Boolean and bool data types. It doesn't seem to affect anything.
EDIT4:
I didn't actually figure out the cause of the problem or a real solution, but I did find a workaround.
So, apparently the issue occurs when I do RequiredIf(AnyBooleanValue, AnythingButTrue). If the DesiredValue is set to anything except true, the Model Binder sets all boolean values in the ViewModel to the default value - false - regardless of what is submitted. I have tried multiple things - RequiredIf(FieldToCheck, "False"), RequiredIf(FieldToCheck, !true), etc. None work. But if I do RequiredIf(FieldToCheck, true), the values get bound correctly!
So the workaround is to add a new field:
public class ViewModel {
public Boolean FieldToCheck { get; set; }
public Boolean IsConditionalFieldRequired { get; set; }
[RequiredIf("IsConditionalFieldRequired",true)]
public string ConditionalField { get; set; }
}
In the view, I have IsConditionalFieldRequired as a hidden field, and whenever FieldToCheck is changed, I use jQuery to set IsConditionalFieldRequired to the inverse of FieldToCheck.
(I added this as an edit instead of an answer because I don't think it's a true solution, just a viable workaround. Obviously it's not the most eloquent way to get things done.)
This is an old question, but I just ran into the same problem today, where I was using a RequiredIf attribute and the ValidationContext.ObjectInstance properties did not have the correct bound values.
In my case, it turned out to be due to my view model class having a constructor which set some default property values. Inside the custom attribute code, the property values were those set in the constructor, but in the controller, the property values were from the posted form.
I worked around it by removing the constructor and setting default values in the view instead.

PropertyChangedEvent and CanExecute issue

I am using MVVM (prism) to develop wpf application.
One of my model class "StandardContact" has its properties directly bound to the view. I use IDataErrorInfo to track and notify whether the model has any error. If there are any errors in Model, I disable the "Save" Command.
As the user enters some data, I use the StandardContact.PropertyChanged handler to see if "Save" command can execute (i.e if the model data entered by user is valid). The problem is that the StandardContact.PropertyChanged handler is called before the IDataErrorInfo's validation code, so CanExecute for "Save" command does not correctly reflect whether the command can be executed or not. What I am looking for is that, before the CanExecute executes, the IDataErrorInfo validation should run so that the CanExecute will query on the latest data in model and decide whether it is enabled or not. Here is the sample code that I am using
Model:
public class StandardContact :EntityBase, IDataErrorInfo
{
public virtual string Name
{
get { return _name; }
set { SetField(ref _name, value, () => Name); }
}
//...
//Validators
public string this[string propertyName]
{
get
{
string error = null;
//....
}
ViewModel
public class SContactEditViewModel : NotificationObject, INavigationAware
{
//....
StandardContact.PropertyChanged +=
new PropertyChangedEventHandler(StandardContact_PropertyChanged);
void StandardContact_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Requery if command can execute
SaveNewCommand.RaiseCanExecuteChanged();
}
}
I just inspected our priprietary MVVM library. Inside the ViewModels indexer (in your case this is the Models indexer) the requested Property is validated:
public string this[string propertyName]
{
get
{
string result = null;
if (CanDataErrorValidated(propertyName))
{
int errorCount = CurrentValidationAdapter.ErrorCount();
result = ValidateProperty(propertyName, GetValidateValue(propertyName));
// if the error flag has been changed after validation
if (errorCount != CurrentValidationAdapter.ErrorCount())
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
}
else
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
return result;
}
}
So the solution of your problem seems to validate the requested property on the fly.
I don't use prism, but if it exposes some sort of IsValid method or property you can use that to trigger your error checking. And if it doesn't you can write your own.
The basic idea without prism is to have to leverage IDataErrorInfo.Error by doing
bool IsValid{ get{return string.IsNullOrEmpty(Error) } // trigger validation
Then inside your Save.CanExecute method
return IsValid; // trigger validation on demand
HTH,
Berryl

Data Annotations Custom Validator not working (as I'd expect) in MVC2

Before I start ... I can't easily migrate the project to MVC3. So.
The problem I'm having is that I've defined a custom validator attribute to check the max AND min length of a string property, StringLengthInRangeAttribute.
When the Controller calls ModelState.IsValid, on a list of Passengers only the validation of a Date property is throwing invalid, when nothing has been supplied. I guess that means my problem is not with the custom validator but all validation?
Update (additional info for clarity):
I have two symptoms of this problem :
1.The Required validator on the strings doesn't fire when they are empty
and
2.My custom validator never gets called (a breakpoint I set never gets hit).
Model:
public class Passenger
{
[Required(ErrorMessageResourceType = typeof(Resources.Messages.Passenger),
ErrorMessageResourceName = "RequireNumber")]
public int Number { get; set; }
[Required(ErrorMessageResourceType = typeof(Resources.Messages.Passenger),
ErrorMessageResourceName = "RequireSurname")]
[StringLengthInRange(MinLength = 2, MaxLength = 30, ErrorMessageResourceType = typeof(Resources.Messages.Passenger),
ErrorMessageResourceName = "MaxLengthSurname")]
public string Surname { get; set; }
}
Custom Validator:
public class StringLengthInRangeAttribute:ValidationAttribute
{
public int MinLength { get; set; }
public int MaxLength { get; set; }
public override bool IsValid(object value)
{
if (((string)value).Length < MinLength)
{
return false;
}
if (((string)value).Length > MaxLength)
{
return false;
}
return true;
}
}
Controller Action:
public ViewResult TailorHoliday(List<SearchAndBook.Models.ViewModels.Passenger> passengers,
int leadPassengerIndex)
{
if(!ModelState.IsValid)
{
return View("PassengerDetails", GetBookingState(_currentSession));
}
//...
return View();
}
Any advice appreciated. This is the first time I've used Data Annotations, so I'm quite prepared to feel stupid for missing something!
If you check the content of the ModelState property (in the debugger) you should be able to see every property that gets validated in ModelState.Keys and for each value you see the actual state in the ModelState.Values. If I look at your controller you seem to post a whole list of passengers. You should check as well, whether your values are really posted (use Firebug or Fiddler). Maybe your fields are outside the form.
Maybe you should show part of your view as well to spot the bug.

Detect whether or not a specific attribute was valid on the model

Having created my own validation attribute deriving from System.ComponentModel.DataAnnotations.ValidationAttribute, I wish to be able to detect from my controller, whether or not that specific attribute was valid on the model.
My setup:
public class MyModel
{
[Required]
[CustomValidation]
[SomeOtherValidation]
public string SomeProperty { get; set; }
}
public class CustomValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// Custom validation logic here
}
}
Now, how do I detect from the controller whether validation of CustomValidationAttribute succeeded or not?
I have been looking at the Exception property of ModelError in the ModelState, but I have no way of adding a custom exception to it from my CustomValidationAttribute.
Right now I have resorted to checking for a specific error message in the ModelState:
public ActionResult PostModel(MyModel model)
{
if(ModelState.Where(i => i.Value.Errors.Where((e => e.ErrorMessage == CustomValidationAttribute.SharedMessage)).Any()).Any())
DoSomeCustomStuff();
// The rest of the action here
}
And changed my CustomValidationAttribute to:
public class CustomValidationAttribute : ValidationAttribute
{
public static string SharedMessage = "CustomValidationAttribute error";
public override bool IsValid(object value)
{
ErrorMessage = SharedMessage;
// Custom validation logic here
}
}
I don't like relying on string matching, and this way the ErrorMessage property is kind of misused.
What are my options?
I think it is meaningful to have a Enum named ExceptionType in the CustomValidationAttribute which clearly identifies the type of Exception raised.
In the controller we may check for the exceptionType and handle accordingly .
try
{
}
Catch(Exception e)
{
Switch(e.ExceptionType)
{
case ExceptionType.Val1:
// Handle accordingly
break;
}
}

Categories