I just want to use code like this to implement a customer validate attribute named UniqueAttribute in MVC, but I am not clearly knowing about lamda expression.
The edit model class
The UniqueAttribute class :
public class UniqueAttribute<TService,TEntity,TKey> : ValidationAttribute
where TService : IDML<TEntity>
where TEntity:IPMMIdentity
{
public Expression<Func<TEntity, TKey>> UniqueExpression { get; set; }
public override bool IsValid(object value)
{
var service = IocContainerHelper.Resolve<TService>();
return !service.Contains(UniqueExpression, (TKey)value);
}
}
I have create a "Contains" method in my class where implement the interface IDML:
public virtual bool Contains<TKey>(Expression<Func<T, TKey>> selector, TKey value)
{
var predicate = Expression.Lambda<Func<T, bool>>(
Expression.Equal(selector.Body, Expression.Constant(value, typeof(TKey)))
, selector.Parameters);
return _repository.Count(predicate)>0;
}
So I want to define my edit model like below:
public class EditUser
{
public int Id { get; set; }
[Unique<IUserService,User,string>(UniqueExpression = t=>t.LoginName)] // error line,can not be complied.
public string LoginName { get; set; }
}
The error message is:
Cannot convert source type 'System.Linq.Expression.Expression<System.Func<User,string>>' to target type 'System.Linq.Expression.Expression<System.Func<TEntity,TKey>>'
How to correct it ? Any help would be much appreciated, thanks.
UPDATE:
I modified validate class, and it works well.
public class UniqueAttribute:ValidationAttribute
{
public UniqueAttribute(Type serviceType, Type entityType, string propertyName)
{
ServiceType = serviceType;
EntityType = entityType;
PropertyName = propertyName;
}
public Type ServiceType { get; private set; }
public Type EntityType { get; private set; }
public string PropertyName { get; private set; }
protected override ValidationResult IsValid(object value, System.ComponentModel.DataAnnotations.ValidationContext validationContext)
{
var propertyInfo = EntityType.GetProperty(PropertyName);
const string methodName = "Get";
object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
var constExpression = Expression.Constant(convertedValue);
var parameter = Expression.Parameter(EntityType, "te");
var memberExpression = Expression.MakeMemberAccess(parameter, propertyInfo);
var equal = Expression.Equal(memberExpression, constExpression);
var lambda = Expression.Lambda(equal, parameter);
var service = IocContainerHelper.Resolve(ServiceType);
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public;
var method = service.GetType().GetMethod(methodName, bindingFlags);
if (method == null)
{
throw new NullReferenceException(string.Format("{0} have not suitable method named '{1}'", service.GetType().FullName, methodName));
}
object id = validationContext.ObjectInstance.GetId();
object data = method.Invoke(service, new object[] { lambda });
if (!data.GetId().Equals(id))
{
var errorMessage = string.Format("{0} is used。", convertedValue);
return new ValidationResult(errorMessage);
}
return ValidationResult.Success;
}
The instance of ServiceType implement a Get(Expression> condition) method:
public virtual T Get(Expression<Func<T, bool>> condition)
{
return _repository.Get(condition);
}
In edit model, it is used like:
[Required]
[Unique(typeof(IUserService),typeof(User),"LoginName")]
[StringLength(50)]
[Display(Name = "Login Name")]
public string LoginName { get; set; }
Hope it can help you.
Related
I am wanting to call a method with the signiture something like
string fullname = UserBuilder.DefaultProperty(e => e.Fullname)
I have created a builder class and want to return the default property from a default supplied
public class BuilderGeneric<TModel>
{
private readonly TModel _defaultModel;
public BuilderGeneric(TModel defaultModel)
{
_defaultModel = defaultModel;
}
public object DefaultProperty<TProperty>([NotNull] Expression<Func<TModel, TProperty>> propertyExpression)
{
// Reflection here to get the property
// ...
var propertyType = myPropInfo.PropertyType;
var propertyValue = myPropInfo.GetValue(_defaultModel);
return propertyValue;
}
}
How can I make it so that I dont return an object back but instead the type of the property in the e => e.Fullname) in that example it would be a string
Thanks #canton7
I was trying to create a generic builder class which I have included below along with a unit test.
I hope this helps anyone trying to to a similar thing.
public class BuilderGeneric<TModel>
where TModel : class
{
private readonly TModel _defaultModel;
private TModel BuilderEntity { get; }
public BuilderGeneric(TModel defaultModel)
{
_defaultModel = defaultModel;
BuilderEntity = (TModel)Activator.CreateInstance(typeof(TModel));
}
public TProperty DefaultProperty<TProperty>(Func<TModel, TProperty> property)
{
return property(_defaultModel);
}
public BuilderGeneric<TModel> With<TProperty>(Expression<Func<TModel, TProperty>> property, TProperty value)
{
var memberExpression = (MemberExpression)property.Body;
var propertyInfo = (PropertyInfo)memberExpression.Member;
propertyInfo.SetValue(BuilderEntity, value);
return this;
}
public TModel Build()
{
return BuilderEntity;
}
}
public class UserBuilder : BuilderGeneric<UserProfile>
{
public UserBuilder()
: base(new UserProfile()
{
FullName = "Ian Bowyer",
EmailAddress = "Email#Email.com",
MyGuid = new Guid("12345678-1234-1234-1234-123456789012")
})
{
}
}
public class UserProfile
{
public string FullName { get; set; }
public string EmailAddress { get; set; }
public Guid MyGuid { get; set; }
}
public class TestGenericBuilder
{
[Test]
public void TestTheDefaultPropertyReturns()
{
// Assert
var userBuilder = new UserBuilder()
.With(e => e.FullName, "Bobby Tables")
.With(e => e.MyGuid, new Guid("85EC85CF-2892-40B8-BF63-0C621F78BE27"));
var user = userBuilder.Build();
// Act
var defaultFullname = userBuilder.DefaultProperty(e => e.FullName);
// Assert
defaultFullname.Should().Be("Ian Bowyer");
user.FullName.Should().Be("Bobby Tables");
}
}
Trying to build an order by expression using expression trees. But I am unable to access an expression bodied property of the query result's class.
This is the class structure:
public class AssetFileRecord : IAuditable, IEntity, INavigateToCustomValues
{
public AssetFileRecord()
{
this.UpdatedTimeStamp = DateTime.UtcNow;
}
public AssetFileRecord GetRecord()
{
return this;
}
public Guid Id { get; set; }
public int DisplayId { get; set; }
public string AssetTagNumber { get; set; }
[JObjectIgnore]
public virtual Account Account { get; set; }
public string AccountNumber => Account?.AccountNumber;
public string AuditTrail { get; set; }
public string OldTagNumber { get; set; }
public ActivityCode ActivityCode { get; set; }
[JObjectIgnore]
public virtual ICollection<AssetFileRecordDepreciation> AssetFileRecordDepreciations { get; set; }
// Depreciation Records
public double? AccumulatedDepreciation => Depreciation()?.AccumulatedDepreciation;
public DateTime? DepreciationAsOfDate => Depreciation()?.DepreciationAsOfDate;
public double? LifeMonths => Depreciation()?.LifeMonths;
public double? DepreciationBasis => Depreciation()?.DepreciationBasis;
public double? PeriodDepreciation => Depreciation()?.PeriodDepreciation;
private AssetFileRecordDepreciation Depreciation()
{
return AssetFileRecordDepreciations?.AsQueryable()?.OrderBy(d => d.AssetFileDepreciationBook.BookNo)?.FirstOrDefault();
}
}
I am unable to get to the property AccumulatedDepreciation which is a property of a virtual property of AssetFileRecord.
Below is the current code that works fine for any other non-expression bodied properties.
public static IQueryable<T> BuildOrderByExpression<T>(IQueryable<T> source, string sortProperty, ListSortDirection sortOrder, bool isFirstOrderTerm = true)
{
var type = typeof(T);
var property = type.GetProperty(sortProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var parameter = Expression.Parameter(type, "p");
// ReSharper disable once AssignNullToNotNullAttribute
Expression orderByExp;
var map = AssetFileRecordExpressionHelper.GetExpressionEmbodiedMemberMappings();
if (!map.TryGetValue($"{type.Name}.{property.Name}", out orderByExp))
{
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
orderByExp = Expression.Lambda(propertyAccess, parameter);
}
var typeArguments = new[] { type, property.PropertyType };
var methodBase = isFirstOrderTerm ? "OrderBy" : "ThenBy";
var methodName = sortOrder == ListSortDirection.Ascending ? methodBase : $"{methodBase}Descending";
var resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, Expression.Quote(orderByExp));
return source.Provider.CreateQuery<T>(resultExp);
}
In the above code "map" is a dictionary of that returns appropriate expression terms for the embodied members except for the ones accessed on top of Depreciation() and is as follows.
public static Dictionary<string, Expression> GetExpressionEmbodiedMemberMappings()
{
var map = new Dictionary<string, Expression>
{
{
$"{nameof(AssetFileRecord)}.{nameof(AssetFileRecord.AccountNumber)}",
(Expression<Func<AssetFileRecord, string>>) (
afr => afr.Account != null ? afr.Account.AccountNumber : null
)
}
};
return map;
}
Expression.Call does not evaluate to a valid SQL query and rather throws an exception.
((System.Data.Entity.Infrastructure.DbQuery<AssetFileRecord>)records).Sql = '((System.Data.Entity.Infrastructure.DbQuery<AssetFileRecord>)records).Sql' threw an exception of type 'System.NotSupportedException'
Intended result: It should append an order by expression to the expression tree generated in the end; while it's failing to do so, when tried to order by an expression bodied property member.
Can someone please help me get this working.
I can successfully replace simple parameter types in a lambda expression thanks to some answers on a previous question but I cannot figure out how to replace parameters from an incoming lambda to a nested parameter.
Consider the following objects:
public class DtoColour {
public DtoColour(string name)
{
Name = name;
}
public string Name { get; set; }
public ICollection<DtoFavouriteColour> FavouriteColours { get; set; }
}
public class DtoPerson
{
public DtoPerson(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
FavouriteColours = new Collection<DtoFavouriteColour>();
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
public ICollection<DtoFavouriteColour> FavouriteColours { get; set; }
}
public class DtoFavouriteColour
{
public DtoColour Colour { get; set; }
public DtoPerson Person { get; set; }
}
public class DomainColour {
public DomainColour(string name)
{
Name = name;
}
public string Name { get; set; }
public ICollection<DomainPerson> People { get; set; }
}
public class DomainPerson {
public DomainPerson(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
Colours = new Collection<DomainColour>();
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
public ICollection<DomainColour> Colours { get; set; }
}
and a Repository:
public class ColourRepository {
private IList<DtoColour> Colours { get; set; }
public ColourRepository()
{
var favColours = new Collection<DtoFavouriteColour>
{
new DtoFavouriteColour() { Person = new DtoPerson("Peter", "Parker") },
new DtoFavouriteColour() { Person = new DtoPerson("John", "Smith") },
new DtoFavouriteColour() { Person = new DtoPerson("Joe", "Blogs") }
};
Colours = new List<DtoColour>
{
new DtoColour("Red") { FavouriteColours = favColours },
new DtoColour("Blue"),
new DtoColour("Yellow")
};
}
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var coonvertedPred = MyExpressionVisitor.Convert(predicate);
return Colours.Where(coonvertedPred).Select(c => new DomainColour(c.Name)).ToList();
}
}
and finally an expression visitor which should convert the predicate into the correct one for the Dto Models
public class MyExpressionVisitor : ExpressionVisitor
{
private ReadOnlyCollection<ParameterExpression> _parameters;
public static Func<DtoColour, bool> Convert<T>(Expression<T> root)
{
var visitor = new MyExpressionVisitor();
var expression = (Expression<Func<DtoColour, bool>>)visitor.Visit(root);
return expression.Compile();
}
protected override Expression VisitParameter(ParameterExpression node)
{
var param = _parameters?.FirstOrDefault(p => p.Name == node.Name);
if (param != null)
{
return param;
}
if(node.Type == typeof(DomainColour))
{
return Expression.Parameter(typeof(DtoColour), node.Name);
}
if (node.Type == typeof(DomainPerson))
{
return Expression.Parameter(typeof(DtoFavouriteColour), node.Name);
}
return node;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_parameters = VisitAndConvert<ParameterExpression>(node.Parameters, "VisitLambda");
return Expression.Lambda(Visit(node.Body), _parameters);
}
protected override Expression VisitMember(MemberExpression node)
{
var exp = Visit(node.Expression);
if (node.Member.DeclaringType == typeof(DomainColour))
{
if (node.Type == typeof(ICollection<DomainPerson>))
{
return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty("FavouriteColours"));
}
return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty(node.Member.Name));
}
if (node.Member.DeclaringType == typeof(DomainPerson))
{
var nested = Expression.MakeMemberAccess(exp, typeof(DtoFavouriteColour).GetProperty("Person"));
return Expression.MakeMemberAccess(nested, typeof(DtoPerson).GetProperty(node.Member.Name));
}
return base.VisitMember(node);
}
}
Currently I get the following Exception
[System.ArgumentException: Expression of type
'System.Collections.Generic.ICollection1[ExpressionVisitorTests.DtoFavouriteColour]'
cannot be used for parameter of type
'System.Collections.Generic.IEnumerable1[ExpressionVisitorTests.DomainPerson]'
of method 'Boolean
Any[DomainPerson](System.Collections.Generic.IEnumerable1[ExpressionVisitorTests.DomainPerson],
System.Func2[ExpressionVisitorTests.DomainPerson,System.Boolean])']
Here is a dotnetfiddle of it not working.
Thank in advance for any help.
After some more searching I came across this answer by John Skeet which has led to me coming up with a working solution which involves adding an override for VisitMethodCall method on the ExpressionVisitor to replace the original MethodInfo with a new one for the correct type of collection.
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(Enumerable) && node.Arguments[0].Type == typeof(ICollection<DomainPerson>))
{
Expression obj = Visit(node.Object);
IEnumerable<Expression> args = Visit(node.Arguments);
if (obj != node.Object || args != node.Arguments)
{
var generic = typeof(Enumerable).GetMethods()
.Where(m => m.Name == node.Method.Name)
.Where(m => m.GetParameters().Length == node.Arguments.Count)
.Single();
var constructed = generic.MakeGenericMethod(typeof(DtoFavouriteColour));
return Expression.Call(obj, constructed, args);
}
}
return node;
}
I also needed to make sure my reference to the _parameters collection wasn't replaced by nested calls to VisitLambda<T> which might happen whilst visiting node.Body.
protected override Expression VisitLambda<T>(Expression<T> node)
{
var parameters = VisitAndConvert(node.Parameters, "VisitLambda");
// ensure parameters set but dont let original reference
// be overidden by nested calls
_parameters = parameters;
return Expression.Lambda(Visit(node.Body), parameters);
}
See dotnetfiddle for fully working solution.
If anyone has a better/more elegant solution please add an answer for me to mark.
You already solved the concrete issue, so I can't say if what I'm going to propose you is better/more elegant, but for sure is a bit more generic (removed the concrete types/properties/assumptions), hence can be reused for translating similar expressions from different model types.
Here is the code:
public class ExpressionMap
{
private Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
private Dictionary<MemberInfo, Expression> memberMap = new Dictionary<MemberInfo, Expression>();
public ExpressionMap Add<TFrom, TTo>()
{
typeMap.Add(typeof(TFrom), typeof(TTo));
return this;
}
public ExpressionMap Add<TFrom, TFromMember, TTo, TToMember>(Expression<Func<TFrom, TFromMember>> from, Expression<Func<TTo, TToMember>> to)
{
memberMap.Add(((MemberExpression)from.Body).Member, to.Body);
return this;
}
public Expression Map(Expression source) => new MapVisitor { map = this }.Visit(source);
private class MapVisitor : ExpressionVisitor
{
public ExpressionMap map;
private Dictionary<Type, ParameterExpression> parameterMap = new Dictionary<Type, ParameterExpression>();
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(Visit(node.Body), node.Parameters.Select(Map));
}
protected override Expression VisitParameter(ParameterExpression node) => Map(node);
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
if (expression == node.Expression)
return node;
Expression mappedMember;
if (map.memberMap.TryGetValue(node.Member, out mappedMember))
return Visit(mappedMember);
return Expression.PropertyOrField(expression, node.Member.Name);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Object == null && node.Method.IsGenericMethod)
{
// Static generic method
var arguments = Visit(node.Arguments);
var genericArguments = node.Method.GetGenericArguments().Select(Map).ToArray();
var method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArguments);
return Expression.Call(method, arguments);
}
return base.VisitMethodCall(node);
}
private Type Map(Type type)
{
Type mappedType;
return map.typeMap.TryGetValue(type, out mappedType) ? mappedType : type;
}
private ParameterExpression Map(ParameterExpression parameter)
{
var mappedType = Map(parameter.Type);
ParameterExpression mappedParameter;
if (!parameterMap.TryGetValue(mappedType, out mappedParameter))
parameterMap.Add(mappedType, mappedParameter = Expression.Parameter(mappedType, parameter.Name));
return mappedParameter;
}
}
}
and the usage for your concrete example:
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var map = new ExpressionMap()
.Add<DomainColour, DtoColour>()
.Add((DomainColour c) => c.People, (DtoColour c) => c.FavouriteColours.Select(fc => fc.Person))
.Add<DomainPerson, DtoPerson>();
var mappedPredicate = ((Expression<Func<DtoColour, bool>>)map.Map(predicate));
return Colours.Where(mappedPredicate.Compile()).Select(c => new DomainColour(c.Name)).ToList();
}
As you can see, it allows you to define a simple mapping from one type to another, and optionally from member of one type to member/expression of another type (as soon as they are compatible) using "fluent" syntax with lambda expressions. The members that have no specified mapping are mapped by name as in the original code.
Once the mappings are defined, the actual processing of course is done by a custom ExpressionVisitor, similar to yours. The difference is that it maps and consolidates ParameterExpressions by type, and also translates every static generic method, thus should work also with Queryable and similar.
Wondering is there a possibility to avoid writing rules for every fields that needs to get validated, for an example all the properties must be validated except Remarks. And I was thinking to avoid writing RuleFor for every property.
public class CustomerDto
{
public int CustomerId { get; set; } //mandatory
public string CustomerName { get; set; } //mandatory
public DateTime DateOfBirth { get; set; } //mandatory
public decimal Salary { get; set; } //mandatory
public string Remarks { get; set; } //optional
}
public class CustomerValidator : AbstractValidator<CustomerDto>
{
public CustomerValidator()
{
RuleFor(x => x.CustomerId).NotEmpty();
RuleFor(x => x.CustomerName).NotEmpty();
RuleFor(x => x.DateOfBirth).NotEmpty();
RuleFor(x => x.Salary).NotEmpty();
}
}
Here you go:
public class Validator<T> : AbstractValidator<T>
{
public Validator(Func<PropertyInfo, bool> filter) {
foreach (var propertyInfo in typeof(T)
.GetProperties()
.Where(filter)) {
var expression = CreateExpression(propertyInfo);
RuleFor(expression).NotEmpty();
}
}
private Expression<Func<T, object>> CreateExpression(PropertyInfo propertyInfo) {
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyInfo);
var conversion = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<T, object>>(conversion, parameter);
return lambda;
}
}
And it can be used as such:
private static void ConfigAndTestFluent()
{
CustomerDto customer = new CustomerDto();
Validator<CustomerDto> validator = new Validator<CustomerDto>(x=>x.Name!="Remarks"); //we specify here the matching filter for properties
ValidationResult results = validator.Validate(customer);
}
I'm looking for a way to get the name and value of a proptery in a POCO object. I've tried many solutions but can't seem to get them to work. I really liked this older solution but it causes a null ref error.
Here's kind of what I'm trying to do:
public class POCO
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Description { get; set; }
}
public class POCOValidationResult
{
public bool IsValid { get; set; }
public string Error { get; set; }
}
public abstract class Validator<T> where T : class
{
public T Entity { get; set; }
public abstract POCOValidationResult Validate();
protected POCOValidationResult ValidateStringPropertyToLengthOf(Expression<Func<T, object>> expression, int maxLength)
{
var propertyName = getPropertyName(expression);
var propertyValue = getPropertyValue(expression);
if (propertyValue.Length > maxLength)
{
return new POCOValidationResult()
{
Error = string.Format("{0} value is too long. Must be less or equal to {1}", propertyName, maxLength.ToString())
};
}
return new POCOValidationResult() { IsValid = true };
}
internal string getPropertyName(Expression<Func<T, object>> expression)
{
var memberExpersion = (MemberExpression)expression.Body;
return memberExpersion.Member.Name;
}
internal string getPropertyValue<R>(Expression<Func<T, R>> expression)
{
//struggling to get this to work
var me = (MemberExpression)expression.Body; // (MemberExpression)((MemberExpression)expression.Body).Expression;
var ce = (ConstantExpression)me.Expression; // Error here!
var fieldInfo = ce.Value.GetType().GetField(me.Member.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var value = fieldInfo.GetValue(ce.Value);
}
}
public class POCOValidator : Validator<POCO>
{
public override POCOValidationResult Validate()
{
var surnameValidationResult = ValidateStringPropertyToLengthOf(p => p.Surname, 10);
if (!surnameValidationResult.IsValid)
return surnameValidationResult;
//var descriptionValidationResult = ValidateStringPropertyToLengthOf(p => p.Description, 100);
//if (!descriptionValidationResult.IsValid)
// return descriptionValidationResult;
//var nameValidationResult = ValidateStringPropertyToLengthOf(p => p.Name, 15);
//if (!nameValidationResult.IsValid)
// return nameValidationResult;
return new POCOValidationResult() { IsValid = true };
}
}
public class WorkerBee
{
public void ImDoingWorkReally()
{
var pocoVallidation = new POCOValidator()
{
Entity = new POCO()
{
ID = 1,
Name = "James",
Surname = "Dean",
Description = "I'm not 007!"
}
};
var vallidationResult = pocoVallidation.Validate();
if (!vallidationResult.IsValid)
{
return;
}
//continue to do work...
}
}
class Program
{
static void Main(string[] args)
{
var workerBee = new WorkerBee();
workerBee.ImDoingWorkReally();
}
}
So as you can see, I'm trying to get the Property's name and value [by using an Expression (p => p.Surname) as a parameter in the method ValidateStringPropertyToLengthOf(...)]. The problem is that I'm getting a null ref error in getPropertyValue(Expression<Func<T, object>> expression) when it calls var ce = (ConstantExpression)me.Expression;
So does anyone have ideas on how to get this to work?
Thanks for taking the time to look into this. I really appreciate it and hope that my question also is helpful for others as I think this can be rather useful if I can get this to work.
EDIT: I've made the change as mentioned below in the comments and still getting the error "Unable to cast object of type 'System.Linq.Expressions.TypedParameterExpression' to type 'System.Linq.Expressions.ConstantExpression" when I run my unit test.
I worked out a solution (unfortunately the comments were not helpful). Here's the code that works:
public class POCO
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Description { get; set; }
}
public class POCOValidationResult
{
public bool IsValid { get; set; }
public string Error { get; set; }
}
public abstract class Validator<T> where T : class
{
public T Entity { get; set; }
public abstract POCOValidationResult Validate();
protected POCOValidationResult ValidateStringPropertyToLengthOf(Expression<Func<T, object>> expression, int maxLength)
{
var propertyName = getPropertyName(expression);
var propertyValue = getPropertyValue(expression);
if (propertyValue.Length > maxLength)
{
return new POCOValidationResult()
{
Error = string.Format("{0} value is too long. Must be less or equal to {1}", propertyName, maxLength.ToString())
};
}
return new POCOValidationResult() { IsValid = true };
}
internal string getPropertyName(Expression<Func<T, object>> expression)
{
var memberExpersion = (MemberExpression)expression.Body;
return memberExpersion.Member.Name;
}
internal string getPropertyValue(Expression<Func<T, object>> expression)
{
var memberExpression = expression.Body as MemberExpression;
var propertyInfo = memberExpression.Member as PropertyInfo;
return propertyInfo.GetValue(Entity, null).ToString();
}
}
public class POCOValidator : Validator<POCO>
{
public override POCOValidationResult Validate()
{
var surnameValidationResult = ValidateStringPropertyToLengthOf(p => p.Surname, 10);
if (!surnameValidationResult.IsValid)
return surnameValidationResult;
var descriptionValidationResult = ValidateStringPropertyToLengthOf(p => p.Description, 100);
if (!descriptionValidationResult.IsValid)
return descriptionValidationResult;
var nameValidationResult = ValidateStringPropertyToLengthOf(p => p.Name, 15);
if (!nameValidationResult.IsValid)
return nameValidationResult;
return new POCOValidationResult() { IsValid = true };
}
}
public class WorkerBee
{
public void ImDoingWorkReally()
{
var pocoVallidation = new POCOValidator()
{
Entity = new POCO()
{
ID = 1,
Name = "James",
Surname = "Dean",
Description = "I'm not 007!"
}
};
var vallidationResult = pocoVallidation.Validate();
if (!vallidationResult.IsValid)
{
return;
}
//continue to do work...
}
}
class Program
{
static void Main(string[] args)
{
var workerBee = new WorkerBee();
workerBee.ImDoingWorkReally();
}
}
Note the change for getPropertyValue:
internal string getPropertyValue(Expression<Func<T, object>> expression)
{
var memberExpression = expression.Body as MemberExpression;
var propertyInfo = memberExpression.Member as PropertyInfo;
return propertyInfo.GetValue(Entity, null).ToString();
}
I noticed that MaudDib's getPropertyValue method only works for properties directly on the object. e.g. it will work for myObj.Value, but not for myObj.Something.Value. The following works for both. (I don't know if there's a better way, though).
Usage: var myValue = GetPropertyValue(myObj, o => o.Something.Value);
public static object GetPropertyValue<T>(T obj, Expression<Func<T, object>> expression)
{
var members = new CompositeExpressionVisitor().GetMembers(expression);
object currentVal = obj;
foreach (var part in members)
{
currentVal = GetPropertyValue(currentVal, part);
}
return currentVal;
}
private static object GetPropertyValue(object obj, MemberInfo member)
{
var propertyInfo = (PropertyInfo)member;
return propertyInfo.GetValue(obj, null);
}
private class CompositeExpressionVisitor : ExpressionVisitor
{
private readonly List<MemberInfo> _members = new List<MemberInfo>();
protected override Expression VisitMember(MemberExpression node)
{
_members.Add(node.Member);
return base.VisitMember(node);
}
public IReadOnlyCollection<MemberInfo> GetMembers(Expression e)
{
Visit(e is LambdaExpression expression ? expression.Body : e);
_members.Reverse();
return _members;
}
}
And, if you want the full path of the expression, e.g. you don't just want the leaf... you can do this:
Usage: var myValue = NameOf(() => myObj.Something.Value);
Returns: myObj.Something.Value
Or: var myValue = NameOf(() => myObj.Something.Value, 1);
Returns: Something.Value
public static string NameOf(Expression<Func<object>> expression, int startIndex = 0)
{
return new CompositeExpressionVisitor().GetPath(expression, startIndex);
}
private class CompositeExpressionVisitor : ExpressionVisitor
{
private readonly List<string> _parts = new List<string>();
protected override Expression VisitMember(MemberExpression node)
{
_parts.Add(node.Member.Name);
return base.VisitMember(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
_parts.Add(node.Name);
return base.VisitParameter(node);
}
public string GetPath(Expression e, int startIndex = 0)
{
Visit(e is LambdaExpression expression ? expression.Body : e);
_parts.Reverse();
return string.Join(".", _parts.Skip(startIndex));
}
}