How to use inherited validators for abstract class in FluentValidation - c#

I want to validate elementes within my object Form.
Form form = new Form();
form.Elements.Add(new FormNumberElement());
form.Elements.Add(new FormDateElement());
The form contains a List<FormElement> which holds each element. Here is their definition.
public class Form
{
public List<FormElement> Elements { get; set; } = new();
}
public abstract class FormElement
{
public string Name { get; set; } = string.Empty;
public abstract string GetDefaultName();
}
public class FormNumberElement : FormElement
{
public int MinValue { get; set; }
public override string GetDefaultName() => "Number";
}
public class FormDateElement : FormElement
{
public DateTime MinDate { get; set; }
public override string GetDefaultName() => "Date";
}
Since I'll want every elemnt to have a valid name, I've created this abstract validator.
public abstract class FormElementValidator<T> : AbstractValidator<T> where T : FormElement
{
public FormElementValidator()
{
RuleFor(x => x.Name)
.Must(ValidateName)
.WithMessage("Please enter a name for the element");
}
private bool ValidateName(FormElement element, string name)
{
if (name == element.GetDefaultName())
{
return false;
}
return true;
}
}
I also have two other validators for both the number and the date element.
public class FormNumberElementValidator : FormElementValidator<FormNumberElement>
{
public FormNumberElementValidator() : base()
{
RuleFor(x => x.MinValue)
.GreaterThan(10);
}
}
public class FormDateElementValidator : FormElementValidator<FormDateElement>
{
public FormDateElementValidator() : base()
{
RuleFor(x => x.MinDate)
.GreaterThan(DateTime.Now);
}
}
Now I want to validate each element within my List<FormElement> from my Form object.
My propblem now is that I don't know how I can get the right validator for each indiviual Element.
I've tried to add a method to my base class FormElement
public IValidator<FormElement> GetValidator();
Which I've implemented into every class. For example:
public override IValidator<FormElement> GetValidator() => (IValidator<FormElement>)new FormNumberElementValidator();
But this apporach does not work because the converting to IValidator<FormElement> fails.
How can I get the right validator for each list of my object?

Related

CollectionEditor error if collection has null items

If my array property has null items, then I can't open CollectionEditor from PropertyGrid. I get error with text 'component'. How can I fix it?
public partial class Form1 : Form
{
public Test[] test { get; set; }
public Form1()
{
InitializeComponent();
test = new Test[5];
test[2] = new Test() { Name = "2" };
propertyGrid1.SelectedObject = this;
}
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Test
{
public string Name { get; set; }
}
Maybe I should override some methods in my custom CollectionEditor, but i don't know which
It depends exactly how you want to work around this, but if you just need to exclude the null values, then you can override the default ArrayEditor class, something like this;
// define the editor on the property
[Editor(typeof(MyArrayEditor), typeof(UITypeEditor))]
public Test[] test { get; set; }
...
public class MyArrayEditor : ArrayEditor
{
public MyArrayEditor(Type type) : base(type)
{
}
protected override object[] GetItems(object editValue)
{
// filter on the objects you want the array editor to use
return base.GetItems(editValue).Where(i => i != null).ToArray();
}
}

How to make class generic to pass dynamic property class in c#

I have demo code which ignores the Extra property. I need to make this class generic.
public class HelloFriend
{
public static void Main(string[] args)
{
var s = new StringBuilder();
s.Append("Id,Name\r\n");
s.Append("1,one\r\n");
s.Append("2,two\r\n");
using (var reader = new StringReader(s.ToString()))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<TestMap>();
csv.GetRecords<Test>().ToList();
}
}
}
public class Test : Test1
{
public int Id { get; set; }
public string Name { get; set; }
}
public abstract class Test1
{
public decimal Extra { get; set; }
}
public class TestMap : CsvClassMap<Test>
{
public TestMap()
{
AutoMap();
Map(m => m.Extra).Ignore();
}
}
As shown in above code if I need to use different Test1, or Test2 instead of Test, then I need to write another TestMap class. How can I avoid this? How can I make this class generic, so I can pass multiple classes like Test to ignore the Extra property?
class CsvClassMap<T> where T:Test1,class
{
//your class logic
}
Do you mean somehting like this?
class Program
{
public static void Main(string[] args)
{
var s = new StringBuilder();
s.Append("Id,Name\r\n");
s.Append("1,one\r\n");
s.Append("2,two\r\n");
using (var reader = new StringReader(s.ToString()))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<TestMap<Test>>();
csv.GetRecords<Test>().ToList();
}
}
}
public class Test : Test1
{
public int Id { get; set; }
public string Name { get; set; }
}
public Abstract class Test1
{
public decimal Extra { get; set; }
}
public class Test2 : Test1
{
//other propertys
}
public class TestMap<T> : CsvClassMap<T> where T : Test1
{
public TestMap()
{
AutoMap();
Map(m => m.Extra).Ignore();
}
}
Ok, it might work and help you for a while as long as you do not add more properties or your class interface stays stable.
Besides, i'd suggest to reinforce your implementation by adding custom attribute and using reflection to detect which properties to ignore. Here is some code to give you an idea:
public class ToBeIgnoredAttribute:Attribute
{
}
public class Test
{
public int Property1 { get; set; }
[ToBeIgnored]
public int Property2 { get; set; }
}
var type= typeof(Test)// or typeof(T);
type.GetProperties().ForEach(prop =>
{
if (prop.GetCustomAttribute<ToBeIgnoredAttribute>() != null)
{
Console.WriteLine($"//Call Map with right overload e.g with property name string {prop.Name}");
}
else
{
Console.WriteLine($"{prop.Name} is not ignored");
}
});
Instead of using AutoMap, only map the properties you want to be mapped.
public class TestMap : ClassMap<Test>
{
public TestMap()
{
Map(m => m.Id);
Map(m => m.Name);
}
}
Another option is to add an attribute to the property.
public abstract class Test1
{
[Ignore]
public decimal Extra { get; set; }
}
public class TestMap : ClassMap<Test>
{
public TestMap()
{
AutoMap();
}
}

