Writing a CompareTo DataAnnotation Attribute - c#

I have a situation where I want to compare to fields (example, ensuring the start time is before the end time). I'm using the System.ComponentModel.DataAnnotations attributes for my validation.
My first thought was something like this:
public enum CompareToOperation
{
EqualTo,
LessThan,
GreaterThan
}
public class CompareToAttribute : ValidationAttribute
{
CompareToOperation _Operation;
IComparable _Comparision;
public CompareToAttribute(CompareToOperation operation, Func<IComparable> comparison)
{
_Operation = operation;
_Comparision = comparison();
}
public override bool IsValid(object value)
{
if (!(value is IComparable))
return false;
switch (_Operation)
{
case CompareToOperation.EqualTo: return _Comparision.Equals(value);
case CompareToOperation.GreaterThan: return _Comparision.CompareTo(value) == 1;
case CompareToOperation.LessThan: return _Comparision.CompareTo(value) == -1;
}
return false;
}
}
public class SimpleClass
{
public DateTime Start {get;set;}
[CompareTo(CompareToOperation.GreaterThan, () => this.Start)] // error here
public DateTime End {get;set;}
}
This doesn't work however, there's a compiler error where the attribute is marked:
Expression cannot contain anonymous methods or lambda expressions
Does anyone have a solution for this? Or a different approach for validating one field compared to the value of another?

Check The AccountMOdel in the default project of MVC2, There is an attribute PropertiesMustMatchAttribute applied to the ChangePasswordModel to validate that the NewPassword and ConfirmPassword Match
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";
private readonly object _typeId = new object();
public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
: base(_defaultErrorMessage)
{
OriginalProperty = originalProperty;
ConfirmProperty = confirmProperty;
}
public string ConfirmProperty
{
get;
private set;
}
public string OriginalProperty
{
get;
private set;
}
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
OriginalProperty, ConfirmProperty);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
return Object.Equals(originalValue, confirmValue);
}
}

A very ugly way that's not nearly as flexible is to put it on the class and use reflection. I haven't tested this, so I'm not actually sure it works, but it does compile :)
public enum CompareToOperation
{
EqualTo,
LessThan,
GreaterThan
}
public class CompareToAttribute : ValidationAttribute
{
CompareToOperation _Operation;
string _ComparisionPropertyName1;
string _ComparisionPropertyName2;
public CompareToAttribute(CompareToOperation operation, string comparisonPropertyName1, string comparisonPropertyName2)
{
_Operation = operation;
_ComparisionPropertyName1 = comparisonPropertyName1;
_ComparisionPropertyName2 = comparisonPropertyName2;
}
private static IComparable GetComparablePropertyValue(object obj, string propertyName)
{
if (obj == null) return null;
var type = obj.GetType();
var propertyInfo = type.GetProperty(propertyName);
if (propertyInfo == null) return null;
return propertyInfo.GetValue(obj, null) as IComparable;
}
public override bool IsValid(object value)
{
var comp1 = GetComparablePropertyValue(value, _ComparisionPropertyName1);
var comp2 = GetComparablePropertyValue(value, _ComparisionPropertyName2);
if (comp1 == null && comp2 == null)
return true;
if (comp1 == null || comp2 == null)
return false;
var result = comp1.CompareTo(comp2);
switch (_Operation)
{
case CompareToOperation.LessThan: return result == -1;
case CompareToOperation.EqualTo: return result == 0;
case CompareToOperation.GreaterThan: return result == 1;
default: return false;
}
}
}
[CompareTo(CompareToOperation.LessThan, "Start", "End")]
public class SimpleClass
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}

