Tag (Suggested addition for HtmlHelperExtensions.cs)

Dec 12, 2010 at 7:50 AM
Edited Dec 14, 2010 at 12:32 AM

Just wanted to submit this. From http://stackoverflow.com/questions/4328373/what-is-the-best-way-to-include-script-references-in-asp-net-mvc-views/4328937#4328937 (please browse the link for contextual reference as an example of usage and purpose) 

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

public static class TagHelper
{
    public static string Tag(this HtmlHelper htmlHelper,
        string tag = null,
        string src = null, string href = null,
        string type = null,
        string id = null, string name = null,
        string style = null, string @class = null,
        string attribs = null)
    {
        var sb = new StringBuilder();
        sb.Append("<");
        if (string.IsNullOrEmpty(tag)) tag = "div";
        sb.Append(tag);
        AppendOptionalAttrib(htmlHelper, sb, "id", id, encode:false, validateScriptableIdent:true);
        AppendOptionalAttrib(htmlHelper, sb, "name", name, encode: false, validateScriptableIdent: true);
        AppendOptionalAttrib(htmlHelper, sb, "type", type);
        AppendOptionalAttrib(htmlHelper, sb, "src", src, encode: false, resolveAbsUrl: true);
        AppendOptionalAttrib(htmlHelper, sb, "href", href, encode: false, resolveAbsUrl: true);
        AppendOptionalAttrib(htmlHelper, sb, "style", style);
        AppendOptionalAttrib(htmlHelper, sb, "class", @class, encode:false, validateClass: true);
        if (!string.IsNullOrEmpty(attribs)) sb.Append(" " + attribs);
        if ((new[] { "script", "div", "p", "a", "h1", "h2", "h3", "h4", "h5", "h6", "center", "table", "form" }).Contains(tag.ToLower()))
            sb.Append("></" + tag + ">");
        else sb.Append(" />");
        return sb.ToString();
    }

    private static void AppendOptionalAttrib(HtmlHelper htmlHelper, StringBuilder sb,
        string attribName, string attribValue, bool? encode=null, 
        bool? resolveAbsUrl=null, bool? validateScriptableIdent=null, bool? validateClass=null)
    {
        if (string.IsNullOrEmpty(attribValue)) return;
        if (string.IsNullOrEmpty(attribName)) throw new ArgumentException("attribName is required.", "attribName");
        var attribNameLcase = attribName.ToLower();
        if (!resolveAbsUrl.HasValue) resolveAbsUrl = attribNameLcase == "src" || attribNameLcase == "href";
        if (!validateScriptableIdent.HasValue)
            validateScriptableIdent = attribNameLcase == "id" || attribNameLcase == "name";
        if (!validateClass.HasValue)
            validateClass = attribNameLcase == "class";
        if (!encode.HasValue) encode = !validateScriptableIdent.Value && !resolveAbsUrl.Value && !validateClass.Value;
        sb.Append(" " + attribName + "=\"");
        if (validateScriptableIdent.Value && !IsScriptableIdValue(attribValue))
            throw new FormatException("Attrib value has invalid characters: " + attribNameLcase + "=" + attribValue);
        if (validateClass.Value && !IsValidClassValue(attribValue))
            throw new FormatException("Attrib value has invalid characters: " + attribNameLcase + "=" + attribValue);
        if (resolveAbsUrl.Value)
        {
            try
            {
                sb.Append(VirtualPathUtility.ToAbsolute(attribValue));
            }
            catch (ArgumentException e)
            {
                if (e.Message.Contains("is not allowed here"))
                {
                    throw new ArgumentException(e.Message + " (Try prefixing the app root, i.e. \"~/\".)", e.ParamName);
                }
                throw;
            }
        }
        else if (encode.Value) sb.Append(htmlHelper.Encode(attribValue));
        else sb.Append(attribValue);
        sb.Append("\"");
    }

    private static bool IsValidClassValue(string value)
    {
        const string _123 = "1234567890";
        const string abc123 = "abcdefghijklmnopqrstuvwxyz_- " + _123;
        if (string.IsNullOrEmpty(value)) return false;
        value = value.ToLower();
        return value.All(c => abc123.Contains(c));
    }

