C# 控制$expand请求返回的内容

C# 控制$expand请求返回的内容,c#,odata,asp.net-web-api2,asp.net-web-api-odata,C#,Odata,Asp.net Web Api2,Asp.net Web Api Odata,因此,使用ODataController,您可以控制如果有人执行/odata/Foos(42)/bar,返回的内容,因为您将被调用FoosController,如下所示: public IQueryable<Bar> GetBars([FromODataUri] int key) { } 我假设它只是在您返回的IQueryable上执行一个.Include(“bar”),所以。。。我如何获得更多的控制权?特别是,我如何做到OData不会中断(即$select、$orderby、$t

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

public IQueryable<Bar> GetBars([FromODataUri] int key) { }
我假设它只是在您返回的
IQueryable
上执行一个
.Include(“bar”)
,所以。。。我如何获得更多的控制权?特别是,我如何做到OData不会中断(即$select、$orderby、$top等继续工作)。

@Alex

1) 您可以将参数添加到GetBars(…int key)中,并使用该参数为查询选项执行更多操作。比如说,

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

虽然不是我想要的解决方案(伙计们,让这成为一个内置功能!),但我找到了一种方法来实现我想要的,尽管方式有点有限(到目前为止,我只支持直接
Where()
filtering)

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

GlobalConfiguration.Configure(config=>{…})
调用中,在调用
config.MapODataServiceRoute()之前添加以下

它必须在前面,因为调用
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);
            }
        }
    }
}
最后,这里是一个
NavigationFilter
类的示例:

class NavigationFilter
{
    [PropertyDeclaringType(typeof(Foo))]
    [PropertyName("Bars")]
    public static Expression<Func<Bar,bool>> OnlyReturnBarsWithSpecificSomeValue()
    {
        var someValue = SomeClass.GetAValue();
        return b => b.SomeValue == someValue;
    }
}
类导航过滤器
{
[PropertyClaringType(typeof(Foo))]
[属性名称(“条”)]
仅限公共静态表达式ReturnBarswithSpecificSomeValue()
{
var someValue=SomeClass.GetAValue();
返回b=>b.SomeValue==SomeValue;
}
}

这两个选项都不能回答我的问题。我不想修改
getbar
的工作方式。那一个工作正常。我的问题是,当有人执行
/odata/Foos时,我希望能够控制从
bar
返回的内容?$expand=bar
getbar()
不幸的是,在该实例中没有被调用。我正在做类似的事情。我担心我必须修改每个查询,但在操作中这样做很好。值得一提的是,有一个
FilterQueryValidator
看起来很有希望,但我不确定是否应该在
*验证器中对给定的查询进行变异
ODataQueryOptions
看起来也很有希望。不管是哪种方式,加上一个,谢谢分享实现。@ta.speot.is是的,我看了这两个,但都没有做我需要的。最终,我发现我需要修改查询本身,所以这就是我最终要做的。不过,如果你能找到一种不那么粗俗的方式,请告诉我。:)@亚历克斯,相信你做得很好。你能分享一下你在这方面的专业知识吗
config.Filters.Add(new NavigationFilterAttribute(typeof(NavigationFilter)));
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);
            }
        }
    }
}
[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; }
}
class NavigationFilter
{
    [PropertyDeclaringType(typeof(Foo))]
    [PropertyName("Bars")]
    public static Expression<Func<Bar,bool>> OnlyReturnBarsWithSpecificSomeValue()
    {
        var someValue = SomeClass.GetAValue();
        return b => b.SomeValue == someValue;
    }
}