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));
}
}
Related
I am doing unit testing, and basically want to check that the data that 2 objects hold is the same
Assert.AreEqual(object1, object2);
Assert.IsTrue(object1.Equals(object2)); //this of course doesn't work
I am searching for the C# equivalent of assertJ
Assert.That(object1).isEqualToComparingFieldByField(object2)
You could either use records (c# 9 +) or you have to override the Equals method (if you have access and you can change the objects that you're working with).
Records example:
var point = new Point(3, 4);
var point2 = new Point(3, 4);
var test = point.Equals(point2); //this is true
public record Point(int X, int Y);
with classes:
public class Point
{
public int X { get; }
public int Y { get; }
public override bool Equals(object? obj)
{
if (obj == null)
return false;
return obj is Point point && (point.X == X && point.Y == Y);
}
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
if you are not allowed to touch the implementation, then you could use serialization and compare the strings:
var obj1Str = JsonConvert.SerializeObject(object1);
var obj2Str = JsonConvert.SerializeObject(object2);
Assert.Equal(obj1Str, obj2Str);
using Newtonsoft.Json nuget
C# classes are reference equality, which means that variables are the same using the standard Equals and == if they point to the same object, you could override that behaivour, but it may break something now or in the future.
Or, you could switch to using a construct that's value equality by default, which structs as well as record classes are. If you can't (or don't want to) do that you can implement a value equals "helper" method yourself. I would not recommend overriding the Equals method or the == operator, as that can (and most likely will) lead to errors in the future instead I recommend you write your own ValueEquals method or extension method, something along the lines of
class Foo
{
public int Count {get; set;}
public string Message {get; set;}
}
public static bool ValueEquals(this Foo self, Foo other)
{
return self.Count == other.Count && self.Message == other.Message;
}
public void MyTest()
{
// Arrange and Act
...
// Assert
Assert.IsTrue(myFoo1.ValueEquals(myFoo2));
}
Depending on whether or not you can/ want to add a ValueEquals to your Foo class you can decide on doing it with an extension method or a normal method.
You could also implement a IEqualityComparer<T> like
public class FooValueEqualityComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo foo1, Foo foo2)
{
return foo1.Count == foo2.Count &&
foo1.Message == foo2.Message;
}
public int GetHashCode(Foo foo)
{
return foo.GetHashCode();
}
}
// Use it
public void MyTest()
{
// Arrange and Act
...
// Assert
Assert.IsTrue(new FooEqualityComparer().Equals(myFoo1, myFoo2));
}
Or, you could write a generic ValueEquals that works for all^* classes using Reflection:
public static class ValueEqualityComparer
{
public static bool ValueEquals<T>(this T self, T other) where T : class
{
var type = self.GetType();
if (type == typeof(string))
return self.Equals(other);
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
var selfValue = property.GetValue(self);
var otherValue = property.GetValue(other);
// String is special, it's not primitive but is value equality like primitives
if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(string))
{
if (!selfValue.Equals(otherValue))
return false;
}
// If the property is a List value equals each member
// Maybe find another type that allows indexing and is less restrictive
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
var selfList = ((IEnumerable)property.GetValue(self)).Cast<object>();
var otherList = ((IEnumerable)property.GetValue(other)).Cast<object>();
try
{
// Using EquiZip from MoreLinq: https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/EquiZip.cs
foreach (var element in selfList.EquiZip(otherList, (selfItem, otherItem) => new { selfItem, otherItem }))
{
if (!ValueEquals(element.selfItem, element.otherItem))
return false;
}
}
catch (InvalidOperationException)
{
// MoreLINQ throws a InvalidOperationException if our two enumerables aren't the same length
return false;
}
}
else
{
if (!ValueEquals(selfValue, otherValue))
return false;
}
}
return true;
}
}
This implementation is by no means perfect, and should honestly only be used for UnitTests and also should be thoroughly tested itself. You can see my tests as a dotnetfiddle here
Or you could do it "dirty" and serialize the objects to a string and compare those values.
The goal of this code is to iterate through multiple nested classes, and multiple any integer by 2. Provided simple example, however, example will be more complicated in future.
How do I change a Object to its underlying class? When I iterate through this function, it reads the type for OuterProduct correctly, but fails for InnerProduct reading as type System.RuntimeType, giving an error below
How can I resolve this code to multiply all nested integers by 2?
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
class Program
{
static void Main(string[] args)
{
var test = new OuterProduct();
test.AmountSold = 5;
test.ProductName = "BookOuter";
test.InnerProduct = new InnerProduct();
test.InnerProduct.ProductNameInner = "BookInner";
test.InnerProduct.AmountSoldInner = 7;
ReadPropertiesTest.ReadPropertiesRecursive(test);
}
}
public class OuterProduct
{
public string ProductName { get; set; }
public int AmountSold { get; set; }
public InnerProduct InnerProduct { get; set; }
}
public class InnerProduct
{
public string ProductNameInner { get; set; }
public int AmountSoldInner { get; set; }
}
public static class ReadPropertiesTest
{
public static void ReadPropertiesRecursive(object test)
{
var type = test.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
if (property.PropertyType == typeof(int) || property.PropertyType == typeof(int?))
{
property.SetValue(test, (int)(property.GetValue(test)) * 2);
}
if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
ReadPropertiesRecursive(property.PropertyType);
}
}
}
}
Resources:
C#: How to get all public (both get and set) string properties of a type
How to iterate through nested properties of an object
System.RuntimeType is the implementation of the class that represents typeof(X) or something.GetType(). When you pass PropertyType to your function you are not passing the property value, but it's type.
You will need to pass the next object in the hierarchy into the recursive function by using GetValue.
Note though that this is dangerous and error prone. For example, if you have a List<> property you obviously cannot increase its Count (it is readonly!). You should check to make sure that the property can be written to using the CanWrite property.
You also need to check for null objects. On top of that we need to handle int differently from int? (otherwise casting null to int will throw). The latter we can clean up a bit with c#7 pattern matching:
public static void ReadPropertiesRecursive(object test)
{
if (test is null) // base case
return;
var type = test.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
// check if we can even read the property
if(!property.CanRead)
continue;
// use pattern matching on the value
// nulls will be ignored
// we *could* cache GetValue but then it means we will invoke it for uninteresting types/properties
// it's also why I don't call GetValue until we've inspected PropertyType
if (property.CanWrite &&
(property.PropertyType == typeof(int) || property.PropertyType == typeof(int?)) &&
property.GetValue(test) is int i)
{
property.SetValue(test, i * 2);
}
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
ReadPropertiesRecursive(property.GetValue(test));
}
}
}
An alternative version that omits some of the checks against PropertyType can also be used. It's a bit cleaner looking but it could potentially perform the GetValue reflection in cases where we don't need/want it (like on a double or a struct):
public static void ReadPropertiesRecursive(object test)
{
if (test is null) // base case
return;
var type = test.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
// check if we can even read the property
if(!property.CanRead)
continue;
// possibly unnecessary if not int or class
var val = property.GetValue(test);
if (property.CanWrite && val is int i)
{
property.SetValue(test, i * 2);
}
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
ReadPropertiesRecursive(val);
}
}
}
Note that you may want to have a whitelist or blacklist of types. Recursing into a Type object for example isn't going to get you much.
Alternative would be to go with more object-oriented approach. Make it responsibility of every class which need to be "updated".
For every type with properties which need to be updated introduce a method to do it.
public class OuterProduct
{
public string ProductName { get; set; }
public int AmountSold { get; set; }
public InnerProduct InnerProduct { get; set; }
public void Update()
{
AmountSold *= 2;
InnerProduct.Update();
}
}
public class InnerProduct
{
public string ProductNameInner { get; set; }
public int AmountSoldInner { get; set; }
public void Update()
{
AmountSoldInner *= 2;
}
}
// Usage is simple
var test = new OuterProduct
{
AmountSold = 5,
ProductName = "BookOuter",
InnerProduct = new InnerProduct
{
ProductNameInner = "BookInner",
AmountSoldInner = 7
}
};
test.Update();
// test.AmountSold == 10 is true
// test.InnerProduct.AmountSoldInner == 14 is true
This approach will simplify code maintenance. For example adding/removing properties or worse case scenario adding some other logic to Update method will be isolated in one class.
In your recursive call you are passing the type, not the actual property value:
if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
ReadPropertiesRecursive(property.PropertyType);
}
should be:
if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
ReadPropertiesRecursive(property.GetValue(test));
}
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.
So I have this in my C# lib:
public static TOut IfNotNull<TIn, TOut>
(this TIn instance, Func<TIn, TOut> func)
{
return instance == null ? default(TOut) : func(instance);
}
Used like:
DateTime? expiration = promo.IfNotNull(p => p.TermsAndConditions.Expiration)
.IfNotNull(e => e.Date);
I keep wracking my brain trying to figure out how to use the C# 4 dynamic keyword to enable this syntax instead:
DateTime? expiration = promoOffer.TermsAndConditions.Maybe()
.Expiration.Maybe()
.Date;
I had a couple examples that I thought worked but they broke down when you start chaining the Maybe()s.
Any ideas?
(Am I wasting my time? Is Maybe() a win over IfNotNull())?
I don't think that using dynamic type is a good idea here, because the syntax isn't going to look much better and you sacrifice the type safety (and IntelliSense) by using dynamic typing.
However, here is an example of what you can do. The idea is that you wrap objects into DynamicWrapper (your monadic value :-)) that can either contain a null value or an actual value. It would inherit from DynamicObject and delegate all calls to the actual object (if there is any) or immediately return null (that would be monadic bind):
class DynamicWrapper : DynamicObject {
public object Object { get; private set; }
public DynamicWrapper(object o) { Object = o; }
public override bool TryGetMember(GetMemberBinder binder, out object result) {
// Special case to be used at the end to get the actual value
if (binder.Name == "Value") result = Object;
// Binding on 'null' value - return 'null'
else if (Object == null) result = new DynamicWrapper(null);
else {
// Binding on some value - delegate to the underlying object
var getMeth = Object.GetType().GetProperty(binder.Name).GetGetMethod();
result = new DynamicWrapper(getMeth.Invoke(Object, new object[0]));
return true;
}
public static dynamic Wrap(object o) {
return new DynamicWrapper(o);
}
}
The example supports only properties and it uses reflection in a pretty inefficient way (I think it could be optimized using DLR). Here is an example how it works:
class Product {
public Product Another { get; set; }
public string Name { get; set; }
}
var p1 = new Product { Another = null };
var p2 = new Product { Another = new Product { Name = "Foo" } };
var p3 = (Product)null;
// prints '' for p1 and p3 (null value) and 'Foo' for p2 (actual value)
string name = DynamicWrapper.Wrap(p1).Another.Name.Value;
Console.WriteLine(name);
Note that you can chain the calls freely - there is only something special at the beginning (Wrap) and at the end (Value), but in the middle, you can write .Another.Another.Another... as many times you want.
This solution is similar to Tomas' except that it uses CallSite to invoke properties on the target instance and also supports casting and extra calls to Maybe (as per your example).
public static dynamic Maybe(this object target)
{
return new MaybeObject(target);
}
private class MaybeObject : DynamicObject
{
private readonly object _target;
public MaybeObject(object target)
{
_target = target;
}
public override bool TryGetMember(GetMemberBinder binder,
out object result)
{
result = _target != null ? Execute<object>(binder).Maybe() : this;
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args, out object result)
{
if (binder.Name == "Maybe" &&
binder.ReturnType == typeof (object) &&
binder.CallInfo.ArgumentCount == 0)
{
// skip extra calls to Maybe
result = this;
return true;
}
return base.TryInvokeMember(binder, args, out result);
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
if (_target != null)
{
// call Execute with an appropriate return type
result = GetType()
.GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.Instance)
.MakeGenericMethod(binder.ReturnType)
.Invoke(this, new object[] {binder});
}
else
{
result = null;
}
return true;
}
private object Execute<T>(CallSiteBinder binder)
{
var site = CallSite<Func<CallSite, object, T>>.Create(binder);
return site.Target(site, _target);
}
}
The following code should demonstrate it in use:
var promoOffer = new PromoOffer();
var expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date;
Debug.Assert((DateTime?) expDate == null);
promoOffer.TermsAndConditions = new TermsAndConditions();
expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date;
Debug.Assert((DateTime?) expDate == null);
promoOffer.TermsAndConditions.Expiration = new Expiration();
expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date;
Debug.Assert((DateTime?) expDate == null);
promoOffer.TermsAndConditions.Expiration.Date = DateTime.Now;
expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date;
Debug.Assert((DateTime?) expDate != null);
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();
}
}