Question

I have developed a MVC helper for generating display and editable tables (a jquery plugin is required to allow dynamic addition and deletion of rows with full postback in the editable tables) e.g.

@Htm.TableDisplayFor(m => m.MyCollection as ICollection)

which used in conjunction with attributes will include totals in the footer, add columns for view and edit links, render hyperlinks for complex type etc. e.g.

[TableColumn(IncludeTotals = true)]

I'm about to publish it on CodeProject but before doing so, would like to solve one issue. The helper first gets the ModelMetadata from the expression, checks that it implements ICollection, then gets the type in the collection (note the following snippet is from accepted answers on SO, but as explained below, is not entirely correct)

if (collection.GetType().IsGenericType)
{
  Type type = collection.GetType().GetGenericArguments()[0]

The type is used to generate ModelMetadata for the table header (there might not be any rows in the table) and each row in the table body (in case some items are inherited types which have additional properties and would otherwise screw up the column layout)

foreach (var item in collection)
{
  ModelMetadata itemMetadata = ModelMetadataProviders.Current
    .GetMetadataForType(() => item, type);

What I would like to be able to do is use IEnumerable rather than ICollection so that .ToList() does not need to be called on linq expressions.

In most cases IEnumerable works fine e.g.

IEnumerable items = MyCollection.Where(i => i....);

is OK because .GetGenericArguments() returns an array containing only one type. The problem is that '.GetGenericArguments()' on some queries returns 2 or more types and there seems to be no logical order. For example

IEnumerable items = MyCollection.OrderBy(i => i...);

returns [0] the type in the collection, and [1] the type used for ordering.

In this case .GetGenericArguments()[0] still works, but

MyCollection.Select(i => new AnotherItem()
{
  ID = i.ID,
  Name = 1.Name
}

returns [0] the type in the original collection and [1] the type of AnotherItem

So .GetGenericArguments()[1] is what I need to render the table for AnotherItem.

My question is, is there a reliable way using conditional statements to get the type I need to render the table?

From my tests so far, using .GetGenericArguments().Last() works in all cases except when using OrderBy() because the sort key is the last type.

A few things I've tried so far include ignoring types that are value types (as will often be the case with OrderBy(), but OrderBy() queries might use a string (which could be checked) or even worse, a class which overloads ==, < and > operators (in which case I would not be able to tell which is the correct type), and I have been unable to find a way to test if the collection implements IOrderedEnumerable.

Was it helpful?

Solution

Solved (using comments posted by Chris Sinclair)

private static Type GetCollectionType(IEnumerable collection)
{
  Type type = collection.GetType();
  if (type.IsGenericType)
  {
    Type[] types = type.GetGenericArguments();
    if (types.Length == 1)
    {
      return types[0];
    }
    else
    {
      // Could be null if implements two IEnumerable
      return type.GetInterfaces().Where(t => t.IsGenericType)
        .Where(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        .SingleOrDefault().GetGenericArguments()[0];
    }
  }
  else if (collection.GetType().IsArray)
  {
    return type.GetElementType();
  }
  // TODO: Who knows, but its probably not suitable to render in a table
  return null;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top