I have the following code to validate an entity:
public class AffiliateValidator : AbstractValidator<Affiliate>
{
public AffiliateValidator ()
{
RuleFor(x => x.IBAN).SetValidator(new ValidIBAN()).Unless( x => String.IsNullOrWhiteSpace(x.IBAN));
}
}
And ValidIBAN() Code:
public class ValidIBAN : PropertyValidator
{
public ValidIBAN()
:base("IBAN \"{PropertyValue}\" not valid.")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var iban = context.PropertyValue as string;
IBAN.IBANResult result = IBAN.CheckIban(iban, false);
return result.Validation == (IBAN.ValidationResult.IsValid);
}
}
}
So,CheckIBAN method of IBAN class does the dirty job.
Now, what I need to to is apply the following rule for another property:
If DirectDebit (bool) is true, then IBAN can't be empty and also it must be valid.
I can do this:
RuleFor(x => x.DirectDebit).Equal(false).When(a => string.IsNullOrEmpty(a.IBAN)).WithMessage("TheMessage.");
But how can I Invoke another rule, IBAN's rule in this case, in order to check if is or not valid?
Often the problems are simpler than they seem. This is the solution I adopt to aply the rule for DirectDebit field.
RuleFor(x => x.DirectDebit).Must(HaveValidAccounts).When(x => x.DirectDebit)
.WithMessage("TheMessage");
and change the rule for IBAN also:
RuleFor(x => x.IBAN).Must(IsValidIBAN)
.Unless(x => String.IsNullOrWhiteSpace(x.IBAN))
.WithMessage("The IBAN \"{PropertyValue}\" is not valid.");
...and then:
private bool HaveValidAccounts(ViewModel instance, bool DirectDebit)
{
if (!DirectDebit)
{ return true; }
bool CCCResult = IsValidCCC(instance.CCC);
bool IBANResult = IsValidIBAN(instance.IBAN);
return CCCResult || IBANResult;
}
private bool IsValidIBAN(string iban)
{
return CommonInfrastructure.Finantial.IBAN.CheckIban(iban, false).Validation == IBAN.ValidationResult.IsValid;
}
the trick is use instance parameter of Must() to do whetever I want.
Related
I need to transform my property (remove white space on a string) first before applying validation.
Specifically I want to check if a string is part of a enum but the string may contain whitespaces (which enums don't allow far as I know)
Something like...
RuleFor(x => x.Value).IsEnumName(typeof(EnumType))
(x.Value should have whitespaces removed first)
The FluentValidation method Transform is designed for this case. The following uses the basic white space remover from Everson's answer:
RuleFor(x => x.Value)
.Transform(x => x.Replace(" ", string.Empty))
.IsEnumName(typeof(EnumType));
I'd opt to go with a stronger white space remover that catches tabs etc
RuleFor(x => x.Value)
.Transform(x => new string(x.Where(c => !Char.IsWhiteSpace(c)).ToArray()))
.IsEnumName(typeof(EnumType));
No need for a custom validator (e.g., Must) and personally I'd avoid writing one unless there was no other way.
I'd pop that white space remover into an extension (MVP, you should handle the null case; my preference would be a null guard but that's another topic):
public static class StringExtensions
{
public static string RemoveWhiteSpace(this string target){
return new string(target.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
}
Then the rule is a lot more readable:
RuleFor(x => x.Value)
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType))
.When(x => x != null);
Something to be aware of: I found that if Transform returned null the IsEnumName rule will pass. Personally I don't like that so I'd include a When rule builder option to only test when Value is provided, or a not empty rule to ensure it is provided.
Working LINQPad sample:
public enum EnumType
{
Value1,
Value2,
Value3
}
public class Foo
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class FooValidator : AbstractValidator<Foo>
{
public FooValidator()
{
RuleFor(x => x.Value)
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType));
.When(x => x != null);
}
}
public static class StringExtensions
{
public static string RemoveWhiteSpace(this string target)
{
return new string(target.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
}
void Main()
{
var validator = new FooValidator();
var foo1 = new Foo { Id = Guid.NewGuid(), Value = "Value 1" };
var result1 = validator.Validate(foo1);
Console.WriteLine(result1.IsValid);
var foo2 = new Foo { Id = Guid.NewGuid(), Value = "Value2" };
var result2 = validator.Validate(foo2);
Console.WriteLine(result2.IsValid);
var foo3 = new Foo { Id = Guid.NewGuid(), Value = "Value 3" };
var result3 = validator.Validate(foo3);
Console.WriteLine(result3.IsValid);
var foo4 = new Foo { Id = Guid.NewGuid(), Value = "This is not a valid enum value" };
var result4 = validator.Validate(foo4);
Console.WriteLine(result4.IsValid);
var foo5 = new Foo { Id = Guid.NewGuid(), Value = null };
var result5 = validator.Validate(foo5);
Console.WriteLine(result5.IsValid);
}
EDIT:
As per your additional comment about wrapping all of this into an extension:
public static class FluentValidationExtensions
{
public static IRuleBuilderOptions<T, string> IsEnumTypeMember<T>(this IRuleBuilderInitial<T, string> target)
{
return target
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType))
.When(x => x != null);
}
}
Then update the rule to use it:
RuleFor(x => x.Value).IsEnumTypeMember();
This is just an MVP, I don't really know all of your use cases; you may want to make it more generic so you could apply it to other enums.
You can use a custom method
RuleFor(x => x.Valor).Must(BeEnumType);
...
private bool BeEnumType(string v)
{
return Enum.IsDefined(typeof(EnumType), v.Replace(" ", string.Empty));
}
There are two classes
class A
{
public string ProductionDivision { get; set; }
}
class B
{
private object _productionDivision;
public enum ProductionDivisionOneofCase
{
None = 0,
IsNullproductionDivision = 15,
ProductionDivisionValue = 16,
}
private ProductionDivisionOneofCase _productionDivisionCase = ProductionDivisionOneofCase.None;
public bool IsNullProductionDivision
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.IsNullproductionDivision ? (bool)_productionDivision : false; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.IsNullproductionDivision;
}
}
public string ProductionDivisionValue
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.ProductionDivisionValue ? (string)_productionDivision : ""; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.ProductionDivisionValue;
}
}
}
I would like to map the ProductionDivision property to one of properties of class B depending on the condition - null(map to IsNullProductionDivision) or not null(map to ProductionDivisionValue) of the source property value. I can achieve it like as below.
CreateMap<A, B>()
.ForMember(g => g.IsNullProductionDivision, m =>
{
m.PreCondition(s => s.ProductionDivision == null);
m.MapFrom(b => true);
})
.ForMember(g => g.ProductionDivisionValue, m =>
{
m.PreCondition(s => s.ProductionDivision != null);
m.MapFrom(b => b.ProductionDivision);
});
If the value of {source property name} is null then the value of IsNull{source property name} is true.
Else if the value of {source property name} is not null then the value of {source property name}Value is the value of {source property name}.
I have many properties that respond this mapping rule. So, I don't want to write mapping rule for each properties like above. I want to configurate a rule for such mapping globally.
How can I configure AutoMapper so that it can handle such complex mapping?
I have found solution. The solution is pretty simple and clear. It turns out as follow:
Full code:
public class Program
{
class A
{
public string ProductionDivision { get; set; }
}
class B
{
private object _productionDivision;
public enum ProductionDivisionOneofCase
{
None = 0,
IsNullproductionDivision = 15,
ProductionDivisionValue = 16,
}
private ProductionDivisionOneofCase _productionDivisionCase = ProductionDivisionOneofCase.None;
public bool IsNullProductionDivision
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.IsNullproductionDivision ? (bool)_productionDivision : false; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.IsNullproductionDivision;
}
}
public string ProductionDivisionValue
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.ProductionDivisionValue ? (string)_productionDivision : ""; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.ProductionDivisionValue;
}
}
}
public class StrinToBoolCustomResolver
: IValueConverter<string, bool>
{
public bool Convert(string sourceMember, ResolutionContext context)
{
return sourceMember == null;
}
}
public class MyAutoMapperProfile
: Profile
{
public MyAutoMapperProfile()
{
// add post and pre prefixes to add corresponding properties in the inner property map
RecognizeDestinationPostfixes("Value");
RecognizeDestinationPrefixes("IsNull");
// add mapping for "value" property
this.ForAllPropertyMaps(map => map.SourceMember.Name + "Value" == map.DestinationName,
(map, expression) =>
{
expression.Condition((source, dest, sMem, destMem) =>
{
return sMem != null;
});
expression.MapFrom(map.SourceMember.Name);
});
// add mapping for "IsNull" property
this.ForAllPropertyMaps(map => "IsNull" + map.SourceMember.Name == map.DestinationName,
(map, expression) =>
{
expression.Condition((source, dest, sMem, destMem) =>
{
return (bool)sMem;
});
//expression.MapFrom(map.SourceMember.Name);
expression.ConvertUsing<string, bool>(new StrinToBoolCustomResolver(), map.SourceMember.Name);
});
CreateMap<A, B>();
//.ForMember(g => g.IsNullProductionDivision, m =>
//{
// m.PreCondition(s => s.ProductionDivision == null);
// m.MapFrom(b => true);
//});
//.ForMember(g => g.ProductionDivisionValue, m =>
//{
// m.PreCondition(s => s.ProductionDivision != null);
// m.MapFrom(b => b.ProductionDivision);
//});
}
}
public static void Main(string[] args)
{
var configuration = new MapperConfiguration(cfg => {
cfg.AddProfile(new MyAutoMapperProfile());
});
var mapper = new Mapper(configuration);
mapper.ConfigurationProvider.AssertConfigurationIsValid();
var a = new A()
{
ProductionDivision = null
};
// b._productionDivisionCase will be equal IsNullproductionDivision
var b = mapper.Map<B>(a);
var c = new A()
{
ProductionDivision = "dsd"
};
// d._productionDivisionCase will be equal ProductionDivisionValue
var d = mapper.Map<B>(c);
}
}
Clarification:
add (post/pre)fixes to add corresponding properties to inner property map. It need to do here because our properties should be catched by AutoMapper. Otherwise properties will be abandoned and mapper configuration will be failed.
After that, we configurate how these properties need to be mapping. We call ForAllPropertyMaps method, filtering all properties and setting a rule to mapping properties suitable with our filter. When the mapper object is creating, the execution plan will be built taking look the specified filters.
For "Value" property we add a condition to check whether the source property is not null. If it is null, then mapping will be missed.
For "IsNull" property First of all, we add a converter to convert string type to bool type. The converter just compares the source string property with null value. If the source property equals null, then the converter returns true. So, the condition receives a true value, returns true, and mapping will be done. Otherwise the converter returns false, so the condition returns false and mapping will be missed.
Thus, the mapping of source property will occur to different destination properties depending on whether the source property is null value. Besides that, corresponding set methods of corresponding destination properties will be not called if it not must to be called.
I'm using CsvHelper to serialize a class to csv file - until here everything works well.
Now I'm trying to find a way to convert the class's enum properties to their int value in the csv, so I could use the CSV for bulk insert later.
I found out the EnumConverter class in CsvHelper but I can't figure out how to properly use it, as all my tries are failing.
Here is my mapping class code
public sealed class MyMapping : CsvClassMap<TradingCalendarException>
{
public MyMapping()
{
EnumConverter enumConverter = new EnumConverter(typeof(CalendarExceptionEntityType));
Map(m => m.ExceptionEntityType).Index(0).Name("EXCEPTION_ENTITY_TYPE").TypeConverter(enumConverter);
Map(m => m.ExceptionEntityIdentifier).Index(1).Name("EXCEPTION_ENTITY_IDENTIFIER");
Map(m => m.OptionType).Index(2).Name("OPTION_TYPE");
Map(m => m.StartDatetime).Index(3).Name("EXCEPTION_START_DATETIME");
Map(m => m.EndDatetime).Index(4).Name("EXCEPTION_END_DATETIME");
Map(m => m.DataSourceType).Index(5).Name("DATA_SOURCE_TYPE");
Map(m => m.Description).Index(6).Name("DESCRIPTION");
}
}
and the writing part
using (StreamWriter file = new StreamWriter(filePath, false, Encoding.UTF8))
{
CsvWriter writer = new CsvWriter(file);
MyMapping mapping = new MyMapping();
writer.Configuration.RegisterClassMap(mapping);
writer.WriteRecords(calendarExceptionList);
}
The rest of the mapping (indexing and naming) is working, it's just the EnumConverter that doesn't do any change.
I didn't find any examples online.
Thank you!
This is the solution I made:
public class CalendarExceptionEnumConverter<T> : DefaultTypeConverter where T : struct
{
public override string ConvertToString(TypeConverterOptions options, object value)
{
T result;
if(Enum.TryParse<T>(value.ToString(),out result))
{
return (Convert.ToInt32(result)).ToString();
}
throw new InvalidCastException(String.Format("Invalid value to EnumConverter. Type: {0} Value: {1}",typeof(T),value));
}
}
and used it as the following:
Map(m => m.ExceptionEntityType).TypeConverter<CalendarExceptionEnumConverter<CalendarExceptionEntityType>>();
I used Yarimi's solution, but found it can't read the enum value back from the .csv (can write ok)
my solution is to make the class extend from EnumTypeConverter, not DefaultTypeConverter.
here is the full code
public class OurEnumConverter<T> : CsvHelper.TypeConversion.EnumConverter where T : struct
{
public OurEnumConverter(): base(typeof(T))
{ }
public override string ConvertToString(CsvHelper.TypeConversion.TypeConverterOptions options, object value)
{
T result;
if (Enum.TryParse<T>(value.ToString(), out result))
{
return (Convert.ToInt32(result)).ToString();
}
return base.ConvertToString(options, value);
//throw new InvalidCastException(String.Format("Invalid value to EnumConverter. Type: {0} Value: {1}", typeof (T), value));
}
public override object ConvertFromString(TypeConverterOptions options, string text)
{
int parsedValue;
//System.Diagnostics.Debug.WriteLine($"{typeof(T).Name} = {text}");
if (Int32.TryParse(text, out parsedValue))
{
return (T)(object)parsedValue;
}
return base.ConvertFromString(options, text);
//throw new InvalidCastException(String.Format("Invalid value to EnumConverter. Type: {0} Value: {1}", typeof(T), text));
}
}
and here is how it's used
public class TickTradeClassMap : CsvHelper.Configuration.CsvClassMap<TickData.TickTrade>
{
public TickTradeClassMap()
{
Map(m => m.price);
Map(m => m.size);
Map(m => m.exchange).TypeConverter<OurEnumConverter<ATExchangeEnum>>();
Map(m => m.condition1).TypeConverter<OurEnumConverter<ATTradeConditionEnum>>();
}
}
This is how I did it for the latest version of CSV Helper which is 7.1.1:
public class AggregateEnumConverter<T> : EnumConverter where T : struct
{
public AggregateEnumConverter() : base(typeof(T)) { }
public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
{
if(!Enum.TryParse(text, out AggregateType aggregateType))
{
// This is just to make the user life simpler...
if(text == "24HAVG")
{
return AggregateType._24HAVG;
}
// If an invalid value is found in the CSV for the Aggregate column, throw an exception...
throw new InvalidCastException($"Invalid value to EnumConverter. Type: {typeof(T)} Value: {text}");
}
return aggregateType;
}
}
Note: the code above is making use of C# 7 new inline out variables.More info here: How should I convert a string to an enum in C#?
This is how you make use of the custom EnumConverter:
/// <summary>
/// Maps Tag class properties to the CSV columns' names
/// </summary>
public sealed class TagMap : ClassMap<Tag>
{
public TagMap(ILogger<CsvImporter> logger)
{
Map(tag => tag.Aggregate).Name("aggregate").TypeConverter<AggregateEnumConverter<AggregateType>>();
}
}
Add a int property to your TradingCalendarException class that casts back and forth to your custom enum, CalendarExceptionEntityType, like:
public int ExceptionEntityTypeInt {
get { return (int)ExceptionEntityType; }
set { ExceptionEntityType = (CalendarExceptionEntityType)value; }
}
Use Map(m => m.ExceptionEntityTypeInt).Index(0).Name("EXCEPTION_ENTITY_TYPE_INT") instead of your enum converter Map(m => m.ExceptionEntityType).Index(0).Name("EXCEPTION_ENTITY_TYPE").TypeConverter(new MyMapping())
I have this validator class:
internal class CustomerTypeValidator : AbstractValidator<CustomerType>
{
public CustomerTypeValidator()
{
RuleFor(x => x.Number).Must(BeANumber).WithState(x => CustomerTypeError.NoNumber);
}
private bool BeANumber(string number)
{
int temp;
bool ok = int.TryParse(number, out temp);
return ok && temp > 0;
}
}
And I have the service class:
public class CustomerTypeService
{
public CustomerType Save(CustomerType customerType)
{
ValidationResult results = Validate(customerType);
if (results != null && !results.IsValid)
{
throw new ValidationException<CustomerTypeError>(results.Errors);
}
//Save to DB here....
return customerType;
}
public bool IsNumberUnique(CustomerType customerType)
{
var result = customerTypeRepository.SearchFor(x => x.Number == customerType.Number).Where(x => x.Id != customerType.Id).FirstOrDefault();
return result == null;
}
public ValidationResult Validate(CustomerType customerType)
{
CustomerTypeValidator validator = new CustomerTypeValidator();
validator.RuleFor(x => x).Must(IsNumberUnique).WithState(x => CustomerTypeError.NumberNotUnique);
return validator.Validate(customerType);
}
}
However I get the following exception:
Property name could not be automatically determined for expression x => x. Please specify either a custom property name by calling 'WithName'.
Is the above not the correct way to add an extra rule?
With the current version of FluentValidation, it is possible to solve the above problem by doing the following:
public bool IsNumberUnique(CustomerType customerType, int id)
{
var result = customerTypeRepository.SearchFor(x => x.Number == customerType.Number).Where(x => x.Id != customerType.Id).FirstOrDefault();
return result == null;
}
public ValidationResult Validate(CustomerType customerType)
{
CustomerTypeValidator validator = new CustomerTypeValidator();
validator.RuleFor(x => x.Id).Must(IsNumberUnique).WithState(x => CustomerTypeError.NumberNotUnique);
return validator.Validate(customerType);
}
In my test, I defined as data a List<IUser> with some record in.
I'd like setup a moq the methode GetList, this method receive a bool as parameter. I'd like in return the IUser list where IsValid is true.
I tried this :
Mock<IUsers> mockUserRepository = new Mock<IUsers>();
mockUserRepository.Setup(mr => mr.GetList(It.IsAny<bool>()))
.Returns((bool i) => _users.Select(x => x.IsValid == i));
But I get this error : cannot convert List<bool> to List<IUser>
class User : IUser
{
public bool IsValid { get; set; }
}
interface IUser
{
bool IsValid { get; set; }
}
interface IUsers
{
List<IUser> GetList(bool isActive);
}
If you want to return the IUser list where IsValid is true, when you should return a list of IUser, not a collection of bool, so use Where to filter the list instead of Select.
// returns IEnumerable<boolean>
.Returns((bool i) => _users.Select(x => x.IsValid == i));
should be
// returns List<IUser>
.Returns((bool i) => _users.Where(x => x.IsValid == i).ToList());