3

I've been using Chris Pratt's MergeHtmlAttributes Html Helper Extension method for a while in my asp.net mvc 5 editor templates. I started the process of switching over an application to Asp.net core 1.1(.net framework 4.5.2). And The htmlhelperExtension isn't working for me.

public static partial class HtmlHelperExtensions
{
    //https://cpratt.co/html-editorfor-and-htmlattributes/
    public static IDictionary<string, object> MergeHtmlAttributes(this HtmlHelper helper, object htmlAttributesObject, object defaultHtmlAttributesObject)
    {
        var concatKeys = new string[] { "class" };

        var htmlAttributesDict = htmlAttributesObject as IDictionary<string, object>;
        var defaultHtmlAttributesDict = defaultHtmlAttributesObject as IDictionary<string, object>;

        RouteValueDictionary htmlAttributes = (htmlAttributesDict != null)
            ? new RouteValueDictionary(htmlAttributesDict)
            : HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributesObject);

        RouteValueDictionary defaultHtmlAttributes = (defaultHtmlAttributesDict != null)
            ? new RouteValueDictionary(defaultHtmlAttributesDict)
            : HtmlHelper.AnonymousObjectToHtmlAttributes(defaultHtmlAttributesObject);

        foreach (var item in htmlAttributes)
        {
            if (concatKeys.Contains(item.Key))
            {
                defaultHtmlAttributes[item.Key] = (defaultHtmlAttributes[item.Key] != null)
                    ? string.Format("{0} {1}", defaultHtmlAttributes[item.Key], item.Value)
                    : item.Value;
            }
            else
            {
                if(item.Key?.ToString() == "divClass")
                {
                    continue;
                }
                defaultHtmlAttributes[item.Key] = item.Value;
            }
        }

        return defaultHtmlAttributes;
    }
}

When I copy the class over it flags the statment: using System.Web.Mvc; -Cannot resolve symbol MVC. And after removing that using statement I get the message cannot resolve symbol "HtmlHelper" in MergeHtmlAttributes(this HtmlHelper helper, ...) I have the option of adding either Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper or .HtmlHelper<Tmodel> I chose .HtmlHelper. After that it refers to the line RouteValueDictionary htmlAttributes = and says it cannot convert IDictionary<string, object> to system.web.Routing.RouteValueDictionary. And I should change the type to IDictionary<string, object> or cast to RouteValueDictionary. Either way I get the following error when trying to use MergeHtmlAttributes in one of my editor templates.

'IHtmlHelper<object>' does not contain a definition for 'MergeHtmlAttributes' and the best extension method overload 'HtmlHelperExtensions.MergeHtmlAttributes(HtmlHelper, object, object)' requires a receiver of type 'HtmlHelper'

This line is throwing the error-> var htmlAttributes = Html.MergeHtmlAttributes(ViewData, defaultHtmlAttributesObject);

Is there a way to get this to work in asp.net core or is there a different method to achieve the same results? Here's an example of one of my editor templates so you can see the MergeHtmlAttributes being used. If I can't build the template like this anymore is there a newer/better way to do it using tag helpers? I really like having the labelfor, txtboxfor, ValidationMessageFor, etc all in one html helper.

@model int?

@{
    var defaultHtmlAttributesObject = new { @class = "form-control" };
    var htmlAttributes = Html.MergeHtmlAttributes(ViewData, defaultHtmlAttributesObject);

    object divClass;
    ViewData.TryGetValue("divClass", out divClass);
    if (divClass == null) { divClass = ""; }

    IDictionary<string, object> validationAttributes = Html.GetUnobtrusiveValidationAttributes("");
    Html.ViewContext.FormContext.RenderedField(ViewData.TemplateInfo.GetFullHtmlFieldName(null), false);
}

<div class="form-group @divClass @(Html.ValidationErrorFor(x => x, " has-error"))">
    @Html.LabelFor(x => x, new { @class = "control-label" })
    @if (validationAttributes.ContainsKey("data-val-required"))
    {<span class="text-danger">*</span>}
    @Html.TextBoxFor(x => x, htmlAttributes)
    @Html.ValidationMessageFor(model => model, "", new { @class = "text-danger" })
