I want to create an Expression(Of Func(Of TModel, TResult))
from the TModel and the string property name.
I've tried it like this:
Error from Expression.Lambda(): Expression of type 'System.Boolean' cannot be used for return type 'System.Object'
<Extension>
Public Function ModelEditor(Of TModel)(html As HtmlHelper(Of TModel)) As MvcHtmlString
Dim meta = html.ViewData.ModelMetadata
Dim htmlString As New StringBuilder()
Dim paramExp = Expression.Parameter(GetType(TModel), "model")
For Each editor In meta.Properties
Dim memberExp = Expression.Property(paramExp, editor.PropertyName)
Dim exp = Expression.Lambda(Of Func(Of TModel, Object))(memberExp, paramExp)
htmlString.Append(html.EditorFor(exp))
Next
Return MvcHtmlString.Create(htmlString.ToString())
End Function
And then I've tried to convert the value:
Error from EditorFor(): Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
Dim memberExp = Expression.Property(paramExp, editor.PropertyName)
' Convert to a Object
Dim convertExp = Expression.Convert(memberExp, GetType(Object))
Dim exp = Expression.Lambda(Of Func(Of TModel, Object))(convertExp, paramExp)
htmlString.Append(html.EditorFor(exp))
When you look at the source code you can see that the ExpressionType
can only be: ExpressionType.ArrayIndex
, ExpressionType.Call
, ExpressionType.MemberAccess
or ExpressionType.Parameter
How can I do this without getting errors? Or am I taking the wrong approach?
CodePudding user response:
I found a solution myself by using a lot of ugly reflection. There's probably still a way better way to do it though.
Option Strict Off
<Extension>
Public Function ModelEditor(Of TModel)(html As HtmlHelper(Of TModel)) As MvcHtmlString
Dim meta = html.ViewData.ModelMetadata
Dim htmlString As New StringBuilder()
Dim paramExp = Expression.Parameter(GetType(TModel), "model")
For Each prop In meta.Properties
' Equivalent to: Function(model) model.{PropertyName}
Dim memberExp = Expression.Property(paramExp, prop.PropertyName)
' Ugly unsafe reflection
Try
' Use reflection to get this: Func(Of TModel, TValue)
Dim delegateType = GetType(Func(Of ,)).MakeGenericType(GetType(TModel), prop.ModelType)
' Use reflection to call this: Expression.Lambda(Of Func(Of TModel, Object))(memberExp, paramExp)
' Result: Expression(Of Func( TModel, TValue))
Dim exp = GetType(Expression).
GetMethods(BindingFlags.Public Or BindingFlags.Static).
FirstOrDefault(Function(x) x.Name = "Lambda" AndAlso x.IsGenericMethod AndAlso x.GetParameters.Length = 2 AndAlso x.GetParameters.Select(Function(y) y.ParameterType).Contains(GetType(ParameterExpression()))).
MakeGenericMethod(delegateType).
Invoke(Nothing, New Object() {memberExp, New ParameterExpression() {paramExp}})
htmlString.Append(EditorExtensions.EditorFor(html, exp))
Catch
Continue For
End Try
Next
Return MvcHtmlString.Create(htmlString.ToString())
End Function