因此,使用ODataController
,如果有人/odata/Foos(42)/Bars
,你可以控制返回的内容,因为你会像FoosController
那样被调用:
public IQueryable<Bar> GetBars([FromODataUri] int key) { }
但是如果你想控制某人/odata/Foos?$expand=Bars
时返回的内容怎么办?你怎么处理那件事呢?它会触发此方法:
public IQueryable<Foo> GetFoos() { }
我认为它只会在您返回的.Include("Bars")
上执行IQueryable<Foo>
,所以...我如何获得更多控制权?特别是,我如何以OData不会破坏的方式进行操作(例如$ select,$ orderby,$ top等继续工作。)
答案 0 :(得分:4)
虽然不是我想要的解决方案(让它成为一个内置功能,伙计们!),我找到了一种方法来做我想要的事情,虽然方式有限(到目前为止我只支持直接{{1}过滤)。
首先,我制作了一个自定义Where()
课程。其目的是在ActionFilterAttribute
完成其事后采取行动,因为它会修改EnableQueryAttribute
已生成的查询。
在EnableQueryAttribute
来电中,在致电GlobalConfiguration.Configure(config => { ... })
之前添加以下 :
config.MapODataServiceRoute()
必须在之前,因为config.Filters.Add(new NavigationFilterAttribute(typeof(NavigationFilter)));
方法以相反的顺序调用。你也可以使用这个过滤器来装饰特定的控制器,虽然我发现很难确保它以正确的顺序运行。 OnActionExecuted()
是你自己创建的一个类,我将发布一个更远的例子。
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<T>.
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<Func<T, bool>>, as that can be
// handed to a .Where(...) call on the ICollection<T>.
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<PropertyDeclaringTypeAttribute>()
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<PropertyNameAttribute>()
where pna.Count() == 0 || pna.Any(p => p.Name == node.Member.Name)
select m).SingleOrDefault();
if (filterMethod != null)
{
// <node>.Where(<expression>)
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
具有类型Foo
的属性,如果您使用ICollection<Bar>
的过滤方法,那么只会在[PropertyDeclaringType(typeof(Foo))]
属性上调用它{ {1}},但不适用于任何其他类。
ICollection<Bar>
做了类似的事情,但是对于属性的名称而不是类型。如果您有一个具有相同Foo
的多个属性的实体类型,您希望根据属性名称进行不同的过滤,这将非常有用。
他们是:
PropertyNameAttribute
最后,这是一个ICollection<T>
类的例子:
[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; }
}
答案 1 :(得分:-2)
@Alex
1)您可以在GetBars(... int key)中添加一个参数,并使用该参数为查询选项执行更多控制器。例如,
public IQueryable<Bar> GetBars(ODataQueryOptions<Bar> options, [FromODataUri] int key) { }
2)或者,您可以在操作GetBars
上添加[EnableQuery],以便让Web API OData执行查询选项。
[EnableQuery]
public IQueryable<Bar> GetBars([FromODataUri] int key) { }