Reputation: 474
I have a class RuleCondition<TValue>
:
public class RuleCondition<TValue>
{
public string PropertyToCheck { get; private set; }
public TValue Value { get; private set; }
public RuleCondition(string propertyToCheck, TValue value)
{
PropertyToCheck = propertyToCheck;
Value = value;
}
}
In the Rule<T>
class I have a field which holds an instance of it:
public abstract class Rule<T>
{
private readonly string mPropertyName;
private readonly object mError;
private readonly RuleCondition<TValue> mCondition;
protected Rule(string propertyName, object error, RuleCondition<TValue> condition)
{
mCondition = condition;
}
// ...
}
When I now want to add my RuleCondition<T>
to a Rule
(which inherits from Collection<Rule<T>>
and DelegateRule<T>
inherits from Rule<T>
) like this
Rules.Add(new DelegateRule<ConnectionSettingsViewModel>(nameof(Username),
"Username: Cannot be empty, starting with a space or a backslash.",
x => !string.IsNullOrWhiteSpace(x.Username),
new RuleCondition<LoginOptions>(nameof(LoginUsage), LoginOptions.Instrument)));
I get an error that the type doesn't match, which is correct. My problem is now: how to get the TValue
for the RuleCondition
whitout having to declare it at the "top" and break all my code. It feels somehow wrong to have the constraint declared everywhere just to get it at the RuleCondition<TValue>
class.
Is there an easier way? What do I miss?
EDIT: T
is already of type *ViewModel
.
I think I have to add more explanation:
The "chain" starts here:
public class ConnectionSettingsViewModel : NotifyDataErrorInfo<ConnectionSettingsViewModel>
{ }
If I would now add the TValue
constraint to the Rule
class like so Rule<T, TValue>
, I also need to provide that constraint to the NotifyDataErrorInfo
class. So to the "top" class. But that feels wrong. To have that constraint declared in every class in between the NotifyDataErrorInfo
and the "last" class RuleCondition
.
EDIT 2:
I took the code from here to implement a control validation to my MVVM application. But I figured I need some conditions to be met for several controls. So that a rule won't be "executed" everytime, but just if a specific RadioButton
e.g. is checked.
Upvotes: 1
Views: 130
Reputation: 3900
Based on your input and that blog post, you can do something like this. Take into account that you'll need to slightly adapt classes presented in that blog.
First, you can create ConditionalRule<T, TValue>
which inherits the DelegateRule<T>
(or any other Rule<T>
you want). For this, you'll need to unseal DelegateRule
class
public class ConditionalRule<T, TValue> : DelegateRule<T>
{
public TValue Value { get; private set; }
public ConditionalRule(string propertyName, object error, Func<T, bool> rule, TValue value)
: base(propertyName, error, rule)
{
Value = value;
}
public override bool Apply(T obj)
{
// do your business driven condition check before calling
return base.Apply(obj);
}
}
Secondly, you must extend RuleCollection<T>
with additional generic method:
public sealed class RuleCollection<T> : Collection<Rule<T>>
{
#region Public Methods
/// <summary>
/// Adds a new <see cref="Rule{T}"/> to this instance.
/// </summary>
/// <param name="propertyName">The name of the property the rules applies to.</param>
/// <param name="error">The error if the object does not satisfy the rule.</param>
/// <param name="rule">The rule to execute.</param>
public void Add(string propertyName, object error, Func<T, bool> rule)
{
this.Add(new DelegateRule<T>(propertyName, error, rule));
}
public void AddConditional<TValue>(string propertyName, object error, Func<T, bool> rule, TValue value)
{
this.Add(new ConditionalRule<T, TValue>(propertyName, error, rule, value)
}
//....
#endregion
}
Furthermore, you'll need to adapt your code which adds rules into collection, depending on the rules you want to add:
Rules.AddConditional<LoginOptions>(
new ConditionalRule<ConnectionSettingsViewModel, LoginOptions>(
nameof(Username),
"Username: Cannot be empty, starting with a space or a backslash.",
x => !string.IsNullOrWhiteSpace(x.Username),
LoginOptions.Instrument
)
);
Please note, I didn't check the code so you can expect some compile time issues, but the general idea would be to create specific rules out of Rule<T>
using inheritance. That way, each rule will nicely fit into RuleCollection<T>
Upvotes: 1