Open RehanSaeed opened 4 years ago
Nicolas commented on 2014-11-08 22:44:40
Hi,
What is RuleCollection
in this private static RuleCollection rules = new RuleCollection();
///
/// Gets the rules which provide the errors.
///
/// The rules this instance must satisfy.
protected static RuleCollection Rules
{
get { return rules; }
}
I cannot figure this out if it is a list or anything else. Can you define it some where. Everything else is awesome.
Muhammad Rehan Saeed commented on 2014-11-09 12:04:44
Hi,
What is
RuleCollection
in thisprivate static RuleCollection rules = new RuleCollection();
/// /// Gets the rules which provide the errors. /// /// The rules this instance must satisfy. protected static RuleCollection Rules { get { return rules; } }
I cannot figure this out if it is a list or anything else. Can you define it some where. Everything else is awesome.
It holds the validation rules for your view model.
For a real example look at the Validation tab in the Elysium Extra sample application. You can find the project on CodePlex, there are posts on my blog or install the Elysium.Extra NuGet package which also contains the sample application and source code.
That example shows how the above code can be used to validate input from various controls including TextBox, ComboBox and DatePicker with cool effects too.
Nicolas commented on 2014-11-10 14:42:36
It holds the validation rules for your view model.
For a real example look at the Validation tab in the Elysium Extra sample application. You can find the project on CodePlex, there are posts on my blog or install the Elysium.Extra NuGet package which also contains the sample application and source code.
That example shows how the above code can be used to validate input from various controls including TextBox, ComboBox and DatePicker with cool effects too.
Thanks
Suhas commented on 2015-06-12 16:07:17
Hey I have been reading your implementations for these classes and have been using them for development. I notice that you have implemented your INotifyErrorInfo
& INotifyPropertyChanged
as two classes rather than interfaces. This would mean that I cannot define a BaseViewModel
class that can handle Error Notifications and Property changed notifications concurrently. Am I incorrect in my assessment? How do you deal with something like this? (example Login page where you need to implement INotifyPropertyChanged
to show a Splash Screen when the password is entered & a key command is pressed but also throw exceptions for your credentials.)
By the way,Great implementation for Elysium Extra. Makes UI much easier!.
Thanks,
Suhas
Suhas commented on 2015-06-12 16:25:50
Sorry. Reread your post. You already thought of that and inherited from the class you defined earlier. My bad! This makes it much easier. Thanks a TON!
Muhammad Rehan Saeed commented on 2015-06-12 17:15:08
Sorry. Reread your post. You already thought of that and inherited from the class you defined earlier. My bad! This makes it much easier. Thanks a TON!
Good stuff. You saved me from writing a really long reply.
Rata commented on 2015-10-20 19:05:03
Hi. Very nice blog, it helped me a lot!
I got a questions:
propertyName
as string when you add Rules
can lead to typo-like mistakes? Maybe it should be done with reflection as far as we initialize Rules
only 1 time on viewmodel creation, so it will not so performance-affective but more safe for human mistakes?PropertyChanged
event in viewmodelBase
?
And if i should use second way, should i make switch based on string property name in method that process PropertyChanged
event, or there is some alternative?I ask this questions because im really afraid of human factor when i use string names instead of types and so on.
Sorry for my bad english.
Muhammad Rehan Saeed commented on 2015-10-21 17:13:30
Hi. Very nice blog, it helped me a lot!
I got a questions:
- Don't you think, that passing
propertyName
as string when you addRules
can lead to typo-like mistakes? Maybe it should be done with reflection as far as we initializeRules
only 1 time on viewmodel creation, so it will not so performance-affective but more safe for human mistakes?- If I need to implement some display-related logic in viewmodel, that should run when my property changed, should i execute my method from property setter or my viewmodel should subscribe on
PropertyChanged
event inviewmodelBase
? And if i should use second way, should i make switch based on string property name in method that processPropertyChanged
event, or there is some alternative?I ask this questions because im really afraid of human factor when i use string names instead of types and so on.
Sorry for my bad english.
Hi,
[CallerMemberName]
attribute or the new C# 6 nameof
operator. Reflection is slow but maybe it works for you if you can't use C# 6.Hope that helps.
Ali commented on 2016-03-03 20:29:13
Hi Rehan
Thanks for your article; i have learned a lot from it; i am new to WPF and wondering if following can be done. I have created following classes and want to add addition rules in owner class for re-usability of FMName class:
public class FMName : NotifyDataErrorInfo
{
private string _FirstName;
private string _MiddleName;
private string _LastName;
static FMName()
{
Rules.Add(new Base.Rules.DelegateRule(
"FirstName", "Name cannot be Empty", x => !string.IsNullOrEmpty(x.FirstName)));
Rules.Add(new Base.Rules.DelegateRule(
"LastName", "Name cannot be Empty", x => !string.IsNullOrEmpty(x.FirstName)));
Rules.Add(new Base.Rules.DelegateRule(
"MiddleName", "Name cannot be Empty", x => !string.IsNullOrEmpty(x.FirstName)));
}
public string LastName
{
get { return this._LastName; }
set { this.SetProperty(ref this._LastName, value); }
}
public string MiddleName
{
get { return _MiddleName; }
set { this.SetProperty(ref this._MiddleName, value); }
}
public string FirstName
{
get { return _FirstName; }
set { this.SetProperty(ref this._FirstName, value); }
}
}
public class Owner : NotifyDataErrorInfo
{
private int _OwnerID;
private FMName _OwnerName = new FMName();
static Owner()
{
Rules.Add(new Base.Rules.DelegateRule(
"Owner.FirstName",
"Owner First Name Cannot have less than 4 Characters",
x => x.OwnerName.FirstName.Length < 4);
}
public int OwnerID
{
get { return this._OwnerID; }
set { this.SetProperty(ref this._OwnerID, value); }
}
public FMName OwnerName
{
get { return _OwnerName; }
set { this.SetProperty(ref this._OwnerName, value); }
}
}
I am getting property doesn't exist in following:
static Owner()
{
Rules.Add(new Base.Rules.DelegateRule(
"Owner.FirstName", "Owner First Name Cannot have less than 4 Characters", x => x.OwnerName.FirstName.Lengt < 4));
}
Thanks again!
Ali
Muhammad Rehan Saeed commented on 2016-03-03 21:25:01
Hi Rehan
Thanks for your article; i have learned a lot from it; i am new to WPF and wondering if following can be done. I have created following classes and want to add addition rules in owner class for re-usability of FMName class:
public class FMName : NotifyDataErrorInfo { private string _FirstName; private string _MiddleName; private string _LastName; static FMName() { Rules.Add(new Base.Rules.DelegateRule( "FirstName", "Name cannot be Empty", x => !string.IsNullOrEmpty(x.FirstName))); Rules.Add(new Base.Rules.DelegateRule( "LastName", "Name cannot be Empty", x => !string.IsNullOrEmpty(x.FirstName))); Rules.Add(new Base.Rules.DelegateRule( "MiddleName", "Name cannot be Empty", x => !string.IsNullOrEmpty(x.FirstName))); } public string LastName { get { return this._LastName; } set { this.SetProperty(ref this._LastName, value); } } public string MiddleName { get { return _MiddleName; } set { this.SetProperty(ref this._MiddleName, value); } } public string FirstName { get { return _FirstName; } set { this.SetProperty(ref this._FirstName, value); } } } public class Owner : NotifyDataErrorInfo { private int _OwnerID; private FMName _OwnerName = new FMName(); static Owner() { Rules.Add(new Base.Rules.DelegateRule( "Owner.FirstName", "Owner First Name Cannot have less than 4 Characters", x => x.OwnerName.FirstName.Length < 4); } public int OwnerID { get { return this._OwnerID; } set { this.SetProperty(ref this._OwnerID, value); } } public FMName OwnerName { get { return _OwnerName; } set { this.SetProperty(ref this._OwnerName, value); } } }
I am getting property doesn't exist in following:
static Owner() { Rules.Add(new Base.Rules.DelegateRule( "Owner.FirstName", "Owner First Name Cannot have less than 4 Characters", x => x.OwnerName.FirstName.Lengt < 4)); }
Thanks again!
Ali
Hmmmm, I'm not sure that would work. You are creating a validation rule for FMName.FirstName but in the Owner class, I would say this is bad from a design perspective. That rule should live in FMName. If you need to turn the rule on or off under certain conditions then this can be part of the rule itself.
Ali commented on 2016-03-03 21:45:20
Hmmmm, I'm not sure that would work. You are creating a validation rule for FMName.FirstName but in the Owner class, I would say this is bad from a design perspective. That rule should live in FMName. If you need to turn the rule on or off under certain conditions then this can be part of the rule itself.
Is there any way I turn the rule on/off from owner class?
Muhammad Rehan Saeed commented on 2016-03-03 22:01:11
Is there any way I turn the rule on/off from owner class?
Your owner class could set a boolean property on the child class to turn the property on or off. When the boolean property changes, you can raise a PropertyChanged
notification for FirstName which will cause the rule to be re-evaluated.
Ali commented on 2016-03-03 22:06:53
Your owner class could set a boolean property on the child class to turn the property on or off. When the boolean property changes, you can raise a
PropertyChanged
notification for FirstName which will cause the rule to be re-evaluated.
I will try that, thanks a lot.
Alex C commented on 2016-04-06 19:19:16
Hi. This is a great article. As a noob, I'm wondering how the errors would be displayed in the XAML. Do you have a sample of that?
Muhammad Rehan Saeed commented on 2016-04-07 09:05:15
Hi. This is a great article. As a noob, I'm wondering how the errors would be displayed in the XAML. Do you have a sample of that?
There is a working example in Elysium Extra or see this.
Nuri commented on 2016-05-09 09:17:43
Hi Muhammad,
Nice page. Thanks. I'm wondering that how can I handle (forexample display dialog to user) when Command throw exception? Command called by View, it mutes when catch exception.
Muhammad Rehan Saeed commented on 2016-05-09 14:51:15
Hi Muhammad,
Nice page. Thanks. I'm wondering that how can I handle (forexample display dialog to user) when Command throw exception? Command called by View, it mutes when catch exception.
First don't use exceptions to write application logic. If something is invalid, try to use an 'if statement' instead. If you still need to catch an exception, then go for it. Not sure what you mean by 'mutes'.
Jason commented on 2016-06-21 22:41:49
Probably one the best MVVM series I've read. How do you deal with binding errors from the view? Consider the situation where the user enters letters into textbox that is bound to an integer property in your view model. The view model is remains in a valid state because the change never got that far.
Muhammad Rehan Saeed commented on 2016-06-22 15:29:21
Probably one the best MVVM series I've read. How do you deal with binding errors from the view? Consider the situation where the user enters letters into textbox that is bound to an integer property in your view model. The view model is remains in a valid state because the change never got that far.
The way to deal with binding errors is to not have them. You get notified of them in the output window in Visual Studio, so it's pretty easy to find and fix them.
If you are asking about how to do the XAML side of validation, then take a look at the Elysium Extra on Codeplex which has a very good example.
Jason commented on 2016-06-23 20:13:41
Thanks for the reply. Silverlight has a BindingValidationError
routedevent on the FrameworkElement
that needs to be handled in a robust application. If you don't, the view could be in an invalid state while the view model remains valid. I suspect WPF and others have similar issues, but using different events. I think the Prism library examples expose all view model properties as nullable even though they are required to get around some of it. Since the binding mechanism defaults to null on binding error, this method will work for required fields. It will fail if the user puts garbage into a field that isn't required. The user will think they saved garbage while the view model saved an empty field. I just thought you might be familiar with the problem and already had a slick implementation. Cheers.
Anders commented on 2016-10-14 09:40:22
That's 300 lines of code to validate if a textbox is empty!!! GEESUS !!!
Muhammad Rehan Saeed commented on 2016-10-14 10:04:54
That's 300 lines of code to validate if a textbox is empty!!! GEESUS !!!
That's 300 lines you don't need to write! Look how clean and simple the ZombieViewModel
looks because of those 300 lines!
Mark commented on 2016-12-21 04:22:57
Awesome work btw!
Artur commented on 2017-03-18 22:57:49
Is it possible to write a required if rule with this approach? I need to raise exception only if not all 3 properties are filled at the same time. When none of them is filled, I don't want to raise exception.
Could you help me? Thanks in advance
Muhammad Rehan Saeed commented on 2017-03-23 16:34:05
Is it possible to write a required if rule with this approach? I need to raise exception only if not all 3 properties are filled at the same time. When none of them is filled, I don't want to raise exception.
Could you help me? Thanks in advance
Something like this? You can refer to other properties in the rule condition.
Rules.Add(new DelegateRule<myviewmodel>(
"Item",
"Item cannot be empty.",
x => string.IsNullOrEmpty(x.SomeOtherProperty) || !string.IsNullOrEmpty(x.Item)));
Kevin Burns commented on 2017-04-11 16:47:29
I am trying to figure out how to implement this with a ViewModel that is part of a base class.
This doesn't work obviously, aside from copying the base class properties/functions constantly. Is there an easier way of handling this?
public sealed class AgentViewModel : EditorBaseViewModel, NotifyDataErrorInfo
{
}
Ole commented on 2017-10-17 16:07:08
Nice article. Looks very clean. How do you handle issues where you have one or more 'related' properties? if you update one property, you need to trigger a validation of the related property(ies), because they could be invalid because of the update.
Muhammad Rehan Saeed commented on 2017-10-18 09:21:41
Nice article. Looks very clean. How do you handle issues where you have one or more 'related' properties? if you update one property, you need to trigger a validation of the related property(ies), because they could be invalid because of the update.
You can pass multiple property names to both the SetProperty
and OnPropertyChanged
methods. To avoid using strings, you can use the nameof keyword.
Kevin Burns commented on 2017-11-07 21:55:21
Is there anyone to implement this properly for a Collection
count? I have an observable collection, and the rule is setup to throw when the count <= 0
. It is showing an error around the listbox bound to the collection, even when the itemsource has items in it. Any ideas?
Muhammad Rehan Saeed commented on 2017-11-13 09:19:51
Is there anyone to implement this properly for a
Collection
count? I have an observable collection, and the rule is setup to throw when thecount <= 0
. It is showing an error around the listbox bound to the collection, even when the itemsource has items in it. Any ideas?
You don't say it but probably the problem you are having is that you are not calling OnPropertyChanged("Count")
which will cause the validation logic to get called and the listbox to update.
Max commented on 2018-01-23 11:31:47
Very nice approach!
I have a ViewModel that contains a list of Sub-ViewModels and that has to be erroneous if any of them has an error. What would be the right approach to fire ErrorsChanged
on the upper ViewModel when any of the Sub-ViewModels raises WhenErrorsChanged
?
Muhammad Rehan Saeed commented on 2018-01-23 12:21:26
Very nice approach! I have a ViewModel that contains a list of Sub-ViewModels and that has to be erroneous if any of them has an error. What would be the right approach to fire
ErrorsChanged
on the upper ViewModel when any of the Sub-ViewModels raisesWhenErrorsChanged
?
I've done this in a few ways myself. Ideally each class is encapsulated and you register for the childs ErrorsChanged
event and bubble it up manually.
Max commented on 2018-01-23 12:39:40
I've done this in a few ways myself. Ideally each class is encapsulated and you register for the childs
ErrorsChanged
event and bubble it up manually.
I've already subscribed to the WhenErrorsChanged
of my Sub-ViewModels, but I don't see how to trigger the WhenErrorsChanged
of the upper ViewModel.
Raising ErrorsChanged
requires DataErrorsChangedEventArgs
with property name, which I don't have.
Ollie commented on 2018-02-21 08:14:57
Referring to Kevin Burns's question from April 11th of 2017: I'm also curious about how this can be solved.
Fred commented on 2019-04-05 08:23:52
Hey Rehan,
thank you for your work on this!
My question is, if it is possible to use the "old school" C# events instead of the Rx events? So how to replace the IObservable WhenErrorsChanged
with the defualt C# events. I'm not sure if I want to use the Rx Framework just for the Observable
class.
Thanks Fred
Muhammad Rehan Saeed commented on 2019-04-05 09:15:35
Hey Rehan,
thank you for your work on this!
My question is, if it is possible to use the "old school" C# events instead of the Rx events? So how to replace the
IObservable WhenErrorsChanged
with the defualt C# events. I'm not sure if I want to use the Rx Framework just for theObservable
class.Thanks Fred
If you're familiar with C# events, you can easily plug them in, in place of the Rx equivalents.
public event EventHandler Foo;
public void OnFoo() => this.Foo?.Invoke(this, EventArgs.Empty);
Thavious commented on 2019-05-17 15:03:36
Why did you implement this set of rules, rather than using something existing like DataAnnotations?
Muhammad Rehan Saeed commented on 2019-06-11 12:31:52
Why did you implement this set of rules, rather than using something existing like DataAnnotations?
I prefer functions over attributes. They are more powerful, simpler to reason about and faster.
Remy Meier commented on 2019-06-12 21:53:06
Quite good coded and commented. My Visual Studio shows me an error for Observable
. This cannot find the class. Which part of the .NET framework is to use for using?
Muhammad Rehan Saeed commented on 2019-06-15 10:38:39
Quite good coded and commented. My Visual Studio shows me an error for
Observable
. This cannot find the class. Which part of the .NET framework is to use for using?
You can find that in the System.Reactive
package.
David Piepgrass commented on 2019-07-31 22:45:44
This is all very nice, but how do you write the corresponding XAML for the view?
Pete commented on 2019-09-04 16:43:41
I love this implementation to establish rules but I have an issue.
I have added rules to my tblCompany model for when creating a new Company the rules ensure the relevant information is added. Although when I use a List as an Itemssource for a Combobox the rules are still in affect. So when a Company is selected the Combobox goes red if one of the rules aren't met. I would like to be able to ignore the rules on the model in some certain circumstances if possible??
Muhammad Rehan Saeed commented on 2019-09-23 09:02:22
I love this implementation to establish rules but I have an issue.
I have added rules to my tblCompany model for when creating a new Company the rules ensure the relevant information is added. Although when I use a List as an Itemssource for a Combobox the rules are still in affect. So when a Company is selected the Combobox goes red if one of the rules aren't met. I would like to be able to ignore the rules on the model in some certain circumstances if possible??
Sure they can be ignored. Just add a property to the view model called something like IsValidationEnabled
. When it is false, make all your rules valid. When it is true, then run the code in your rules as normal.
MarkusB commented on 2019-10-08 09:11:15
Very nice solution! But i have a problem with inherited classes.
I've created a BaseClass named NotifyDataErrorInfo
like you and a Class
public class ValidationExample : NotifyDataErrorInfoBase<ValidationExample>
{
}
All works great.
Now i want another inherited class
public class ValidationDerivedExample : ValidationExample
{
private int _subVar;
public int SubVar
{
get { return this._subVar; }
set { this.SetProperty(ref this._subVar, value, "SubVar"); }
}
static ValidationDerivedExample()
{
Rules.Add(new DelegateRule<ValidationDerivedExample> (
"SubVar",
"SubVar cannot be < 0.",
x => (x.SubVar < 0));
}
}
And add Rules -> that doesn't work Can you give me please an advice.
Muhammad Rehan Saeed commented on 2019-10-10 20:11:28
Very nice solution! But i have a problem with inherited classes.
I've created a BaseClass named
NotifyDataErrorInfo
like you and a Classpublic class ValidationExample : NotifyDataErrorInfoBase<ValidationExample> { }
All works great.
Now i want another inherited class
public class ValidationDerivedExample : ValidationExample { private int _subVar; public int SubVar { get { return this._subVar; } set { this.SetProperty(ref this._subVar, value, "SubVar"); } } static ValidationDerivedExample() { Rules.Add(new DelegateRule<ValidationDerivedExample> ( "SubVar", "SubVar cannot be < 0.", x => (x.SubVar < 0)); } }
And add Rules -> that doesn't work Can you give me please an advice.
Yes it gets a bit tricky here. I can't remember what my goto solution was but you can fix this in a couple of ways. You can cast in the rule:
Rules.Add(new DelegateRule<ValidationExample> (
"SubVar",
"SubVar cannot be < 0.",
x => (((ValidationDerivedExample)x).SubVar < 0));
Or you can move the generic type up the chain:
public class ValidationExample<T> : NotifyDataErrorInfoBase<T>
where T : ValidationSubExample
{
}
public class ValidationDerivedExample : ValidationExample<ValidationDerivedExample>
{
}
MarkusB commented on 2019-10-11 08:21:32
Yes it gets a bit tricky here. I can't remember what my goto solution was but you can fix this in a couple of ways. You can cast in the rule:
Rules.Add(new DelegateRule<ValidationExample> ( "SubVar", "SubVar cannot be < 0.", x => (((ValidationDerivedExample)x).SubVar < 0));
Or you can move the generic type up the chain:
public class ValidationExample<T> : NotifyDataErrorInfoBase<T> where T : ValidationSubExample { } public class ValidationDerivedExample : ValidationExample<ValidationDerivedExample> { }
Hello Rehan,
thanks for the fast reply.
Casting the rule i tried befor, unfortunately it doesn't work on runtime, i get an error at
Rules.Add(ValidationDerivedExample)
because x
is not of type ValidationExample
And move up the chain, is also no solution, because i want the error handly in my 'base classes' too.
Is there a possible solution without the generic type in the rule
, delegateRule
and ruleCollection
?
Kiwi commented on 2019-10-11 13:39:09
I'm working on a personal project and I'm using your example code, but I'm calling NotifyDataErrorInfo
ModelBase
because I want my models to implement the INotifyDataError
and the INotifyPropertyChanged
.
From my limited knowledge of MVVM, if I want the models to implement the INotify interfaces, and I want my ViewModels to wrap around the models (and not just be a pass-through), I need to have the ViewModel intercept the model's notify event, and re-raise those events.
I don't have a 1 to 1 Model to ViewModel ratio - I have a 1 to Many Model to ViewModel ratio. I'm not sure if this is a bad design decision, but I want the models to stay alive longer than the ViewModels they will be passed to, so for that I also new weak event handlers in my ViewModels coupling to my model's notify events. To accomplish, I have made the following ViewModelBase
class:
namespace MyNamespace.ViewModels
{
abstract class ViewModelBase : Models.ModelBase where T : ViewModelBase
{
private object[] models;
private static readonly Dictionary<(Type, string), string> propertyMappings = new Dictionary<(Type, string), string>();
protected object[] Models
{
get => this.models;
private set => this.models = value;
}
protected static Dictionary<(Type, string), string> PropertyMappings
{
get => propertyMappings;
}
private void Model_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
string propertyName = e.PropertyName;
if (PropertyMappings.ContainsKey((sender.GetType(), propertyName))) propertyName = PropertyMappings[(sender.GetType(), propertyName)];
// If PropertyMappings returned a null property name, that means skip the property.
if (string.IsNullOrWhiteSpace(propertyName)) return;
// Re-raise property changed event. Chain of events are: Model.ErrorsChanged(Model.Property) -> ViewModel.ErrorsChanged(ViewModel.Property).
this.OnErrorsChanged(propertyName);
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
string propertyName = e.PropertyName;
if (PropertyMappings.ContainsKey((sender.GetType(), propertyName))) propertyName = PropertyMappings[(sender.GetType(), propertyName)];
// If PropertyMappings returned a null property name, that means skip the property.
if (string.IsNullOrWhiteSpace(propertyName)) return;
// Re-raise property changed event. Chain of events are: Model.PropertyChanged(Model.Property) -> ViewModel.PropetyChanged(ViewModel.Property).
this.OnPropertyChanged(propertyName);
}
protected ViewModelBase(object model) : this(new object[] { model }) { }
protected ViewModelBase(params object[] models)
{
if (models == null) throw new ArgumentNullException(nameof(models));
if (models.Length < 1) throw new ArgumentException("At least one model is required.", nameof(models));
this.Models = models;
foreach (object model in this.Models)
{
if (model is INotifyPropertyChanged property)
WeakEventManager<object, PropertyChangedEventArgs>.AddHandler(model, nameof(property.PropertyChanged), this.Model_PropertyChanged);
if (model is INotifyDataErrorInfo data)
WeakEventManager<object, DataErrorsChangedEventArgs>.AddHandler(model, nameof(data.ErrorsChanged), this.Model_ErrorsChanged);
}
}
protected override void DisposeManaged()
{
foreach (object model in this.Models)
{
if (model is INotifyPropertyChanged property)
WeakEventManager<object, PropertyChangedEventArgs>.RemoveHandler(model, nameof(property.PropertyChanged), this.Model_PropertyChanged);
if (model is INotifyDataErrorInfo data)
WeakEventManager<object, DataErrorsChangedEventArgs>.RemoveHandler(model, nameof(data.ErrorsChanged), this.Model_ErrorsChanged);
}
}
}
}
So if my design isn't complete garbage, it should be able to have ViewModels that extend this ViewModelBase
that are tied to 1 or more object models that may or may not implement the INotify interfaces. It should also be weakly bound to those events, allowing the ViewModels to GCed when the View they are attached to leaves the scope.
One part I'm a little iffy on is Dictionary<(Type, string), string> and how it will work with sub classes. I'm not overly worried about it because my small personal project shouldn't have any models that have sub classes (beyond the initial ModelBase). IE, I won't have "class Model1 : ModelBase" + "class SubModelA : Model1" - I'll only have the "Model1" class in that example, and not the "SubModelA". It would be nice if I had designed Dictionary<(Type, string), string> to work correctly whether the object raising the event was subclass or not - but I only plan on crossing that bridge when I have to (which currently seems unlikely - at least in this personal project).
Anyway, thank you for your articles - I found them very informative. I shared the above class in the hopes that if you have 5 minutes, you'll share any thoughts / wisdom you have on my approach. If not, no biggie. I'm sure I'll find the hidden gotchas once I start debugging the project.
Muhammad Rehan Saeed commented on 2019-10-12 10:20:10
Hello Rehan,
thanks for the fast reply.
Casting the rule i tried before, unfortunately it doesn't work on runtime, i get an error at Rules.Add (ValidationDerivedExample)x is not of type ValidationExample
and move up the chain, is also no solution, because i want the error handy in my 'base classes' too.
Is there a possible solution without the generic type in the
rule
,delegateRule
andruleCollection
?
You should be able to specify rules in both classes using the generics solution.
Muhammad Rehan Saeed commented on 2019-10-12 10:26:31
I'm working on a personal project and I'm using your example code, but I'm calling
NotifyDataErrorInfo
ModelBase
because I want my models to implement theINotifyDataError
and theINotifyPropertyChanged
.From my limited knowledge of MVVM, if I want the models to implement the INotify interfaces, and I want my ViewModels to wrap around the models (and not just be a pass-through), I need to have the ViewModel intercept the model's notify event, and re-raise those events.
I don't have a 1 to 1 Model to ViewModel ratio - I have a 1 to Many Model to ViewModel ratio. I'm not sure if this is a bad design decision, but I want the models to stay alive longer than the ViewModels they will be passed to, so for that I also new weak event handlers in my ViewModels coupling to my model's notify events. To accomplish, I have made the following
ViewModelBase
class:namespace MyNamespace.ViewModels { abstract class ViewModelBase : Models.ModelBase where T : ViewModelBase { private object[] models; private static readonly Dictionary<(Type, string), string> propertyMappings = new Dictionary<(Type, string), string>(); protected object[] Models { get => this.models; private set => this.models = value; } protected static Dictionary<(Type, string), string> PropertyMappings { get => propertyMappings; } private void Model_ErrorsChanged(object sender, DataErrorsChangedEventArgs e) { string propertyName = e.PropertyName; if (PropertyMappings.ContainsKey((sender.GetType(), propertyName))) propertyName = PropertyMappings[(sender.GetType(), propertyName)]; // If PropertyMappings returned a null property name, that means skip the property. if (string.IsNullOrWhiteSpace(propertyName)) return; // Re-raise property changed event. Chain of events are: Model.ErrorsChanged(Model.Property) -> ViewModel.ErrorsChanged(ViewModel.Property). this.OnErrorsChanged(propertyName); } private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e) { string propertyName = e.PropertyName; if (PropertyMappings.ContainsKey((sender.GetType(), propertyName))) propertyName = PropertyMappings[(sender.GetType(), propertyName)]; // If PropertyMappings returned a null property name, that means skip the property. if (string.IsNullOrWhiteSpace(propertyName)) return; // Re-raise property changed event. Chain of events are: Model.PropertyChanged(Model.Property) -> ViewModel.PropetyChanged(ViewModel.Property). this.OnPropertyChanged(propertyName); } protected ViewModelBase(object model) : this(new object[] { model }) { } protected ViewModelBase(params object[] models) { if (models == null) throw new ArgumentNullException(nameof(models)); if (models.Length < 1) throw new ArgumentException("At least one model is required.", nameof(models)); this.Models = models; foreach (object model in this.Models) { if (model is INotifyPropertyChanged property) WeakEventManager<object, PropertyChangedEventArgs>.AddHandler(model, nameof(property.PropertyChanged), this.Model_PropertyChanged); if (model is INotifyDataErrorInfo data) WeakEventManager<object, DataErrorsChangedEventArgs>.AddHandler(model, nameof(data.ErrorsChanged), this.Model_ErrorsChanged); } } protected override void DisposeManaged() { foreach (object model in this.Models) { if (model is INotifyPropertyChanged property) WeakEventManager<object, PropertyChangedEventArgs>.RemoveHandler(model, nameof(property.PropertyChanged), this.Model_PropertyChanged); if (model is INotifyDataErrorInfo data) WeakEventManager<object, DataErrorsChangedEventArgs>.RemoveHandler(model, nameof(data.ErrorsChanged), this.Model_ErrorsChanged); } } } }
So if my design isn't complete garbage, it should be able to have ViewModels that extend this
ViewModelBase
that are tied to 1 or more object models that may or may not implement the INotify interfaces. It should also be weakly bound to those events, allowing the ViewModels to GCed when the View they are attached to leaves the scope.One part I'm a little iffy on is Dictionary<(Type, string), string> and how it will work with sub classes. I'm not overly worried about it because my small personal project shouldn't have any models that have sub classes (beyond the initial ModelBase). IE, I won't have "class Model1 : ModelBase" + "class SubModelA : Model1" - I'll only have the "Model1" class in that example, and not the "SubModelA". It would be nice if I had designed Dictionary<(Type, string), string> to work correctly whether the object raising the event was subclass or not - but I only plan on crossing that bridge when I have to (which currently seems unlikely - at least in this personal project).
Anyway, thank you for your articles - I found them very informative. I shared the above class in the hopes that if you have 5 minutes, you'll share any thoughts / wisdom you have on my approach. If not, no biggie. I'm sure I'll find the hidden gotchas once I start debugging the project.
These are good questions. Nobody teaches this stuff and I had a great deal of trouble trying things and finding what I thought was the best way for my purposes. I quite often ended up with two scenarios:
INotifyPropertyChanged
interface. Then the view model wraps the properties of the model and does all the property changed stuff.INotifyPropertyChanged
and my view model exposed the model as a single property.What you end up doing depends on your scenario. Whether you have control over the model etc. The second scenario is a bit quicker and less work but the first probably a more pure solution.
Shimmy commented on 2019-12-05 08:47:10
Hi Rehan, I've been subscribed to your blog for a while and love it!
I'm using ReactiveUI validation in a WPF project, and I was wondering if it's possible to keep the entities clean (I've only decorated them with data-annotations attributes or IValidatoableObject
implementations), and perform all validation logic in the VM, all this while binding directly to the entities (i.e. exposing them as a whole via a property in the VM, not proxying the properties individually in the VM.
https://rehansaeed.com/model-view-viewmodel-mvvm-part4-inotifydataerrorinfo/