Question

I am working on an MVC project and would like to pass the Html.TextboxFor method the name of a property. This is my viewmodel

public class RuleViewModel<T> where T : class, IValidatableObject
{
    private T _rule;
    public T RuleModel
    {
        get
        {
            return _rule
                ?? (_rule = Activator.CreateInstance<T>());
        }
    }

    public RuleMetadata Metadata { get; set; }

    public Expression<Func<RuleViewModel<T>, Object>> GetParameterByName(PropertyInfo pi)
    {
        var fieldName = Expression.Parameter(typeof(RuleViewModel<T>), "x");
        var fieldExpression = Expression.PropertyOrField(Expression.Property(fieldName, "RuleModel"), pi.Name);
        var exp = Expression.Lambda<Func<RuleViewModel<T>, Object>>(fieldExpression, fieldName);
        return exp;
    }

}

Then in the view I do this

  @foreach (var prop in Model.RuleModel.GetType().GetProperties())
    {
        var result = Model.Metadata.Columns.SingleOrDefault(m => m.ColumnName == prop.Name);
        if (result != null)
        {
            <td>
                @Html.TextBoxFor(Model.GetParameterByName(prop))
            </td>
        }
    }

The problem is that when the property is of type decimal?, I get a cannot convert nullable decimal to object error. I looked around and found that you can use Expression.Convert to fix this but when I do that I get an error on the view

Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.

Any help would be appreciated. This is a proof of concept project that I'm working on and without this piece it is dead in the water.

Was it helpful?

Solution

The problem is that you can't just use object as TProperty when calling TextBoxFor<TModel, TProperty>(). It expects a lambda expression of the form Func<TModel, TProperty>, and the variance rules of C# are such that a Func<TModel, decimal?> is not assignment compatible with Func<TModel, object>. You also can't simply use Convert(), because the MVC internals won't accept a lambda whose body is a Convert expression.

What you can do is use dynamic binding to invoke TextBoxFor<TModel, TProperty>() with the correct type arguments:

public Expression GetParameterByName(PropertyInfo pi)
{
    var fieldName = Expression.Parameter(typeof(RuleViewModel<T>), "x");
    var fieldExpression = Expression.PropertyOrField(
        Expression.Property(fieldName, "RuleModel"),
        pi.Name);
    var exp = Expression.Lambda(
        typeof(Func<,>).MakeGenericType(typeof(RuleViewModel<T>), fieldExpression.Type),
        fieldExpression,
        fieldName);
    return exp;
}

// ...

@InputExtensions.TextBoxFor(Html, (dynamic)Model.GetParameterByName(prop))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top