OData / WebApi

OData Web API: A server library built upon ODataLib and WebApi
https://docs.microsoft.com/odata
Other
855 stars 473 forks source link

Disable $filter for property of navigation property #2388

Open engenb opened 3 years ago

engenb commented 3 years ago

My goal is to disable $filter for a specific property of a navigation property such that I can do a query like

$filter=navProp/any(x: x/id eq <id param>)

but not

$filter=navProp/any(x: x/name eq '<name param>')

Assemblies affected

Microsoft.AspNetCore.OData 7.5.2

Reproduce steps

Please consider the following model:

public class Foo
{
  public Guid Id { get; set; }
  public IEnumerable<Bar> Bars { get; set; }
}

public class Bar
{
  public Guid Id { get; set; }
  public string Name { get; set; }
}

public class ModelConfiguration : IModelConfiguration
{
  public void Apply(ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix)
  {
    var fooSet = builder.EntitySet<Foo>("Foos");
    var fooType = fooSet.EntityType;

    fooType.HasKey(x => x.Id);
    fooType.HasMany(x => d.Bars)
        .Filter(QueryOptionSetting.Disabled, nameof(Bar.Name));

    fooType
      .Count()
      .Page(1000, 1000)
      .Select()
      .Filter()
      .Expand(SelectExpandType.Allowed)
      .OrderBy();
  }
}

with the request

https://<hostname>/Foos
?$expand=bars
&$filter=bars/any(b: b/name eq 'Test')

Expected result

400 Bad Request - query rejected as $filter on Foo/Bars/Name is disabled

Actual result

200 Success

Additional detail

I've tried a few approaches such as defining a separate entity type for Bar and disabling the filter property on that without success. Is this possible?

xuzhg commented 3 years ago

@engenb Thanks for reporting this. It's a bug in Model bound query for the "ResourceRangeVariableReference".

Workaround

As a workaround, you can use the attribute for the property.

  [NonFilterable]
  public string Name { get; set; }

And remove the ".Filter(QueryOptionSetting.Disabled, nameof(Bar.Name));"

you will get

{
    "error": {
        "code": "",
        "message": "The query specified in the URI is not valid. The property 'Name' cannot be used in the $filter query option.",
        "details": [],
        "innererror": {
            "message": "The property 'Name' cannot be used in the $filter query option.",
            "type": "Microsoft.OData.ODataException",
            "stacktrace": "   at Microsoft.AspNet.OData.Q
...
}

Investigation

1) fooType.HasMany(x => d.Bars) .Filter(QueryOptionSetting.Disabled, nameof(Bar.Name)); only creates the "ModelBoundQuerySettings" on the "Bars" navigation property on "Foo" type.

2) The code at https://github.com/OData/WebApi/blob/master/src/Microsoft.AspNet.OData.Shared/Query/Validators/FilterQueryValidator.cs#L430-L433 has the problem to process the "ResourceRangeVariableReference".

Thanks, -Sam

engenb commented 3 years ago

thanks @xuzhg !

if it helps in any other investigation if/when a fix is developed for this, I also tried things like creating a separate type such as

builder.EntityType<Bar>()
  .Filter(QueryOptionSetting.Disabled, nameof(Bar.Name));

and also tried:

builder.EntitySet<Bar>("Bars").EntityType
  .Filter(QueryOptionSetting.Disabled, nameof(Bar.Name));

I expected either of these would disable filtering on the Name completely which wouldn't be ideal, but this didn't seem to have any effect when filtering Foo.Bars.Name either.

I look forward to a fix for this. I publish my "model" classes as a nuget package so my api clients can use the same models when interacting with the api. This workaround, while appreciated, will require my model (and my api clients, by extension) to take a dependency on Microsoft.AspNetCore.OData. This is fine for now but ideally wouldn't be necessary.