Grid "Read" action with specifications without ASP.NET helpers

6 posts, 0 answers
  1. Michal
    Michal avatar
    11 posts
    Member since:
    Aug 2013

    Posted 04 Nov 2013 Link to this post

    Hi, I am trying to code functionality in WCF API which would return grid data by specifications (serverGrouping, serverFiltering, serverSorting);

    My MVC code works, and it looks like this:

    [AutoMapperConfigurationActionFilter(typeof(OrderDomainMvcProfile))]
    [NHibernateSession]
    public virtual ActionResult OrderGrid_Select([DataSourceRequest]DataSourceRequest request)
    {
       var filterSpecification = KendoToSpecificationHelper.Filter<OrderDomainEntities.Order>(request.Filters);
       var sortSpecificationList = KendoToSpecificationHelper.OrderSort(request.Groups, request.Sorts);
       var groupSpecification = KendoToSpecificationHelper.OrderGroup(request.Groups);
    where "KendoToSpecificationHelper" is written-on-my-own class that gets data from request and creates Specifications (see Specification pattern); 

    Now I would like to code tse same functionality in WCF REST Service, which receives requests from distributed interface; The code I've just started looks like:

    public OrderContract[] ListSpecifiedOrders([DataSourceRequest]DataSourceRequest request)
            {
                _ContextHelper.SetCurrentWebOperationContext();
                if (_ContextHelper.AssertContextIsNull())
                    return null;
     
                try
                {
                    var filterSpecification = KendoToSpecificationHelper.Filter<Order>(request.Filters);
                    var sortSpecificationList = KendoToSpecificationHelper.OrderSort(request.Groups, request.Sorts);
                    var groupSpecification = KendoToSpecificationHelper.OrderGroup(request.Groups);
    It is very similar to MVC code, but... it is not working :/. My pure javascript Kendo Grid code below:

    01.jQuery(ORDER_GRID_ID).kendoGrid({
    02.           // below js code to render control....
    03.           // ..............
    04.            "dataSource": {
    05.                    "transport": {
    06.                        "read": function (options) {
    07.                            getOrders(options);
    08.                        },
    09.                    },
    10.                    "schema": {
    11.                        "groups": [{
    12.                            "field": "number"
    13.                        }],
    14.                        "data": "data",
    15.                        "total": "total"
    16.                    },
    17.                    "pageSize": 10,
    18.                    "serverPaging": true,
    19.                    "serverSorting": true,
    20.                    "serverFiltering": true,
    21.                    //"serverGrouping": true,
    22.                    "serverAggregates": true,
    23.                    "error": listOrdersView.OrderGrid_Error
    24.                }
    25.}

    When GRID reads data, it sends to a "getOrders(options)" function an object "options" with all required data; WCF correctly translates JSON to DataSourceRequest, but there is a problem with translating Sort list elements. My sort elements contains "Member" and "Dir" fields - WCF ignores Dir field, and always sets direction of sorting to "Ascending".


    Is there any way to omit DataSourceRequest type parameter in a REST method, and manually create on a REST side filterDescriptors, groupDescriptors and sortDescriptors from only that JSON that Kendo Control is sending in Request? How to do that properly?
  2. Daniel
    Admin
    Daniel avatar
    2231 posts

    Posted 06 Nov 2013 Link to this post

    Hello,

    The DataSourceRequestAttribute cannot be used with WCF service because it uses the MVC Controller binding context to get the state values. It is possible to use the methods that are used internally by the attribute binder to parse the sent filters to a collection of sort, filter and group descriptors. I attached the grid-wcf-crud sample project modified to parse the sent state values and use the ToDataSourceResult method.
    As for the getOrders function - could you provide the code used to serialize the values and for the objects used as parameter to the service method?

    Regards,
    Daniel
    Telerik
    Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
  3. Michal
    Michal avatar
    11 posts
    Member since:
    Aug 2013

    Posted 20 Nov 2013 Link to this post

    Thank You for answer; project that You have uploaded is an Expression builder implementation project, but I guess there is already implemented Kendo.Mvc.ExpressionBuilder class to do it exactly. 

    I ve done already Sorting and Grouping - You helped me much - and would like to code filtering specification;

    I have implemented Specification classes to achieve Specification design pattern. It has following constructors:

    Specification(Expression<Func<T, bool>> predicate)

    i.e.:

    new Specification<T>(ExpressionBuilder.Expression<T>(filterDescriptors));

    Is there any existing way to convert default json object from grid to IFilterDescriptors? I mean, I'd like to use Kendo.Mvc.ExpressionBuilder to build expression for filtering by list of filter descriptors.
  4. Daniel
    Admin
    Daniel avatar
    2231 posts

    Posted 22 Nov 2013 Link to this post

    Hello again,

    I am not sure if I understand correctly your question but If you wish to apply the filters to the data based on the expression generated from the list of filter descriptors then you can use the Where method:
    if (filterDescriptors.Any())
    {
        data = data.Where(ExpressionBuilder.Expression<MyModel>(filterDescriptors));
    }
    An overload of the Where method that accepts a list of filter descriptors is also included in the Kendo.Mvc.Extensions namespace:
    using Kendo.Mvc.Extensions;
     
    ...
           
    data = data.Where(filterDescriptors);


    Regards,
    Daniel
    Telerik
    Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
  5. Michal
    Michal avatar
    11 posts
    Member since:
    Aug 2013

    Posted 27 Nov 2013 Link to this post

    Thank You Daniel, I decided to use Microsoft Dynamic Expressions builder to convert filter descriptors to Linq Expression, as It was used in the project, You have send it earlier; It works fine.


    Now My grid works correctly, but I would like to omit AggregateFunctionsGroup structure to build grouping expression; I'd like to create similar structure on my own just to build correct grouping, without classes from Kendo namespace (to make WCF REST service more comprehensive). Is it possible?

    My group descriptor WCF contract:

    [DataContract(Name = "GroupDescriptor", Namespace = "http://xxxxx/api/xxx/1.0/xxxx")]
        public class GroupDescriptor
        {
            [DataMember]
            public string Field { get; set; }
     
            [DataMember]
            public string SortDirection { get; set; }
     
            [DataMember]
            public string Member { get; set; }
     
            [DataMember]
            public AggregateDescriptor[] Aggregates { get; set; }
        }


    Then I am creating Expression:

    public static GroupSpecification<Order, AggregateFunctionsGroup> OrderGroup (IList<GroupDescriptor> groupDescriptors)
            {
                Expression<Func<IEnumerable<Order>, IEnumerable<AggregateFunctionsGroup>>> selector = null;
                foreach (var group in groupDescriptors.Reverse())
                {
                    if (selector == null)
                    {
                        switch (group.Member)
                        {
                            case "number":
                                selector = orders => BuildInnerGroup(orders, o => o.Number, "Number");
                                break;
                            //and so on...
                            
                        }
                    }
                    else
                    {
                        switch (group.Member)
                        {
                            case "number":
                                selector = BuildGroup(o => o.Number, selector, "Number");
                                break;
                            //ando so on...
                        }
                    }
                }
                return (selector == null) ? null : new GroupSpecification<Order, AggregateFunctionsGroup>() { Predicate = selector };
            }


    And finally, "BuildGroup()" and "BuildInnerGroup()" methods:


    private static Expression<Func<IEnumerable<TEntity>, IEnumerable<AggregateFunctionsGroup>>> BuildGroup<T, TEntity>(Func<TEntity, T> groupSelector, Expression<Func<IEnumerable<TEntity>, IEnumerable<AggregateFunctionsGroup>>> selectorBuilder, string memberName)
            {
                return g => g.GroupBy(groupSelector)
                             .Select(c => new AggregateFunctionsGroup
                             {
                                 Member = memberName,
                                 Key = ConvertKey(c.Key),
                                 HasSubgroups = true,
                                 Items = selectorBuilder.Compile().Invoke(c).ToList()
                             });
            }
     
            private static IEnumerable<AggregateFunctionsGroup> BuildInnerGroup<T, TEntity>(IEnumerable<TEntity> group, Func<TEntity, T> groupSelector, string memberName)
            {
                return group.GroupBy(groupSelector)
                        .Select(i => new AggregateFunctionsGroup
                        {
                            Member = memberName,
                            Key = ConvertKey(i.Key),
                            Items = i.ToList()
                        });
            }


    Is it possible to replace "AggregateFunctionsGroup" type on my own-created structure?
  6. Daniel
    Admin
    Daniel avatar
    2231 posts

    Posted 29 Nov 2013 Link to this post

    Hello,

    It is possible to use a custom class. However, it should have the same properties in order for the data to be correctly processed on the client:
    public class MyGroup
    {
        public MyGroup()
        {
            Aggregates = new Dictionary<string, object>();
        }
        public object Key { get; set; }
        public bool HasSubgroups { get; set; }
        public string Member { get; set; }
        public IEnumerable Items { get; set; }
        public Dictionary<string,object> Aggregates { get; set; }
    }


    Regards,
    Daniel
    Telerik
    Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
Back to Top