使用$ expand请求控制返回的内容

因此,使用ODataController ,您可以控制返回的内容,如果有人执行/odata/Foos(42)/Bars ,因为您将在FoosController上调用, FoosController所示:

 public IQueryable GetBars([FromODataUri] int key) { } 

但是如果你想控制什么时候有什么东西/odata/Foos?$expand=Bars ? 你怎么处理那件事呢? 它会触发此方法:

 public IQueryable GetFoos() { } 

而且我认为它只会在您返回的IQueryable上执行.Include("Bars") ,所以…我如何获得更多控制权? 特别是,我如何以OData不破坏的方式进行操作(例如$ select,$ orderby,$ top等继续工作。)

虽然不是我想要的解决方案(让这个内置function,伙计!),我找到了一种方法来做我想要的,虽然方式有点限制(到目前为止我只支持直接Where()过滤)。

首先,我创建了一个自定义的ActionFilterAttribute类。 它的目的是在EnableQueryAttribute完成它之后采取行动,因为它修改了EnableQueryAttribute生成的查询。

在您的GlobalConfiguration.Configure(config => { ... })调用中,在调用config.MapODataServiceRoute() 之前添加以下内容:

 config.Filters.Add(new NavigationFilterAttribute(typeof(NavigationFilter))); 

它必须在之前,因为OnActionExecuted()方法以相反的顺序调用。 你也可以使用这个filter来装饰特定的控制器,虽然我发现很难确保它以正确的顺序运行。 NavigationFilter是你自己创建的一个类,我将发布一个更远的例子。

NavigationFilterAttribute及其内部类, ExpressionVisitor与注释相对较好,所以我只需粘贴它们,不再进一步评论如下:

 public class NavigationFilterAttribute : ActionFilterAttribute { private readonly Type _navigationFilterType; class NavigationPropertyFilterExpressionVisitor : ExpressionVisitor { private Type _navigationFilterType; public bool ModifiedExpression { get; private set; } public NavigationPropertyFilterExpressionVisitor(Type navigationFilterType) { _navigationFilterType = navigationFilterType; } protected override Expression VisitMember(MemberExpression node) { // Check properties that are of type ICollection. if (node.Member.MemberType == System.Reflection.MemberTypes.Property && node.Type.IsGenericType && node.Type.GetGenericTypeDefinition() == typeof(ICollection<>)) { var collectionType = node.Type.GenericTypeArguments[0]; // See if there is a static, public method on the _navigationFilterType // which has a return type of Expression>, as that can be // handed to a .Where(...) call on the ICollection. var filterMethod = (from m in _navigationFilterType.GetMethods() where m.IsStatic let rt = m.ReturnType where rt.IsGenericType && rt.GetGenericTypeDefinition() == typeof(Expression<>) let et = rt.GenericTypeArguments[0] where et.IsGenericType && et.GetGenericTypeDefinition() == typeof(Func<,>) && et.GenericTypeArguments[0] == collectionType && et.GenericTypeArguments[1] == typeof(bool) // Make sure method either has a matching PropertyDeclaringTypeAttribute or no such attribute let pda = m.GetCustomAttributes() where pda.Count() == 0 || pda.Any(p => p.DeclaringType == node.Member.DeclaringType) // Make sure method either has a matching PropertyNameAttribute or no such attribute let pna = m.GetCustomAttributes() where pna.Count() == 0 || pna.Any(p => p.Name == node.Member.Name) select m).SingleOrDefault(); if (filterMethod != null) { // .Where() var expression = filterMethod.Invoke(null, new object[0]) as Expression; var whereCall = Expression.Call(typeof(Enumerable), "Where", new Type[] { collectionType }, node, expression); ModifiedExpression = true; return whereCall; } } return base.VisitMember(node); } } public NavigationFilterAttribute(Type navigationFilterType) { _navigationFilterType = navigationFilterType; } public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { HttpResponseMessage response = actionExecutedContext.Response; if (response != null && response.IsSuccessStatusCode && response.Content != null) { ObjectContent responseContent = response.Content as ObjectContent; if (responseContent == null) { throw new ArgumentException("HttpRequestMessage's Content must be of type ObjectContent", "actionExecutedContext"); } // Take the query returned to us by the EnableQueryAttribute and run it through out // NavigationPropertyFilterExpressionVisitor. IQueryable query = responseContent.Value as IQueryable; if (query != null) { var visitor = new NavigationPropertyFilterExpressionVisitor(_navigationFilterType); var expressionWithFilter = visitor.Visit(query.Expression); if (visitor.ModifiedExpression) responseContent.Value = query.Provider.CreateQuery(expressionWithFilter); } } } } 

接下来,为了缩小过滤范围,有一些简单的属性类。

如果将PropertyDeclaringTypeAttribute放在NavigationFilter上的某个方法上,则只有在该属性属于该类型时才会调用该方法。 例如,给定一个具有ICollection类型属性的类Foo ,如果你有一个带[PropertyDeclaringType(typeof(Foo))]的filter方法,那么它只会在Foo上调用ICollection属性,但是不适用于任何其他课程。

PropertyNameAttribute执行类似的操作,但是对于属性的名称而不是类型。 如果您的实体类型具有相同ICollection多个属性,并且您希望根据属性名称进行不同的过滤,则此选项非常有用。

他们来了:

 [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public class PropertyDeclaringTypeAttribute : Attribute { public PropertyDeclaringTypeAttribute(Type declaringType) { DeclaringType = declaringType; } public Type DeclaringType { get; private set; } } [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public class PropertyNameAttribute : Attribute { public PropertyNameAttribute(string name) { Name = name; } public string Name { get; private set; } } 

最后,这是一个NavigationFilter类的示例:

 class NavigationFilter { [PropertyDeclaringType(typeof(Foo))] [PropertyName("Bars")] public static Expression> OnlyReturnBarsWithSpecificSomeValue() { var someValue = SomeClass.GetAValue(); return b => b.SomeValue == someValue; } } 

@Alex

1)您可以在GetBars(… int key)中添加一个参数,并使用该参数为查询选项执行更多控制器。 例如,

 public IQueryable GetBars(ODataQueryOptions options, [FromODataUri] int key) { } 

2)或者,您可以在动作GetBars上添加[ GetBars ]以允许Web API OData执行查询选项。

 [EnableQuery] public IQueryable GetBars([FromODataUri] int key) { }