From the look of it, this cannot be done.
ValidationAttribute is applied on a property and as such is restricted to that property only.
I assume the question is not an abstract one and you do have a real issue that requires the presence of such a validator. Probably it's the repeat password textbox? :-)
In any case, to work around the problem you have you need to rely on the context you work in. ASP.NET Web Forms did it with the ControlToCompare and since everything is a control and we have naming containers in place it's fairly easy to figure things out based on a simple string.
In ASP.NET MVC you can in theory do the same thing, BUT! Client side will be fairly easy and natural - just use the #PropertyName and do your stuff in javascript. Serverside though you would need to access something external to your attribute class - the Request object - and that is a no no as far as I'm concerned.
All in all, there IS always a reason for things (not)happening and, in my opinion, a reason why Microsoft did not implement this kind of validator in a first place is - it is not possible without things described above.
BUT! I really hope I'm wrong. I DO need the compare validation to be easy to use...

I think you need something like this:
public class EqualsAttribute : ValidationAttribute
{
private readonly String _To;
public EqualsAttribute(String to)
{
if (String.IsNullOrEmpty(to))
{
throw new ArgumentNullException("to");
}
if (String.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key");
}
_To = to;
}
protected override Boolean IsValid(Object value, ValidationContext validationContext, out ValidationResult validationResult)
{
validationResult = null;
var isValid = IsValid(value, validationContext);
if (!isValid)
{
validationResult = new ValidationResult(
FormatErrorMessage(validationContext.DisplayName),
new [] { validationContext.MemberName });
}
return isValid;
}
private Boolean IsValid(Object value, ValidationContext validationContext)
{
var propertyInfo = validationContext.ObjectType.GetProperty(_To);
if (propertyInfo == null)
{
return false;
}
var propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
return Equals(value, propertyValue);
}
public override Boolean IsValid(Object value)
{
throw new NotSupportedException();
}
}

Related

AutoFixture: How to set (nested) properties manually based on a pattern

I have the following nested classes, that are coming from XSD files generated via xsd.exe.
public class MyClass
{
public AnotherClass[] PropertyOne;
public DateTime PropertyTwo;
public bool PropertyTwoSpecified
}
public class AnotherClass
{
public DateTime AnotherPropertyOne
public bool AnotherPropertyOneSpecified
public int AnotherPropertyTwo
public bool AnotherPropertyTwoSpecified
}
Now I would like to use AutoFixture to generate instances with synthetic data.
var fixture = new Fixture();
fixture.Customize(new AutoFakeItEasyCustomization());
var myClassFake = fixture.Create<MyClass>();
I know I can use .with to set single properties, but how can I set properties based on a specific pattern? Especially when this properties are nested in arrays?
I basically have to ensure that all properties ending with *Specified are getting set to true. Including the once nested into PropertyOne
Do I have to use my one reflection based method, e.g. an extension method (e.g. myClassFake.EnableAllProperties()) or is there an AutoFixture-way to achieve my goal?
Edit
I know I can use fixture.Register<bool>(() => true); to set all my bools to true. This solves my very specific problem, but still feels clumsy and not generally applicable. Still looking for the precise way to solve this.
I ended up creating two implementations of ISpecimenBuilder that work perfectly for my situation.
This one sets all boolean properties ending on *Specified to true without affecting other boolean properties.
public class SpecifiedBoolSpecimenBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var pi = request as PropertyInfo;
if (pi == null)
{
return new NoSpecimen();
}
if (pi.PropertyType != typeof(bool) || !pi.Name.EndsWith("Specified"))
{
return new NoSpecimen();
}
return true;
}
}
This one sets a specific property to a range of random values:
public class OidSpecimenBuilder : ISpecimenBuilder
{
public int Min { get; set; }
public int Max { get; set; }
public OidSpecimenBuilder(int min, int max)
{
this.Min = min;
this.Max = max;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as PropertyInfo;
if (pi == null)
{
return new NoSpecimen();
}
if (pi.PropertyType != typeof(long) || pi.Name != "OID")
{
return new NoSpecimen();
}
return context.Resolve(new RangedNumberRequest(typeof(long), Min, Max));
}
}

NHibernate IUserType and custom functions for HQL and Linq

