How to provide validation for child classes that implement different properties? - c#

I have a parent class implementing INotifyPropertyChanged, and the parent class has multiple children. The children have different properties that all call PropertyChanged. I want to add validation, but I really don't want to have to write validations for every child class. The validation rules are supplied from the database, so I would have to eventually pull the validation rules for each child, then check the value against the rules. If I did that, I think that it would have too much redundant code, and I would like to have it placed at the parent level since PropertyChanged triggers on the string value of the value itself.
Is it possible to have the validation method on the parent class so I wouldn't have to write a validation method for every child class? Mind you, the properties in every child class are different.
Below is what I currently have, with validation in the child class.
public Parent : INotifyChanged {
/// <summary>
/// Occurs when a property is changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged"/> for a given
/// property.
/// </summary>
/// <param name="propertyName"></param>
protected void OnPropertyChanged(String propertyName) {
// Get the hanlder
PropertyChangedEventHandler handler = this.PropertyChanged;
// Check that the event handler is not null
if(null != handler) {
// Fire the event
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Child1 Class:
public Child1 : Parent, IDataErrorInfo {
private Dictionary<string, string> m_validationErrors = new Dictionary<string, string>();
private void Validate() {
this.RemoveError("Child1Description");
if(!Regex.IsMatch(Child1Description, "^([a-zA-Z '-]+)$") && !String.IsNullOrWhiteSpace(Description)) {
this.AddError("Child1Description", "Only non-numerics allowed.");
}
}
private void AddError(string columnName, string msg) {
if(!m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Add(columnName, msg);
}
}
private void RemoveError(string columnName) {
if(m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Remove(columnName);
}
}
public string Error {
get {
if(m_validationErrors.Count > 0) {
return "Field data is invalid.";
}
else return null;
}
}
public string this[string columnName] {
get {
if(m_validationErrors.ContainsKey(columnName)) {
return m_validationErrors[columnName];
}
else {
return null;
}
}
}
/// <summary>
/// Description of the air entity
/// </summary>
public string Child1Description {
get {
return Child1description;
}
set {
description = value;
Validate();
OnPropertyChanged("Child1Description");
}
}
}
Child2 Class:
public Child2 : Parent, IDataErrorInfo {
private Dictionary<string, string> m_validationErrors = new Dictionary<string, string>();
private void Validate() {
this.RemoveError("Child2Description");
if(!Regex.IsMatch(Child2Description, "^([a-zA-Z '-]+)$") && !String.IsNullOrWhiteSpace(Description)) {
this.AddError("Child2Description", "Only non-numerics allowed.");
}
}
private void AddError(string columnName, string msg) {
if(!m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Add(columnName, msg);
}
}
private void RemoveError(string columnName) {
if(m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Remove(columnName);
}
}
public string Error {
get {
if(m_validationErrors.Count > 0) {
return "Field data is invalid.";
}
else return null;
}
}
public string this[string columnName] {
get {
if(m_validationErrors.ContainsKey(columnName)) {
return m_validationErrors[columnName];
}
else {
return null;
}
}
}
/// <summary>
/// Description of the air entity
/// </summary>
public string Child2Description {
get {
return Child2description;
}
set {
description = value;
Validate();
OnPropertyChanged("Child2Description");
}
}
}

I don't believe you're going to be able to do what you want.
In the trivial case, you may be able to make it work. Once you move into more complex types, I don't know that you're saving yourself much effort by having the validation in the parent instead of the child.
The trivial case is going to be where multiple children have similar types of properties. You can enforce calling the properties the same way and then you can write a validation rule within the parent that triggers on the name of the property. However, you could argue that those properties should then be part of the parent and inherited by the child.
The more complex case is individual properties at each child with little to no similarity to the properties of other children. Whether you put the validation code at the child or the parent makes no difference. You have to write the validation code for each individual property you wish to validate.
Given that your validation rules will be stored in a DB, you could write a method in the parent that would allow the children to retrieve the validation rule(s) for their properties. The child would still validate its own properties, but you would have common code for accessing the rules.

I ended up passing the property name, the property value and the list of rules I wanted to use to validate. Storing the Validate method in the parent, so it would run regardless if which child used it, it would use its own rules, but keep the same validation error message dictionary.
protected void Validate(string propertyName, string propertyValue, List<ValidRule> validRules) {
string temp = propertyValue.ToString();
this.RemoveError(propertyName);
if(propertyName.Equals("Description")) {
foreach(ValidRule validRule in validRules) {
if(!Regex.IsMatch(propertyValue, validRule.Rule) && !String.IsNullOrWhiteSpace(propertyValue)) {
this.AddError(propertyName, validRule.ErrorMessage);
break;
}
}
}
}

Actually it is do able just not the way you think you want it to occur.
The below would be the steps I would follow to do something similar.
get the Microsoft enterprise library for starters as you will be using the Microsoft.Practices.EnterpriseLibrary.Validation reference.
Create a validation class that inherits from Validator<T> (this is part of the Enterprise library).
Override the method DoValidate(T objectToValidate, object currentTarget, string key, ValidationResults validationResults) and currentTarget is the object being validated. You can pull the validation rules from the current target.
You then create attribute for that validate making it inherit from ValueValidatorAttribute.
You override the method DoCreateValidator(Type targetType, Type ownerType, MemberValueAccessBuilder memberValueAccessBuilder, ValidatorFactory validatorFactory) in the attribute class.
Once these first 5 steps are done it means you can attribute properties you want to validate and let the validator pick up the validation rule to use from the class (list of rules or dictionary or rules to perform to the property totally your choice).
The next step is to move the interface IDataErrorinfo to the parent class, create a Validate() method that takes the results from the call Microsoft.Practices.EnterpriseLibrary.Validation.Validation.Validate(this); which returns validation errors should they have occurred.
Its up to you on how where you want to place the method call. The simplest way when testing would be to place it in the OnPropertyChanged method you have.

Related

INotifyPropertyChanged with Properties that have the same name

I'm working on a system to represent data. In it we use a templetized interface that implements INotifyPropertyChanged.
public interface IScalar<T> : ISignal, INotifyPropertyChanged
{
void Check(T value);
/// <summary>
/// Formats the specified value based on the item's formatting
/// characteristics. Will throw an exception if the specified value
/// could not be properly converted to the underlying type.
/// </summary>
/// <param name="value">Value to format.</param>
/// <returns>Formatted value.</returns>
string Format(T value);
T Value { get; set; }
string Units { get; set; }
}
We end having a class that implements IScalar<double> and IScalar<string>. Is there a way to make sure the correct PropertyChanged event is fired? It uses a string representation of the property name. And since I have two properties with the same name I can't guarantee the right event will be fired. We are looking to have a grid in WPF bind to a list of IScalar
You can't implicitly implement an generic interface with two different type parameters. You have to make at least one explicit. Here you see a sample implementation for your class. As you can see you can bind to StringValue and DoubleValue:
public class Both : IScalar<string>, IScalar<double>
{
public string StringValue { get; set; }
string IScalar<string>.Value
{
get
{
return StringValue;
}
set
{
this.StringValue = value;
}
}
public double DoubleValue { get; set; }
double IScalar<double>.Value
{
get
{
return DoubleValue;
}
set
{
DoubleValue = value;
}
}
// other methods and properties left out
}
When you need to raise PropertyChanged, you can raise that event for either StringValue or DoubleValue.
You can't have two properties with the same name on your datacontext.
If you did, you would have a compile error reflecting ambiguity.
Remember that your source is your datacontext.
In addition, the databinding system relies on both source and path in order to perform databinding.
Apart from Scott's proper answer, you may also want to get away from calling the PropertyChanged Method by passing in strings. Here's how you do that:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
INPC using member/method name is now supported in the attribute class CallerMemberName found in the System.runtime.compilerservices assembly.
You can read more about it here.
ADVANTAGES:
It allows you to easily establish a base class once and for all that handles all notifications based only on the method name. The setter methods merely have this line of code:
OnPropertyChanged();

How to validate view controls against data in the viewmodel

I'm new at WPF, and I'm getting my head around validators, it seems that you need to inherit from ValidationRule and override the Validate function, this totally separated from the view model, but what if I want to validate against some list/collection/set/dictionary in the viewmodel, to check whether this new input is not already in the list, a good example would be creating a validation to see whether or not an username is not already taken.
There are several different ways to do Validation in WPF. There's two main ways I can think of off the top of my head
Create Validation Rules then apply them in XAML
Implement IDataErrorInfo in your ViewModel
Validation Rules are specified in your XAML (GUI), while implementing IDataErrorInfo moves the Validation logic into your ViewModel (Business logic). While, ValidationRules are nice because you can create your own and reuse them, they also fail to provide validation in your business logic which is most likely required.
The concept of Client vs. Server side validation is interesting, perhaps as it pertains to Silverlight, but since you tagged this as WPF, I'm assuming the only difference is whether the validation occurs in the Views or ViewModels (UI or Business logic). It seem to me that even if your UI validated inputs, your ViewModels would still need to do proper validation.
Therefore, I suggest implementing IDataErrorInfo. By the way, the reason IDataErrorInfo works, is because a ValidationRule exists that checks for the IDataErrorInfo Interface! Here's an example of how I would do it in my ViewModelBase class:
Note: the immediately following examples ignore the fact you will likely need INotifyPropertyChanged notifications to update your bindings and instead focuses simply on Validation.
public class ViewModelBase : IDataErrorInfo
{
private Dictionary<string, string> errors = new Dictionary<string, string>();
// required for IDataErrorInfo
public virtual string Error
{
get { return String.Join(Environment.NewLine, errors); }
}
// required for IDataErrorInfo
public string this[string propertyName]
{
get
{
string result;
errors.TryGetValue(propertyName, out result);
return result;
}
}
// Useful property to check if you have errors
public bool HasErrors
{
get
{
return errors.Count > 0;
}
}
protected void SetError<T>(string propertyName, String error)
{
if (error == null)
errors.Remove(propertyName);
else
errors[propertyName] = error;
OnHasErrorsChanged();
}
protected string GetError<T>(string propertyName, String error)
{
String s;
errors.TryGetValue(propertyName, out s);
return s;
}
protected virtual void OnHasErrorsChanged()
{
}
}
Then your ViewModels can implement it like this:
public class MyViewModel : ViewModelBase
{
private string someProperty;
public string SomeProperty
{
get
{
return someProperty;
}
set
{
if(someProperty != null)
{
someProperty = value;
SetError("SomeProperty", ValidateSomeProperty());
}
}
}
private string ValidateSomeProperty()
{
if(String.IsNullOrEmpty(SomeProperty))
return "Value is required";
return null;
}
}
In your UI, you'll need to add ValidatesOnDataErrors and NotifyOnValidationError like this:
Text="{Binding SomeProperty, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
Note: Passing in strings to represent properties is kinda ugly (it's not refactor safe if you rename the property but forget to rename the string). INotifyPropertyChanged is the same way when you want to notify of property changes for DataBindings. Prism's NotificationObject has a refactor safe solution to this and it looks like this instead:
Replace GetError / SetError in the previous example with this:
protected void SetError<T>(Expression<Func<T>> prop, String error)
{
String propertyName = PropertySupport.ExtractPropertyName(prop);
if (error == null)
errors.Remove(propertyName);
else
errors[propertyName] = error;
OnHasErrorsChanged();
}
protected string GetError<T>(Expression<Func<T>> prop, String error)
{
String propertyName = PropertySupport.ExtractPropertyName(prop);
String s;
errors.TryGetValue(propertyName, out s);
return s;
}
And then my ViewModelBase is something like this:
public class ViewModelBase : NotificationObject, IDataErrorInfo
Then to implement properties:
public class MyViewModel : ViewModelBase
{
private string someProperty;
public string SomeProperty
{
get
{
return someProperty;
}
set
{
if(someProperty != null)
{
someProperty = value;
SetError( () => SomeProperty, ValidateSomeProperty()); // update validation for property
RaisePropertyChanged( () => SomeProperty); // notify data bindings
}
}
}
I didn't show the implementation for RaisePropertyChanged but it is in Prism's NotificationObject which is open source and a free download. You can instead implement INotifyPropertyChanged yourself and either raise the event with strings (not refactor safe) or implement it yourself similar to the above implemention for SetError (extract the property name and fire the event with it).
You'll need this helper method:
public static class PropertySupport
{
public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpresssion)
{
if (propertyExpresssion == null)
{
throw new ArgumentNullException("propertyExpression");
}
var memberExpression = propertyExpresssion.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
}
var property = memberExpression.Member as PropertyInfo;
if (property == null)
{
throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
}
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
{
throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
}
return memberExpression.Member.Name;
}
}
Edit: Simplified Refactor Safe Alternative
If you are using .NET 4.5 or later, you can use the CallerMemberAttribute like this example shows for INotifyPropertyChanged and my SetError implementation. Then you wouldn't need to extract the property name yourself via reflection as above (It simplifies it quite a bit).
Sorry to get off track talking about Property Change Notifications, but they go hand in hand if you want your DataBindings and Validation to work!
I Finally did it, this is how :
Xaml:
<Window.Resources>
<l:DataResource x:Key="ValidateFieldMethod" BindingTarget="{Binding IsFieldValid}"/>
</Window.Resources>
<xctk:IntegerUpDown Width="50" Maximum="300" Minimum="0">
<xctk:IntegerUpDown.Value>
<Binding Path="SelectedItem.TargetPosition" Mode="TwoWay">
<Binding.ValidationRules>
<l:CustomValidationRule Validator="{l:DataResourceBinding DataResource={StaticResource ValidateFieldMethod}}" />
</Binding.ValidationRules>
</Binding>
</xctk:IntegerUpDown.Value>
</xctk:IntegerUpDown>
The ValidatorRule:
public delegate bool CheckAgainstDataDelegate(object newValue, string fieldName);
public class CustomValidationRule : ValidationRule
{
public CheckAgainstDataDelegate Validator { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingGroup owner)
{
return Validate((object)owner, cultureInfo);
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
{
switch (ValidationStep)
{
case ValidationStep.UpdatedValue:
case ValidationStep.CommittedValue:
value = (object)owner;
break;
}
return new ValidationResult(Validator(value, ((BindingExpression) owner).ResolvedSourcePropertyName), null);
}
[Obsolete("Use Validator property of type delegate instead to validate the data",true)]
public override ValidationResult Validate(object value, CultureInfo cultureInfo) { return null; } //not used anymore
}
In the viewModel :
private CheckAgainstDataDelegate _isFieldValid;
public CheckAgainstDataDelegate IsFieldValid
{
get
{
return _isFieldValid
?? (_isFieldValid = delegate (object newValue,string propertyName)
{
switch (propertyName)
{
case "TargetPosition":
var newV = (int) newValue;
return Items.All(e => e.TargetPosition != newV);
default:
throw new Exception("Property Assigned to unknown field");
}
});
}
}
I used the help of http://www.wpfmentor.com/2009/01/how-to-add-binding-to-property-on.html to Bind in the ValidationrRule.
What do you Think?

Capture property change using its setter

I want to be notified when a property changes so that I can log the oldvalue and new value of the property in database.
So I decided to go with the approach of property setter and have a generic method that handles all properties.
I created below class:
public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
public virtual T OldValue { get; private set; }
public virtual T NewValue { get; private set; }
public PropertyChangedExtendedEventArgs(string propertyName,
T oldValue, T newValue)
: base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
//write to database the values!!!
}
}
and on my property I call it as such:
private string _surname;
public string Surname
{
get { return _surname; }
set
{
string temp = Surname;
_surname = value;
Helper.PropertyChangedExtendedEventArgs("Surname", temp, value);
}
}
but it is first time working with generics so got few concerns :
how do I call this on my property?
is this a good approach?
would I be able to call a function in public
PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue) and save to database?
You seem to have got a bit of confused in property change usage.
Typically, components that wish to be observable about their property changes INotifyPropertyChanged interface. So as such correct implementation would be something like
private string _surname;
public string Surname
{
get { return _surname; }
set
{
if (_surname != value) // IMP: you want to inform only if value changes
{
string temp = Surname;
_surname = value;
// raise property change event,
NotifyPropertyChanged(temp, _surname);
}
}
}
Typically, base implementation could provide helper implementation to raise the event - for example,
public abstract Component : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged<T>(T oldVal, T newVal, [CallerMemberName] String propertyName = "")
{
var e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedExtendedEventArgs(propertyName, oldVal, newVal));
}
}
}
Now, its consumer's responsibility on how to react to property changes. This separates observable components from unrelated concern of what to do when some property changes. Typically, one will have some the common implementation that would say - save the current object state in stacked manner as to provide undo-redo functionality.
So in your case, you wish to log them to database (?), there should be code that would listen to this property change events and does the logging. There will be some controller/binding code that would iterate through all objects implementing this interface and hook up the event. Typically, the root level container does such house keeping - for example, in a designer surface, its the root element (or code that is handling root element) would hook up event whenever a new component is created and added to the design surface.