Pass Class Property as Generic Type

I am attempting to implement a base class for FluentValidation that will quickly build a validator for classes. My base class functions attempt to take a class's property as a Generic type argument in order to apply rules. But as you'll see in the code its not quite syntactically (among other things) correct.
It probably much easier to explain in code:
public class BaseValidator<T> : AbstractValidator<T>
{
public void ruleForText<U>(string msg)
{
RuleFor(obj => obj.U).NotEmpty().WithMessage(msg);
RuleFor(obj => obj.U).Length(1, 100).WithMessage(msg);
RuleFor(obj => obj.U).Matches("[A-Z]*").WithMessage(msg);
}
public void ruleForEmail<U>(string msg)
{
RuleFor(obj => obj.U).NotEmpty().WithMessage(msg);
RuleFor(obj => obj.U).EmailAddress().WithMessage(msg);
}
}
public class Member {
public string Name { get; set; }
public string Email { get; set; }
}
public class Post{
public string Title { get; set; }
}
public class MemberValidator :BaseValidator<Member>
{
public MemberValidator()
{
// Not valid syntax to pass name or even Member.Name
// How can I pass Member.Name as the generic type?
ruleForText<Name>();
ruleForEmail<Email>();
}
}
public class PostValidator :BaseValidator<Post>
{
public MemberValidator()
{
ruleForText<Title>();
}
}
This might be what you are looking for. You need to pass in an expression with the function parameter being a string.
public class BaseValidator<T> : AbstractValidator<T>
{
public void RuleForText(Expression<Func<T, string>> expression, string msg)
{
RuleFor(expression).NotEmpty().WithMessage(msg);
RuleFor(expression).Length(1, 100).WithMessage(msg);
RuleFor(expression).Matches("[A-Z]*").WithMessage(msg);
}
public void RuleForEmail(Expression<Func<T, string>> expression, string msg)
{
RuleFor(expression).NotEmpty().WithMessage(msg);
RuleFor(expression).EmailAddress().WithMessage(msg);
}
}
public class MemberValidator : BaseValidator<Member>
{
public MemberValidator()
{
RuleForText(member => member.Name, "My Message");
RuleForEmail(member => member.Email, "My Message");
}
}
public class Member
{
public string Name { get; set; }
public string Email { get; set; }
}

Fluent validation Vary object validator according to the class it's used in

