9

したがって、 を使用する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 などは引き続き機能します)。

4

2 に答える 2

4

私が望んでいた解決策ではありませんが (これを組み込み機能にしてください!)、多少制限された方法ではありますが、私が望んでいたことを行う方法を見つけました (これまでのところ、直接Where()フィルタリングのみをサポートしています)。

まず、カスタムActionFilterAttributeクラスを作成しました。その目的は、生成されたクエリを変更するときに、 がその処理を行ったにアクションを実行することです。EnableQueryAttributeEnableQueryAttribute

呼び出しで、への呼び出しのGlobalConfiguration.Configure(config => { ... })に次を追加します。config.MapODataServiceRoute()

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

OnActionExecuted()メソッドは逆の順序で呼び出されるため、前でなければなりません。このフィルターを使用して特定のコントローラーを装飾することもできますが、その方法で正しい順序で実行されることを確認するのは難しいことがわかりました。これNavigationFilterは自分で作成するクラスです。その例を下に掲載します。

NavigationFilterAttribute、およびその内部クラスである anExpressionVisitorは、コメント付きで比較的よく文書化されているため、以下にそれ以上コメントせずに貼り付けます。

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にメソッドの 1 つを配置NavigationFilterすると、プロパティがそのタイプにある場合にのみそのメソッドが呼び出されます。たとえば、Footype のプロパティを持つクラスが与えられたICollection<Bar>場合、 の filter メソッドがある場合、それは のプロパティ[PropertyDeclaringType(typeof(Foo))]に対してのみ呼び出され、他のクラスに対しては呼び出されません。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; }
}

NavigationFilter最後に、クラスの例を次に示します。

class NavigationFilter
{
    [PropertyDeclaringType(typeof(Foo))]
    [PropertyName("Bars")]
    public static Expression<Func<Bar,bool>> OnlyReturnBarsWithSpecificSomeValue()
    {
        var someValue = SomeClass.GetAValue();
        return b => b.SomeValue == someValue;
    }
}
于 2015-10-05T09:29:52.457 に答える