I have a class implementation that inherits from AbstractValidator<T>
to keep things simple: Assume I have
public class Users{
[Required]
public string Name {get;set;}
}
what I woul like to do is to invoke
RuleFor(x => x.Name).NotEmpty();
but the properties will be know at runtime so I would like to do this with reflection and expression trees..
I'm failing to invoke RuleFor method, below is my implementation and the commented parts are things I've tried.. any help is much appreciated.
GenValidator<T> : AbstractValidator<T>
{
// constructor etc..
public async Task<IEnumerable<string>> ValidateValueAsync(T model, string propertyName)
{
try
{
var loType = model.GetType();
var property = loType.GetProperty(propertyName);
var param = Expression.Parameter(loType);
var propertyExpression = Expression.Property(param, property);
var lambdaExpressionType = typeof(Expression);
var lambda = Expression.Lambda(typeof(Func<,>).MakeGenericType(model.GetType(), typeof(object)), propertyExpression, param);
var lambdaMethod = typeof(Expression).GetMethod("Lambda", new Type[] { typeof(Type), typeof(Expression), typeof(ParameterExpression[]) });
var lambda2 = lambdaMethod.MakeGenericMethod(loType, typeof(object)).Invoke(null, new object[] { propertyExpression, new ParameterExpression[] { param } });
var compileMethod = lambda.GetType().GetMethod("Compile");
var func = compileMethod.Invoke(lambda2, null);
var ruleForMethod = typeof(AbstractValidator<T>).GetMethod("RuleFor");
var genericRuleForMethod = ruleForMethod.MakeGenericMethod(loType);
genericRuleForMethod.Invoke(this, new object[] { func });
//RuleFor<object>(lambda).NotEmpty().WithMessage("The property is required.");
//RuleFor(lambda.Compile()).NotEmpty().WithMessage("The property is required.");
}
catch (Exception ex)
{
}
//var lambda = Expression.Lambda<Func<T, object>>(property, param);
//RuleFor(lambda).NotEmpty().WithMessage("The property is required.");
var valContext = ValidationContext<T>.CreateWithOptions(model, x => x.IncludeProperties(_propertyName));
var result = await ValidateAsync(valContext);
if (result.IsValid)
{
return Array.Empty<string>();
}
return result.Errors.Select(e => e.ErrorMessage);
}
}
EDIT
public class MyValidator
{
public MyValidator(string propertyname)
{
// this is how you call using fluentvalidation but it's hardcoded.
RuleFor(x => x.Name).NotEmpty();
// I do know want to hard code.
//I want to do some magical stuff at this point
//and invoke the RuleFor method, I only have propertyname as a
//string, type of model and model at this point
}
}
EDIT2
private void GenericRuleFor(T Model, string propertyName)
{
var property = Model.GetType().GetProperty(propertyName);
var param = Expression.Parameter(Model.GetType());
var propertyExpression = Expression.Property(param, property);
var lambda = Expression.Lambda(typeof(Func<,>).MakeGenericType(Model.GetType(), property.PropertyType), propertyExpression, param);
var abstractValidatorType = typeof(AbstractValidator<>).MakeGenericType(Model.GetType());
var ruleForMethod = abstractValidatorType.GetMethods().First(m => m.Name == "RuleFor" && m.IsGenericMethodDefinition);
var genericRuleForMethod = ruleForMethod.MakeGenericMethod(property.PropertyType);
genericRuleForMethod.Invoke(this, new object[] { lambda });
}
this is the modified version of my code which still does not work..
it throws an exception when I invoke genericRuleForMethod
telling me that object type does not match Target type
Here is my working version of your GenericRuleFor is below. The main difference seems like you are trying to get the RuleFor method from the typeof(AbstractValidator<>) I just get it from the this.GetType()
private void GenericRuleFor(string propertyName)
{
var type = typeof(T);
var property = type.GetProperty(propertyName);
var param = Expression.Parameter(type);
var propertyExpression = Expression.Property(param, property);
var lambda = Expression.Lambda(typeof(Func<, >).MakeGenericType(type, property.PropertyType), propertyExpression, param);
var thisType = this.GetType();
var ruleForMethod = thisType.GetMethod("RuleFor", BindingFlags.Public | BindingFlags.Instance);
var genericRuleForMethod = ruleForMethod.MakeGenericMethod(property.PropertyType);
// result is used by extension method
var result = genericRuleForMethod.Invoke(this, new object[]{lambda});
//NotEmpty method is an Extension metot which is contained by DefaultValidatorExtensions
var extensionsType = typeof(DefaultValidatorExtensions);
var notEmptyMethod = extensionsType.GetMethod("NotEmpty", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(type, property.PropertyType);
notEmptyMethod.Invoke(null, new object[]{result});
}
Here is the fiddle
Related
code:
static Func<T,object> CompileGetValueExpression<T>(PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var property = Expression.Property(instance, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
return Expression.Lambda<Func<T,object>>(convert, instance).Compile();
}
e.g
void Main()
{
var data = new Test{prop1 = 1};
var type = data.GetType();
var prop = type.GetProperties().First();
var function = CompileGetValueExpression<Test>(prop);
var result = function(data); //result:1
}
class Test{
public int prop1 { get; set; }
}
question
Is this expression function exactly equal to below method?
object GetterFunction(Test i) => i.prop1 as object;
I think they are. One evidence is if you dump expression created in CompileGetValueExpression as string, it outputs i => (i.prop1 As Object).
Try to modify the last line of CompileGetValueExpression
Expression.Lambda<Func<T, object>>(convert, instance).ToString();
If I have a dynamically created ParameterExpression:
class Product
{
public string Name { get; set; }
}
var propertyName = "Name";
var propertyType = typeof(Product).GetProperty(propertyName).PropertyType;
var parameterExpression = Expression.Parameter(propertyType , propertyName);
How can I covert it into a Func<Product, TPropertyType>?
I specifically want to pass this into the Where or OrderBy linq methods used by entity framework.
I'm also open to other suggestions not using Expressions, but I highly doubt it's possible.
Edit 1: Removed the where use case as Where and OrderBy will have different implementations Removed in an attempt to narrow the scope of the question.
Here is an example with generating expressions for OrderBy and Where. As Johnathon Sullinger said in comments, you must know type of property you ordering by at compile time, because it is mentioned in signature of OrderBY. However you don't have to know it for Where:
class Product
{
public string Name { get; set; }
}
static void Main(string[] args)
{
var products = new List<Product> {
new Product { Name = "ZZZ"},
new Product { Name = "AAA"}
};
var propertyName = "Name";
var ordered = products.AsQueryable().OrderBy(GetOrderExpression<string>(propertyName));
Console.WriteLine(ordered.ElementAt(0).Name);
Console.WriteLine(ordered.ElementAt(1).Name);
var filtered = products.AsQueryable().Where(GetWhereExpression(propertyName, "AAA"));
Console.WriteLine(filtered.Count());
Console.WriteLine(filtered.ElementAt(0).Name);
Console.ReadKey();
}
static Expression<Func<Product, TKey>> GetOrderExpression<TKey>(string propertyName)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var lambda = Expression.Lambda<Func<Product, TKey>>(prop, "p", new[] { prm });
return lambda;
}
static Expression<Func<Product, bool>> GetWhereExpression(string propertyName, object value)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var equal = Expression.Equal(prop, Expression.Constant(value));
var lambda = Expression.Lambda<Func<Product, bool>>(equal, "p", new[] { prm });
return lambda;
}
Hope it helps.
I looked at the other SO versions of this question but it seems the casting out of a method works for others. I am not sure what I am doing wrong here. I am new to the Expression Building part of Linq.
My extensions method is as follows:
void Main()
{
var people = LoadData().AsQueryable();
var expression = people.PropertySelector<Person>("LastName");
expression.Should().BeOfType(typeof(Expression<Func<Person, object>>));
var result = people.OrderBy(expression);
}
public static class Extensions
{
public static Expression<Func<T, object>> PropertySelector<T>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, object>>)lambda;
}
}
#region
private Random rand = new Random();
// Define other methods and classes here
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public IEnumerable<Person> LoadData()
{
IList<Person> people = new List<Person>();
for (var i = 0; i < 15; i++)
{
people.Add(new Person
{
FirstName = $"FirstName {i}",
LastName = $"LastName {i}",
Age = rand.Next(1, 100)
});
}
return people;
}
#endregion
I get an exception on the return during the cast. At this point T is type Person and object is a string. My lambda.GetType() is reporting that it's of type Expression<Func<Person, string>> The exception is:
Unable to cast object of type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.String]]' to type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.Object]]'.
What about my cast is incorrect? Thanks.
EDIT:
I updated with my full code that I am playing with in LinqPad. I am really just trying to figure out if there is an easy way to generate a lambda expression by passing in the property name. I was doing something like this before but I was just doing a switch on property name then using lambda syntax to create the OrderBy query dynamically.
This is strictly me trying to learn how Expression static methods can be used to achieve the same result as the example below. I am trying to mimic the below via extension method. But it doesn't have to be that way. It was just easiest to try while dinking around in LinqPad.
Expression<Func<Loan, object>> sortExpression;
switch (propertyFilter)
{
case "Age":
sortExpression = (l => l.Age);
break;
case "LastName":
sortExpression = (l => l.LastName);
break;
default:
sortExpression = (l => l.FirstName);
break;
}
var sortedLoans = loans.AsQueryable().OrderBy(sortExpression);
sortedLoans.Dump("Filtered Property Result");
Your code is creating a Func<UserQuery, String> because you're geting it's intrinsic type with
var propertyInfo = properties.Single(p => p.Name == propertyName);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
If you want to return a Func<T, object> then create a Func<T, object>, not a Func<T, (reflected property type)>, else the better solution is to use a Func<TOut, TIn> and create a totally generic function.
To keep current method signature you could do this:
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(object));
var typeAs = Expression.TypeAs(property, typeof(object));
var lambda = Expression.Lambda(funcType, typeAs, alias);
and better way is to change your method to
public static Expression<Func<T, Tout>> PropertySelector<T, Tout>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, Tout>>)lambda;
}
and call it with
var expression = people.PropertySelector<Person, string>("LastName");
I got the result I wanted. After comments from #Gusman, #IvanStoev and #PetSerAl I got it to function. I can move on with my exploring and learning again. Thank you very much. Final result was to template the Propertytype.
public static Expression<Func<T, TPropertyType>> PropertySelector<T, TPropertyType>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(TPropertyType));
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, TPropertyType>>)lambda;
}
I use custom validation attributes instead of the built in DataAnnotation ones. In the DbContext I run through the entities with reflection and configure the model based on these attributes. Unfortunately the following code doesn't work for integer properties. It is probably because it a cannot find the suitable overload for the "Property" method.
/// <summary>
/// This method should return the
/// property configuration object of the modelbuilder
/// mb.Entity<User>().Property(p=>p.Age)
/// </summary>
private PrimitivePropertyConfiguration GetStringPropertyConfiguration(DbModelBuilder mb, Type entityType, string propertyName)
{
//mb.Entity<User>()
var structuralConfiguration = typeof(DbModelBuilder).GetMethod("Entity").MakeGenericMethod(entityType).Invoke(mb, null);
//p=>p.Age
var param = Expression.Parameter(entityType);
var propertyExpression = Expression.Lambda(Expression.Property(param, propertyName), param);
//.Property()
var propertyMethod = structuralConfiguration.GetType().GetMethod("Property", new[] { propertyExpression.GetType() });
if (propertyMethod != null)
{
var stringpropertyConfiguration =
propertyMethod.Invoke(structuralConfiguration, new[] {propertyExpression}) as
PrimitivePropertyConfiguration;
return stringpropertyConfiguration;
}
else
{
throw new Exception("This should not happen");
}
}
//to test
public class Entity
{
public string StringProperty { get; set; }
public int IntegerProperty { get; set; }
}
var stringPropertyConfig = GetStringPropertyConfiguration(mb, typeof (Entity), "StringProperty");
var intPropertyConfig = GetStringPropertyConfiguration(mb, typeof(Entity), "IntegerProperty");
You need to have your Expression<Func<>> strongly typed to your property return type.
Something like this would work for a property of type int :
private PrimitivePropertyConfiguration GetStructPropertyConfiguration(DbModelBuilder mb, Type entityType, string propertyName, Type propertyType)
{
var structuralConfiguration = typeof(DbModelBuilder).GetMethod("Entity")
.MakeGenericMethod(entityType).Invoke(mb, null);
var param = System.Linq.Expressions.Expression.Parameter(entityType);
var funcType = typeof(Func<,>).MakeGenericType(entityType, propertyType);
var expressionType = typeof(System.Linq.Expressions.Expression);
var lambdaMethod = expressionType.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(mi => mi.Name == "Lambda").FirstOrDefault();
var genericLambdaMethod = lambdaMethod.MakeGenericMethod(funcType);
var prop = System.Linq.Expressions.Expression.Property(param, propertyName);
var inv = genericLambdaMethod.Invoke(null, new object[] { prop, new[] { param } });
var propertyMethod = this.GetType().GetMethods(BindingFlags.Public|BindingFlags.Instance)
.Where(mi => mi.Name == "Property").FirstOrDefault();
var propertyMethodGeneric = propertyMethod.MakeGenericMethod(propertyType);
var mapping = propertyMethodGeneric.Invoke(structuralConfiguration , new[]{inv})
as PrimitivePropertyConfiguration;
return mapping;
}
I want to write a method which creates mocks for any interface.
public T GetMock<T>(IDictionary<string, object> data) where T : class
I care only about property getters first. All getters should return values which are stored in the dictionary. Property name is a key in this dictionary. Following code illustrates intended usage:
public interface IFoo
{
string Property1 { get; }
int Property2 { get; }
DateTime Property3 { get; }
}
[Test]
public void TestY()
{
var data = new Dictionary<string, object>
{
{"Property1", "Hello"},
{"Property2", 5},
{"Property3", DateTime.Today}
};
var mock = GetMock<IFoo>(data);
Assert.AreEqual("Hello", mock.Property1);
Assert.AreEqual(5, mock.Property2);
Assert.AreEqual(DateTime.Today, mock.Property3);
}
The point is that I want to mock ANY interface. So my generic mock crreation looks like:
public T GetMock<T>(IDictionary<string, object> data) where T : class
{
var mock = new Mock<T>();
var type = typeof(T);
var properties = type.GetProperties();
foreach (var property in properties)
{
var attributeName = property.Name;
var parameter = Expression.Parameter(type);
var body = Expression.Property(parameter, attributeName);
var lambdaExpression = Expression.Lambda<Func<T, object>>(body, parameter);
Func<object> getter = () => data[attributeName];
mock.Setup(lambdaExpression).Returns(getter);
}
return mock.Object;
}
It should work but there is an issue with type conversion. The test fails with a message:
System.ArgumentException : Expression of type 'System.Int32' cannot be
used for return type 'System.Object'
I guess I am missing some conversion lambda. Any suggestions how to fix the problem?
Guess the only option is to use Reflection, because current version is 4.2, but still - there's no "Mock.Setup(Expression expr)" implementation, as stated Patrick.
So, here's my sample:
public static class ConfigFactory<T> where T : class {
static T cachedImplInstance;
public static T BuildConfigGroupWithReflection() {
if (cachedImplInstance == null) {
Type interfaceType = typeof(T);
MethodInfo setupGetMethodInfo = typeof(Mock<T>).GetMethod("SetupGet");
Mock<T> interfaceMock = new Mock<T>();
IDictionary<Type, MethodInfo> genericSetupGetMethodInfos = new Dictionary<Type, MethodInfo>();
IDictionary<Type, MethodInfo> specificReturnsMethodInfos = new Dictionary<Type, MethodInfo>();
if (setupGetMethodInfo != null)
foreach (PropertyInfo interfaceProperty in interfaceType.GetProperties()) {
string propertyName = interfaceProperty.Name;
Type propertyType = interfaceProperty.PropertyType;
ParameterExpression parameter = Expression.Parameter(interfaceType);
MemberExpression body = Expression.Property(parameter, propertyName);
var lambdaExpression = Expression.Lambda(body, parameter);
MethodInfo specificSetupGetMethodInfo =
genericSetupGetMethodInfos.ContainsKey(propertyType) ?
genericSetupGetMethodInfos[propertyType] :
genericSetupGetMethodInfos[propertyType] = setupGetMethodInfo.MakeGenericMethod(propertyType);
object setupResult = specificSetupGetMethodInfo.Invoke(interfaceMock, new[] { lambdaExpression });
MethodInfo returnsMethodInfo =
specificReturnsMethodInfos.ContainsKey(propertyType) ?
specificReturnsMethodInfos[propertyType] :
specificReturnsMethodInfos[propertyType] = setupResult.GetType().GetMethod("Returns", new[] { propertyType });
if (returnsMethodInfo != null)
returnsMethodInfo.Invoke(setupResult, new[] { Settings.Default[propertyName] });
}
cachedImplInstance = interfaceMock.Object;
}
return cachedImplInstance;
}
}
Notice line "returnsMethodInfo.Invoke(setupResult, new[] { Settings.Default[propertyName] });" - you may put your dictionnary here.
Say, we have interface:
public interface IConfig {
string StrVal { get; }
int IntVal { get; }
StringCollection StrsVal { get; }
string DbConnectionStr { get; }
string WebSvcUrl { get; }
}
Then, usage is as follows (assuming we have "Settings" of our project with corresponding Names/Types/Values):
IConfig cfg0 = ConfigFactory<IConfig>.BuildConfigGroupWithReflection();
This is a half answer, since I don't see any support in Moq for doing this. To get the correct Func, do the following:
// In your for loop from above...
var attributeName = property.Name;
var parameter = Expression.Parameter(type);
var body = Expression.Property(parameter, attributeName);
// Add this line to create the correct Func type
var func = typeof(Func<,>).MakeGenericType(typeof(T), property.PropertyType);
// Then use this Func to create the lambda
var lambdaExpression = Expression.Lambda(func, body, parameter);
The problem is that Setup doesn't have an overload that allows you to pass in a non-generic expression that represents a Func. In otherwords, this won't compile:
// Error: cannot convert from 'System.Linq.Expressions.LambdaExpression'
// to 'System.Linq.Expressions.Expression<System.Action<T>>'
mock.Setup(lambdaExpression);
So at this point you're stuck.
You could submit an issue (or pull request) to the Moq project, though I don't know if this application has a wide enough audience...