// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.CSharp.RuntimeBinder;
using Binder = Microsoft.CSharp.RuntimeBinder.Binder;
namespace System.Web.Helpers
{
///
/// Default data source that sorts results if a sort column is specified.
///
internal sealed class WebGridDataSource : IWebGridDataSource
{
private readonly WebGrid _grid;
private readonly Type _elementType;
private readonly IEnumerable _values;
private readonly bool _canPage;
private readonly bool _canSort;
public WebGridDataSource(WebGrid grid, IEnumerable values, Type elementType, bool canPage, bool canSort)
{
Debug.Assert(grid != null);
Debug.Assert(values != null);
_grid = grid;
_values = values;
_elementType = elementType;
_canPage = canPage;
_canSort = canSort;
}
public SortInfo DefaultSort { get; set; }
public int RowsPerPage { get; set; }
public int TotalRowCount
{
get { return _values.Count(); }
}
public IList GetRows(SortInfo sortInfo, int pageIndex)
{
IEnumerable rowData = _values;
if (_canSort)
{
rowData = Sort(_values.AsQueryable(), sortInfo);
}
rowData = Page(rowData, pageIndex);
try
{
// Force compile the underlying IQueryable
rowData = rowData.ToList();
}
catch (ArgumentException)
{
// The OrderBy method uses a generic comparer which fails when the collection contains 2 or more
// items that cannot be compared (e.g. DBNulls, mixed types such as strings and ints et al) with the exception
// System.ArgumentException: At least one object must implement IComparable.
// Silently fail if this exception occurs and declare that the two items are equivalent
rowData = Page(_values.AsQueryable(), pageIndex);
}
return rowData.Select((value, index) => new WebGridRow(_grid, value: value, rowIndex: index)).ToList();
}
private IQueryable Sort(IQueryable data, SortInfo sortInfo)
{
if (!String.IsNullOrEmpty(sortInfo.SortColumn) || ((DefaultSort != null) && !String.IsNullOrEmpty(DefaultSort.SortColumn)))
{
return Sort(data, _elementType, sortInfo);
}
return data;
}
private IEnumerable Page(IEnumerable data, int pageIndex)
{
if (_canPage)
{
Debug.Assert(RowsPerPage > 0);
return data.Skip(pageIndex * RowsPerPage).Take(RowsPerPage);
}
return data;
}
private IQueryable Sort(IQueryable data, Type elementType, SortInfo sort)
{
Debug.Assert(data != null);
if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(elementType))
{
// IDynamicMetaObjectProvider properties are only available through a runtime binder, so we
// must build a custom LINQ expression for getting the dynamic property value.
// Lambda: o => o.Property (where Property is obtained by runtime binder)
// NOTE: lambda must not use internals otherwise this will fail in partial trust when Helpers assembly is in GAC
var binder = Binder.GetMember(CSharpBinderFlags.None, sort.SortColumn, typeof(WebGrid), new[]
{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
});
var param = Expression.Parameter(typeof(IDynamicMetaObjectProvider), "o");
var getter = Expression.Dynamic(binder, typeof(object), param);
return SortGenericExpression(data, getter, param, sort.SortDirection);
}
else
{
// The IQueryable data source is cast as IQueryable