In short I wanted to create a custom IUserType to represent IPAddress from .NET (as an inet type in postgresql) and to be able to query it with custom functions via HQL and Linq. I have trouble with implementing a custom function for Linq.
What I have thus far:
A) I am able to map it:
public class SomeEntityMapper : ClassMap<SomeEntity> {
public SomeEntityMapper() {
...
Map(x => x.IpAddressField)
.CustomSqlType("inet")
.CustomType<IPAddressUserType>()
...
}
}
IUserType implementation below:
[Serializable]
public class IPAddressUserType : IUserType
{
public new bool Equals(object x, object y)
{
if (x == null && y == null)
return true;
if (x == null || y == null)
return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
if (x == null)
return 0;
return x.GetHashCode();
}
public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner)
{
if (names.Length == 0)
throw new InvalidOperationException("Expected at least 1 column");
if (rs.IsDBNull(rs.GetOrdinal(names[0])))
return null;
object value = rs[names[0]];
return value;
}
public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session)
{
NpgsqlParameter parameter = (NpgsqlParameter) cmd.Parameters[index];
parameter.NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Inet;
if (value == null)
{
parameter.Value = DBNull.Value;
}
else
{
parameter.Value = value;
}
}
public object DeepCopy(object value)
{
if (value == null)
return null;
IPAddress copy = IPAddress.Parse(value.ToString());
return copy;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
if (cached == null)
return null;
if (IPAddress.TryParse((string)cached, out var address))
{
return address;
}
return null;
}
public object Disassemble(object value)
{
if (value == null)
return null;
return value.ToString();
}
public SqlType[] SqlTypes => new SqlType[] { new NpgsqlSqlType(DbType.String, NpgsqlTypes.NpgsqlDbType.Inet), };
public Type ReturnedType => typeof(IPAddress);
public bool IsMutable => false;
}
public class NpgsqlSqlType : SqlType
{
public NpgsqlDbType NpgDbType { get; }
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType)
: base(dbType)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, int length)
: base(dbType, length)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, byte precision, byte scale)
: base(dbType, precision, scale)
{
NpgDbType = npgDbType;
}
}
}
B) I am able to query it using HQL and a custom function (inet_equals) I implemented.
using(var session = _factory.OpenSession()) {
var q = session.CreateQuery("from SomeEntity as s WHERE inet_equals(s.IpAddressField, :ip)");
q.SetParameter("ip", IPAddress.Parse("4.3.2.1"), NHibernateUtil.Custom(typeof(IPAddressUserType)));
}
Custom Postgresql Dialect extending the HQL functions implementation below:
public class CustomPostgresqlDialect : PostgreSQL83Dialect
{
public CustomPostgresqlDialect()
{
RegisterFunction("inet_equals", new SQLFunctionTemplate(NHibernateUtil.Boolean, "(?1::inet = ?2::inet)"));
}
}
C) I want to be able to query it using LINQ like so:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(IPAddress.Parse("4.3.2.1")));
}
Custom LINQ Generator for NHibernate provider below:
public static class InetExtensions
{
public static bool InetEquals(this IPAddress value, IPAddress other)
{
throw new NotSupportedException();
}
}
public class InetGenerator : BaseHqlGeneratorForMethod
{
public InetGenerator()
{
SupportedMethods = new[]
{
ReflectHelper.GetMethodDefinition(() => InetExtensions.InetEquals(default(IPAddress), default(IPAddress)))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, System.Linq.Expressions.Expression targetObject, ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
HqlExpression lhs = visitor.Visit(arguments[0]).AsExpression();
HqlExpression rhs = visitor.Visit(arguments[1]).AsExpression();
return treeBuilder.BooleanMethodCall(
"inet_equals",
new[]
{
lhs,
rhs
}
);
}
}
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public ExtendedLinqToHqlGeneratorsRegistry()
: base()
{
this.Merge(new InetGenerator());
}
}
Unfortunately when using LINQ I get this exception:
HibernateException: Could not determine a type for class: System.Net.IPAddress
Interestingly, this is the exact same error I get if I omit the NHibernateUtil.Custom(typeof(IPAddressUserType)) parameter inside the HQL query.
Which leads me to believe I am on the right track, but I can't figure out what I may be missing exactly. I assume I need to inform NHibernate inside the Generator that this is a custom UserType (just like I did with the HQL query via the NHibernateUtil.Custom(typeof(IPAddressUserType)) parameter.
I found the solution.
To hint NHibernate to use the proper UserType use: MappedAs extension method, like so:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(
IPAddress.Parse("4.3.2.1").MappedAs(NHibernateUtil.Custom(typeof(IPAddressUserType))
);
}
The year is 2019 and I'm still stuck on version 3.3.2.GA of NHibernate and the MappedAs extension method exists only on version 4.x onwards.
My scenario was the need to pass a large string as a parameter to a supported method in an HqlGeneratorForMethod.
I created the following class to store any large string to use as a parameter of any method throughout the application:
public class StringClob
{
public StringClob()
{
}
public StringClob(string value)
{
Value = value;
}
public virtual string Value { get; protected set; }
}
In order to link the NHibernateUtil.StringClob type with the string value I had the idea to create a mapping for my class without informing table mapping just the property (I use FluentNHibernate to map class):
public class StringClobMap : ClassMap<StringClob>
{
public StringClobMap()
{
Id(x => x.Value, "VALUE").CustomType("StringClob").CustomSqlType("VARCHAR(MAX)").Length(int.MaxValue / 2);
}
}
And now hypothetically following the example of #krdx the usage would look like this:
using(var session = _factory.OpenSession())
{
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(new StringClob("<large_string_here>")));
// ...
}
Therefore by passing the StringClob class as a parameter, NHibernate gets the custom type defined in the mapping.
I hope I helped those who still use NHibernate 3.3.2.GA.

