Hi all i am using mvvmcross and portable class libraries , so i cannot use prism or componentmodel data annotations, to validate my classes. basically i have a modelbase that all my models inherit from.
My validate code below is horribly broken, basically im looking for the code that data annotations uses to iterate thru all the properties on my class that is inheriting the base class ,
i have written various attributes that are there own validators inheriting from "validatorBase" which inherits from attribute. i just cannot for the life of me figure out thecode that says ... ok im a class im going to go through all the properties in me that have an attribute of type ValidatorBase and run the validator. my code for these are at the bottom
public class ModelBase
{
private Dictionary<string, IEnumerable<string>> _errors;
public Dictionary<string, IEnumerable<string>> Errors
{
get
{
return _errors;
}
}
protected virtual bool Validate()
{
var propertiesWithChangedErrors = new List<string>();
// Get all the properties decorated with the ValidationAttribute attribute.
var propertiesToValidate = this.GetType().GetRuntimeProperties()
.Where(c => c.GetCustomAttributes(typeof(ValidatorBase)).Any());
foreach (PropertyInfo propertyInfo in propertiesToValidate)
{
var propertyErrors = new List<string>();
TryValidateProperty(propertyInfo, propertyErrors);
// If the errors have changed, save the property name to notify the update at the end of this method.
bool errorsChanged = SetPropertyErrors(propertyInfo.Name, propertyErrors);
if (errorsChanged && !propertiesWithChangedErrors.Contains(propertyInfo.Name))
{
propertiesWithChangedErrors.Add(propertyInfo.Name);
}
}
// Notify each property whose set of errors has changed since the last validation.
foreach (string propertyName in propertiesWithChangedErrors)
{
OnErrorsChanged(propertyName);
OnPropertyChanged(string.Format(CultureInfo.CurrentCulture, "Item[{0}]", propertyName));
}
return _errors.Values.Count == 0;
}
}
here is my validator
public class BooleanRequired : ValidatorBase
{
public override bool Validate(object value)
{
bool retVal = true;
retVal = value != null && (bool)value == true;
var t = this.ErrorMessage;
if (!retVal)
{
ErrorMessage = "Accept is Required";
}
return retVal;
}
}
and here is an example of its usage
[Required(ErrorMessage = "Please enter the Amount")]
public decimal Amount
{
get { return _amount; }
set { _amount = value; }//SetProperty(ref _amount, value); }
}
Related
My question is about validation using custom attributes in C#.
I don't quite understand how the validation works. I have declared an attribute with the validation rule in it but when the error should be thrown it is not.
Attribute:
[AttributeUsage(AttributeTargets.Property)]
public class NotNullAttribute : Attribute
{
public bool IsValid(object value)
{
if (value is string && (string)value != "")
{
return false;
}
return true;
}
}
Inside the attribute I check if the property is of type string and if its value is an empty string because that is what I have to check.
The task is to check if a property is a string and if its an empty string then its not valid, otherwise it is.
My Person class:
class Person
{
[NotNull]
public string Name { get; set; }
}
Here I am applying the custom attribute.
Main method:
class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "";
Console.WriteLine("Validation done");
Console.ReadKey();
}
}
This is where I instantiate the Person class and assign an empty string to the Name property. This is where the error should be thrown I guess.
So my question is why isn't the validation applied? Should I have called the IsValid method from the attribute it self somehow?
I would take some explanation about this, thank you in advance!
The attribute itself is just a "decorator" of the property. If nothing calls it, it will not be automatically executed nor used.
In your case, however, I don't see the point of using an attribute, when you can use property itself:
private string _name = "";
public string Name
{
get
{
return _name;
}
set
{
if ( string.IsNullOrEmpty(value) )
{
//throw or fallback
}
else
{
_name = value;
}
}
}
Doing basic value validation is exactly the job property setters are great for. In case someone uses an invalid value, you can throw an exception, or set a fallback value for example.
If you would still prefer using attributes, you still need to have some code that performs the validation itself. And still, anyone can assign any valid value to the property, unless validation is performed.
For example ASP.NET MVC uses attribute validation during Model Binding - it checks the validation attributes on the bound model class and verifies it before the action method begins executing.
Example of attribute validation
Here is a simple example of how to make your code work with reflection.
First here is a slightly updated version of the validation attribute:
[AttributeUsage(AttributeTargets.Property)]
public class NotNullAttribute : Attribute
{
public bool IsValid(object value)
{
if (!string.IsNullOrEmpty(value as string))
{
return false;
}
return true;
}
}
Your code actually only allowed a null or "" value, which I guess is opposite of what you wanted. This version is valid only when the string is not null and not empty.
Now create a Validate method in your Program class:
private static bool Validate(object model)
{
foreach (var propertyInfo in model.GetType().GetProperties())
{
foreach (var attribute in propertyInfo.GetCustomAttributes(true))
{
var notNullAttribute = attribute as NotNullAttribute;
if (notNullAttribute != null)
{
if (!notNullAttribute.IsValid(propertyInfo.GetValue(model)))
{
return false;
}
}
}
}
return true;
}
This basically gathers all properties of the type of the passed in parameter, checks all attributes of the properties for NotNullAttribute and then executes the attribute's IsValid method against the current value from the model.
Finally here is how you can call it from Main:
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "d";
if (Validate(p1))
{
Console.WriteLine("Valid");
}
else
{
Console.WriteLine("Invalid");
}
Console.WriteLine("Validation done");
Console.ReadKey();
}
Now, if you are planning on adding more validation attributes, I would create an interface first:
public interface IValidationAttribute
{
bool IsValid(object value);
}
Then derive all your validation attributes from IValidationAttribute and in Validate method use IValidationAttribute in place of NotNullAttribute. This way the code becomes more future-proof as you can just program against the interface and add new validation attributes anytime.
public class BankAccount
{
public enum AccountType
{
Saving,
Current
}
[Required(ErrorMessage="First Name Required")]
[MaxLength(15,ErrorMessage="First Name should not more than 1`5 character")]
[MinLength(3,ErrorMessage="First Name should be more than 3 character")]
public string AccountHolderFirstName { get; set; }
[Required(ErrorMessage="Last Name Required")]
[MaxLength(15,ErrorMessage="Last Name should not more than 1`5 character")]
[MinLength(3,ErrorMessage="Last Name should be more than 3 character")]
public string AccountHolderLastName { get; set; }
[Required]
[RegularExpression("^[0-9]+$", ErrorMessage = "Only Number allowed in AccountNumber")]
public string AccountNumber { get; set; }
public AccountType AcType { get; set; }
[AccountBalaceCheckAttribute]
public double AccountBalance { get; set; }
}
How to Validate
public class GenericValidator
{
public static bool TryValidate(object obj, out ICollection<ValidationResult> results)
{
var context = new ValidationContext(obj, serviceProvider: null, items: null);
results = new List<ValidationResult>();
return Validator.TryValidateObject(
obj, context, results,
validateAllProperties: true
);
}
}
Example
static void Main(string[] args)
{
var bankAccount = new BankAccount();
ICollection<ValidationResult> lstvalidationResult;
bool valid = GenericValidator.TryValidate(bankAccount, out lstvalidationResult);
if (!valid)
{
foreach (ValidationResult res in lstvalidationResult)
{
Console.WriteLine(res.MemberNames +":"+ res.ErrorMessage);
}
}
Console.ReadLine();
}
Currently I'm developing .net 4.5 wpf MVVM application with validation system handled by INotifyDataErrorInfo. At some point in application I have to check if there are any validation errors, currently it's done like this:
public class RootViewModel : BindableBase
{
//class code
if (designInformation.Technology == Technology.CVT)
{
if (designInformation.HasErrors) return;
if (InfoInputViewModel.TrafoProperties.HasErrors) return;
if (InfoInputViewModel.CapacitorVoltageTransformerViewModel.CapacitorVoltageDivider.HasErrors) return;
if (InfoInputViewModel.CapacitorVoltageTransformerViewModel.IntermediateVoltageTransformer.HasErrors) return;
if (SpecialDesignViewModel.SpecialDesignInformation.HasErrors) return;
foreach (var item in InfoInputViewModel.SecondaryWindings.WindingsCollection)
{
if (item.HasErrors) return;
}
performCalculationsCVT();
}
}
And I'm looking for a way to simplify this code by getting all errors from model at once, but don't know where to start with this problem.
Bellow is implementation of INotifyDataErrorInfo interface i use.
public class ValidableBase : BindableBase, INotifyDataErrorInfo
{
protected readonly Dictionary<string, ICollection<string>>
_validationErrors = new Dictionary<string, ICollection<string>>();
#region INotifyDataErrorInfo Implementation
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
protected void RaiseErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) || !_validationErrors.ContainsKey(propertyName))
return null;
return _validationErrors[propertyName];
}
public bool HasErrors
{
get { return _validationErrors.Count > 0; }
}
public void AddError(string propertyName, string message)
{
if (_validationErrors.ContainsKey(propertyName))
{
string value = _validationErrors[propertyName].First();
value += Environment.NewLine;
value += message;
_validationErrors[propertyName] = new List<string> { value };
}
else
_validationErrors[propertyName] = new List<string> { message };
RaiseErrorsChanged(propertyName);
}
public void RemoveError(string propertyName)
{
_validationErrors.Remove(propertyName);
RaiseErrorsChanged(propertyName);
}
[XmlIgnore]
public Dictionary<string, ICollection<string>> ValidationErrors
{
get { return this._validationErrors; }
}
#endregion
}
}
Obviously, the base class has no idea what properties a particular child class has, let alone if they implement INDEI. You'll have to write the logic to do this. There are many ways you can accomplish this.
For me, I'd add an abstract method to the base class thusly
abstract class ValidableBase
{
// snip
protected abstract IEnumerable<ValidableBase> GetValidableProperties();
// snip
Then change HasErrors to call HasErrors recursively on the results of the above call
public bool HasErrors
{
get { return _validationErrors.Count > 0 ||
GetValidableProperties().Any(x => x.HasErrors); }
}
An example implementation of GetValidableProperties might be
protected override IEnumerable<ValidableBase> GetValidableProperties()
{
yield return SomeProperty; // a Validable property
yield return SomeOtherProperty; // this too
foreach(var prop in SomeCollectionProperty) // collection of Validable
yield return prop;
}
Lastly, I'd rename Validable to Validatable, which is the correct (english) spelling. If I were Spanish or French, I'd probably skip that last step.
I'm using the INotifyDataErrorInfo interface to implement a general MVVM validation mechanism. I'm implementing the interface by calling OnValidate instead of OnPropertyChanged:
public void OnValidate(dynamic value, [CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
Validate(propertyName, value);
}
In the Validate Method I'm generating the validation errors, add them to a Dictionary and raise the ErrorsChanged event if a validation error was found or cleared:
if (entry.Validate(strValue, out errorNumber, out errorString) == false)
{
_validationErrors[propertyName] = new List<string> {errorString};
RaiseErrorsChanged(propertyName);
}
else if (_validationErrors.ContainsKey(propertyName))
{
_validationErrors.Remove(propertyName);
RaiseErrorsChanged(propertyName);
}
The HasErrors property is implemented by looking at the errors dictionary:
public bool HasErrors
{
get { return _validationErrors.Any(kv => kv.Value != null
&& kv.Value.Count > 0); }
}
To prevent the save button from being enabled when there is a validation error - The save command canExecuteMethod looks at the HasErrors property:
private bool IsSaveEnabled()
{
return HasErrors == false;
}
Everything works fine except the case where I'm having binding errors - if the binded value is (for example) an integer a non integer is entered - the textbox's ErrorContent is updated with an error string: "Value 'something' could not be converted".
But the INotifyDataErrorInfo mechanism is not updated about this. The HasErrors remains false and Save is enabled although there is an error in the view.
I would like to find a way to propagate the binding exception to the INotifyDataErrorInfo mechanism so I would be able to:
Disable the Save button (must).
Change the validation error message to a more meaningful error string (nice to have).
I would like to find a general MVVM solution without adding code behind in the view.
Thank you for the help
the string int case doesn't work with MVVM because your viewmodel doesn't get any information because of the binding exception.
I see two ways to get the validation you want:
Just use string properties in your viewmodel and when you have to go to your model just convert the string to your model type.
Create behaviors or "special" controls so the the input in your view is always "convertible" to your viewmodel type.
Btw I use the second approach because I have to :) but the first will always work and seems easier to me.
Here is the solution that I have found. It makes the INotifyDataErrorInfo behave correctly in the ViewModel (When there is any validation error – the HasError is true), and it allows adding validation errors from the viewModel. Other than this, it does not require changes in the view, changes in the binding or converters.
This solution involves:
Adding a custom validation rule.
Adding a base user control (which all view must derive from).
Adding some code in the ViewModel base.
Adding a custom validation rule – Validation Entity which does the actual validation and raises an event when the validation changes:
class ValidationEntity : ValidationRule
{
public string Key { get; set; }
public string BaseName = "Base";
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
var fullPropertyName = BaseName + "." + Key;
ValidationEntry entry;
var validationResult = new ValidationResult(true, null);
if ((entry = ValidationManager.Instance.FindValidation(fullPropertyName)) != null)
{
int errorNumber;
string errorString;
var strValue = (value != null) ? value.ToString() : string.Empty;
if (entry.Validate(strValue, out errorNumber, out errorString) == false)
{
validationResult = new ValidationResult(false, errorString);
}
}
if (OnValidationChanged != null)
{
OnValidationChanged(Key, validationResult);
}
return validationResult;
}
public event Action<string, ValidationResult> OnValidationChanged;
}
Adding a base user control which keeps a list of the active textboxs, and adds the validation rule to each textbox binding:
This is the code at the user control base:
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
_textBoxes = FindAllTextBoxs(this);
var vm = DataContext as ViewModelBase;
if (vm != null) vm.UpdateAllValidationsEvent += OnUpdateAllValidationsEvent;
foreach (var textbox in _textBoxes)
{
var binding = BindingOperations.GetBinding(textbox, TextBox.TextProperty);
if (binding != null)
{
var property = binding.Path.Path;
var validationEntity = new ValidationEntity {Key = property};
binding.ValidationRules.Add(validationEntity);
validationEntity.ValidationChanged += OnValidationChanged;
}
}
}
private List<TextBox> FindAllTextBoxs(DependencyObject fe)
{
return FindChildren<TextBox>(fe);
}
private List<T> FindChildren<T>(DependencyObject dependencyObject)
where T : DependencyObject
{
var items = new List<T>();
if (dependencyObject is T)
{
items.Add(dependencyObject as T);
return items;
}
var count = VisualTreeHelper.GetChildrenCount(dependencyObject);
for (var i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var children = FindChildren<T>(child);
items.AddRange(children);
}
return items;
}
When the ValidationChange event happens – the view is called to be notified about the validation error:
private void OnValidationChanged(string propertyName, ValidationResult validationResult)
{
var vm = DataContext as ViewModelBase;
if (vm != null)
{
if (validationResult.IsValid)
{
vm.ClearValidationErrorFromView(propertyName);
}
else
{
vm.AddValidationErrorFromView(propertyName, validationResult.ErrorContent as string);
}
}
}
The ViewModel base keeps two lists:
_notifyvalidationErrors which is used by the INotifyDataErrorInfo interface to display the validation errors.
_privateValidationErrors which is used to display the errors generated from the Validation rule to the user.
When adding a validation error from the view – the _notifyvalidationErrors is updated with an empty value (just to denote there is a validation error) the error string is not added to the _notifyvalidationErrors. If we add it to there we would get the validation error string twice in the textbox ErrorContent.
The validation error string is also added to _privateValidationErrors (Because we want to be able to keep it at the viewmodel)
This is the code at the ViewModel base:
private readonly Dictionary<string, List<string>> _notifyvalidationErrors =
new Dictionary<string, List<string>>();
private readonly Dictionary<string, List<string>> _privateValidationErrors =
new Dictionary<string, List<string>>();
public void AddValidationErrorFromView(string propertyName, string errorString)
{
_notifyvalidationErrors[propertyName] = new List<string>();
// Add the error to the private dictionary
_privateValidationErrors[propertyName] = new List<string> {errorString};
RaiseErrorsChanged(propertyName);
}
public void ClearValidationErrorFromView(string propertyName)
{
if (_notifyvalidationErrors.ContainsKey(propertyName))
{
_notifyvalidationErrors.Remove(propertyName);
}
if (_privateValidationErrors.ContainsKey(propertyName))
{
_privateValidationErrors.Remove(propertyName);
}
RaiseErrorsChanged(propertyName);
}
The INotifyDataErrorInfo implementation in the view:
public bool HasErrors
{
get { return _notifyvalidationErrors.Any(kv => kv.Value != null); }
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void RaiseErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
{
List<string> errorsForProperty;
_notifyvalidationErrors.TryGetValue(propertyName, out errorsForProperty);
return errorsForProperty;
}
The user has an option to add validation errors from the view by calling the ViewModelBase AddValidationError and ClearValidationError methods.
public void AddValidationError(string errorString, [CallerMemberName] string propertyName = null)
{
_notifyvalidationErrors[propertyName] = new List<string>{ errorString };
RaiseErrorsChanged(propertyName);
}
public void ClearValidationError([CallerMemberName] string propertyName = null)
{
if (_notifyvalidationErrors.ContainsKey(propertyName))
{
_notifyvalidationErrors.Remove(propertyName);
RaiseErrorsChanged(propertyName);
}
}
The view can get a list of all current validation errors from the ViewModel base by calling the GetValidationErrors and GetValidationErrorsString methods.
public List<string> GetValidationErrors()
{
var errors = new List<string>();
foreach (var key in _notifyvalidationErrors.Keys)
{
errors.AddRange(_notifyvalidationErrors[key]);
if (_privateValidationErrors.ContainsKey(key))
{
errors.AddRange(_privateValidationErrors[key]);
}
}
return errors;
}
public string GetValidationErrorsString()
{
var errors = GetValidationErrors();
var sb = new StringBuilder();
foreach (var error in errors)
{
sb.Append("● ");
sb.AppendLine(error);
}
return sb.ToString();
}
Set
ValidatesOnExceptions="True"
In your Binding expression.
Per the below sample code posted I am able to see the values as Risk and Default in the dropdown.
But since I have a setting [DefaultValue("Risk")] above the property named "DummyProperty" I would expect the Risk value selected in the Property Grid Dropdown. But it’s not happening. What am I missing here?
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
string sDummy;
[DefaultValue("Risk")]
[Category("Test")]
[ParamDesc("SystemType")]
[TypeConverter(typeof(PropertyGridTypeConverter))]
public String DummyProperty
{
get { return sDummy; }
set { sDummy = value; }
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class ParamDesc : Attribute
{
public ParamDesc(string PD)
{ PropDesc = PD; }
public string PropDesc
{ get; set; }
}
class PropertyGridTypeConverter : TypeConverter
{
List<string> lst = new List<string>();
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
if (context != null)
{
AttributeCollection ua = context.PropertyDescriptor.Attributes;
ParamDesc cca = (ParamDesc)ua[typeof(ParamDesc)];
switch (cca.PropDesc)
{
case "SystemType":
lst = new List<string> {"Risk", "Default"};
break;
case "DateType":
lst = new List<string> {"Daily", "Monthly"};
break;
}
}
lst.Sort();
return new StandardValuesCollection(lst);
}
}
Somewhat confusingly, the DefaultValue custom attribute isn't used to set default values on properties like you want. In fact, it isn't directly used by the runtime at all. It's intended instead for use by the Visual Studio designer.
You'll probably just want to initialize the value elsewhere (such as in the UserControl1 constructor).
More information here:
.Net DefaultValueAttribute on Properties
I have 2 classes:
public class CustomerViewModel {
public SystemViewModel system { get;set; }
}
public class SystemViewModel {
public bool isReadOnly { get; set; }
}
On the method controller action I have a custom filter attribute which executes some code and determines whether or the user has ReadOnly or Write access. This attribute can be applied to multiple actions across multiple controllers.
So far using reflection I can get access to the model using:
var viewModel = filterContext.Controller.ViewData.Model;
I can not cast this model to CustomerViewModel because on a different action it might be something like SalaryViewModel. What I do know is that any model that requires the readonly property will have SystemViewModel property.
From my custom filter I need a way to be able to change the value of readonly.
So far I have this:
public override void OnActionExecuted(ActionExecutedContext filterContext) {
var viewModel = filterContext.Controller.ViewData.Model;
var systemViewModelPropertyInfo = model.GetType()
.GetProperties()
.FirstOrDefault(p => p.PropertyType == typeof(SystemViewModel));
if (systemViewModelPropertyInfo != null) {
// Up to here, everything works, systemViewModelPropertyInfo is of
// type PropertyInfo, and the systemViewModelPropertyInfo.PropertyType
// shows the SystemViewModel type
// If we get here, the model has the system property
// Here I need to try and set the IsReadOnly property to true/false;
// This is where I need help please
}
}
SOLVED
Thanks to everyone who pitched in to help solve this. Special thanks to Julián Urbano for having the solution I was looking for.
Here is my resulting code from within my filter:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
try
{
var viewModel = filterContext.Controller.ViewData.Model;
var systemViewModelPropertyInfoCount = viewModel.GetType().GetProperties().Count(p => p.PropertyType == typeof(SystemViewModel));
if(systemViewModelPropertyInfoCount == 1)
{
var systemViewModelPropertyInfo = viewModel.GetType().GetProperties().First(p => p.PropertyType == typeof(SystemViewModel));
if(systemViewModelPropertyInfo != null)
{
var systemViewModel = systemViewModelPropertyInfo.GetValue(viewModel, null) as SystemViewModel;
if(systemViewModel != null)
{
var admin = GetAdmin(filterContext.HttpContext.User.Identity.Name);
if(admin != null && _adminService.HasPermission(admin, _privilege, Access.Level.ReadWrite))
systemViewModel.ReadOnly = false;
else
systemViewModel.ReadOnly = true;
}
}
} else if(systemViewModelPropertyInfoCount > 1)
{
throw new Exception("Only once instance of type SystemViewModel allowed");
}
}
catch (Exception exception)
{
Log.Error(MethodBase.GetCurrentMethod(), exception);
filterContext.Controller.TempData["ErrorMessage"] = string.Format("Technical error occurred");
filterContext.Result = new RedirectResult("/Error/Index");
}
finally
{
base.OnActionExecuted(filterContext);
}
}
I can not cast this model to CustomerViewModel because on a different action it might be something like SalaryViewModel. What I do know is that any model that requires the readonly property will have SystemViewModel property.
option 1
Seems to me that the best option is to write an interface like:
public interface IWithSystemViewModel {
SystemViewModel System {get;}
}
and implement it from your classes, much like:
public class CustomerViewModel : IWithSystemViewModel{
public SystemViewModel System { get;set; }
}
public class SalaryViewModel : IWithSystemViewModel{
public SystemViewModel System { get;set; }
}
so you can cast it and access the isReadOnly property:
IWithSystemViewModel viewModel = filterContext.Controller.ViewData.Model as IWithSystemViewModel;
if(viewModel!=null){
viewModel.System.isReadOnly ...
}
option 2
If you want to stick to using reflection:
var viewModel = filterContext.Controller.ViewData.Model;
SystemViewModel theSystem = viewModel.GetType().GetProperty("system")
.GetValue(viewModel, null) as SystemViewModel;
theSystem.isReadOnly ...
Tricky thing: in your code, you select the property whose type is SystemViewModel. But what if the object actually has several SystemViewModel properties that you don't know about? Are you sure you're accessing the proper one? You may force all of them to use the same name, but then again, that would be like using the interface in option 1 above.
I'd definitely go with option 1.
var viewModel = new CustomerViewModel();
var systemViewModelPropertyInfo = viewModel.GetType()
.GetProperties()
.FirstOrDefault(p => p.PropertyType == typeof(SystemViewModel));
if (systemViewModelPropertyInfo != null) {
var systemViewModelProperty = systemViewModelPropertyInfo.GetValue(viewModel, null) as SystemViewModel;
// get the desired value of isReadOnly here...
var isReadOnly = false;
// here, systemViewModelProperty may be null if it has not been set.
// You can decide what to do in that case. If you need a value to be
// present, you'll have to do something like this...
if (systemViewModelProperty == null) {
systemViewModelPropertyInfo.SetValue(viewModel, new SystemViewModel { isReadOnly = isReadOnly }, null);
}
else {
systemViewModelProperty.isReadOnly = isReadOnly;
}
}
That said, this whole process would probably be easier if you implemented an interface...
public interface IHaveSystemViewModel {
SystemViewModel system { get; set; }
}
var model = viewModel as IHaveSystemViewModel;
if (model != null) {
// again, you need to make sure you actually have a reference here...
var system = model.system ?? (model.system = new SystemViewModel());
system.isReadOnly = false; // or true
}