Search code examples
c#expressionradiobuttonlist

Pass property expression to method for selecting value from object


What I'm trying to do is create an html extension method to build a group of radio buttons in an mvc project. I know I could write a for loop but this would be a very useful method for reuse in other projects so I'd like to figure this out.

What I want to do is be able to pass the expression for the model's property that the radio buttons are for (so I can name the radio buttons the same thing and choose the selected option), a collection of objects that are the options, and the property of the option objects to be used as the value for the radio buttons.

Here is what I have so far but it doesn't work. (I get error "Cannot convert method group 'Value' to non-delegate type 'string'. Did you intend to invoke the method?").

public static HtmlString RadioOptionsFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, 
        Expression<Func<TModel, TProperty>> modelExpression, 
        IEnumerable<object> radioOptions,  
        Expression<Func<object, string>> valueExpression)
    {
        StringBuilder sb = new StringBuilder();
        var propertyName = ((MemberExpression)(modelExpression.Body)).Member.Name;

        radioOptions.Select(el => sb.Append(htmlHelper.RadioButton(propertyName, valueExpression.Compile()(el))));

        return new HtmlString(sb.ToString());
    }


@Html.RadioOptionsFor(x => x.Category, Model.CategoryOptions, e => e.Value)

UPDATE: Okay so StriplingWarrior helped me get to a working solution. The issue was declaring the second expression functions's parameter as an object. It should be a generic type so i just needed to change the method declaration for that. I also added a helper method to build a radio button with a wrapping label element. I also did some linq stringbuilding so I don't have to loop over the set twice. Lastly I added selecting the current value of the model and removing duplicate ids of the radio button elements. Here is the final solution.

public static string RadioButtonWithLabel<TModel>(this HtmlHelper<TModel> htmlHelper, string name, string value, string selectedValue)
    {
        var sb = new StringBuilder();
        sb.Append("<label for=\"").Append(name).AppendLine("\">")
            .AppendLine(htmlHelper.RadioButton(name, value, selectedValue.Equals(value), new { id = "" }).ToString())
            .Append(value).AppendLine("</label>");

        return sb.ToString();
    }

    public static HtmlString RadioOptionsFor<TModel, TProperty, TElement>(this HtmlHelper<TModel> htmlHelper, 
        Expression<Func<TModel, TProperty>> modelExpression, 
        string selectedValue,
        IEnumerable<TElement> radioOptions,  
        Expression<Func<TElement, string>> valueExpression)
    {
        var propertyName = ((MemberExpression)(modelExpression.Body)).Member.Name;

        var result = radioOptions.Aggregate(new StringBuilder(), (sb, el) => sb.Append(htmlHelper.RadioButtonWithLabel(propertyName, valueExpression.Compile()(el), selectedValue)));

        return new HtmlString(result.ToString());
    }

Called like this

@Html.RadioOptionsFor(x => x.Category, Model.Category, Model.CategoryOptions, e => e.Value)

Solution

  • Expression<Func<object, string>> valueExpression expects your expression to yield a string: what is the type of e.Value?

    It seems like e.Value shouldn't exist because e is an object. Is this an extension method? If so you probably need to pass:

    e => e.Value()
    

    So, deciphering your error message: it cannot convert "Value" (which is a method or delegate) to a string that's expected by the expression: did you intend to invoke the method to yield a string?