    private static bool IsScriptableIdValue(string value)
    {
        const string _123 = "1234567890";
        const string abc123 = "abcdefghijklmnopqrstuvwxyz_" + _123;
        if (string.IsNullOrEmpty(value)) return false;
        value = value.ToLower();
        if (_123.Contains(value[0])) return false; // don't start with a #
        return value.All(c => abc123.Contains(c));
    }
}

 

Dec 13, 2010 at 12:26 AM
Edited Dec 13, 2010 at 12:33 AM

Thanks Jon Davis for posting this through.

We'll put this through.

We welcome other and Jon Davis alike to post or email us extension library/methods that they would like to include.

Like myself, I use this extension library as part of my new MVC project, and if there are new extension methods that i wanted to use in my project, i just add it to DNPExtensions.

So please send in your if you want them to be included.

Happy coding.

- Michael T.

Dec 13, 2010 at 12:29 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Dec 13, 2010 at 11:17 PM

It's not my place to make requests of the user community here, but I will express a personal preference.  When I see a code segment like the one in this thread, I often wonder what problem it intends to solve, and exactly how the author expects it to be used. What does the .NET BCL/FCL not do that this new method does? Many extension methods (including most of those I gathered in the library that I contributed to this project) do not have such documentation.  This makes it difficult for anyone to standardize them, categorize them, document them in the FOSS spirit, or to use them.  Undocumented code is unapproachable and, while we might have the most useful code in our toolbox, if it's not documented it might be the least used.

So I am simply hoping that anyone who makes contributions to projects like this will provide some limited information about what the code is supposed to do and at least one example for using it.

For anyone else, if you see some code that you understand and appreciate immediately, you can contribute with a comment like "I would use that like this: foo = object.Method(zz), and the result would help me ... like this..."  That example can be combined with the code into the library package.  That's the way FOSS works...

Thanks!

Dec 14, 2010 at 12:21 AM
Edited Dec 14, 2010 at 12:30 AM

In the case of this particular code which was shared, the "problem it intends to solve" is demonstrated by the hyperlink to Stack Overflow that I provided at the beginning of the post. In this case, it is about the convenience of declaring a tag using the syntax:

<%= Html.Tag("script", src:"~/myscript.js") %>

.. rather than the ugly .NET BCL/FCL flavor ..

<%= "<scr" + "ipt src=\"" + ResolveUrl("~/myscript.js") + "\"></script>" %>

Other examples would be to validate the text for the values being added into, say, a class attribute. Additional rules can be added, but the point is that HTML (etc) is a specification, it does not vary from person to person, so it's perfectly reasonable to build against such a spec with conformance rules.

I would expect that if someone contributed a code snippet via the forum, but the description and usage examples are in the forum post description outside the snippet, that the maintainers of the project, should they decide to include the snippet, would take a minute or two to merge / format parts or all of the description and examples in the XML comment summary. Otherwise, they can just link here to the forum post in the XML comment summary, which is pretty much what they have been doing.

Dec 14, 2010 at 12:39 AM
stimpy77 wrote:

In the case of this particular code which was shared, the "problem it intends to solve" is demonstrated by the hyperlink to Stack Overflow that I provided at the beginning of the post. In this case, it is about the convenience of declaring a tag using the syntax:

<%= Html.Tag("script", src:"~/myscript.js") %>

.. rather than the ugly .NET BCL/FCL flavor ..

<%= "<scr" + "ipt src=\"" + ResolveUrl("~/myscript.js") + "\"></script>" %>

Other examples would be to validate the text for the values being added into, say, a class attribute. Additional rules can be added, but the point is that HTML (etc) is a specification, it does not vary from person to person, so it's perfectly reasonable to build against such a spec with conformance rules.

I would expect that if someone contributed a code snippet via the forum, but the description and usage examples are in the forum post description outside the snippet, that the maintainers of the project, should they decide to include the snippet, would take a minute or two to merge / format parts or all of the description and examples in the XML comment summary. Otherwise, they can just link here to the forum post in the XML comment summary, which is pretty much what they have been doing.

I would just use T4MVC from MVC Contrib for this particular problem... as it include view validation...

but yes i could see how this this TagHelper could be very useful elsewhere.

Thanks again for the post Tony

Dec 14, 2010 at 6:13 AM

View validation has nothing to do with HTML conformance. Also I'm not using T4MVC nor MVC Contrib and don't plan to; I'm more likely to use a lib like this, though. But thanks, I'm glad you see some utility for it.