Vadzim Savenok Vadzim Savenok - 6 months ago 57
CSS Question

C# html-helper extend existing method without overriding?

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!

Answer

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);
    }
}