控制$expand请求返回的内容

本文关键字:返回 请求 expand 控制 | 更新日期: 2023-09-27 18:14:57

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

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

但是如果你想控制当某人执行/odata/Foos?$expand=Bars时返回的内容呢?你是怎么处理的?它触发了这个方法:

public IQueryable<Foo> GetFoos() { }

我假设它只是在你返回的IQueryable<Foo>上做一个.Include("Bars"),所以…我怎样才能获得更多的控制权?特别是,我如何以这样一种方式做到OData不会中断(即像$select, $orderby, $top等东西继续工作)

控制$expand请求返回的内容

虽然不是我想要的解决方案(让这成为内置功能,伙计们!),我已经找到了一种方法来做我想要的,尽管在某种程度上是有限的(到目前为止,我只支持直接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))]的过滤器方法,那么它只会被Foo上的ICollection<Bar>属性调用,而不会被任何其他类调用。

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; }
}

最后,这里有一个NavigationFilter类的例子:

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

@Alex

1)你可以在GetBars(…Int键),并使用该参数为查询选项做更多的控制器。例如,

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

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

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