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
// taken from http://www.telerik.com/forums/problems-with-grid-using-signalr-and-serverfiltering-sorting-and-paging#l1juxVjyvkye5eSO6Z9TrA
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
7 Answers, 1 is accepted
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
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
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
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?
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
Hi Bernd,
Did you ever solve this?
Why are you asking? Do you have the same problem?