0

I have a model (simplified for relevance) like this:

public abstract class TitleCreateModel : ICreateModel
{
    [Required]
    [MaxLength(400)]
    public string TitleName { get; set; }

    [Required]
    [MaxLength(4)]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [MaxLength(5)]
    public string Test { get; set; }

    [Required]
    [MaxLength(2)]
    public int Wut { get; set; }
}

Then I have a custom HTML helper class and an Expression extension class (both unabridged):

public class InputHelper
{
    public static HtmlString Input<T>(Expression<Func<T, Object>> expression, string id, string label)
    {
        var req = expression.GetAttribute<T, Object, RequiredAttribute>();
        var max = expression.GetAttribute<T, Object, MaxLengthAttribute>();
        var required = "";
        var maxlength = "";
        if(req!=null)
        {
            required = "req";
        }
        if(max!=null)
        {
            maxlength = "maxlength='" + max.Length + "'";
        }
        return new HtmlString("<div class=\"clearfix\"><label for=\""+id+"\">" + label + "</label>" +
                              "<div class=\"input\"><input id=\""+id+"\" class=\""+required+"\" type=\"text\" "+maxlength+"/></div></div>");
    }
}

public static class ExpressionExtensions
{
    public static TAttribute GetAttribute<TIn, TOut, TAttribute>(this Expression<Func<TIn, TOut>> expression) where TAttribute : Attribute
    {
        var memberExpression = expression.Body as MemberExpression;
        if (memberExpression != null)
        {
            var attributes = memberExpression.Member.GetCustomAttributes(typeof(TAttribute), true);
            return attributes.Length > 0 ? attributes[0] as TAttribute : null;
        }
        return null;
    }
}

In my Razor script, I make the following calls:

@(InputHelper.Input<string>(m => Model.Title.TitleName, "titlename", "Title Name"))

@(InputHelper.Input<string>(m => Model.Title.Test, "testfield", "Test Field"))

@(InputHelper.Input<int>(m => Model.Title.Wut, "tester", "Test Field 2"))

@(InputHelper.Input<DateTime>(m => Model.Title.ReleaseDate, "release_year", "Release Year"))

For some reason, the GetAttribute method only finds the attributes for TitleName and Test, both of which are string properties for TitleCreateModel. It's unable to find the attributes for ReleaseDate and Wut, and I have no idea why.

Jay Sun
  • 1,583
  • 3
  • 25
  • 33

2 Answers2

1

Modify the expression used in Input method to Expression<Func<T, TOut>> expression

public static HtmlString Input<T, TOut>(Expression<Func<T, TOut>> expression, string id, string label)
{
    var req = expression.GetAttribute<T, TOut, RequiredAttribute>();
    var max = expression.GetAttribute<T, TOut, MaxLengthAttribute>();
    var required = "";
    var maxlength = "";
    if(req!=null)
    {
        required = "req";
    }
    if(max!=null)
    {
        maxlength = "maxlength='" + max.Length + "'";
    }
    return new HtmlString("<div class=\"clearfix\"><label for=\""+id+"\">" + label + "</label>" +
                          "<div class=\"input\"><input id=\""+id+"\" class=\""+required+"\" type=\"text\" "+maxlength+"/></div></div>");
}
Eranga
  • 32,181
  • 5
  • 97
  • 96
  • This worked, but I have no clue why it did. Is there a good resource on expressions that you can link to? – Jay Sun Nov 15 '11 at 16:24
1

With the DateTime and int, the Expression is not of type MemberExpression, due to the boxing I believe, so this line:

var memberExpression = expression.Body as MemberExpression;

will return a null.

There's a good question and answer on this topic here, which just involves modifying your Func generic as Eranga as specified.

Community
  • 1
  • 1
Kasaku
  • 2,192
  • 16
  • 26
  • So why does it work when I genericize TResult in Func? I'm completely new to expressions and this all seems like black magic to me. – Jay Sun Nov 15 '11 at 16:24
  • Because you were stating that the function must return the generic Object type, this would require an extra operation on a value-type to box the type as an Object (see [boxing documentation](http://msdn.microsoft.com/en-us/library/yz2be5wk%28VS.80%29.aspx)) so the expression had that extra step included. If you use the generic type it doesn't need to explicitly return it as an Object and that step is removed. – Kasaku Nov 15 '11 at 16:33