1

I have a requirement to roll my own BeginLabel helper for Mvc. I checked/stole the concept from the Mvc source for the html.beginForm / ajax.beginForm methods.

public static Label BeginLabel(this HtmlHelper htmlHelper)
{
    TagBuilder tagBuilder = new TagBuilder("label");
    HttpResponseBase response = htmlHelper.ViewContext.HttpContext.Response;
    response.Write(tagBuilder.ToString(TagRenderMode.StartTag));
    return new Label(response);
}

The Label simply implements IDisposable interface to enable closing off the label:

protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        _disposed = true;
        _httpResponse.Write("</label>");
    }
}

Usage looks like this:

@using (Html.BeginLabel())
{
    @Html.TextBoxFor(f => f.FirstName)
    @Html.ValidationMessageFor(f => f.FirstName)
}

It looks like i'm missing something as the labels always get rendered at the top of the html and while this is obvious to me because i'm writing to the response, I can't see how the native BeginForm() is achieving this. Can anyone shed any light on this?

tereško
  • 58,060
  • 25
  • 98
  • 150
Phil Cooper
  • 3,083
  • 39
  • 63
  • It looks like this is being covered in http://stackoverflow.com/questions/2435898/create-extension-method-to-produce-open-closing-tags-like-html-beginform – Phil Cooper Aug 02 '11 at 08:39
  • Seems like an awful lot of work for such little benefit. Plus, i'm not sure what you get from it. a label without any text? Also, i'm not sure you want the validation message inside the label either, semantically speaking. – Erik Funkenbusch Oct 22 '11 at 21:07

1 Answers1

3
public class MvcLabel : IDisposable
{
    // Fields
    private bool _disposed;
    private readonly TextWriter _writer;

    public MvcLabel(ViewContext viewContext)
    {
        if (viewContext == null)
        {
            throw new ArgumentNullException("viewContext");
        }
        this._writer = viewContext.Writer;
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this._disposed)
        {
            this._disposed = true;
            this._writer.Write("</label>");
        }
    }

    public void EndLabel()
    {
        this.Dispose(true);
    }
}

and

public static class HtmlHelperExtension
{
    // Methods
    public static MvcLabel BeginLabel(this HtmlHelper html, string expression)
    {
        return html.BeginLabel(expression, null);
    }

    public static MvcLabel BeginLabel(this HtmlHelper html, string expression, string labelText)
    {
        return LabelHelper(html, ModelMetadata.FromStringExpression(expression, html.ViewData), expression, labelText);
    }

    public static MvcLabel BeginLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
    {
        return html.BeginLabelFor<TModel, TValue>(expression, null);
    }

    public static MvcLabel BeginLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText)
    {
        return LabelHelper(html, ModelMetadata.FromLambdaExpression<TModel, TValue>(expression, html.ViewData), ExpressionHelper.GetExpressionText(expression), labelText);
    }

    public static MvcLabel BeginLabelForModel(this HtmlHelper html)
    {
        return html.BeginLabelForModel(null);
    }

    public static MvcLabel BeginLabelForModel(this HtmlHelper html, string labelText)
    {
        return LabelHelper(html, html.ViewData.ModelMetadata, string.Empty, labelText);
    }

    public static void EndLabel(this HtmlHelper htmlHelper)
    {
        htmlHelper.ViewContext.Writer.Write("</label>");
    }

    internal static MvcLabel LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string labelText = null)
    {
        string str = labelText ?? (metadata.DisplayName ?? (metadata.PropertyName ?? htmlFieldName.Split(new char[] { '.' }).Last<string>()));

        TagBuilder tagBuilder = new TagBuilder("label");
        tagBuilder.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));

        html.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));

        if (!string.IsNullOrEmpty(str))
        {
            tagBuilder = new TagBuilder("span");
            tagBuilder.SetInnerText(str);
            html.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.Normal));
        }

        return new MvcLabel(html.ViewContext);
    }
}

Hope i can help others...

Krnl
  • 46
  • 1
  • 4
  • This confuses me, the author wants to utilize the ability for the label tag to contain the element, which doesn't use the "for" attribute. I'm not sure why you're putting spans in there either. – Erik Funkenbusch Oct 22 '11 at 21:10
  • yeah this is not quite right but wouldn't take many changes to be right. – DanH Feb 15 '12 at 00:14
  • Also puzzles me why people use the 2 phase dispose pattern with no unmanaged resources. Its this way in the MS code too. Its like an ever perpetuated myth! – DanH Feb 15 '12 at 00:20
  • I ended up doing the same thing before seeing your solution: https://github.com/toddlucas/bootstrap-mvc.net – Todd Nov 13 '13 at 09:18