I have an angular grid which uses a GridDataResult posting back to an API controller in my .netcore project with a DataSourceRequest parameter.
The first column of my grid has a Guid, declared in a model both server and client side.
When I try filter on this column the server throws an exception:
"Provided expression should have string type".
I have searched both this forum and the MVC forum to try apply a server-side request filter to no avail.
How should this be handled?
7 Answers, 1 is accepted
I'm going to try explain this further in the hope of getting an answer. Here's my .NET class:
public
class
NcEntityDto : EntityDto<Guid>
{
public
string
DisplayName {
get
;
set
; }
public
string
ExtensionData {
get
;
set
; }
public
long
? OrganizationUnitId {
get
;
set
; }
public
string
OrganizationUnitDisplayName {
get
;
set
; }
public
virtual
List<NcFormSubmissionDto> FormSubmissions {
get
;
set
; }
public
virtual
int
CarePlanCount {
get
;
set
; }
public
virtual
List<NcCarePlanDto> CarePlans {
get
;
set
; }
public
virtual
int
WarningCount {
get
;
set
; }
public
virtual
List<NcWarningDto> Warnings {
get
;
set
; }
}
Which is dynamically created in my angular project through swagger and adheres to the interface below:
export interface INcEntityDto {
id: string | undefined;
displayName: string | undefined;
extensionData: string | undefined;
organizationUnitId: number | undefined;
organizationUnitDisplayName: string | undefined;
formSubmissions: NcFormSubmissionDto[] | undefined;
carePlanCount: number | undefined;
carePlans: NcCarePlanDto[] | undefined;
warningCount: number | undefined;
warnings: NcWarningDto[] | undefined;
}
A call is made from my component to get data from the API:
getEntitiesForKendo() {
this
.gridLoading =
true
;
this
._kendoGridService.getEntities(
this
.state)
.subscribe((data) => {
this
.gridData = data;
this
.gridLoading =
false
;
});
}
And the grid is rendered:
<kendo-grid
#ncEntityGrid
*ngIf=
"!gridLoading"
[data]=
"gridData"
[pageSize]=
"state.take"
[skip]=
"state.skip"
[sort]=
"state.sort"
[filter]=
"state.filter"
[group]=
"state.group"
[scrollable]=
"'scrollable'"
[pageable]=
"true"
[sortable]="{
allowUnsort:
true
,
mode:
'multiple'
}"
[groupable]=
"true"
[filterable]=
"true"
[columnMenu]=
"true"
[resizable]=
"true"
(dataStateChange)=
"dataStateChange($event)"
>
<ng-template kendoGridToolbarTemplate media=
"md"
>
<button type=
"button"
kendoGridExcelCommand icon=
"file-excel"
>{{ l(
'ExportToExcel'
)}}</button>
<!-- <button type=
"button"
(click)=
"saveGridSettings(ncEntityGrid)"
>Save settings</button> -->
</ng-template>
<kendo-grid-column field=
"id"
title=
"{{ l('Id')}}"
width=
"120"
[hidden]=
"true"
media=
"md"
></kendo-grid-column>
<kendo-grid-column field=
"displayName"
title=
"{{ l('DisplayName')}}"
[minResizableWidth]=
"140"
width=
"140"
></kendo-grid-column>
...
...
...[code removed
for
brevity]
When a string is entered into the filter for the Id column (which is a string representation of a Guid) the API controller throws the error:
"Provided expression should have string type".
public
async Task<JsonResult> GetEntities([DataSourceRequest]DataSourceRequest request)
{
var model = await _ncEntityAppService.GetAllForKendoGrid();
//request.Filters.Add(new FilterDescriptor() { Member = "id", MemberType = typeof(Guid) });
return
Json(model.ToDataSourceResult(request));
}
Obviously the grid is passing a string and the application of the filters in the grid is expecting the Id to be of type Guid.
I don't know how much more expansive I can be. One thing I can say is that when I previously worked with Telerik questions in the support forum were answered promptly but I can see some questions here have been hanging around for days. What is the SLA for getting answers during my trial period?
For handling the Guid expression you will have to manually traverse the "Filters" collection of the DataSourceRequest object, find the filter expression for the "id" field and change its MemberType to "Guid".
As for the response time, the forum threads are for the community and although that we are doing our best to handle all threads as soon as possible, the support tickets are handled with higher priority. As a trial user you could also open support tickets, but have in mind that trial support does not fall into the 24 hours response time policy and it could take up to 72 hours for our team to answer.
Regards,
Konstantin Dikov
Progress Telerik
Thank you, Konstantin,
Here is my controller method:
public
async Task<JsonResult> GetEntities([DataSourceRequest]DataSourceRequest request)
{
if
(request.Filters !=
null
)
{
ModifyFilters(request.Filters);
}
var query = _ncEntityService.GetEntityIQueryableForKendoGrid();
var model = query.ToDataSourceResult(request);
foreach
(var entity
in
ObjectMapper.Map<List<NcEntityDto>>(model.Data))
{
entity.OrganizationUnit.DisplayName = await _ncOrganizationUnitService.GetOrganisationUnitBreadcrumb(entity.OrganizationUnit.Id);
}
return
Json(model);
}
Here is my method to change the type:
private
void
ModifyFilters(IEnumerable<IFilterDescriptor> filters)
{
if
(filters.Any())
{
foreach
(var filter
in
filters)
{
var descriptor = filter
as
FilterDescriptor;
if
(descriptor !=
null
&& descriptor.Member ==
"id"
)
{
descriptor.MemberType =
typeof
(Guid);
}
else
if
(filter
is
CompositeFilterDescriptor)
{
ModifyFilters(((CompositeFilterDescriptor)filter).FilterDescriptors);
}
}
}
}
And below is the stack trace of the error. The error is thrown during invocation of .toDataSourceResult() and is internal to kendo. Is there anything obvious I am doing wrong?
ERROR 2018-08-11 17:10:50,121 [3 ] Mvc.ExceptionHandling.AbpExceptionFilter - Method 'System.String ToLower()' declared on type 'System.String' cannot be called with instance of type 'System.Guid'
System.ArgumentException: Method 'System.String ToLower()' declared on type 'System.String' cannot be called with instance of type 'System.Guid'
at System.Linq.Expressions.Expression.ValidateCallInstanceType(Type instanceType, MethodInfo method)
at System.Linq.Expressions.Expression.ValidateStaticOrInstanceMethod(Expression instance, MethodInfo method)
at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterOperatorExtensions.GenerateToLowerCall(Expression stringExpression, Boolean liftMemberAccess)
at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterOperatorExtensions.GenerateCaseInsensitiveStringMethodCall(MethodInfo methodInfo, Expression left, Expression right, Boolean liftMemberAccess)
at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterOperatorExtensions.CreateExpression(FilterOperator filterOperator, Expression left, Expression right, Boolean liftMemberAccess)
at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterDescriptorExpressionBuilder.CreateBodyExpression()
at Kendo.Mvc.FilterDescriptor.CreateFilterExpression(ParameterExpression parameterExpression)
at Kendo.Mvc.FilterDescriptorBase.CreateFilterExpression(Expression instance)
at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterDescriptorCollectionExpressionBuilder.CreateBodyExpression()
at Kendo.Mvc.Infrastructure.Implementation.Expressions.FilterExpressionBuilder.CreateFilterExpression()
at Kendo.Mvc.Extensions.QueryableExtensions.Where(IQueryable source, IEnumerable`1 filterDescriptors)
at Kendo.Mvc.Extensions.QueryableExtensions.CreateDataSourceResult[TModel,TResult](IQueryable queryable, DataSourceRequest request, ModelStateDictionary modelState, Func`2 selector)
at Kendo.Mvc.Extensions.QueryableExtensions.ToDataSourceResult(IQueryable queryable, DataSourceRequest request)
at Nuagecare.Web.Host.Controllers.KendoController.<GetEntities>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at lambda_method(Closure , Object )
at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextExceptionFilterAsync>d__23.MoveNext()
Thank you for the provided code snippets.
I will just update this thread with the answer that resolved the issue from a private thread.
------------
I inspected the ModifyFilters function and the only thing that it was missing is actually casting the string filter value to a Guid so that the ToDataSourceResult() extension method can work with the appropriate filter value:
private
void
ModifyFilters(IList<IFilterDescriptor> filters)
{
if
(filters.Any())
{
foreach
(var filter
in
filters)
{
var descriptor = filter
as
FilterDescriptor;
if
(descriptor !=
null
&& descriptor.Member ==
"id"
)
{
descriptor.MemberType =
typeof
(Guid);
descriptor.Value = Guid.Parse(descriptor.Value.ToString());
}
else
if
(filter
is
CompositeFilterDescriptor)
{
ModifyFilters(((CompositeFilterDescriptor)filter).FilterDescriptors);
}
}
}
}
Kind Regards,
Alex Hajigeorgieva
Progress Telerik
I will include here the code from our private message. For those coming across this problem Alex pointed out that's it's not possible to do anything other than an "eq" operator on a Guid. Therefore I implemented the following filter in my grid:
<
kendo-grid-column
field
=
"id"
title
=
"{{ l('Id')}}"
width
=
"120"
[hidden]="true"
media
=
"md"
[sortable]="false">
<
ng-template
kendoGridFilterMenuTemplate let-filter
let-column
=
"column"
let-filterService
=
"filterService"
>
<
kendo-grid-string-filter-menu
[extra]="false" [column]="column" [filter]="filter" [filterService]="filterService"
operator
=
"eq"
eq
=
"eq"
>
<
kendo-filter-eq-operator
></
kendo-filter-eq-operator
>
</
kendo-grid-string-filter-menu
>
</
ng-template
>
</
kendo-grid-column
>
And handled it in my controller with the following code:
private
void
ModifyFilters(IList<IFilterDescriptor> filters)
{
if
(filters.Any())
{
foreach
(var filter
in
filters)
{
var descriptor = filter
as
FilterDescriptor;
if
(descriptor !=
null
&& descriptor.Member ==
"id"
)
{
descriptor.Value = ToGuid(descriptor.Value.ToString());
descriptor.MemberType =
typeof
(Guid);
}
else
if
(filter
is
CompositeFilterDescriptor)
{
ModifyFilters(((CompositeFilterDescriptor)filter).FilterDescriptors);
}
}
}
}
public
static
Guid ToGuid(
string
descriptorValueToString)
{
Guid newGuid;
if
(Guid.TryParse(descriptorValueToString,
out
newGuid))
{
return
Guid.Parse(descriptorValueToString);
}
return
Guid.Empty;
}
Hope this helps anyone with the same problem, thanks to Alex.
Thank you very much for updating the forum thread.
It appears that it is possible to compare guids similar to numbers according to this Microsoft article:
https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/comparing-guid-and-uniqueidentifier-values
In case you wish to explore these filters or need additional assistance, please let us know.
Regards,
Alex Hajigeorgieva
Progress Telerik
I have the same problem with fields that are int but are serialized as double. I implemented the same workaround, although I think this is a poor solution.
Maybe tomorrow I will have to add another "if" in the extension method, to modify another FilterDescriptor?