【问题标题】:Controlling what is returned with an $expand request控制 $expand 请求返回的内容
【发布时间】:2015-12-27 00:10:40
【问题描述】:

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

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

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

public IQueryable<Foo> GetFoos() { }

我假设它只是在您返回的IQueryable&lt;Foo&gt; 上执行.Include("Bars"),所以......我如何获得更多控制权?特别是,我该如何以不破坏 OData 的方式进行操作(即 $select、$orderby、$top 等东西继续工作。)

【问题讨论】:

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


    【解决方案1】:

    虽然不是我想要的解决方案(将其作为内置功能,伙计们!),但我找到了一种方法来做我想做的事,尽管方式有点有限(到目前为止,我只支持直接 Where() 过滤)。

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

    在您的GlobalConfiguration.Configure(config =&gt; { ... }) 调用中,将以下调用添加到config.MapODataServiceRoute()

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

    它必须在之前,因为OnActionExecuted() 方法以相反的顺序调用。您还可以使用此过滤器装饰特定的控制器,尽管我发现很难确保它以正确的顺序运行。 NavigationFilter 是您自己创建的类,我将在下面发布一个示例。

    NavigationFilterAttribute 及其内部类ExpressionVisitor 在 cmets 中的记录相对较好,因此我将在下面直接粘贴它们,无需进一步的 cmets:

    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 上的某个方法上,则仅当属性属于该类型时才会调用该方法。例如,给定一个具有ICollection&lt;Bar&gt; 类型属性的类Foo,如果您有一个带有[PropertyDeclaringType(typeof(Foo))] 的过滤器方法,那么它只会被Foo 上的ICollection&lt;Bar&gt; 属性调用,而不会被其他任何属性调用类。

    PropertyNameAttribute 执行类似的操作,但针对的是属性名称而不是类型。如果您的实体类型具有相同 ICollection&lt;T&gt; 的多个属性,并且您希望根据属性名称进行不同的过滤,这可能会很有用。

    他们在这里:

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

    【讨论】:

    • 我正在做类似的事情。我担心我必须修改每个查询,但在操作中这样做很好。对于它的价值,有一个看起来很有希望的FilterQueryValidator,但我不确定是否应该在*Validator 中改变一个给定的查询。 asp.net/web-api/overview/odata-support-in-aspnet-web-api/…
    • ODataQueryOptions 看起来也很有希望。无论哪种方式,加一个,并感谢分享实施。
    • @ta.speot.is 是的,我看了这两个,但都没有我需要的。最终,我发现我需要修改查询本身,所以这就是我最终要做的。但是,如果您发现一种不那么骇人听闻的方法,请告诉我。 :)
    • @Alex 相信你做得很好。您能否分享您在这方面的专业知识stackoverflow.com/questions/46319844/…
    【解决方案2】:

    @亚历克斯

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

    【讨论】:

    • 这些选项都不是我问题的答案。我不想修改GetBars 的工作方式。那个按预期工作。我的问题是,当有人执行 /odata/Foos?$expand=Bars 时,我希望能够控制从 Bars 返回的内容。
    • GetBars() 在那种情况下很遗憾没有被调用。
    猜你喜欢
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    • 2022-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    • 2018-05-03
    相关资源
    最近更新 更多