I am attempting to genericise a complex control which is used in my website quite often but with different fields. The functionality in the control is always the same, it's just the underlying fields which change.
To achieve the method of showing different fields I am attempting to create a HTMLHelper extension which accepts a Expression<Func<TModel,TProperty>>
as a parameter, which would contain the properties of a class required for display in the control. For example:
The view:
@model Project.Core.Page
@Html.MyHelper(p => new { p.Author.Name, p.Author.Location, p.Author.Age });
It's the extension I'm having problems with - how can I iterate over the provided params in the lambda to provide each with a TextBoxFor()
, or even manually create a input
element and populate it with the value
and name
of the lambda parameter?
The extention in psuedo:
public static MvcHtmlString MyHelper<TModel,TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel,TProperty>> expression) {
foreach (var parameter in expression.???) {
// helper.TextBoxFor(???)
// TagBuilder("input").Attributes("name", expression.???)
}
}
I feel like I've been staring at this for far too long, and I'm also feeling there's a more simple way I'm overlooking of achieving this.
Any help is greatly appreciated. If you need further details, or I've missed something important, let me know.
If you assume the following:
MemberExpressions
, and do not contain a call to a method on the model or on its childrenThen you can achieve what you want by using the following approach:
Edit:
After realizing that my first example could not handle objects with complex properties, I updated the code to use a helper method to access property values. This method traverses over the property chain using recursion to return the appropriate values.
public static MvcHtmlString MyHelper<TModel,object>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel,object>> expression) {
var newExpression = expression.Body as NewExpression;
TModel model = helper.ViewData.Model;
foreach (MemberExpression a in newExpression.Arguments) {
var propertyName = a.Member.Name;
var propertyValue = GetPropertyValue<TModel>(model, a);
// Do whatever you need to with the property name and value;
}
}
private static object GetPropertyValue<T>(T instance, MemberExpression me) {
object target;
if (me.Expression.NodeType == ExpressionType.Parameter) {
// If the current MemberExpression is at the root object, set that as the target.
target = instance;
}
else {
target = GetPropertyValue<T>(instance, me.Expression as MemberExpression);
}
// Return the value from current MemberExpression against the current target
return target.GetType().GetProperty(me.Member.Name).GetValue(target, null);
}
Note: I did not implement this directly as a MVC extension method in my IDE, so a slight variation of the syntax may be required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With