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

时间:2015-09-29 13:25:46

标签: c# odata asp.net-web-api2 asp.net-web-api-odata

因此,使用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等继续工作。)

2 个答案:

答案 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) { }