Databinding to index property

I have a control bind to an index property of an object which implements INotifyPropertyChanged.
The problem is, I don't know how to notify the property changed signal for that particular index string.
I was told that I can use OnPropertyChanged("") to notify the whole object need to be changed.
But what I need is something like OnPropertyChanged("Some index property string").
Is there anyway to do it?
Many thanks.
ps:
What I am trying to do is apply MVVM pattern.
I use a viewmodel class to wrap a normal POCO object. So when I bind, I bind to the [index property], so that I can notify changed. This method saves me from:
wrap the inner domain POCO object for EVERY property I need.
notify property changed in every wrapped property.
CODE
public class ViewModelEx<T_Self, T_Core> : ViewModelEx<T_Self> where T_Self : ViewModelEx<T_Self, T_Core>
{
private static Type _s_coreType = typeof(T_Core);
private static Dictionary<string, PropertyInfo> _s_corePropInfos = new Dictionary<string, PropertyInfo>();
private static PropertyInfo GetPropertyInfo(string prop)
{
if (_s_corePropInfos.ContainsKey(prop) == false)
_s_corePropInfos.Add(prop, _s_coreType.GetProperty(prop));
return _s_corePropInfos[prop];
}
public T_Core Core { get; set; }
public object this[string propName]
{
get
{
return GetPropertyInfo(propName).GetValue(Core, null);
}
set
{
GetPropertyInfo(propName).SetValue(Core, value, null);
IsModified = true;
//RaisePropertyChanged(propName);
RaisePropertyChanged("");
}
}
public R Val<R>(Expression<Func<T_Core, R>> expr)
{
return (R)this[Core.GetPropertyStr(expr)];
}
public void Val<R>(Expression<Func<T_Core, R>> expr, R val)
{
this[Core.GetPropertyStr(expr)] = val;
}
You cannot create notifications for specific index bindings in WPF, you can only notify all index-bindings:
RaisePropertyChanged(Binding.IndexerName);
Which should be the same as:
RaisePropertyChanged("Item[]");
You could override this string using the IndexerNameAttribute.
(In Silverlight you can actually specify an index inside the brackets to only affect that specific binding.)

c# marking class property as dirty

The following is a simple example of an enum which defines the state of an object and a class which shows the implementation of this enum.
public enum StatusEnum
{
Clean = 0,
Dirty = 1,
New = 2,
Deleted = 3,
Purged = 4
}
public class Example_Class
{
private StatusEnum _Status = StatusEnum.New;
private long _ID;
private string _Name;
public StatusEnum Status
{
get { return _Status; }
set { _Status = value; }
}
public long ID
{
get { return _ID; }
set { _ID = value; }
}
public string Name
{
get { return _Name; }
set { _Name = value; }
}
}
when populating the class object with data from the database, we set the enum value to "clean". with the goal of keeping most of the logic out of the presentation layer, how can we set the enum value to "dirty" when a property is changed.
i was thinking something along the lines of;
public string Name
{
get { return _Name; }
set
{
if (value != _Name)
{
_Name = value;
_Status = StatusEnum.Dirty;
}
}
}
in the setter of each property of the class.
does this sound like a good idea, does anyone have any better ideas on how the dirty flag can be assigned without doing so in the presentation layer.
When you really do want a dirty flag at the class level (or, for that matter, notifications) - you can use tricks like below to minimise the clutter in your properties (here showing both IsDirty and PropertyChanged, just for fun).
Obviously it is a trivial matter to use the enum approach (the only reason I didn't was to keep the example simple):
class SomeType : INotifyPropertyChanged {
private int foo;
public int Foo {
get { return foo; }
set { SetField(ref foo, value, "Foo"); }
}
private string bar;
public string Bar {
get { return bar; }
set { SetField(ref bar, value, "Bar"); }
}
public bool IsDirty { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void SetField<T>(ref T field, T value, string propertyName) {
if (!EqualityComparer<T>.Default.Equals(field, value)) {
field = value;
IsDirty = true;
OnPropertyChanged(propertyName);
}
}
protected virtual void OnPropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You might also choose to push some of that into an abstract base class, but that is a separate discussion
One option is to change it on write; another is to keep a copy of all the original values and compute the dirtiness when anyone asks for it. That has the added benefit that you can tell exactly which fields have changed (and in what way) which means you can issue minimal update statements and make merge conflict resolution slightly easier.
You also get to put all the dirtiness-checking in one place, so it doesn't pollute the rest of your code.
I'm not saying it's perfect, but it's an option worth considering.
If you want to implement it in this way, and you want to reduce the amount of code, you might consider applying Aspect Oriented Programming.
You can for instance use a compile-time weaver like PostSharp , and create an 'aspect' that can be applied to properties. This aspect then makes sure that your dirty flag is set when appropriate.
The aspect can look like this:
[Serializable]
[AttributeUsage(AttributeTargets.Property)]
public class ChangeTrackingAttribute : OnMethodInvocationAspect
{
public override void OnInvocation( MethodInvocationEventArgs e )
{
if( e.Delegate.Method.ReturnParameter.ParameterType == typeof(void) )
{
// we're in the setter
IChangeTrackable target = e.Delegate.Target as IChangeTrackable;
// Implement some logic to retrieve the current value of
// the property
if( currentValue != e.GetArgumentArray()[0] )
{
target.Status = Status.Dirty;
}
base.OnInvocation (e);
}
}
}
Offcourse, this means that the classes for which you want to implement ChangeTracking, should implement the IChangeTrackable interface (custom interface), which has at least the 'Status' property.
You can also create a custom attribute ChangeTrackingProperty, and make sure that the aspect that has been created above, is only applied to properties that are decorated with this ChangeTrackingProperty attribute.
For instance:
public class Customer : IChangeTrackable
{
public DirtyState Status
{
get; set;
}
[ChangeTrackingProperty]
public string Name
{ get; set; }
}
This is a little bit how I see it.
You can even make sure that PostSharp checks at compile-time whether classes that have properties that are decorated with the ChangeTrackingProperty attribute, implement the IChangeTrackable interface.
This method is based on a set of different concepts provided in this thread. I thought i'd put it out there for anyone that is looking for a way to do this cleanly and efficiently, as i was myself.
The key of this hybrid concept is that:
You don't want to duplicate the data to avoid bloating and resource hogging;
You want to know when the object's properties have changed from a given original/clean state;
You want to have the IsDirty flag be both accurate, and require little processing time/power to return the value; and
You want to be able to tell the object when to consider itself clean again. This is especially useful when building/working within the UI.
Given those requirements, this is what i came up with, and it seems to be working perfectly for me, and has become very useful when working against UIs and capturing user changes accurately. I have also posted an "How to use" below to show you how I use this in the UI.
The Object
public class MySmartObject
{
public string Name { get; set; }
public int Number { get; set; }
private int clean_hashcode { get; set; }
public bool IsDirty { get { return !(this.clean_hashcode == this.GetHashCode()); } }
public MySmartObject()
{
this.Name = "";
this.Number = -1;
MakeMeClean();
}
public MySmartObject(string name, int number)
{
this.Name = name;
this.Number = number;
MakeMeClean();
}
public void MakeMeClean()
{
this.clean_hashcode = this.Name.GetHashCode() ^ this.Number.GetHashCode();
}
public override int GetHashCode()
{
return this.Name.GetHashCode() ^ this.Number.GetHashCode();
}
}
It's simple enough and addresses all of our requirements:
The data is NOT duplicated for the dirty check...
This takes into account all property changes scenarios (see scenarios below)...
When you call the IsDirty property, a very simple and small Equals operation is performed and it is fully customizable via the GetHashCode override...
By calling the MakeMeClean method, you now have a clean object again!
Of course you can adapt this to encompass a bunch of different states... it's really up to you. This example only shows how to have a proper IsDirty flag operation.
Scenarios
Let's go over some scenarios for this and see what comes back:
Scenario 1
New object is created using empty constructor,
Property Name changes from "" to "James",
call to IsDirty returns True! Accurate.
Scenario 2
New object is created using paramters of "John" and 12345,
Property Name changes from "John" to "James",
Property Name changes back from "James" to "John",
Call to IsDirty returns False. Accurate, and we didn't have to duplicate the data to do it either!
How to use, a WinForms UI example
This is only an example, you can use this in many different ways from a UI.
Let's say you have a two forms ([A] and [B]).
The first([A]) is your main form, and the second([B]) is a form that allows the user to change the values within the MySmartObject.
Both the [A] and the [B] form have the following property declared:
public MySmartObject UserKey { get; set; }
When the user clicks a button on the [A] form, an instance of the [B] form is created, its property is set and it is displayed as a dialog.
After form [B] returns, the [A] form updates its property based on the [B] form's IsDirty check. Like this:
private void btn_Expand_Click(object sender, EventArgs e)
{
SmartForm form = new SmartForm();
form.UserKey = this.UserKey;
if(form.ShowDialog() == DialogResult.OK && form.UserKey.IsDirty)
{
this.UserKey = form.UserKey;
//now that we have saved the "new" version, mark it as clean!
this.UserKey.MakeMeClean();
}
}
Also, in [B], when it is closing, you can check and prompt the user if they are closing the form with unsaved changes in it, like so:
private void BForm_FormClosing(object sender, FormClosingEventArgs e)
{
//If the user is closing the form via another means than the OK button, or the Cancel button (e.g.: Top-Right-X, Alt+F4, etc).
if (this.DialogResult != DialogResult.OK && this.DialogResult != DialogResult.Ignore)
{
//check if dirty first...
if (this.UserKey.IsDirty)
{
if (MessageBox.Show("You have unsaved changes. Close and lose changes?", "Unsaved Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
e.Cancel = true;
}
}
}
As you can see from the examples above, this can be a very useful thing to have since it really streamlines the UI.
Caveats
Every time you implement this, you have to customize it to the object you're using. E.g.: there's no "easy" generic way of doing this without using reflection... and if you use reflection, you lose efficiency, especially in large and complex objects.
Hopefully this helps someone.
Take a look at PostSharp (http://www.postsharp.org/).
You can easily create a Attribute which marks it as dirty you can add the attrubute to each property that needs it and it keeps all your code in one place.
Roughly speaking Create an interface which has your status in make the class implement it.
Create an attribute which can be applied on properties and cast to your interface in order to set the value when something changes one of the marked properties.
Your approach is basically how I would do it. I would just
remove the setter for the Status property:
public StatusEnum Status
{
get { return _Status; }
// set { _Status = value; }
}
and instead add a function
public SetStatusClean()
{
_Status = StatusEnum.Clean;
}
As well as SetStatusDeleted() and SetStatusPurged(), because I find it better indicates the intention.
Edit
Having read the answer by Jon Skeet, I need to reconsider my approach ;-) For simple objects I would stick with my way, but if it gets more complex, his proposal would lead to much better organised code.
If your Example_Class is lightweight, consider storing the original state and then comparing the current state to the original in order to determine the changes. If not your approach is the best because stroing the original state consumes a lot of system resources in this case.
Apart from the advice of 'consider making your type immutable', here's something I wrote up (and got Jon and Marc to teach me something along the way)
public class Example_Class
{ // snip
// all properties are public get and private set
private Dictionary<string, Delegate> m_PropertySetterMap;
public Example_Class()
{
m_PropertySetterMap = new Dictionary<string, Delegate>();
InitializeSettableProperties();
}
public Example_Class(long id, string name):this()
{ this.ID = id; this.Name = name; }
private void InitializeSettableProperties()
{
AddToPropertyMap<long>("ID", value => { this.ID = value; });
AddToPropertyMap<string>("Name", value => { this.Name = value; });
}
// jump thru a hoop because it won't let me cast an anonymous method to an Action<T>/Delegate
private void AddToPropertyMap<T>(string sPropertyName, Action<T> setterAction)
{ m_PropertySetterMap.Add(sPropertyName, setterAction); }
public void SetProperty<T>(string propertyName, T value)
{
(m_PropertySetterMap[propertyName] as Action<T>).Invoke(value);
this.Status = StatusEnum.Dirty;
}
}
You get the idea.. possible improvements: Use constants for PropertyNames & check if property has really changed.
One drawback here is that
obj.SetProperty("ID", 700); // will blow up int instead of long
obj.SetProperty<long>("ID", 700); // be explicit or use 700L
Here is how i do it.
In cases where i do not need to test for specific fields being dirty,
I have an abstract class:
public abstract class SmartWrap : ISmartWrap
{
private int orig_hashcode { get; set; }
private bool _isInterimDirty;
public bool IsDirty
{
get { return !(this.orig_hashcode == this.GetClassHashCode()); }
set
{
if (value)
this.orig_hashcode = this.orig_hashcode ^ 108.GetHashCode();
else
MakeClean();
}
}
public void MakeClean()
{
this.orig_hashcode = GetClassHashCode();
this._isInterimDirty = false;
}
// must be overridden to return combined hashcodes of fields testing for
// example Field1.GetHashCode() ^ Field2.GetHashCode()
protected abstract int GetClassHashCode();
public bool IsInterimDirty
{
get { return _isInterimDirty; }
}
public void SetIterimDirtyState()
{
_isInterimDirty = this.IsDirty;
}
public void MakeCleanIfInterimClean()
{
if (!IsInterimDirty)
MakeClean();
}
/// <summary>
/// Must be overridden with whatever valid tests are needed to make sure required field values are present.
/// </summary>
public abstract bool IsValid { get; }
}
}
As well as an interface
public interface ISmartWrap
{
bool IsDirty { get; set; }
void MakeClean();
bool IsInterimDirty { get; }
void SetIterimDirtyState();
void MakeCleanIfInterimClean();
}
This allows me to do partial saves, and preserve the IsDirty state if there is other details to save. Not perfect, but covers a lot of ground.
Example of usage with interim IsDirty State (Error wrapping and validation removed for clarity):
area.SetIterimDirtyState();
if (!UpdateClaimAndStatus(area))
return false;
area.MakeCleanIfInterimClean();
return true;
This is good for most scenarios, however for some classes i want to test for each field with a backing field of original data, and either return a list of changes or at least an enum of fields changed.
With an enum of fields changed i can then push that up through a message chain for selective update of fields in remote caches.
You could also think about boxing your variables, which comes at a performance cost, but also has its merits. It is pretty consise and you cannot accidentally change a value without setting your dirty status.
public class Variable<T>
{
private T _value;
private readonly Action<T> _onValueChangedCallback;
public Variable(Action<T> onValueChangedCallback, T value = default)
{
_value = value;
_onValueChangedCallback = onValueChangedCallback;
}
public void SetValue(T value)
{
if (!EqualityComparer<T>.Default.Equals(_value, value))
{
_value = value;
_onValueChangedCallback?.Invoke(value);
}
}
public T GetValue()
{
return _value;
}
public static implicit operator T(Variable<T> variable)
{
return variable.GetValue();
}
}
and then hook in a callback that marks your class as dirty.
public class Example_Class
{
private StatusEnum _Status = StatusEnum.New;
private Variable<long> _ID;
private Variable<string> _Name;
public StatusEnum Status
{
get { return _Status; }
set { _Status = value; }
}
public long ID => _ID;
public string Name => _Name;
public Example_Class()
{
_ID = new Variable<long>(l => Status = StatusEnum.Dirty);
_Name = new Variable<string>(s => Status = StatusEnum.Dirty);
}
}
Another method is to override the GetHashCode() method to somthing like this:
public override int GetHashCode() // or call it GetChangeHash or somthing if you dont want to override the GetHashCode function...
{
var sb = new System.Text.StringBuilder();
sb.Append(_dateOfBirth);
sb.Append(_marital);
sb.Append(_gender);
sb.Append(_notes);
sb.Append(_firstName);
sb.Append(_lastName);
return sb.ToString.GetHashCode();
}
Once loaded from the database, get the hash code of the object. Then just before you save check if the current hash code is equal to the previous hash code. if they are the same, don't save.
Edit:
As people have pointed out this causes the hash code to change - as i use Guids to identify my objects, i don't mind if the hashcode changes.
Edit2:
Since people are adverse to changing the hash code, instead of overriding the GetHashCode method, just call the method something else. The point is detecting a change not whether i use guids or hashcodes for object identification.

Categories