I have a class which gets reused in multiple places. It has a default validator applied to it, which has the following attribute, [Validator(typeof(ChildValidator))].
I wanted to be to override the validator that's been applied to Child class depending on the parent class that's using it. Below is a simplified version of my model. So instead of validate Child with ChildValidator, I want to apply rules in ChildValidator2. Can this be done?
Note: the follow code will always fail the validation because it will always apply rule in ChildValidator
Models:
[Validator(typeof(ParentValidator))]
public class Parent
{
public bool IgnoreChild { get; set; }
public DateTime Birthdate { get; set; }
public Child Children { get; set; }
}
[Validator(typeof(ChildValidator))]
public class Child
{
public string ChildProperty{ get; set; }
public DateTime Birthdate { get; set; }
}
Validators:
public class ParentValidator : AbstractValidator<Parent>
{
public ParentValidator()
{
RuleFor(model => model.Name).NotEmpty();
When(x=>x.IgnoreChild, () =>
{
RuleForEach(model => model.Child).SetValidator(new ChildValidator2());
});
}
}
public class ChildValidator : AbstractValidator<Child>
{
public ChildValidator()
{
RuleFor(model => model.ChildProperty).NotEmpty();
//Compare birthday to make sure date is < Parents birthday
}
}
public class ChildValidator2 : AbstractValidator<Child>
{
public ChildValidator2()
{
RuleFor(model => model.ChildProperty).Empty();
}
}
If you debug FluentValidation source code, you will see that AttributedValidatorFactory works only for parameters of controller action (Parent), not it's child property classes (Child). If you didn't applied validator by SetValidator() method - no validation actions would be performed. So, your experiment results could be caused by another reason, not one you though.
Try to specify validators explicitly for both values of IgnoreChild flag:
public class ParentValidator : AbstractValidator<Parent>
{
public ParentValidator()
{
RuleFor(model => model.Name).NotEmpty();
When(x=> x.IgnoreChild, () =>
{
RuleForEach(model => model.Child).SetValidator(new ChildValidator2());
});
When(x=> !x.IgnoreChild, () =>
{
RuleForEach(model => model.Child).SetValidator(new ChildValidator());
});
}
}

Update the Base Class attributes when instantiate Child class C#

I want the following, is it possible in C#
public class BaseClass
{
public string Name {get;set;}
public DateTime Login {get;set;}
}
public class ChildA : BaseClass
{
public string SchoolName{get; set;}
public string ClassName{get; set;}
}
public class childB : BaseClass
{
public string StreetAdrees{get; set;}
}
Now I want that if I create an instance of any child class Name="John" and Login "2013-12-12" or from database already set its irritating to set these attribute for every class
just like that
ChildA obj=new ChildA();
obj.Name and obj.Login already have Data
Specify constructor in base class, then create constructors in child classes which inherit from base classes constuctor like below
public class ChildA : BaseClass
{
public ChildA():base(){}
public string SchoolName{get; set;}
public string ClassName{get; set;}
}
public class BaseClass
{
public BaseClass()
{
//set Data
.....
}
....
}
read more about base keyword
In the example below, children would actually point to the same instance of base
The example uses cache, but it could be anything else (session, application state, etc).
public class BaseClass
{
private string _name;
private DateTime _login;
public string Name
{
get
{
return Instance._name;
}
set
{
_name = value;
}
}
public DateTime Login
{
get
{
return Instance._login;
}
set
{
_login = value;
}
}
public static BaseClass Instance
{
get
{
// check if null, return a new instance if null etc...
return HttpContext.Current.Cache["BaseClassInstance"] as BaseClass;
}
set
{
HttpContext.Current.Cache.Insert("BaseClassInstance", value);
}
}
}
public class ChildA : BaseClass
{
public string SchoolName { get; set; }
public string ClassName { get; set; }
}
public class childB : BaseClass
{
public string StreetAdrees { get; set; }
}
testing it:
BaseClass.Instance = new BaseClass() { Login = DateTime.Now, Name = "Test" };
ChildA ch = new ChildA();
ChildA ch2 = new ChildA();
childB chb = new childB();
Response.Write(ch.Login.Millisecond);
Response.Write("<BR/>");
Response.Write(chb.Login.Millisecond);
Result:
906
906

Categories

Resources