I have a collection of IEnumerable<School>
that is being passed to an extension
method that populates a DropDownList
. I would also like to pass the
DataValueField
and DataTextField
as an argument but I wanted them to be
strongly typed.
Basically, I don't want to pass a string
for the DataValueField
and DataTextField
arguments, it's error prone.
public static void populateDropDownList<T>(this DropDownList source,
IEnumerable<T> dataSource,
Func<T, string> dataValueField,
Func<T, string> dataTextField) {
source.DataValueField = dataValueField; //<-- this is wrong
source.DataTextField = dataTextField; //<-- this is wrong
source.DataSource = dataSource;
source.DataBind();
}
Called like so...
myDropDownList.populateDropDownList(states,
school => school.stateCode,
school => school.stateName);
My question is, how can I pass the DataValueField
and DataTextField
strongly typed as an argument to populateDropDownList?
If you're only trying to use property chains, you could change the parameter to Expression<Func<T, string>>
and then extract the property names involved - you'll need to dissect the Expression<TDelegate>
you get... you'd expect that the Body
will be a MemberExpression
representing a property access. If you've got more than one (school.address.FirstLine
) then the target expression of one member access will be another one, etc.
From that, you can build up a string to use in the DataValueField
(and the DataTextField
). Of course, the caller can still screw you over:
myDropDownList.populateDropDownList(states,
school => school.stateCode.GetHashCode().ToString(),
school => school.stateName);
... but you can detect it and throw an exception, and you're still refactor-proof for good callers.
Based off Jon's answer and this post, it gave me an idea. I passed the DataValueField
and DataTextField
as Expression<Func<TObject, TProperty>>
to my extension method. I created a method that accepts that expression and returns the MemberInfo
for that property. Then all I have to call is .Name
and I've got my string
.
Oh, and I changed the extension method name to populate
, it was ugly.
public static void populate<TObject, TProperty>(
this DropDownList source,
IEnumerable<TObject> dataSource,
Expression<Func<TObject, TProperty>> dataValueField,
Expression<Func<TObject, TProperty>> dataTextField) {
source.DataValueField = getMemberInfo(dataValueField).Name;
source.DataTextField = getMemberInfo(dataTextField).Name;
source.DataSource = dataSource;
source.DataBind();
}
private static MemberInfo getMemberInfo<TObject, TProperty>(Expression<Func<TObject, TProperty>> expression) {
var member = expression.Body as MemberExpression;
if(member != null) {
return member.Member;
}
throw new ArgumentException("Member does not exist.");
}
Called like so...
myDropDownList.populate(states,
school => school.stateCode,
school => school.stateName);
With what you were trying, even if you did get it to compile/run, it would still be wrong because the Value & Text fields would've been set to a value in the list instead of the property name (ie, DataValueField = "TX"; DataTextField = "Texas";
instead of DataValueField = "stateCode"; DataTextField = "stateName";
like you really want).
public static void populateDropDownList<T>(this DropDownList source,
IEnumerable<T> dataSource,
Func<string> dataValueField,
Func<string> dataTextField) {
source.DataValueField = dataValueField();
source.DataTextField = dataTextField();
source.DataSource = dataSource;
source.DataBind();
}
myDropDownList.populateDropDownList(states,
"stateCode",
"stateName");