3

I'm using MVC for REST so that I can take advantage of Razor for outputting different types. CSV is one of those outputs. Instead of writing this template for each input type:

ID,Created,Content
@foreach (var item in Model.TimeData)
{
<text>@item.ID,@item.Created,"@Html.Raw(item.Content.Replace("\"", "\"\""))"</text>
}

I wanted to make use of params and System.Linq.Expressions.Expression to write something like this:

@{
    Html.WriteCsv<TimeObject>(Model.TimeData, p => p.ID, p => p.Created, p => p.Content);   
}

I started writing a generic HtmlHelper and quickly realized I had problems with value types (memberExpression will be null). The code below attempts to just write out the CSV heading (ID,Created,Content), but it only outputs "Content" (because ID and Created are value types (int and DateTime).

public static void WriteCsv<TModel>(this HtmlHelper htmlHelper, List<TModel> list, params Expression<Func<TModel, object>>[] expressions)
{
    foreach (var expression in expressions)
    {
        MemberExpression memberExpression = expression.Body as MemberExpression;

        if (memberExpression != null)
        {
            var propertyInfo = (PropertyInfo)memberExpression.Member;

            htmlHelper.ViewContext.Writer.Write(propertyInfo.Name + Environment.NewLine);
        }
    }
}

I tried replacing object with dynamic, thinking that would work, but when I quick watch expression.Body, it still seems to think it's dealing with an object (the DebugView property is (System.Object)$p.ID).

Is this impossible in C# 4.0?

Here's the type I'm using it on:

[DataContract(IsReference = true, Namespace = "urn:test:TimeObject")]
public class TimeObject
{
    [DataMember]
    public long ID { get; set; }

    [DataMember]
    public string Content { get; set; }

    [DataMember]
    public DateTime Created { get; set; }
}
7
  • 2
    This is tangential to your question, but using a custom ActionResult would be better. You can even use the one here: notesfor.net/post/2010/06/28/… Commented Apr 18, 2011 at 19:05
  • Are you sure the value types are properties? maybe they're fields which would require you to cast it as (FieldInfo) Commented Apr 18, 2011 at 19:10
  • @Talljoe that's an option, but I'd prefer not to use magic strings, which that solution relies on (for ignored fields). It seems it also does not support complex objects (if CSVUserModel had a User on it, which contained Username and Password). Commented Apr 18, 2011 at 19:11
  • @Slaks @Jose It does not work... memberExpression is null. Here's my TimeObject class: [DataContract(IsReference = true, Namespace = "urn:test:TimeObject")] public class TimeObject { [DataMember] public long ID { get; set; } [DataMember] public string Content { get; set; } [DataMember] public DateTime Created { get; set; } } Commented Apr 18, 2011 at 19:13
  • 1
    @Langdon, writing your own is pretty simple or you could build the content in the controller and pass it to FileResult (which does the right thing regarding mime type, optional filename, etc). I'm not convinced Razor handles non-HTML well. Commented Apr 18, 2011 at 19:15

1 Answer 1

8

In the case that the expression references a value type, the compiler has to box the reference; it does so implicitly. This complication means that the expression tree for a Value Type member expression is not simply a MemberExpression, so your cast is returning null.

The following is a general solution for obtaining the property name from a Value Type or Reference Type member expression, taken from this question:

private string GetPropertyName(Expression<Func<object, object>> f) {
    var body = f.Body;
    if (body.NodeType==ExpressionType.Convert)
      body = ((UnaryExpression) body).Operand;
    if ((body as MemberExpression) != null) {
        return (body as MemberExpression).Member.Name;
    }
    return "";
}
Sign up to request clarification or add additional context in comments.

Comments

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.