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);
}
Related
Is there a way to perform a LINQ query on Entity Framework DBSets and only return the properties that have a specific custom attribute?
My goal is to perform a query and return only the properties/columns that I need for an export.
I also want to make this an extension for IEnumerable as I have many separate EF Classes that will use this export attribute.
This is how I am visualizing it:
public class Person
{
[Export]
public string FirstName { get; set; }
[Export]
public string LastName { get; set; }
[Export]
public string Address { get; set; }
[NeverExport]
public string SocialSecurityNumber { get; set; }
}
public void main()
{
var people = PersonRepository.GetAll();
var exportResults = people.GetByAttribute(ExportAttribute);
{
public static IEnumerable<object> GetByAttribute(this IEnumerable<object> list, Attribute attribute)
{
return list
.Select(x =>
// Select properties with { attribute } (ie. [Export])
);
}
I've made a very basic example for your problem. In short, you want to get all Properties, where a specific Attribute is present. You can achive this via reflection. First, you want all properties of a specific type, then only the properties, where a specific attribute is present.
Here is my example code:
using System;
using System.Linq;
using System.Reflection;
namespace Sandbox
{
public class Program
{
public static void Main(string[] args)
{
var attrs = typeof(User).GetProperties().Where(x => x.GetCustomAttributes().Any(y => y.GetType() == typeof(Custom)));
var users = new User[]
{
new User() { ID = 1, Name = "John", Lastname = "Doe" },
new User() { ID = 2, Name = "Jane", Lastname = "Doe" }
};
foreach(var u in users)
{
foreach(var attr in attrs)
{
Console.WriteLine(typeof(User).GetProperty(attr.Name).GetValue(u, null));
}
}
}
}
public class User
{
public string Name { get; set; }
[Custom]
public int ID { get; set; }
[Custom]
public string Lastname { get; set; }
}
public class Custom : System.Attribute
{
}
}
The following code projects any IQueryable to IQueryable<ExpandoObject> and you can use it for exporting data. Additionally code caches projection expression for reusing later in application.
Usage is simple:
var exportResult = anyQuery.ProjectForExport().ToList();
And implementation:
[AttributeUsage(AttributeTargets.Property)]
public class ExportAttribute : Attribute
{
}
public static class ExportTools
{
private static readonly ConstructorInfo _expandoObjectConstructor =
typeof(ExpandoObject).GetConstructor(Type.EmptyTypes) ?? throw new InvalidOperationException();
private static readonly MethodInfo _expandoAddMethodInfo = typeof(IDictionary<string, object>).GetTypeInfo()
.GetRuntimeMethods()
.Single(mi => mi.Name == nameof(IDictionary<string, object>.Add) && mi.GetParameters().Length == 2);
public static class ProjectionCache<T>
{
public static Expression<Func<T, ExpandoObject>> ProjectionExpression { get; } = BuildProjection();
private static Expression<Func<T, ExpandoObject>> BuildProjection()
{
var param = Expression.Parameter(typeof(T), "e");
var properties = typeof(T).GetProperties()
.Where(p => p.GetCustomAttributes().Any(a => a is ExportAttribute)).ToList();
if (properties.Count == 0)
throw new InvalidOperationException($"Type '{typeof(T).Name}' has no defined Export properties.");
var expr = (Expression)Expression.ListInit(
Expression.New(_expandoObjectConstructor),
properties.Select(p =>
{
var readerExpr = Expression.MakeMemberAccess(param, p);
return Expression.ElementInit(_expandoAddMethodInfo, Expression.Constant(p.Name), readerExpr);
}));
var lambda = Expression.Lambda<Func<T, ExpandoObject>>(expr, param);
return lambda;
}
}
public static IQueryable<ExpandoObject> ProjectForExport<T>(this IQueryable<T> query)
{
return query.Select(ProjectionCache<T>.ProjectionExpression);
}
}
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 have following situation:
public class BaseClass
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
public class Product: BaseClass
{
/* Some methods*/
}
public class Country: BaseClass
{
/* Some methods*/
}
public class MyCustomClass
{
public Product Product { get; set; }
public Country Country { get; set; }
}
I want to create a list of expressions which I would later use to query my MyCustomClass, example
var listOfExpressions= new List<Expression<Func<MyCustomClass, bool>>>();
if (x == 1)
listOfExpressions.Add(x => x.Product.Field1 == "Fruit")
/*.
.
.*/
if (y == 2)
listOfExpressions.Add(x => x.Country.Field2 == "Western")
}
Now this looks really ugly with these bunch of Ifs and I would prefer to have a method in BaseClass for which you would pass int ({1,2,3}) and it would return me the expression. The problem is that I need to have expression of type <MyCustomClass, bool> but in my BaseClass I can't directly get that, is there an way I could acheive this without complicating code too much?
I am thinking about something like this:
public class BaseClass
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
protected Expression<Func<BaseClass, bool>> GetExpression(int key, string value)
{
switch (key)
{
case 1:
return x => x.Field1 == value;
case 2:
return x => x.Field2 == value;
case 3:
return x => x.Field3 == value;
}
}
By now I am stuck of how to get Expression<Func<BaseClass, bool>> to be used in Expression<Func<MyCustomClass, bool>>.
EDIT: The purpose of the expression:
I want to constuct a list of expressions to later use it to query database using Entity framework core. Example:
var query = DbContext.MyCustomClass.AsQueryable();
foreach (var expression in listOfExpressions)
{
query = query.Where(expression);
}
You can build your Expression on you own. Just add another parameter to GetExpression:
public static Expression<Func<MyCustomClass, bool>> GetExpression(
string derrivedName, // "Product" or "Country"
int fieldId,
string value)
{
var x = Expression.Parameter(typeof(MyCustomClass), "x");
var lambda = Expression.Lambda<Func<MyCustomClass, bool>>(
Expression.Equal(
Expression.Constant(value),
Expression.PropertyOrField(
Expression.PropertyOrField(x, derrivedName),
$"Field{fieldId}")
),
x);
return lambda;
}
Now you can use it like:
var exp3 = GetExpression("Product", 1, "Fruit");
This line will create an expression x => ("Fruit" == x.Product.Field1)
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));
}
}