How to access property from lambda - c#

I'd want to refactor this code to get rid of relying on TModel, TValue
public static string DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
// null checks
DescriptionAttribute descriptionAttribute = null;
if (expression.Body is MemberExpression memberExpression)
{
descriptionAttribute = memberExpression.Member
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.SingleOrDefault();
}
return descriptionAttribute?.Description ?? string.Empty;
}
which has invocation like that:
#Html.DescriptionFor(x => x.MyModel.MyOtherModel.Property)
to something like that
public static string DescriptionFor2<T>(Expression<Func<T>> expression)
{
if (expression == null)
throw new ArgumentNullException(nameof(expression));
if (!(expression.Body is MemberExpression memberExpression))
{
return string.Empty;
}
foreach (var property in typeof(T).GetProperties())
{
if (property == ((expression.Body as MemberExpression).Member as PropertyInfo))
{
var attr = property
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault();
return attr?.Description ?? string.Empty;
}
}
return string.Empty;
}
with invocation like that:
#MyHtml.DescriptionFor2<MyOtherModel>(x => x.MyProperty);
But here's error:
Delegate 'Func' does not take 1 arguments

You can do this:
void Main()
{
Console.WriteLine(MyHtml.DescriptionFor2<Test>((t) => t.StringMember));
Console.WriteLine(MyHtml.DescriptionFor2<Test>((t) => t.BooleanMember));
Console.WriteLine(MyHtml.DescriptionFor2<Test>((t) => t.IntegerMember));
}
public class Test
{
[Description("This is a string member")]
public string StringMember { get; set; }
[Description("This is a boolean member")]
public bool BooleanMember { get; set; }
[Description("This is a integer member")]
public int IntegerMember { get; set; }
}
public static class MyHtml
{
public static string DescriptionFor2<T>(Expression<Func<T, dynamic>> expression)
{
var result = string.Empty;
var member = GetMemberExpression(expression)?.Member?.Name;
if (member != null)
{
var property = typeof(T).GetProperty(member);
if (property != null)
{
var attr = property
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault();
result = attr?.Description ?? string.Empty;
}
}
return result;
}
private static MemberExpression GetMemberExpression<T>(Expression<Func<T, dynamic>> expression)
{
var member = expression.Body as MemberExpression;
var unary = expression.Body as UnaryExpression;
return member ?? (unary != null ? unary.Operand as MemberExpression : null);
}
}
public class DescriptionAttribute : Attribute
{
public string Description { get; set; }
public DescriptionAttribute(string description)
{
Description = description;
}
}
It involves getting the body of the expression as a member and using its name to resolve the property on the object. The dynamic data type is used to get rid of the second type parameter, however you can also define it explicitly for better compile-time error catching:
public static string DescriptionFor2<T, U>(Expression<Func<T, U>> expression)
and calling it:
MyHtml.DescriptionFor2<Test, string>((t) => t.StringMember);
MyHtml.DescriptionFor2<Test, bool>((t) => t.BooleanMember);
MyHtml.DescriptionFor2<Test, int>((t) => t.IntegerMember);
EDIT: edited answer because initial solution did not work for value types, see Expression for Type members results in different Expressions (MemberExpression, UnaryExpression)

Related

Access properties by passing in a lambda

