Хотя не решение, которое я хотел (сделать это встроенная функция, ребята!), Я нашел способ сделать то, что я хотел, хотя и в несколько ограниченном объеме (до сих пор я только поддерживать прямой 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))]
, тогда он будет вызываться только для 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;
}
}
Я делаю что-то подобное. Я боялся, что мне придется модифицировать каждый запрос, но делать это в действии приятно. Для чего это стоит, есть «FilterQueryValidator», который выглядит многообещающим, но я не уверен, что нужно мутировать заданный запрос внутри '' Validator''. http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-security-guidance –
'ODataQueryOptions' также выглядит многообещающим. В любом случае, плюс один и спасибо за совместное использование. –
@ ta.speot.is Да, я посмотрел на обоих, но не сделал то, что мне было нужно. В конечном счете, я обнаружил, что мне нужно изменить сам запрос, так что это я и сделал. Если вы найдете менее хакерский способ обойти это, сообщите мне. :) – Alex