I try to get the named arguments for MyAttribute with Roslyn.
var sourceCode = (#"
public class MyAttribute : Attribute
{
public string Test { get; set; }
}
[MyAttribute(Test = ""Hello"")]
public class MyClass { }
");
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("MyCompilation", new[] { syntaxTree }, new[] { mscorlib });
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var syntaxRoot = syntaxTree.GetRoot();
var classNode = syntaxRoot.DescendantNodes().OfType<ClassDeclarationSyntax>().Skip(1).First();
var classModel = (ITypeSymbol)semanticModel.GetDeclaredSymbol(classNode);
var firstAttribute = classModel.GetAttributes().First();
However firstAttribute.AttributeClass.Kind equals to ErrorType and consequently firstAttribute.NamedArguments contains no elements.
The code isn't an anlyzer or something I have more complete context like a solution.
I can't see roslyn is missing any references or something else. What can I do to fully analyze the attribute?
You need to fully qualify Attribute type name:
var sourceCode = (#"
public class MyAttribute : System.Attribute // < here
{
public string Test { get; set; }
}
[MyAttribute(Test = ""Hello"")]
public class MyClass { }
");
Then it will work as you expect:
var firstNamedArg = firstAttribute.NamedArguments[0];
var key = firstNamedArg.Key; // "Test"
var value = firstNamedArg.Value.Value; // "Hello"
Alternatively, you can add using System; at the top:
var sourceCode = (#"
using System;
public class MyAttribute : Attribute
{
public string Test { get; set; }
}
[MyAttribute(Test = ""Hello"")]
public class MyClass { }
");
Instead of using the Roslyn's SemanticModel you can also simply use the Syntax API to get the atttribute's argument info:
var firstAttribute = classNode.AttributeLists.First().Attributes.First();
var attributeName = firstAttribute.Name.NormalizeWhitespace().ToFullString();
Console.WriteLine(attributeName);
// prints --> "MyAttribute"
var firstArgument = firstAttribute.ArgumentList.Arguments.First();
var argumentFullString = firstArgument.NormalizeWhitespace().ToFullString();
Console.WriteLine(argumentFullString);
// prints --> Test = "Hello"
var argumentName = firstArgument.NameEquals.Name.Identifier.ValueText;
Console.WriteLine(argumentName);
// prints --> Test
var argumentExpression = firstArgument.Expression.NormalizeWhitespace().ToFullString();
Console.WriteLine(argumentExpression);
// prints --> "Hello"
Once you have your xxxDeclaredSymbol you can get all attributes like so
var attributes = methodSymbol.GetAttributes().ToArray();
You can then loop over each AttributeData in the array and use this extension I wrote, which will give you a list of all names + values passed to the attribute's constructor whether they were named or not...
// Used as a 3 value tuple for Name + TypeName + actual value
public class NameTypeAndValue
{
public string Name { get; private set; }
public string TypeFullName { get; private set; }
public object Value { get; private set; }
public NameTypeAndValue(string name, string typeFullName, object value)
{
Name = name;
TypeFullName = typeFullName;
Value = value;
}
}
public static class ITypeSymbolExtensions
{
// Converts names like `string` to `System.String`
public static string GetTypeFullName(this ITypeSymbol typeSymbol) =>
typeSymbol.SpecialType == SpecialType.None
? typeSymbol.ToDisplayString()
: typeSymbol.SpecialType.ToString().Replace("_", ".");
}
public static bool TryGetAttributeAndValues(
this AttributeData attributeData,
string attributeFullName,
SemanticModel model,
out IEnumerable<NameTypeAndValue> attributeValues)
{
var attributeValuesList = new List<NameTypeAndValue>();
var constructorParams = attributeData.AttributeConstructor.Parameters;
// Start with an indexed list of names for mandatory args
var argumentNames = constructorParams.Select(x => x.Name).ToArray();
var allArguments = attributeData.ConstructorArguments
// For unnamed args, we get the name from the array we just made
.Select((info, index) => new KeyValuePair<string, TypedConstant>(argumentNames[index], info))
// Then we use name + value from the named values
.Union(attributeData.NamedArguments.Select(x => new KeyValuePair<string, TypedConstant>(x.Key, x.Value)))
.Distinct();
foreach(var argument in allArguments)
{
attributeValuesList.Add(
new NameTypeAndValue(
name: argument.Key,
typeFullName: argument.Value.Type.GetTypeFullName(),
value: argument.Value.Value));
}
attributeValues = attributeValuesList.ToArray();
return true;
}
Related
Hi I have a scenario to filter the data based sub-object field please help me. From controller as query I pass Expression String.
class MasterDocument
{
private Id;
public ICollection<SubDocument> documents { get; set; }
}
class SubDocument
{
private Id;
public int Age { get; set; }
}
var filterQuery = "documents.Age == 25";
var filteredResult = MasterDocument.Where(filterQuery).ToList();
to filter the Data
how to create Expression from string to filter data from Substructure.
Well, that's quite complicated topic, but i will first give code example and later focus on caveats:
I would follow approach and define it as another extension method:
using System.Linq.Expressions;
namespace ConsoleApp2;
public static class WhereExtensions
{
public static IEnumerable<T> Where<T>(
this IEnumerable<T> collection,
string filterExpression)
{
// Most probably you'd like to have here more sophisticated validations.
var itemsToCompare = filterExpression.Split("==")
.Select(x => x.Trim())
.ToArray();
if (itemsToCompare.Length != 2)
{
throw new InvalidOperationException();
}
var source = Expression.Parameter(typeof(T));
var property = itemsToCompare[0];
var valueToCompareAgainst = itemsToCompare[1];
var memberExpr = source.GetMemberExpression(property);
var comparisonExpr = Expression.Equal(
Expression.Call(memberExpr, typeof(object).GetMethod("ToString")),
Expression.Constant(valueToCompareAgainst)
);
var predicate = Expression.Lambda<Func<T, bool>>(comparisonExpr, source);
return collection.Where(predicate.Compile());
}
public static MemberExpression GetMemberExpression(
this ParameterExpression parameter,
string memberExpression)
{
var properties = memberExpression.Split('.');
if (!properties.Any())
{
throw new InvalidOperationException();
}
var memberExpr = Expression.PropertyOrField(parameter, properties[0]);
foreach (var property in properties.Skip(1))
{
memberExpr = Expression.PropertyOrField(memberExpr, property);
}
return memberExpr;
}
}
and the usage would be:
using ConsoleApp2;
var example = new[]
{
new TestClass() { Id = 1, Description = "a" },
new TestClass() { Id = 2, Description = "a" },
new TestClass() { Id = 3, Description = "b" },
new TestClass() { Id = 4, Description = "b" },
};
var result1 = example.Where("Id == 1").ToList();
var result2 = example.Where("Description == b").ToList();
Console.WriteLine("Items with Id == 1");
result1.ForEach(x => Console.WriteLine($"Id: {x.Id} , Descr: {x.Description}"));
Console.WriteLine("Items with Description == b");
result2.ForEach(x => Console.WriteLine($"Id: {x.Id} , Descr: {x.Description}"));
class TestClass
{
public int Id { get; set; }
public string Description { get; set; }
}
This codes returns:
NOW, THE CAVEATS
It's very tricky to cast value to compare against to an arbitrary type T, that's why i reversed the problem, and I call "ToString" on whetever member we want to compare
Expression.Call(memberExpr, typeof(object).GetMethod("ToString"))
But this also could have it's own issues, as often "ToString" returns default tpye name. But works well with integers and simple value types.
I want to know you can I get by reflection a Type to execute a method that implements a certain interface.
System.Attribute implementation
[AttributeUsage( AttributeTargets.Property )]
public class ExampleAttribute : Attribute
{
public ExampleAttribute( string key )
{
Key = key;
}
public string Key { get; set; }
public Type Executor { get; set; }
}
Model that uses the attribute annotation (AttributeTargets.Property)
public class Example
{
[ExampleAttribute( "property" )]
public string Property { get; set; }
[ExampleAttribute( "lock", Executor = typeof( LockExecutor ) )]
public string Lock { get; set; }
}
public interface IExecutor
{
object TranslateValue( object value );
}
public class LockExecutor: IExecutor
{
public object TranslateValue( object value )
{
var lock = value.ToString() == "LOCK";
return lock;
}
}
I want to know how can I get by reflection the object of type IExecutor to execute the specific implementation (LockExecutor).
I hope it is not trying to translate something and it is just an abstraction.
var ex = new Example{Property = "Prop", Lock = "LOCK"};
var type = ex.GetType();
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public) // get properties
.Select(t =>
{
var attr = t.GetCustomAttribute<ExampleAttribute>(); // read attribute
var executor = attr == null ? null :attr.Executor; // it has attribute?
IExecutor instance = null;
if (executor != null) // attribute has executor
instance = (IExecutor)Activator.CreateInstance(executor); // create instance
return new
{
Property = t, Executor = instance
};
}
).Where(t => t.Executor != null).ToList(); // select the ones which has executor
properties.ForEach(t =>
{
var val = t.Property.GetValue(ex); // get value from instance
var translated = t.Executor.TranslateValue(val); // execute
Console.WriteLine(translated); // dump
}
);
Fiddle
I am not sure what is the rational behind the requirement, but you can use this code:
var example = new Example() { Property = "x", Lock = "y" };
var property = example.GetType().GetProperty("Lock");
var attribute = property.GetCustomAttributes(true).OfType<ExampleAttribute>().FirstOrDefault();
var executer = Activator.CreateInstance(attribute.Executor);
var method = attribute.Executor.GetMethod("TranslateValue");
var result = method.Invoke(executer, new[] { example.Lock });
I am getting this exception:
variable 'e' of type 'MyClass' referenced from scope '', but it is not defined
I want to create dynamic rules with nested property. When I create static rules it works fine, but not in dynamic mode. Also, I want to use expression for dynamic rules using these lines of code if possible:
PatternBuilder customerPattern = builder.LeftHandSide().Pattern(typeof(Customer), "customer");
Expression<Func<Customer, bool>> customerCondition = customer => customer.Name == "John Do";
customerPattern.Condition(customerCondition);
I tried the code below to create and execute rules dynamically, but I am getting an exception. Why?
class Program
{
static void Main(string[] args)
{
try
{
CustomRuleRepository repository = new CustomRuleRepository();
List<RuleEngineEntity> rules = new List<RuleEngineEntity>();
rules.Add(new RuleEngineEntity { FieldName = "Age", Name = "CustomerCheck", Value = 20 });
repository.LoadRules(rules);
//Compile rules
var factory = repository.Compile();
//Create a working session
var session = factory.CreateSession();
RuleEngineRequestModel ruleEngineRequestModel = new RuleEngineRequestModel { ruleList = rules, customerData = new Customer { Name = "A", Age = 24 } };
session.Insert(ruleEngineRequestModel);
var IspassedorNot = session.Fire();
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
}
}
public class RuleEngineRequestModel
{
public List<RuleEngineEntity> ruleList { get; set; }
public Customer customerData { get; set; }
}
public class RuleEngineEntity
{
public string Name { get; set; }
public int Value { get; set; }
public string Operator { get; set; }
public string FieldName { get; set; }
}
public class Customer
{
public string Name { get; set; }
public int Age { get; set; }
}
public class CustomRuleRepository : IRuleRepository
{
private readonly IRuleSet _ruleSet = new RuleSet("customerRule");
public IEnumerable<IRuleSet> GetRuleSets()
{
return new[] {_ruleSet};
}
public void LoadRules(List<RuleEngineEntity> list)
{
_ruleSet.Add(
BuildRule(list)
);
}
public List<IRuleDefinition> BuildRule(List<RuleEngineEntity> list)
{
NRules.RuleModel.Builders.RuleBuilder builder = null;
List<IRuleDefinition> rulesList = new List<IRuleDefinition>();
builder = new NRules.RuleModel.Builders.RuleBuilder();
builder.Name("CustomerDetail");
ParameterExpression customerParameter = null;
LambdaExpression customerCondition = null;
PatternBuilder customerPattern = null;
try
{
var orGroup = builder.LeftHandSide().Group(GroupType.Or);
foreach (var item in list)
{
var andGroup = orGroup.Group(GroupType.And);
customerPattern = andGroup.Pattern(typeof(RuleEngineRequestModel), item.Name);
customerParameter = customerPattern.Declaration.ToParameterExpression();
customerCondition =
Expression.Lambda(
Expression.GreaterThan(CreateParameterExpression(typeof(RuleEngineRequestModel), "customerData", typeof(Customer), item.FieldName),
Expression.Constant(item.Value)), customerParameter);
customerPattern.Condition(customerCondition);
}
Expression<Action<IContext>> action =
(ctx) => Console.WriteLine("Action triggered");
builder.RightHandSide().Action(action);
rulesList.Add(builder.Build());
}
catch (Exception e)
{
}
return rulesList;
}
public Expression CreateParameterExpression(Type type, string propertyName, Type type2, string propertyName2)
{
ParameterExpression pe = Expression.Parameter(type, "e");
Expression left = Expression.Property(pe, type.GetProperty(propertyName));
return Expression.Property(left, type2.GetProperty(propertyName2));
}
}
You have a CreateParameterExpression method, that's supposed to create a property access expression. But in that method you are creating a parameter expression there with a name "e", which does not correspond to any pattern that you defined in your rule. This causes the "variable not found" exception.
You simply need one MemberExpression to access the customerData property, and then another one to access the nested property, configured in the RuleEngineEntity.
Here is an updated BuildRule method that implements the necessary expression tree.
public List<IRuleDefinition> BuildRule(List<RuleEngineEntity> list)
{
var builder = new NRules.RuleModel.Builders.RuleBuilder();
builder.Name("CustomerDetail");
var orGroup = builder.LeftHandSide().Group(GroupType.Or);
foreach (var item in list)
{
var andGroup = orGroup.Group(GroupType.And);
var modelPattern = andGroup.Pattern(typeof(RuleEngineRequestModel), item.Name);
var modelParameter = modelPattern.Declaration.ToParameterExpression();
var customerData = Expression.Property(modelParameter, nameof(RuleEngineRequestModel.customerData));
var customerCondition = Expression.Lambda(
Expression.GreaterThan(
Expression.Property(customerData, item.FieldName),
Expression.Constant(item.Value)),
modelParameter);
modelPattern.Condition(customerCondition);
}
Expression<Action<IContext>> action =
ctx => Console.WriteLine("Action triggered");
builder.RightHandSide().Action(action);
var rule = builder.Build();
return new List<IRuleDefinition> {rule};
}
I have found some direction for this problem but have not found anything which I can apply to this problem.
I want to filter lists of different types by stated properties they hold. I can use linq to dynamically filter a List by Test.id but I cant manage to filter a List through MyClass.Name
I have these classes.
public class Test
{
public int Id { get; set; }
public MyClass myclass { get; set; }
}
public class MyClass
{
public string Name { get; set; }
}
This is what I'm trying to do.
static void Main(string[] args)
{
var source = new List<Test> {
new Test { Id = 1,myclass = new MyClass() { Name = "bob" } },
new Test { Id = 2,myclass= new MyClass() { Name = "joe" } } };
var x = myFilter(source,"Name", "bob");
Console.WriteLine(x.Count());
}
public static IEnumerable<T> myFilter<T>(List<T> source, string propertyName, string searchString)
{
// get the myclass property then the stated property(Name) value within it
searchString = searchString.ToLower();
return source.Where(s => (s.GetType().GetProperty("myclass")
.GetType().GetProperty(propertyName)
.GetValue(s.GetType().GetProperty("myclass"),null).ToString() ?? " ")
.ToLower().Contains(searchString));
}
The count return 0 when I am expecting 1. for Test.MyClass.Name = "bob"
Is there a solution for this or is there a better way to do it besides reflection?
Thanks
you need to use the PropertyType of the returned myclass property:
public static IEnumerable<T> myFilter<T>(List<T> source, string propertyName, string searchString)
{
// get the myclass property then the stated property(Name) value within it
searchString = searchString.ToLower();
return source.Where(s => (s.GetType().GetProperty("myclass")
.PropertyType.GetProperty(propertyName)
.GetValue(s.GetType().GetProperty("myclass").GetValue(s)).ToString() ?? " ")
.ToLower().Contains(searchString));
}
You should be able to use the following:
var count = source.Count(test =>
string.Compare(test.myClass.Name, "Bob",
StringComparison.CurrentCultureIgnoreCase) == 0);
This will compare the string value of the Name Property and only count where the name is equal to "bob" and it will ignore the case.
If you want to return the Test object instead then you can use the following
var results = source.Where(test =>
string.Compare(test.myClass.Name, "Bob",
StringComparison.CurrentCultureIgnoreCase) == 0);
If I have these two classes:
public class A
{
public int Id { get; set; }
}
public class B
{
public string Name { get; set; }
}
Can I use a generic method like this:
public void InitMethod(object classProperty)
To pass in data like this:
var a = new A() { Id = 1 };
var b = new B() { Name = "John" };
InitMethod(a.Id);
InitMethod(b.Name);
And get the following information from within the method:
Class name (ex: "A", "B")
Property name (ex: "Id", "Name")
Property value (ex: 1, "John")
Sort of, although it may be more trouble than it is worth.
ASP.Net MVC frequently uses expressions to get at property info in a strongly-typed fashion. The expression doesn't necessarily get evaluated; instead, it is parsed for its metadata.
This isn't specific to MVC; I mention it to cite an established pattern in a Microsoft framework.
Here's a sample that gets a property name and value from an expression:
// the type being evaluated
public class Foo
{
public string Bar {
get;
set;
}
}
// method in an evaluator class
public TProperty EvaluateProperty<TProperty>( Expression<Func<Foo, TProperty>> expression ) {
string propertyToGetName = ( (MemberExpression)expression.Body ).Member.Name;
// do something with the property name
// and/or evaluate the expression and get the value of the property
return expression.Compile()( null );
}
You call it like this (note the expressions being passed):
var foo = new Foo { Bar = "baz" };
string val = EvaluateProperty( o => foo.Bar );
foo = new Foo { Bar = "123456" };
val = EvaluateProperty( o => foo.Bar );
In this example you need to pass object to InitMethod not property of that object, maybe it will be OK.
class Program
{
static void Main(string[] args)
{
InitMethod(new A() { Id = 100 });
InitMethod(new B() { Name = "Test Name" });
Console.ReadLine();
}
public static void InitMethod(object obj)
{
if (obj != null)
{
Console.WriteLine("Class {0}", obj.GetType().Name);
foreach (var p in obj.GetType().GetProperties())
{
Console.WriteLine("Property {0} type {1} value {2}", p.Name, p.GetValue(obj, null).GetType().Name, p.GetValue(obj, null));
}
}
}
}