I am trying to write this method:
public static T Nullify<T>(T item, params Func<T, object> [] properties)
{
// Sets any specified properties to null, returns the object.
}
I will call it like this:
var kitten = new Kitten() { Name = "Mr Fluffykins", FurColour = "Brown" };
var anonymousKitten = Nullify(kitten, c => c.Name);
However I am unsure of how to do this. Any ideas?
Another approach is to do this (it doesn't need to be an extension method)
public static T Nullify<T>(this T item, params Expression<Func<T, object>> [] properties)
{
foreach(var property in properties)
{
var memberSelectorExpression = property.Body as MemberExpression;
if (memberSelectorExpression != null)
{
var propertyInfo = memberSelectorExpression.Member as PropertyInfo;
if (propertyInfo != null)
{
propertyInfo.SetValue(item, null, null);
}
}
}
return item;
}
Usage
item.Nullify(i => i.PropertyName, i => i.PropertyName2)
You'd need to pass a "setter method" not a "reader method" in properties.
static void Nullify<T, D>(T item, params Action<T, D>[] properties)
where D : class
{
foreach (var property in properties)
{
property(item, null);
}
}
usage:
Nullify<Kitten, string>(kitten, (c, d) => { c.Name = d; });
But this will just set the data for you. If you want a copy and then apply the properties, the items would probably have to be clonable (alternatively you can go though some hell with reflection):
static T Nullify<T, D>(T item, params Action<T, D>[] properties)
where D : class
where T : ICloneable
{
T copy = (T)item.Clone();
foreach (var property in properties)
{
property(copy, null);
}
return copy;
}
class Kitten : ICloneable
{
public string Name { get; set; }
public string FurColour { get; set; }
public object Clone()
{
return new Kitten() { Name = this.Name, FurColour = this.FurColour };
}
}
usage
var anonymousKitten = Nullify(kitten, (c, d) => { c.Name = d; });
Without modifying your method definition much:
namespace ConsoleApplication
{
public class Kitten : ISimpleClone<Kitten>
{
public string Name { get; set; }
public string FurColour { get; set; }
public int? Number { get; set; }
public Kitten SimpleClone()
{
return new Kitten { Name = this.Name, FurColour = this.FurColour, Number = this.Number };
}
}
public interface ISimpleClone<T>
{
T SimpleClone();
}
public class Program
{
public static PropertyInfo GetProperty<TObject, TProperty>(Expression<Func<TObject, TProperty>> propertyExpression)
{
MemberExpression body = propertyExpression.Body as MemberExpression;
if (body == null)
{
var unaryExp = propertyExpression.Body as UnaryExpression;
if (unaryExp != null)
{
body = ((UnaryExpression)unaryExp).Operand as MemberExpression;
}
}
return body.Member as PropertyInfo;
}
public static T Nullify<T>(T item, params Expression<Func<T, object>>[] properties)
where T : ISimpleClone<T>
{
// Creates a new instance
var newInstance = item.SimpleClone();
// Gets the properties that will be null
var propToNull = properties.Select(z => GetProperty<T, object>(z));
var filteredProp = propToNull
.Where(z => !z.PropertyType.IsValueType || Nullable.GetUnderlyingType(z.PropertyType) != null) // Can be null
.Where(z => z.GetSetMethod(false) != null && z.CanWrite); // Can be set
foreach (var prop in filteredProp)
{
prop.SetValue(newInstance, null);
}
return newInstance;
}
public static void Main(string[] args)
{
var kitten = new Kitten() { Name = "Mr Fluffykins", FurColour = "Brown", Number = 12 };
var anonymousKitten = Nullify(kitten, c => c.Name, c => c.Number);
Console.Read();
}
}
}
Looks a bit hacky though....

Read override property attribute

I have overridden property in class and would like to read custom attribute values, but it do not work. Could anyone explain why it is not working and how to solve the problem?
public class Validator
{
[Serializable]
public class CollectionAttribute : Attribute
{
public virtual string[] Data { get; set; }
public string Default;
}
}
class Helpers
{
public static MemberInfo GetMemberInfo<T, TU>(Expression<Func<T, TU>> expression)
{
var member = expression.Body as MemberExpression;
if (member != null)
return member.Member;
throw new ArgumentException("Expression is not a member access", "expression");
}
public static string GetName<T, TU>(Expression<Func<T, TU>> expression)
{
return GetMemberInfo(expression).Name;
}
public static string GetCollection<T, TU>(Expression<Func<T, TU>> expression)
{
var attribute = (Validator.CollectionAttribute[])GetMemberInfo(expression).GetCustomAttributes(typeof(Validator.CollectionAttribute), true);
return string.Join(",", attribute[0].Data);
}
}
Do not work.
public class TestClass:TestBaseClass
{
[Validator.Collection(Data = new[] { "doc", "docx", "dot", "dotx", "wpd", "wps", "wri" })]
public override string InputFormat { get; set; }
}
public class TestBaseClass
{
public virtual string InputFormat { get; set; }
}
Helpers.GetCollection((TestClass p) => p.InputFormat)
//The attribute variable in GetCollection method always null. It seems code looks for atribute in Base class.
Works fine.
public class TestClass
{
[Validator.Collection(Data = new[] { "doc", "docx", "dot", "dotx", "wpd", "wps", "wri" })]
public override string InputFormat { get; set; }
}
Helpers.GetCollection((TestClass p) => p.InputFormat)
The declaring type of InputFormat is TestBaseClass which doesn't have that attribute. And the PropertyInfo which returned is for the declaring type, not for the actual type of the parameter.
What you have to do, is retrieve the actual type of the expression's parameter, and then return the PropertyInfo for that type.
public static MemberInfo GetMemberInfo<T, TU>(Expression<Func<T, TU>> expression)
{
var member = expression.Body as MemberExpression;
if (member != null)
{
// Getting the parameter's actual type, and retrieving the PropertyInfo for that type.
return expression.Parameters.First().Type.GetProperty(member.Member.Name);
}
throw new ArgumentException("Expression is not a member access", "expression");
}

C#: Get the name of a property and check its value

I have the following class Franchise:
public class Franchise
{
public string FolderName { get; set; }
public string InstallerExeName { get; set; }
}
I have a method that checks specific property value for uniqness among all franchises in the db.
public bool ValidateFolderName(string folderName)
{
var allFranchises = _franchiseService.GetAll();
var result = allFranchises.Any(f => f.FolderName == folderName);
return result;
}
The problem is I have to check another property for uniqness:
public bool ValidateInstallerExeName(string installerExeName)
{
var allFranchises = _franchiseService.GetAll();
var result = allFranchises.Any(f => f.InstallerExeName == installerExeName);
return result;
}
I want to avoid code duplication by making a generic method. Something like:
public bool ValidateProperty(string propertyName)
{
var allFranchises = _franchiseService.GetAll();
// Not sure how to write this line
var result = allFranchises.Any(f => f.[propertyName] == propertyName);
return result;
}
The problem is I am not sure how to re-write this line of code so that it can get the property name and check its value by the provided parameter:
var result = allFranchises.Any(f => f.[propertyName] == propertyName);
I know I can do something like this with reflection:
franchise.GetType().GetProperty(propertyName).GetValue(franchise, null);
but I am not sure how can I make this to fit my case. Any help with working example will be greatly appreciated. Thanks!
Here is a full working example using reflection:
class Program
{
private static List<Franchise> allFranchises;
static void Main(string[] args)
{
allFranchises = new List<Franchise>
{
new Franchise() { FolderName=#"c:\1", InstallerExeName="1.exe" },
new Franchise() { FolderName=#"c:\2", InstallerExeName="2.exe" },
new Franchise() { FolderName=#"c:\3", InstallerExeName="3.exe" },
new Franchise() { FolderName=#"c:\4", InstallerExeName="4.exe" },
new Franchise() { FolderName=#"c:\5", InstallerExeName="5.exe" },
};
Console.WriteLine(ValidateProperty("FolderName", #"c:\2", allFranchises));
Console.WriteLine(ValidateProperty("InstallerExeName", "5.exe", allFranchises));
Console.WriteLine(ValidateProperty("FolderName", #"c:\7", allFranchises));
Console.WriteLine(ValidateProperty("InstallerExeName", "12.exe", allFranchises));
}
public static bool ValidateProperty(string propertyName, object propertyValue, IEnumerable<Franchise> validateAgainst)
{
PropertyInfo propertyInfo = typeof(Franchise).GetProperty(propertyName);
return validateAgainst.Any(f => propertyInfo.GetValue(f, null) == propertyValue);
}
}
public class Franchise
{
public string FolderName { get; set; }
public string InstallerExeName { get; set; }
}
It will print out:
True
True
False
False
as expected.
public bool ValidateProperty<TType, TPropertyType>(Func<TType, TPropertyType> propertySelector, TPropertyType propertyValue)
{
return _franchiseService.GetAll().Any(f => propertySelector(f) == propertyValue);
}
You can call it like this:
if( ValidateProperty(x => x.FirstName, "Joe") )
This does not use reflection and you have intellisense for your propertyname as well.
You may use an extension method:
public static bool ValidateProperty(
this IEnumerable<Franchise> franchises,
string property,
object value)
{
var prop = typeof(Franchise).GetProperty(property);
if (prop == null)
throw new ArgumentException("Property does not exist");
return franchises.Any(f =>
prop.GetValue(f) == value);
}
Use it like this:
var r = _franchiseService.GetAll().ValidateProperty("FolderName", "myfolder1");
You can build the function you want using System.Linq.Expressions
public class Franchise
{
public string FolderName { get; set; }
public string InstallerExeName { get; set; }
bool ValidateProperty(string propertyName) {
var allFranchises = new List<Franchise>();
var parameter = Expression.Parameter(typeof(Franchise));
var property = Expression.Property(parameter, propertyName);
var thisExpression = Expression.Constant(this, typeof(Franchise));
var value = Expression.Property(thisExpression, propertyName);
var notThis = Expression.ReferenceNotEqual(thisExpression, property);
var equal = Expression.Equal(value, property);
var lambda = Expression.Lambda<Func<Franchise, bool>>(Expression.And(notThis, equal));
// lamda is now the equivalent of:
// x => !object.ReferenceEquals(this, x) && object.Equals(x.Property, this.Property)
return allFranchises.Any(lambda.Compile());
}
}
If allFranchises is of type IQueryable you use allFranchises.Any(lambda)
You could also caches the expression for later use if you are worried about performance.
}

C# expression - extract the string name (Deep)

I have the function bellow, returning "Test" when I call it GetPropertyName<Model>(x => x.Test).
How can I return deep call eg: User.UserName.
I want to see User.UserName when I call it GetPropertyName<Model>(x => x.User.FullName)
Thanks
public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
{
var body = expression.Body as MemberExpression;
if (body == null)
{
body = ((UnaryExpression)expression.Body).Operand as MemberExpression;
}
if (body != null)
{
return body.Member.Name;
}
return null;
}
Edit 1
this is my classes:
public class Place : BaseEntity
{
public virtual User UserManager { get; set; }
}
public class User : BaseEntity
{
public virtual string FullName { get; set; }
}
This method loops through the nodes in the expression, casts the node to a property and adds it to a list.
private IList<PropertyInfo> GetProperties<T>(Expression<Func<T, object>> fullExpression)
{
Expression expression;
switch (fullExpression.Body.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = fullExpression.Body as UnaryExpression;
expression = ((ue != null) ? ue.Operand : null) as MemberExpression;
break;
default:
expression = fullExpression.Body as MemberExpression;
break;
}
var props = new List<PropertyInfo>();
while(expression != null && expression.NodeType == ExpressionType.MemberAccess)
{
var memberExp = expression as MemberExpression;
if(memberExp.Member is FieldInfo)
throw InvalidExpressionException.FieldFound;
var prop = memberExp.Member as PropertyInfo;
props.Insert(0, prop);
expression = memberExp.Expression;
}
return props;
}
If you then want to get a string like this: User.UserName, you can do it this way:
var props = GetProperties<Model>(m => m.User.Username);
var propNames = props.Select(p => p.Name);
string names = string.Join(".", propNames);

Get display annotation value on mvc3 server side code

Is there a way to get the value of an annotation in server side code? For example, I have:
public class Dummy
{
[Display(Name = "Foo")]
public string foo { get; set; }
[Display(Name = "Bar")]
public string bar { get; set; }
}
I want to be able to get the value "Foo" on server side with out posting it back to the page, but like an attribute of the class, or something of the sort. Like a #Html.LabelFor(model => model.Foo) But in c# server code.
Is that possible?
Thank you.
Something like this?
string displayName = GetDisplayName((Dummy x) => x.foo);
// ...
public static string GetDisplayName<T, U>(Expression<Func<T, U>> exp)
{
var me = exp.Body as MemberExpression;
if (me == null)
throw new ArgumentException("Must be a MemberExpression.", "exp");
var attr = me.Member
.GetCustomAttributes(typeof(DisplayAttribute), false)
.Cast<DisplayAttribute>()
.SingleOrDefault();
return (attr != null) ? attr.Name : me.Member.Name;
}
Or, if you want to be able to call the method against an instance and take advantage of type inference:
var dummy = new Dummy();
string displayName = dummy.GetDisplayName(x => x.foo);
// ...
public static string GetDisplayName<T, U>(this T src, Expression<Func<T, U>> exp)
{
var me = exp.Body as MemberExpression;
if (me == null)
throw new ArgumentException("Must be a MemberExpression.", "exp");
var attr = me.Member
.GetCustomAttributes(typeof(DisplayAttribute), false)
.Cast<DisplayAttribute>()
.SingleOrDefault();
return (attr != null) ? attr.Name : me.Member.Name;
}
You will need to use reflection. Here is a sample console program that does what you want.
class Program
{
static void Main(string[] args)
{
Dummy dummy = new Dummy();
PropertyInfo[] properties = dummy.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
IEnumerable<DisplayAttribute> displayAttributes = property.GetCustomAttributes(typeof(DisplayAttribute), false).Cast<DisplayAttribute>();
foreach (DisplayAttribute displayAttribute in displayAttributes)
{
Console.WriteLine("Property {0} has display name {1}", property.Name, displayAttribute.Name);
}
}
Console.ReadLine();
}
}
public class Dummy
{
[Display(Name = "Foo")]
public string foo { get; set; }
[Display(Name = "Bar")]
public string bar { get; set; }
}
This would produce the following result:
http://www.codetunnel.com/content/images/reflectresult.jpg

Categories

Resources