120

I use an anonymous object to pass my Html Attributes to some helper methods. If the consumer didn't add an ID attribute, I want to add it in my helper method.

How can I add an attribute to this anonymous object?

Boris Callens
  • 90,659
  • 85
  • 207
  • 305

4 Answers4

85

The following extension class would get you what you need.

public static class ObjectExtensions
{
    public static IDictionary<string, object> AddProperty(this object obj, string name, object value)
    {
        var dictionary = obj.ToDictionary();
        dictionary.Add(name, value);
        return dictionary;
    }

    // helper
    public static IDictionary<string, object> ToDictionary(this object obj)
    {
        IDictionary<string, object> result = new Dictionary<string, object>();
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(obj);
        foreach (PropertyDescriptor property in properties){
            result.Add(property.Name, property.GetValue(obj));
        }
        return result;
    }
}
Mark Bell
  • 28,985
  • 26
  • 118
  • 145
Khaja Minhajuddin
  • 6,653
  • 7
  • 45
  • 47
  • 4
    100% correct. Even though generally you use anonymous types for Html Attributes, it is actually an IDictionary and therefore you can add/remove easily from it. – Peter Munnings Jan 20 '12 at 09:34
  • 13
    http://msdn.microsoft.com/en-us/library/system.web.mvc.htmlhelper.anonymousobjecttohtmlattributes(v=vs.108).aspx does the same thing, but it's built into System.Web.Mvc.HtmlHelper. – Mir Dec 17 '12 at 21:55
  • 1
    Why is ToDictionary public and not private, it doesn't make sense to expose this method? – Professor of programming Nov 13 '15 at 17:32
  • Excellent! It could be better if it supports "JsonPropertyAttribute" – Mahdi Ataollahi Jun 25 '23 at 13:57
56

I assume you mean anonymous types here, e.g. new { Name1=value1, Name2=value2} etc. If so, you're out of luck - anonymous types are normal types in that they're fixed, compiled code. They just happen to be autogenerated.

What you could do is write new { old.Name1, old.Name2, ID=myId } but I don't know if that's really what you want. Some more details on the situation (including code samples) would be ideal.

Alternatively, you could create a container object which always had an ID and whatever other object contained the rest of the properties.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
19

If you're trying to extend this method:

public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues);

Although I'm sure Khaja's Object extensions would work, you might get better performance by creating a RouteValueDictionary and passing in the routeValues object, add your additional parameters from the Context, then return using the ActionLink overload that takes a RouteValueDictionary instead of an object:

This should do the trick:

    public static MvcHtmlString MyLink(this HtmlHelper helper, string linkText, string actionName, object routeValues)
    {
        RouteValueDictionary routeValueDictionary = new RouteValueDictionary(routeValues);

        // Add more parameters
        foreach (string parameter in helper.ViewContext.RequestContext.HttpContext.Request.QueryString.AllKeys)
        {
            routeValueDictionary.Add(parameter, helper.ViewContext.RequestContext.HttpContext.Request.QueryString[parameter]);
        }

        return helper.ActionLink(linkText, actionName, routeValueDictionary);
    }
Levitikon
  • 7,749
  • 9
  • 56
  • 74
  • Works like a charm, this should be the accepted answer! BTW: If you are using the object overloads of ActionLink() so far, you have to wrap htmlAttributes with HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes) in order to call the correct overload! – D.R. Aug 28 '13 at 10:10
-1
public static string TextBox(this HtmlHelper html, string value, string labelText, string textBoxId, object textBoxHtmlAttributes, object labelHtmlAttributes){}

This would accept the id value the textbox should have and the label should refer to. If the consumer now doesn't include the "id" property in the textBoxHtmlAttributes, the method will create an incorrect label.

I can check through reflection if this attribute is added in the labelHtmlAttributes object. If so, I want to add it or create a new anonymous object that has it added. But because I can't create a new anonymous type by walking through the old attributes and adding my own "id" attribute, I'm kind of stuck.

A container with a strongly typed ID property and then an anonymous typed "attributes" property would require code rewrites that don't weigh up to the "add an id field" requirement.

Hope this response is understandable. It's the end of the day, can't get my brains in line anymore..

Boris Callens
  • 90,659
  • 85
  • 207
  • 305
  • Well... you *could* create a new type (with CodeDOM etc) which contained the relevant properties. But the code would be ugly as hell. Can I suggest that instead of taking an object and looking at the properties via reflection, you take a IDictionary or something? (Continued) – Jon Skeet Oct 24 '08 at 15:03
  • You could build that dictionary up with a helper method which did the whole reflection thing - and possibly have a wrapper method which did precisely that - but a dictionary sounds like it's closer to what you're really trying to represent; anonymous object initializers are just syntactically handy. – Jon Skeet Oct 24 '08 at 15:04