Bob.at.Indigo.Health
Bob.at.Indigo.Health

Reputation: 11895

Getting to the Model from within a Razor helper

Is there a way to pass a view model to an @helper function?

I have a custom editor that displays Yes/No/Maybe radio buttons. The code for this editor shown below. Since it requires a custom function plus 3 lines of code to display each radio button, I'd like to encapsulate that code that creates each radio button and its associated label in a global @helper function, but I can't figure out how to move the RadioButtonFor into the global helper function.

Bob

This is the existing code:

@model System.String

@functions {
    // Returns an anonymous object that specifies the radio button HTML options
    private object HTMLOptions(string id, bool @checked) {
        if (@checked) {
            return new { id, @checked = "checked" };
        } else {
            return new { id };
        }
    }
}

@{
    string value = Model + "";
    string id;
}

<div class="option-list">
    @{id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioYes");}
    @Html.RadioButtonFor(q => q, "true", HTMLOptions(id, value == "true"))
    @Html.LabelFor(q => q, "Yes", new {@for = id})
    <br />

    @{id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioNo");}
    @Html.RadioButtonFor(q => q, "false", HTMLOptions(id, value == "false"))
    @Html.LabelFor(q => q, "No", new {@for = id})
    <br />

    @{id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioMaybe");}
    @Html.RadioButtonFor(q => q, "maybe", HTMLOptions(id, value == "maybe"))
    @Html.LabelFor(q => q, "Maybe", new {@for = id})
    <br />
</div>

I want to be able to write a general-purpose helper that encapsulates RadioButtonFor and LabelFor, plus my additional code to include the "checked" attribute, in one call, like this:

@model something

<div class="option-list">
    @MyRadioButtonAndLabelFor(q => something.x, "Yes",   "true") @* (model, label, value) *@
    @MyRadioButtonAndLabelFor(q => something.x, "No",    "false")
    @MyRadioButtonAndLabelFor(q => something.x, "Maybe", "maybe")
</div>

Upvotes: 0

Views: 2115

Answers (1)

Darin Dimitrov
Darin Dimitrov

Reputation: 1038830

Try like this:

@model System.String

@functions {
    private object HtmlAttributes(string id, string model, string value) {
        if (model == value) {
            return new { id, @checked = "checked" };
        }

        return new { id };
    }
}

@helper Radios(HtmlHelper<string> html) {
    var model = html.ViewData.Model;

    @html.RadioButtonFor(q => q, "true", HtmlAttributes("yes", model, "true"))
    @Html.LabelFor(q => q, "Yes", new { @for = "yes" })
    <br/>

    @html.RadioButtonFor(q => q, "false", HtmlAttributes("no", model, "false"))
    @Html.LabelFor(q => q, "No", new { @for = "no" })
    <br/>

    @html.RadioButtonFor(q => q, "maybe", HtmlAttributes("maybe", model, "maybe"))
    @Html.LabelFor(q => q, "Maybe", new { @for = "maybe" })
    <br/>
}

<div class="option-list">
    @Radios(Html)
</div>


UPDATE:

It seems that I have misread your question. After your update I understand better what you are trying to achieve. In order to achieve it you could write a custom Html helper:

using System;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;

public static class HtmlExtensions
{
    public static IHtmlString MyRadioButtonAndLabelFor<TModel, TValue>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TValue>> expression,
        string label,
        object value
    )
    {
        var name = ExpressionHelper.GetExpressionText(expression);
        var id = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(
            name + value
        );
        var sb = new StringBuilder();
        sb.Append(htmlHelper.RadioButtonFor(expression, value, new { id = id }));
        var labelTag = new TagBuilder("label");
        labelTag.SetInnerText(label);
        labelTag.Attributes["for"] = id;
        sb.Append(labelTag.ToString());
        return new HtmlString(sb.ToString());
    }
}

and then you could have a view model:

public class MyViewModel
{
    public string Value { get; set; }
}

a controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel
        {
            Value = "false"
        });
    }
}

and a corresponding view:

@model MyViewModel

<div class="option-list">
    @Html.MyRadioButtonAndLabelFor(q => q.Value, "Yes",   "true")
    @Html.MyRadioButtonAndLabelFor(q => q.Value, "No",    "false")
    @Html.MyRadioButtonAndLabelFor(q => q.Value, "Maybe", "maybe")
</div>

Remark: In order for the MyRadioButtonAndLabelFor custom extension method to be brought in scope you need to add the namespace in which its containing class (HtmlExtensions) has been declared either in the namespaces tag in your ~/Views/web.config (not ~/web.config) or within the view itself by adding a @using ... directive.


You could also define the helper inline inside your Razor view if you prefer:

@functions {
    public IHtmlString MyRadioButtonAndLabelFor<TModel, TValue>(
        HtmlHelper<TModel> htmlHelper,
        System.Linq.Expressions.Expression<Func<TModel, TValue>> expression,
        string label,
        object value
    )
    {
        var name = ExpressionHelper.GetExpressionText(expression);
        var id = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(name + value);

        var sb = new StringBuilder();
        sb.Append(htmlHelper.RadioButtonFor(expression, value, new { id = id }));
        var labelTag = new TagBuilder("label");
        labelTag.SetInnerText(label);
        labelTag.Attributes["for"] = id;
        sb.Append(labelTag.ToString());

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

and then use it like this:

<div class="option-list">
    @MyRadioButtonAndLabelFor(Html, q => q.Value, "Yes",   "true")
    @MyRadioButtonAndLabelFor(Html, q => q.Value, "No",    "false")
    @MyRadioButtonAndLabelFor(Html, q => q.Value, "Maybe", "maybe")
</div>

As far as Razor helpers are concerned (@helper definition), well, they cannot be generic, therefore you will have troubles declaring such a construct.

Upvotes: 2

Related Questions