2

I have already searched for the question and found possible answer, but I still need some help.

I am trying to write an html-helper to extend functionality of already existing LabelFor method

public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
  {
     ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);


     string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
     //string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
     var labelText = html.LabelFor(expression);
     if (String.IsNullOrEmpty(labelText.ToString()))
     {
        return MvcHtmlString.Empty;
     }

     if (metadata.IsRequired)
     {
        labelText = new MvcHtmlString(labelText.ToString().Substring(0, labelText.ToString().Length - 8).Trim() +
            "<span style=\"color:red\" class=\"required-marker\">*</span></label>");
     }
     TagBuilder tag = new TagBuilder("label");
     tag.MergeAttributes(htmlAttributes);
     tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
     tag.SetInnerText(labelText.ToString());
     return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
  }

I am trying to add a functionality, where Method would check if variable has a "required" flag, and then performs something (adds red * at the end of label in that case)

[Required]
[Display(Name = "Year")]
public string ProjectYr { get; set; }

However, I feel like I am overwriting the entire LabelFor functionality. Is there a way to simply add new functionality to existing LabelFor method while preserving all functions of the original without overriding it? Override doesn't work anyway since my method is static.

Thank you very much in advance!

Community
  • 1
  • 1
Vadzim Savenok
  • 930
  • 3
  • 14
  • 37

1 Answers1

2

Here's a complete example that does what your asking

Accessing the model attributes in a helper extension class

public static class LabelExtensions
{
    public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
        Expression<Func<TModel, TValue>> expression, IDictionary<String, Object> htmlAttributes,
        String requiredMarker = "*")
    {
        return LabelHelper(html, ModelMetadata.FromLambdaExpression(expression, html.ViewData),
            ExpressionHelper.GetExpressionText(expression), null, htmlAttributes, requiredMarker);
    }

    public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
        Expression<Func<TModel, TValue>> expression, Object htmlAttributes, String requiredMarker)
    {
        return LabelFor(html, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), requiredMarker);
    }

    internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, String htmlFieldName,
        String labelText = null, IDictionary<String, Object> htmlAttributes = null, String requiredMarker = null)
    {
        var resolvedLabelText = labelText ??
                                metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();

        var tag = new TagBuilder("label");
        tag.Attributes.Add("for",
            TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
        tag.SetInnerText(resolvedLabelText);
        tag.MergeAttributes(htmlAttributes, true);

        if (metadata.IsRequired && !String.IsNullOrWhiteSpace(requiredMarker))
        {
            var requiredSpan = new TagBuilder("span") {InnerHtml = requiredMarker};
            requiredSpan.AddCssClass("required");

            tag.InnerHtml += requiredSpan;
        }

        var result = tag.ToString(TagRenderMode.Normal);

        return new MvcHtmlString(result);
    }
}

and here's the unit tests

public static class LabelExtensionFixtures
{
    [TestFixture]
    public class should_return_label_with_required_info : MvcExtensionFixtureBase
    {
        private class TestClass
        {
            [Required]
            public Guid Id { get; set; }
        }

        private MvcHtmlString _expectedResult;
        private HtmlHelper<TestClass> _sut;
        private MvcHtmlString _result;

        [SetUp]
        public void Given()
        {
            //arrange
            _expectedResult =
                MvcHtmlString.Create(
                    "<label class=\"control-label col-md-2\" for=\"Id\">Id<span class=\"required\">*</span></label>");
            _sut = CreateHtmlHelper(new TestClass {Id = Guid.NewGuid()});

            //act
            _result = _sut.LabelFor(model => model.Id, new { @class = "control-label col-md-2" }, "*");
        }

        [Test]
        public void Test()
        {
            //asert
            Assert.That(_result.ToHtmlString(), Is.EqualTo(_expectedResult.ToHtmlString()));
        }
    }
}

public abstract class MvcExtensionFixtureBase
{
    protected HtmlHelper<T> CreateHtmlHelper<T>(T instance)
    {
        var viewDataDictionary = new ViewDataDictionary<T>(instance);
        var viewContext = A.Fake<ViewContext>();
        A.CallTo(() => viewContext.ViewData).Returns(viewDataDictionary);

        var viewDataContainer = A.Fake<IViewDataContainer>();
        A.CallTo(() => viewDataContainer.ViewData).Returns(viewDataDictionary);

        return new HtmlHelper<T>(viewContext, viewDataContainer);
    }
}
Community
  • 1
  • 1
Fran
  • 6,440
  • 1
  • 23
  • 35
  • Oh wow, thanks a ton! I was just thinking along the same lines, and I simply needed to properly form it. I will definitely try it out! – Vadzim Savenok Jun 03 '16 at 17:11
  • Good day Fran. Once again, thank you very much for your help, I was using this solution extensively. However, there is a little hiccup I stumbled upon, which I described in details in [my other post](http://stackoverflow.com/questions/38003156/stack-overflow-exception-in-mvchtmlstring?noredirect=1#comment63450739_38003156). Your feedback will be truly valuable! – Vadzim Savenok Jun 23 '16 at 23:20