Is there a way to perform a chained null check in a dynamic/expando?

C# has the usefull Null Conditional Operator. Well explained in this answer too.
I was wondering if it is possible to do a similar check like this when my object is a dynamic/expando object. Let me show you some code:
Given this class hierarchy
public class ClsLevel1
{
public ClsLevel2 ClsLevel2 { get; set; }
public ClsLevel1()
{
this.ClsLevel2 = new ClsLevel2(); // You can comment this line to test
}
}
public class ClsLevel2
{
public ClsLevel3 ClsLevel3 { get; set; }
public ClsLevel2()
{
this.ClsLevel3 = new ClsLevel3();
}
}
public class ClsLevel3
{
// No child
public ClsLevel3()
{
}
}
If i perform this kind of chained null check, it works
ClsLevel1 levelRoot = new ClsLevel1();
if (levelRoot?.ClsLevel2?.ClsLevel3 != null)
{
// will enter here if you DO NOT comment the content of the ClsLevel1 constructor
}
else
{
// will enter here if you COMMENT the content of the ClsLevel1
}
Now, i will try to reproduce this behaviour with dynamics (ExpandoObjects)
dynamic dinRoot = new ExpandoObject();
dynamic DinLevel1 = new ExpandoObject();
dynamic DinLevel2 = new ExpandoObject();
dynamic DinLevel3 = new ExpandoObject();
dinRoot.DinLevel1 = DinLevel1;
dinRoot.DinLevel1.DinLevel2 = DinLevel2;
//dinRoot.DinLevel1.DinLevel2.DinLevel3 = DinLevel3; // You can comment this line to test
if (dinRoot?.DinLevel1?.DinLevel2?.DinLevel3 != null)
{
// Obviously it will raise an exception because the DinLevel3 does not exists, it is commented right now.
}
Is there a way to simulate this behaviour with dynamics? I mean, check for a null in a long chain of members?
If you want to support this in a more natural way you can inherit from DynamicObject and provide a custom implementation:
class MyExpando : DynamicObject
{
private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name.ToLower();
result = _dictionary.ContainsKey(name) ? _dictionary[name] : null;
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_dictionary[binder.Name.ToLower()] = value;
return true;
}
}
Testing:
private static void Main(string[] args)
{
dynamic foo = new MyExpando();
if (foo.Boo?.Lol ?? true)
{
Console.WriteLine("It works!");
}
Console.ReadLine();
}
The output will be "It works!". Since Boo does not exist we get a null reference so that the Null Conditional Operator can work.
What we do here is to return a null reference to the output parameter of TryGetMember every time a property is not found and we always return true.
EDIT: fixed, as ExpandoObjects and extension methods do not work well together. Slightly less nice, but hopefully still usable.
Helper method(s):
public static class DynamicExtensions
{
public static Object TryGetProperty(ExpandoObject obj, String name)
{
return name.Split('.')
.Aggregate((Object)obj, (o, s) => o != null
? TryGetPropertyInternal(o, s)
: null);
}
private static Object TryGetPropertyInternal(Object obj, String name)
{
var dict = obj as IDictionary<String, Object>;
return (dict?.ContainsKey(name) ?? false) ? dict[name] : null;
}
}
Usage:
if (DynamicExtensions.TryGetProperty(dinRoot, "DinLevel1.DinLevel2.DinLevel3") != null)

