How to use Kendo UI Grid with ToDataSourceResult(), IQueryable<T>, ViewModel and AutoMapper?

rGiosa picture rGiosa · May 10, 2013 · Viewed 37.9k times · Source

What is the best approach to load/filter/order a Kendo grid with the following classes:

Domain:

public class Car
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual bool IsActive { get; set; }
}

ViewModel

public class CarViewModel
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual string IsActiveText { get; set; }
}

AutoMapper

Mapper.CreateMap<Car, CarViewModel>()
      .ForMember(dest => dest.IsActiveText, 
                 src => src.MapFrom(m => m.IsActive ? "Yes" : "No"));

IQueryable

var domainList = RepositoryFactory.GetCarRepository().GetAllQueryable();

DataSourceResult

var dataSourceResult = domainList.ToDataSourceResult<Car, CarViewModel>(request, 
                          domain => Mapper.Map<Car, ViewModel>(domain));

Grid

...Kendo()
  .Grid<CarViewModel>()
  .Name("gridCars")
  .Columns(columns =>
  {
     columns.Bound(c => c.Name);
     columns.Bound(c => c.IsActiveText);
  })
  .DataSource(dataSource => dataSource
     .Ajax()
     .Read(read => read.Action("ListGrid", "CarsController"))
  )
  .Sortable()
  .Pageable(p => p.PageSizes(true))

Ok, the grid loads perfectly for the first time, but when I filter/order by IsActiveText I get the following message:

Invalid property or field - 'IsActiveText' for type: Car

What is the best approach in this scenario?

Answer

Skorunka František picture Skorunka František · Jul 3, 2013

I don't like the way Kendo has implemented "DataSourceRequestAttribute" and "DataSourceRequestModelBinder", but thats another story.

To be able to filter/sort by VM properties which are "flattened" objects, try this:

Domain model:

public class Administrator
{
    public int Id { get; set; }

    public int UserId { get; set; }

    public virtual User User { get; set; }
}

public class User
{
    public int Id { get; set; }

    public string UserName { get; set; }

    public string Email { get; set; }
}

View model:

public class AdministratorGridItemViewModel
{
    public int Id { get; set; }

    [Displaye(Name = "E-mail")]
    public string User_Email { get; set; }

    [Display(Name = "Username")]
    public string User_UserName { get; set; }
}

Extensions:

public static class DataSourceRequestExtensions
{
    /// <summary>
    /// Enable flattened properties in the ViewModel to be used in DataSource.
    /// </summary>
    public static void Deflatten(this DataSourceRequest dataSourceRequest)
    {
        foreach (var filterDescriptor in dataSourceRequest.Filters.Cast<FilterDescriptor>())
        {
            filterDescriptor.Member = DeflattenString(filterDescriptor.Member);
        }

        foreach (var sortDescriptor in dataSourceRequest.Sorts)
        {
            sortDescriptor.Member = DeflattenString(sortDescriptor.Member);
        }
    }

    private static string DeflattenString(string source)
    {
        return source.Replace('_', '.');
    }
}

Attributes:

[AttributeUsage(AttributeTargets.Method)]
public class KendoGridAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        foreach (var sataSourceRequest in filterContext.ActionParameters.Values.Where(x => x is DataSourceRequest).Cast<DataSourceRequest>())
        {
            sataSourceRequest.Deflatten();
        }
    }
}

Controller action for Ajax data load:

[KendoGrid]
public virtual JsonResult AdministratorsLoad([DataSourceRequestAttribute]DataSourceRequest request)
    {
        var administrators = this._administartorRepository.Table;

        var result = administrators.ToDataSourceResult(
            request,
            data => new AdministratorGridItemViewModel { Id = data.Id, User_Email = data.User.Email, User_UserName = data.User.UserName, });

        return this.Json(result);
    }