Tuesday, May 3, 2011

LINQ Anonymous Types + MVC Views Help

I've seen many questions about this, but i've never really got the answer that I need.

I'm converting a fairly large web application from Web Forms to MVC and after a while I encountred a problem with passing data to the view. In the Action I execute the code:


//This is just an example
ViewData["QProducts"] = from p in db.Products select new{Name = p.Name, Date = p.ToShortDateString() }
ViewData["QUsers"] = from u in db.Users select u;
I use a foreach loop to iterate over the objects in html, like this:
foreach(var q in (IEnumerable)ViewData["QEvents"]){ 
    /*Print the data here*/
}

Before using MVC I just used a asp:Repeater, but sinec this is MVC I can't use asp.net controls.

How am I supposed to pass this data to the View? I don't really have the option of not using Anonymous Types here. <%#ViewData.Eval()%> obviously won't work.

Any Ideas?

From stackoverflow
  • Consider explicitly converting to a list and casting the ViewData:

    ViewData["QUsers"] = (from u in db.Users select u).ToList();
    
    foreach(Users u in (List<Users>)ViewData["QUsers"]){ 
    
        /*Print the data here*/
    
    }
    

    You can pass data in a few ways, using ViewData as you are above, or TempData to pass between Actions. You can also use ViewData.Model to contain a strongly typed model. Note that you will have to change the definition of the view to be something like

    ViewPage<User>
    

    As for a nice repeater replacement try http://www.codeplex.com/MVCContrib. They have a Grid Html Helper that may help.

  • Rather than an anonymous type, create a type to hold the name and date:

    public class NameDate
    {
      public string Name { get; set; }
      public DateTime Date { get; set; }
    }
    

    Then use that in your Linq query:

    from p in db.Products select new NameDate { Name = p.Name, Date = p.Date }
    

    Strongly type your view to be MyView<IEnumerable<NameDate>> and then just do a foreach ( var nameDate in ViewData.Model )...

    impClaw : Thanks for a good reply.
    Mike Scott : You're welcome - but sorry I forgot to quote the generic, so it's not legible. Will edit it...
    emzero : So there's no way to avoid having to create a new class? I really like Anonymous types in this cases, they are a quick and strongly-typed way of getting some generated field and I don't like creating a whole new class just for 1 particular case. Is there a clean way to pass the anonymous type to the view?
    Mike Scott : emzero, you can still use an anonymous type but you'll only be able to access its properties via reflection. Creating your own class gives you strong type checking.
  • If you want to avoid creating a separate class just for displaying your one projection, you could also resort to using a dictionary, like so:

    from person in personList select new Dictionary<string, string>
    { 
        { "Name", person.Firstname + " " + person.Lastname },
        { "Id", person.Id.ToString() }
    };
    

    You can then type your viewpage to

    ViewPage<IEnumerable<Dictionary<string, string>>>
    

    And finally iterate over the list in the view like so:

    <% foreach (Dictionary<string, string> p in (IEnumerable)ViewData.Model) 
    { %>
       <li> <%=p["Id"] %> - <%= p["Name"] %> </li>
    <% } %>
    

    Needless to say, the drawback is that your code is now rather full of "magic strings", making it more error prone because of the absence of compile time checking.

  • If you're feeling a little lazy, you can use this code here... It's a little long, but basically it's a wrapper for Reflection...

    var something = { Name = "Jim", Age = 25 };
    AnonymousType type = AnonymousType.Create(something);
    
    //then used...
    string name = type.Get<string>("Name");
    int age = type.Get<int>("Age", -1 /* optional default value */);
    

    And here is the code...

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Reflection;
    
    namespace Code {
    
     /// <summary>
     /// A convenient method of accessing the values of an 
     /// anonymous type without needing to define a separate class
     /// </summary>
     public class AnonymousType {
    
      #region Constants
    
      private const string EXCEPTION_MISSING_PARAMETER_INFORMATION = 
       "Unable to match the parameter '{0}' to a property in the AnonymousType. This could be due to a casting problem or a Property that does not exist.";
      private const string EXCEPTION_COULD_NOT_ACCESS_FIELD =
       "Unable to find a field named '{0}' (of type {1})";
      private const string EXCEPTION_COULD_NOT_ACCESS_FIELD_AT_INDEX =
       "Unable to find a field named '{0}' at the requested index (of type {1})";
    
      #endregion
    
      #region Constructors
    
      /// <summary>
      /// Creates a new AutoType for methods that return Anonymus types
      /// </summary>
      public AnonymousType(object type) {
       this._Init(type, false);
      }
    
      /// <summary>
      /// Creates a new AutoType for methods that return Anonymus types and
      /// detetrmins if exceptions should be thrown if a type is missing
      /// </summary>
      public AnonymousType(object type, bool supressErrors) {
       this._Init(type, supressErrors);
      }
    
      /// <summary>
      /// Initalizes the data for the is type
      /// </summary>
      private void _Init(object type, bool supressErrors) {
       this.SupressExceptions = supressErrors;
       this.m_Type = type.GetType();
       this.m_TypeData = type; 
      }
    
      #endregion
    
      #region Static Routines
    
      /// <summary>
      /// Creates a new Anonymous Type from the provided object data
      /// </summary>
      public static AnonymousType Create(object data) {
       return new AnonymousType(data);
      }
    
      /// <summary>
      /// Creates a new Anonymous Type from the provided object data
      /// </summary>
      public static AnonymousType Create(object data, bool supressErrors) {
       return new AnonymousType(data, supressErrors);
      }
    
      #endregion
    
      #region Private Members
    
      /// <summary>
      /// The type that will be accessed via reflection
      /// </summary>
      private Type m_Type;
    
      /// <summary>
      /// The actual typs that is being used
      /// </summary>
      private object m_TypeData;
    
      #endregion
    
      #region Properties
    
      /// <summary>
      /// Determines if errors should be thrown if any casting errors take place
      /// </summary>
      public bool SupressExceptions { get; set; }
    
    
      /// <summary>
      /// Accessess a property by name and returns an object
      /// </summary>
      public object this[string property] {
       get {
        return this.Get<object>(property);
       }
      }
    
      #endregion
    
      #region Public Methods
    
      /// <summary>
      /// Checks if this Anonymous Type has the specified property
      /// </summary>
      public bool Has(string property) {
       return ((m_Type.GetProperty(property) as PropertyInfo) != null);
      }
    
      /// <summary>
      /// Returns if this Anonymous type has the specified property and that
      /// the value matches the type specified
      /// </summary>
      public bool Has(string property, Type isType) {
    
       //try and get the property
       PropertyInfo prop = m_Type.GetProperty(property) as PropertyInfo;
    
       //If this type doesn't exist at all, just return false
       if (prop == null) { return false; }
    
       //if it does exist, verify the type
       if (prop.PropertyType.Equals(isType)) { return true; }
       return false;
    
      }
    
      /// <summary>
      /// Returns a type value using the specified type
      /// </summary>
      public T Get<T>(string property) {
    
       //return this value if needed            
       PropertyInfo prop = m_Type.GetProperty(property) as PropertyInfo;
       try {
        return (T)prop.GetValue(this.m_TypeData, null);
       }
       catch (Exception ex) {
        if (this.SupressExceptions) { return default(T); }
        throw new Exception(
         string.Format(EXCEPTION_COULD_NOT_ACCESS_FIELD, property, typeof(T).Name),
         ex
         );
       }
      }
    
    
    
      /// <summary>
      /// Returns a type value using the specified type
      /// </summary>
      public T Get<T>(string property, object[] index) {
    
       //return this value if needed
       PropertyInfo prop = m_Type.GetProperty(property) as PropertyInfo;
       try {
        return (T)prop.GetValue(this.m_TypeData, index);
       }
       catch (Exception ex) {
        if (this.SupressExceptions) { return default(T); }
        throw new Exception(
         string.Format(EXCEPTION_COULD_NOT_ACCESS_FIELD_AT_INDEX, property, typeof(T).Name),
         ex
         );
       }
      }
    
    
    
      /// <summary>
      /// Returns a type value using the specified type but includes a default value
      /// if one it missing
      /// </summary>
      public T Get<T>(string property, T defaultValue) {
       //return this value if needed
       PropertyInfo prop = m_Type.GetProperty(property) as PropertyInfo;
       if (prop == null) { return defaultValue; }
       try {
        return (T)prop.GetValue(this.m_TypeData, null);
       }
       catch (Exception ex) {
        if (this.SupressExceptions) { return defaultValue; }
        throw new Exception(
         string.Format(EXCEPTION_COULD_NOT_ACCESS_FIELD, prop, typeof(T).Name),
         ex
         );
       }
    
      }
    
    
    
      /// <summary>
      /// Accepts a delegate that will use the names of the passed in
      /// parameters as properties to map to. If the property does not
      /// exist, then the method will fail.
      /// </summary>
      public void Use<T1>(Action<T1> with) {
    
       //set a default for each of the params
       T1 param1 = default(T1);
    
       //get the parameters for this method
       var paramList = with.Method.GetParameters();
    
       //update each of the parameters            
       string paramName = string.Empty;
       try {
        for (int i = 0; i < paramList.Length; i++) {
    
         //find the correct matching property for this parameter
         paramName = paramList[i].Name;
         switch (i + 1) {
          case 1:
           param1 = this.Get<T1>(paramName);
           break;
         }
        }
    
       }
       catch (Exception ex) {
        throw new ArgumentException(
         string.Format(EXCEPTION_MISSING_PARAMETER_INFORMATION, paramName),
         ex
         );
       }
    
       //otherwise, execute the method provided
       with(param1);
    
      }
    
    
    
      /// <summary>
      /// Accepts a delegate that will use the names of the passed in
      /// parameters as properties to map to. If the property does not
      /// exist, then the method will fail.
      /// </summary>
      public void Use<T1, T2>(Action<T1, T2> with) {
    
       //set a default for each of the params
       T1 param1 = default(T1);
       T2 param2 = default(T2);
    
       //get the parameters for this method
       var paramList = with.Method.GetParameters();
    
       //update each of the parameters            
       string paramName = string.Empty;
       try {
        for (int i = 0; i < paramList.Length; i++) {
    
         //find the correct matching property for this parameter
         paramName = paramList[i].Name;
         switch (i + 1) {
          case 1:
           param1 = this.Get<T1>(paramName);
           break;
    
          case 2:
           param2 = this.Get<T2>(paramName);
           break;
         }
        }
    
       }
       catch (Exception ex) {
        throw new ArgumentException(
         string.Format(EXCEPTION_MISSING_PARAMETER_INFORMATION, paramName),
         ex
         );
       }
    
       //otherwise, execute the method provided
       with(param1, param2);
    
      }
    
    
    
      /// <summary>
      /// Accepts a delegate that will use the names of the passed in
      /// parameters as properties to map to. If the property does not
      /// exist, then the method will fail.
      /// </summary>
      public void Use<T1, T2, T3>(Action<T1, T2, T3> with) {
    
       //set a default for each of the params
       T1 param1 = default(T1);
       T2 param2 = default(T2);
       T3 param3 = default(T3);
    
       //get the parameters for this method
       var paramList = with.Method.GetParameters();
    
       //update each of the parameters            
       string paramName = string.Empty;
       try {
        for (int i = 0; i < paramList.Length; i++) {
    
         //find the correct matching property for this parameter
         paramName = paramList[i].Name;
         switch (i + 1) {
          case 1:
           param1 = this.Get<T1>(paramName);
           break;
    
          case 2:
           param2 = this.Get<T2>(paramName);
           break;
    
          case 3:
           param3 = this.Get<T3>(paramName);
           break;
         }
        }
    
       }
       catch (Exception ex) {
        throw new ArgumentException(
         string.Format(EXCEPTION_MISSING_PARAMETER_INFORMATION, paramName),
         ex
         );
       }
    
       //otherwise, execute the method provided
       with(param1, param2, param3);
    
      }
    
    
    
      /// <summary>
      /// Accepts a delegate that will use the names of the passed in
      /// parameters as properties to map to. If the property does not
      /// exist, then the method will fail.
      /// </summary>
      public void Use<T1, T2, T3, T4>(Action<T1, T2, T3, T4> with) {
    
       //set a default for each of the params
       T1 param1 = default(T1);
       T2 param2 = default(T2);
       T3 param3 = default(T3);
       T4 param4 = default(T4);
    
       //get the parameters for this method
       var paramList = with.Method.GetParameters();
    
       //update each of the parameters            
       string paramName = string.Empty;
       try {
        for (int i = 0; i < paramList.Length; i++) {
    
         //find the correct matching property for this parameter
         paramName = paramList[i].Name;
         switch (i + 1) {
          case 1:
           param1 = this.Get<T1>(paramName);
           break;
    
          case 2:
           param2 = this.Get<T2>(paramName);
           break;
    
          case 3:
           param3 = this.Get<T3>(paramName);
           break;
    
          case 4:
           param4 = this.Get<T4>(paramName);
           break;
         }
        }
    
       }
       catch (Exception ex) {
        throw new ArgumentException(
         string.Format(EXCEPTION_MISSING_PARAMETER_INFORMATION, paramName),
         ex
         );
       }
    
       //otherwise, execute the method provided
       with(param1, param2, param3, param4);
    
      }
    
      #endregion
    
      #region Working With Arrays
    
      /// <summary>
      /// Returns the specified property as an array of AnonymousTypes
      /// </summary>
      public AnonymousType[] AsArray(string property) {
       object[] values = this.Get<object[]>(property);
       return values.Select(o => {
        if (o is AnonymousType) { return (AnonymousType)o; }
        return new AnonymousType(o);
       }).ToArray();
      }
    
      /// <summary>
      /// Performs the specified action on each value in an array of AnonymousTypes
      /// </summary>
      public void WithEach(string property, Action<AnonymousType> action) {
       foreach (AnonymousType value in this.AsArray(property)) {
        action(value);
       }
      }
    
      #endregion
    
      #region Static Methods
    
      /// <summary>
      /// Returns the type of data for the provided object
      /// </summary>
      public static T Get<T>(object data, string property) {
       return new AnonymousType(data).Get<T>(property);
      }
    
      /// <summary>
      /// Returns the type of data for the provided object
      /// </summary>
      public static T Get<T>(object data, string property, T defaultValue) {
       return new AnonymousType(data).Get<T>(property, defaultValue);
      }
    
      /// <summary>
      /// Returns the type of data for the provided object
      /// </summary>
      public static AnonymousType[] AsArray(object data, string property) {
       return new AnonymousType(data).AsArray(property);
      }
    
      /// <summary>
      /// Performs the following action on each of the values in the specified
      /// property value for a user
      /// </summary>
      public static void WithEach(object data, string property, Action<AnonymousType> action) {
       new AnonymousType(data).WithEach(property, action);
      }
    
      #endregion
    
     }
    }
    
  • Can't you just use the RouteValueDictionary from MVC?

0 comments:

Post a Comment