How do you use CsvHelper to write a class derived from DynamicObject?

I was hoping to use a dynamically typed object to write to a CSV file.
I'm receiving a 'CsvHelper.CsvWriterException' within the CsvWriter.WriteObject method with this message: "No properties are mapped for type 'WpmExport.DynamicEntry'."
Here is the class that I'm trying use :
public class DynamicEntry : DynamicObject
{
private Dictionary<string, object> dictionary = new Dictionary<string, object>();
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
string name = binder.Name.ToLower();
return dictionary.TryGetValue(name, out result);
}
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
dictionary[binder.Name.ToLower()] = value;
return true;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return dictionary.Keys.AsEnumerable();
}
}
Anyone with any ideas or working examples? The documentation at http://joshclose.github.io/CsvHelper/ hints that it is possible but doesn't provide any guidance.
TIA
The functionality does not exist yet. You can write dynamic but not DynamicObject. You can view a thread on the subject here. https://github.com/JoshClose/CsvHelper/issues/187
When the functionality get implemented, I'll update the answer with the version it's in.
Update
This functionality will be available in 3.0. You can currently try out the 3.0-beta from NuGet.
Because I cannot wait for Version 3.0 (and CsvHelper.Excel to support it), I have found a interim-solution.
Got the class to export:
public partial class EntryReportInventory
{
public Guid DeviceId { get; set; }
[ReportProperty]
public string DeviceName { get; set; }
public Dictionary<string, object> InventoryValues { get; set; }
public EntryReportInventory(Device device, Dictionary<string, object> inventoryValues)
{
this.DeviceId = device.Id;
this.DeviceName = device.Name;
this.InventoryValues = inventoryValues;
}
}
Created Mapper:
Type genericClass = typeof(DefaultCsvClassMap<>);
Type constructedClass = genericClass.MakeGenericType(typeof(EntryReportInventory));
return (CsvClassMap)Activator.CreateInstance(constructedClass);
And now the magic. I iterate all properties.
foreach (PropertyInfo property in mapping)
{
...
if (isInventoryReportBaseType && typeof(Dictionary<string, object>).IsAssignableFrom(property.PropertyType))
{
var dataSource = (ReportInventoryBase)Activator.CreateInstance(entityType, dbContext);
foreach (var item in dataSource.ColumnNameAndText)
{
var columnName = item.Key;
var newMap = new CsvPropertyMap(property);
newMap.Name(columnName);
newMap.TypeConverter(new InventoryEntryListSpecifiedTypeConverter(item.Key));
customMap.PropertyMaps.Add(newMap);
}
...
}
And my converter is:
public class InventoryEntryListSpecifiedTypeConverter : CsvHelper.TypeConversion.ITypeConverter
{
private string indexKey;
public InventoryEntryListSpecifiedTypeConverter(string indexKey)
{
this.indexKey = indexKey;
}
public bool CanConvertFrom(Type type)
{
return true;
}
public bool CanConvertTo(Type type)
{
return true;
}
public object ConvertFromString(TypeConverterOptions options, string text)
{
throw new NotImplementedException();
}
public string ConvertToString(TypeConverterOptions options, object value)
{
var myValue = value as Dictionary<string, object>;
if (value == null || myValue.Count == 0) return null;
return myValue[indexKey] + "";
}
}
Don't know why, but it works to pass the same property several times.
That's it :)
You only have to have a list before (here: dataSource.ColumnNameAndText, filled from an external source) to identify the columns/values.

Get value of a property that was passed in as a string

I am trying to create a custom validation that says "if the otherValue is true, then this value must be greater than 0. I am able to get the value in, but the way I currently have the otherValue set up, I only have the name of the property, not the value. Probably because it passed in as a string. This attribute is going to be on 5 or 6 different properties and each time, it will be calling a different otherValue. Looking for help on how to get the actual value (it is a bool) of otherValue.
Here is my current code:
public class MustBeGreaterIfTrueAttribute : ValidationAttribute, IClientValidatable
{
// get the radio button value
public string OtherValue { get; set; }
public override bool IsValid(object value)
{
// Here is the actual custom rule
if (value.ToString() == "0")
{
if (OtherValue.ToString() == "true")
{
return false;
}
}
// If all is ok, return successful.
return true;
}
======================EDIT=========================
Here is where I am at now, and it works! Now I need a refernce on how to make it so I can put in a different errorMessage when adding the attribute in the model:
public class MustBeGreaterIfTrueAttribute : ValidationAttribute, IClientValidatable
{
// get the radio button value
public string OtherProperty { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var otherPropertyInfo = context.ObjectInstance.GetType();
var otherValue = otherPropertyInfo.GetProperty(OtherProperty).GetValue(context.ObjectInstance, null);
// Here is the actual custom rule
if (value.ToString() == "0")
{
if (otherValue.ToString().Equals("True", StringComparison.InvariantCultureIgnoreCase))
{
return new ValidationResult("Ensure all 'Yes' answers have additional data entered.");
}
}
// If all is ok, return successful.
return ValidationResult.Success;
}
// Add the client side unobtrusive 'data-val' attributes
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ValidationType = "requiredifyes";
rule.ErrorMessage = this.ErrorMessage;
rule.ValidationParameters.Add("othervalue", this.OtherProperty);
yield return rule;
}
}
So I should be able to do this:
[MustBeGreaterIfTrue(OtherProperty="EverHadRestrainingOrder", ErrorMessage="Enter more info on your RO.")]
public int? ROCounter { get; set; }
The ValidationAttribute has a pair of IsValid methods and for your scenario you have to use other guy.
public class MustBeGreaterIfTrueAttribute : ValidationAttribute
{
// name of the OtherProperty. You have to specify this when you apply this attribute
public string OtherPropertyName { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherProperty = validationContext.ObjectType.GetProperty(OtherPropertyName);
if (otherProperty == null)
return new ValidationResult(String.Format("Unknown property: {0}.", OtherPropertyName));
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (value.ToString() == "0")
{
if (otherPropertyValue != null && otherPropertyValue.ToString() == "true")
{
return null;
}
}
return new ValidationResult("write something here");
}
}
Example Usage:
public class SomeModel
{
[MustBeGreaterIf(OtherPropertyName="Prop2")]
public string Prop1 {get;set;}
public string Prop2 {get;set;}
}
Ref: http://www.concurrentdevelopment.co.uk/blog/index.php/2011/01/custom-validationattribute-for-comparing-properties/
I'm having a little bit of trouble understanding what you want to do, but,
If you want to get the boolean representation of OtherValue, you could do
bool.Parse(this.OtherValue);
also, for string comparison, it sometimes helps to take case and whitespace out of the picture
if (this.OtherValue.ToString().ToLower().Trim() == "true")
validationContext has ObjectInstance which is an instance of SomeModel class so you can use:
(validationContext.ObjectInstance as SomeModel).Prop1/Prop2

Categories

Resources