Home > Enterprise >  Expression of type 'System.Boolean' cannot be used for return type 'System.Object
Expression of type 'System.Boolean' cannot be used for return type 'System.Object

Time:10-25

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
  • Related