Grid with SignalR binding and server operations

8 posts, 0 answers
  1. Bernd
    Bernd avatar
    92 posts
    Member since:
    Feb 2013

    Posted 28 Dec 2016 Link to this post

    Hi.

    I have a grid bound to SignalR. To be able to use server side operations I use the Kendo.DynamicLinq lib. Fitlering works fine. Aggregates don't.

    Problem is: My aggregates field is always null.

    My data source request class:

    using Kendo.DynamicLinq;
    using System.Collections.Generic;
     
    namespace idee5.Dispatcher.Models {
        public class CalendarEventGridDataSourceRequest : DataSourceRequest {
            public IEnumerable<int> Workplaces { get; set; }
            // these are missing in the Kendo Dlinq class
            public int PageSize { get; set; }
            public IEnumerable<Aggregator> Aggregates { get; set; }
        }
    }

    I took the field names from this post and it's attached soltuion.

    The hub method to tetrieve the data:

    01.public async Task<DataSourceResult> OperationEvents(CalendarEventGridDataSourceRequest request) {
    02.    // workaround for ArgumentNullException in ToDataSourceResult
    03.    if (request.Filter != null && request.Filter.Filters.Count() == 0 && request.Filter.Field == null) request.Filter = null;
    04. 
    05.    // SignalR has no session, so get the preferences from the preference service
    06.    string userId = Context.User.Identity.GetUserId();
    07.    PreferenceProfile activeProfile = await Task.Run(() => _userSettingService.ReadActiveProfile(userId));
    08.    IQueryable<CalendarEvent> result = await _schedulerService.OperationEventsAsync(activeProfile);
    09. 
    10.    IEnumerable<int> workplaces = request.Workplaces ?? new List<int>();
    11.     DataSourceResult retVal = result.Where(ce => ce.Operation.AllowedWorkplaces.Count(awp => workplaces.Contains(awp.WorkplaceId ?? 0)) > 0)
    12.        .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter, request.Aggregates);
    13.    // Late view model creation.
    14.    // Kendo.DynamicLinq adds the filters at the end which hits the database for view model creation before filters get applied
    15.    retVal.Data = retVal.Data.OfType<CalendarEvent>().Select((ce) => _mapper.Map<CalendarEventViewModel>(ce)).ToList();
    16.    return retVal;
    17.}

    What is needed to have a signalr bound grid with server filtering, sorting and grouping/aggregation?

    Kind regards

    Bernd

  2. Alexander Popov
    Admin
    Alexander Popov avatar
    1444 posts

    Posted 02 Jan 2017 Link to this post

    Hello Bernd,

    I am not sure what is causing this behavior in your case. Would be able to share a runnable sample project or at least the Grid's configuration?

    Regards,
    Alexander Popov
    Telerik by Progress
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
  3. Bernd
    Bernd avatar
    92 posts
    Member since:
    Feb 2013

    Posted 04 Jan 2017 in reply to Alexander Popov Link to this post

    Hello Alexander.

    The runnable solution is too big and your team mates weren't able to start my stripped down version I made for a support ticket.

    But here is the grid configuration. Hope it helps.

    @(Html.Kendo().Grid<CalendarEventViewModel>()
        .Name(componentName: "eventGrid")
        .Pageable(p => { p.PageSizes(enabled: true); p.Refresh(enabled: true); p.Enabled(value: true); })
        .Scrollable(s => { s.Height(value: "auto"); s.Enabled(value: true); })
        .Filterable()
        .Groupable()
        .Selectable()
        .Sortable(s => s.Enabled(value: true))
        .Resizable(r => r.Columns(value: true))
        .Reorderable(r => r.Columns(value: true))
        .ColumnMenu(cm => cm.Enabled(value: true))
        .ToolBar(tb => tb.Excel())
        .Columns(columns => {
            columns.Bound(c => c.Operation.OperationIdFormatted)
                .Sortable(value: false)
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.MasterSystemId)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.MasterSystemId)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Title)
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.Title)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.Title)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Operation.WorkOrder.EndProduct.ArticleNumber)
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(ItemViewModel)}.{nameof(ItemViewModel.ArticleNumber)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(ItemViewModel)}.{nameof(ItemViewModel.ArticleNumber)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Operation.WorkOrder.EndProduct.Dimensions)
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(ItemViewModel)}.{nameof(ItemViewModel.Dimensions)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(ItemViewModel)}.{nameof(ItemViewModel.Dimensions)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Operation.TotalTimeSpan)
                .Sortable(value: false)
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.TotalTimeSpan)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.TotalTimeSpan)}", resourceSet: ResourceSet.Properties))
                .ClientTemplate(value: "#if (data.TotalTimeSpan) {# #:kendo.toString(TotalTimeSpan.Hours, '00')#:#:kendo.toString(TotalTimeSpan.Minutes, '00')#:#:kendo.toString(TotalTimeSpan.Seconds, '00')# #}#")
                .EditorTemplateName(templateName: "TimeSpan");
            columns.Bound(c => c.Operation.WorkOrder.QuantityToProduce).Format(value: "{0:n}")
                .ClientGroupHeaderTemplate(value: "#=sum#")
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.QuantityToProduce)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.QuantityToProduce)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Operation.WorkOrder.UnitOfMeasure.Label)
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(UnitOfMeasureViewModel)}.{nameof(UnitOfMeasureViewModel.Label)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(UnitOfMeasureViewModel)}.{nameof(UnitOfMeasureViewModel.Label)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Operation.AllowedWorkplaces)
                .ClientTemplate(value: "#=workplacesTemplate(Operation.AllowedWorkplaces)#")
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.AllowedWorkplaces)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.AllowedWorkplaces)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Operation.QuantityConfirmed).Format(value: "{0:n}")
                .ClientGroupHeaderTemplate(value: "#=sum#")
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.QuantityConfirmed)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.QuantityConfirmed)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Operation.QuantityOpen).Format(value: "{0:n}")
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.QuantityOpen)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.QuantityOpen)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Operation.TimeSpanPerUnit)
                .Sortable(value: false)
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.TimePerUnit)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(OperationViewModel)}.{nameof(OperationViewModel.TimePerUnit)}", resourceSet: ResourceSet.Properties))
                .ClientTemplate(value: "#if (data.TimeSpanPerUnit) {# #:kendo.toString(TimeSpanPerUnit.Hours, '00')#:#:kendo.toString(TimeSpanPerUnit.Minutes, '00')#:#:kendo.toString(TimeSpanPerUnit.Seconds, '00')# #}#");
            columns.Bound(c => c.StartWeek)
                .ClientGroupHeaderTemplate(value: "Woche: #=value# - Offen: #=aggregates['Operation-WorkOrder-QuantityOpen'].sum#")
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.StartWeek)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.StartWeek)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.EndWeek)
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.EndWeek)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.EndWeek)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Start).Format(value: "{0:g}")
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.Start)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.Start)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.End).Format(value: "{0:g}")
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.End)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.End)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Workplace.MasterSystemId)
               .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(WorkplaceViewModel)}.{nameof(WorkplaceViewModel.MasterSystemId)}" })
               .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(WorkplaceViewModel)}.{nameof(WorkplaceViewModel.MasterSystemId)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Description)
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Properties, data_resource_id = $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.Description)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(CalendarEventViewModel)}.{nameof(CalendarEventViewModel.Description)}", resourceSet: ResourceSet.Properties));
            columns.Bound(c => c.Operation.WorkOrder.OrderType.Id).Hidden()
                .HeaderHtmlAttributes(new { data_resource_set = ResourceSet.Entities, data_resource_id = $"{nameof(OrderType)}" })
                .Title(Html.GetGlobalResourceString(resourceKey: $"{nameof(OrderType)}", resourceSet: ResourceSet.Entities))
                .ClientTemplate(value: "#=Operation.WorkOrder.OrderType.Label#");
        })
        .DataSource(ds => ds
            .SignalR()
            .AutoSync(enabled: false) // to prevent the popup from closing after changing a field http://www.telerik.com/forums/issue-in-kendo-grid-with-signalr-add-and-edit-mode
            // mimic ServerOperation(true)
            .ServerFiltering(enabled: true)
            .ServerPaging(enabled: true)
            .ServerSorting(enabled: true)
            .ServerGrouping(enabled: true)
            .ServerAggregates(enabled: true)
            .PageSize(pageSize: 20)
            .Filter(f => f.Add(ce => ce.Workplace.UnlimitedCapacity).IsEqualTo(value: true))
            .Transport(tr => tr
                .ParameterMap(handler: "addGridParameters")
                .Promise(handler: "hubStart")
                .Hub(handler: "schedulerHub")
                .Client(c => c
                    .Read(method: "operationEvents")
                    .Update(method: "updateWithScheduling"))
                .Server(s => s
                    .Read(method: "operationEvents")
                    .Update(method: "updateWithScheduling"))
                )
                .Schema(schema => schema
                    .Data(data: "Data")
                    .Total(total: "Total")
                    .Aggregates(aggregates: "Aggregates")
                    .Model(m => {
                        m.Id(f => f.Id);
                        m.Field(f => f.Id).Editable(enabled: false);
                        m.Field(f => f.StartWeek);
                        m.Field(f => f.EndWeek);
                        m.Field(f => f.ActivityStatus);
                        // same naming as in the scheduler
                        m.Field(memberName: nameof(CalendarEventViewModel.Description).PascalToCamelCase(), memberType: typeof(string)).From(nameof(CalendarEventViewModel.Description));
                        m.Field(memberName: nameof(CalendarEventViewModel.End).PascalToCamelCase(), memberType: typeof(DateTime)).From(nameof(CalendarEventViewModel.End));
                        m.Field(memberName: nameof(CalendarEventViewModel.EndTimezone).PascalToCamelCase(), memberType: typeof(string)).From(nameof(CalendarEventViewModel.EndTimezone));
                        m.Field(f => f.EventType);
                        m.Field(memberName: nameof(CalendarEventViewModel.IsAllDay).PascalToCamelCase(), memberType: typeof(bool)).From(nameof(CalendarEventViewModel.IsAllDay));
                        m.Field(f => f.Operation);
                        m.Field(f => f.OperationId);
                        m.Field(f => f.Operation.WorkOrder.EndConfirmed);
                        m.Field(memberName: nameof(CalendarEventViewModel.Recurrence).PascalToCamelCase(), memberType: typeof(string)).From(nameof(CalendarEventViewModel.Recurrence));
                        m.Field(memberName: nameof(CalendarEventViewModel.RecurrenceException).PascalToCamelCase(), memberType: typeof(string)).From(nameof(CalendarEventViewModel.RecurrenceException));
                        m.Field(memberName: "recurrenceID", memberType: typeof(int?)).From(nameof(CalendarEventViewModel.RecurrenceID));
                        m.Field(memberName: nameof(CalendarEventViewModel.RecurrenceRule).PascalToCamelCase(), memberType: typeof(string)).From(nameof(CalendarEventViewModel.RecurrenceRule));
                        m.Field(memberName: nameof(CalendarEventViewModel.Start).PascalToCamelCase(), memberType: typeof(DateTime)).From(nameof(CalendarEventViewModel.Start));
                        m.Field(memberName: nameof(CalendarEventViewModel.StartTimezone).PascalToCamelCase(), memberType: typeof(string)).From(nameof(CalendarEventViewModel.StartTimezone));
                        m.Field(f => f.Status);
                        m.Field(memberName: nameof(CalendarEventViewModel.Title).PascalToCamelCase(), memberType: typeof(string)).From(nameof(CalendarEventViewModel.Title))
                            .DefaultValue(this.LocalResources(key: "CalendarEventViewModel.Title.DefaultValue"));
                        m.Field(f => f.Workplace);
                        m.Field(f => f.WorkplaceId);
                    })
                )
                .Aggregates(agg => {
                    agg.Add(ce => ce.Operation.QuantityConfirmed).Sum();
                    agg.Add(ce => ce.Operation.QuantityDefective).Sum();
                    agg.Add(ce => ce.Operation.WorkOrder.QuantityToProduce).Sum();
                    agg.Add(ce => ce.Operation.WorkOrder.QuantityOpen).Sum();
                })
                .Events(e => e.Error(handler: "scheduler_error"))
        )
        .ClientDetailTemplateId(id: "operationDetailTemplate")
        .Events(e => {e.DataBound(handler: "dispatcher.onGridDataBound"); e.Change(handler: "dispatcher.onGridChange"); })
        .Deferred()
    )

    Kind regards

    Bernd

  4. Alexander Popov
    Admin
    Alexander Popov avatar
    1444 posts

    Posted 06 Jan 2017 Link to this post

    Hello,

    I reviewed the provided code snippets but I am still not sure what's causing the issue.
    Does the OperationEvents request.Aggregates argument gets populated on the server? I've also noticed that the aggregate fields are nested properties of the Operation object. The Grid's DataSource however is unable to resolve the data type of nested properties, which might be related. You can exclude this as a possibility by trying to use top-level ViewModel fields for the aggregates or using a flattened ViewModel.

    Regards,
    Alexander Popov
    Telerik by Progress
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
  5. Bernd
    Bernd avatar
    92 posts
    Member since:
    Feb 2013

    Posted 27 Jan 2017 in reply to Alexander Popov Link to this post

    Hello Alexander.

    Sorrry for the delay.

    I flattened moved the aggregate properties to the top level, but the aggregates argument still isn't populated.

    Any other ideas?

  6. Alexander Popov
    Admin
    Alexander Popov avatar
    1444 posts

    Posted 31 Jan 2017 Link to this post

    Hello,

    May I ask you for some code snippets showing the model, as well as the request and response headers?

    Regards,
    Alexander Popov
    Telerik by Progress
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
  7. Phil
    Phil avatar
    3 posts
    Member since:
    Jan 2019

    Posted 18 Oct 2019 Link to this post

    Hi Bernd,

     

    Did you ever solve this?

  8. Bernd
    Bernd avatar
    92 posts
    Member since:
    Feb 2013

    Posted 18 Oct 2019 Link to this post

    More or less. The customer didn't really need the aggregates and I removed them.

    Why are you asking? Do you have the same problem?
Back to Top