</div>

F.Y.I. while converting to asp.net core 1.1 (and .net framework 4.5.2) I ended up putting the connection string in it's app.config file which allowed EF6 to work with Asp.net core so I can keep using the EF code I had built, for whatever reason it wouldn't find the connection string in appsettings.json.

1
  • I think the function definition should be: public static IDictionary<string, object> MergeHtmlAttributes(this IHtmlHelper helper, object htmlAttributesObject, object defaultHtmlAttributesObject) Once I do that then I get "Cannot resolve symbol AnonymousObjectToHtmlAttributes" in IHtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributesObject); I found how AnonymousObjectToHtmlAttributes used to be implemented on github here: github.com/aspnet/Mvc/blob/dev/src/… Commented May 14, 2017 at 4:14

1 Answer 1

3

Initially I didn't realize Microsoft was using IHtmlHelper now instead of HtmlHelper.

Once I modified the code to reflect that change and found Microsoft's mvc repository on github so that I was able to find their implementation of AnonymousObjectToHtmlAttributes it all fell together.

The below code is working for me, but I might have to make some changes for edge cases I haven't yet thought of.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Routing;

using Microsoft.AspNetCore.Mvc.Rendering;

public static class HtmlHelperExtensions
{
    //http://cpratt.co/html-editorfor-and-htmlattributes/
    /// <summary>
    /// This is used with the EditorTemplates to copy the page element's htmlAttributes over to the editorTemplates 
    /// while still being able to specify values to always use (like @class = "form-control") in the editorTemplates.
    /// </summary>
    public static IDictionary<string, object> MergeHtmlAttributes(this IHtmlHelper helper, object htmlAttributesObject, object defaultHtmlAttributesObject)
    {
        var concatKeys = new[] { "class" };

        var htmlAttributesDict = htmlAttributesObject as IDictionary<string, object>;
        var defaultHtmlAttributesDict = defaultHtmlAttributesObject as IDictionary<string, object>;

        RouteValueDictionary htmlAttributes = new RouteValueDictionary(htmlAttributesDict != null
            ? htmlAttributesDict
            : AnonymousObjectToHtmlAttributes(htmlAttributesObject));

        RouteValueDictionary defaultHtmlAttributes = new RouteValueDictionary(defaultHtmlAttributesDict != null
            ? defaultHtmlAttributesDict
            : AnonymousObjectToHtmlAttributes(defaultHtmlAttributesObject));

        foreach (var item in htmlAttributes)
        {
            if (concatKeys.Contains(item.Key))
            {
                defaultHtmlAttributes[item.Key] = defaultHtmlAttributes[item.Key] != null
                    ? string.Format("{0} {1}", defaultHtmlAttributes[item.Key], item.Value)
                    : item.Value;
            }
            else
            {
                if(item.Key == "divClass")
                {
                    continue;
                }
                defaultHtmlAttributes[item.Key] = item.Value;
            }
        }

        return defaultHtmlAttributes;
    }

    private static IDictionary<string, object> AnonymousObjectToHtmlAttributes(object htmlAttributes)
    {
        var dictionary = htmlAttributes as IDictionary<string, object>;
        if (dictionary != null)
        {
            return new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase);
        }

        dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

        if (htmlAttributes != null)
        {
            var stringAttributes = htmlAttributes.ToString();
            stringAttributes = stringAttributes.Replace("{", "").Replace("}", "");
            string[] attributesArray = stringAttributes.Split(new[] { ','}, StringSplitOptions.RemoveEmptyEntries);

            foreach (var helper in attributesArray)
            {
                string[] attribKeyValue = helper.Trim().Split(' ');
                dictionary[attribKeyValue.First()] = attribKeyValue.Last();
            }
        }

        return dictionary;
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Should not is be = instead of space? string[] attribKeyValue = helper.Trim().Split(' ');
Here: var htmlAttributes = Html.MergeHtmlAttributes(ViewData, DefaultHtmlAttributesObject); The code block above that starts with @model int? is a better example of how I used it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.