Zwar nicht die Lösung, die ich (machen dies zu einem integrierte Funktion, Jungs!) Wollte, habe ich einen Weg gefunden, zu tun, was ich wollte, wenn auch in einer etwas eingeschränkt Weise (bisher nur ich direkt unterstützen Where()
Filterung).
Zuerst machte ich eine benutzerdefinierte ActionFilterAttribute
Klasse. Sein Zweck ist es, Maßnahmen zu ergreifen nach die EnableQueryAttribute
hat seine Sache getan, wie es die Abfrage ändert, die EnableQueryAttribute
produziert hat.
In Ihrem GlobalConfiguration.Configure(config => { ... })
Aufruf, fügen Sie den folgenden vor den Anruf zu config.MapODataServiceRoute()
:
config.Filters.Add(new NavigationFilterAttribute(typeof(NavigationFilter)));
Es hat vor sein, weil die OnActionExecuted()
Verfahren in umgekehrter Reihenfolge aufgerufen werden. Sie können mit diesem Filter auch spezielle Controller einrichten, obwohl es für mich schwieriger geworden ist, sicherzustellen, dass er in der richtigen Reihenfolge ausgeführt wird. Die NavigationFilter
ist eine Klasse, die Sie selbst erstellen, ich werde ein Beispiel von einem weiter unten veröffentlichen.
NavigationFilterAttribute
, und seine innere Klasse wird ein ExpressionVisitor
relativ gut mit Kommentaren dokumentiert, so dass ich sie einfach unten ohne weitere Kommentare einfügen:
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);
}
}
}
}
Als nächstes gibt es ein paar einfache Attributklassen sind, für der Zweck, die Filterung zu verengen.
Wenn Sie PropertyDeclaringTypeAttribute
auf eine der Methoden auf Ihrem NavigationFilter
setzen, wird es nur diese Methode aufrufen, wenn die Eigenschaft auf diesem Typ ist. Wenn Sie zum Beispiel eine Klasse Foo
mit einer Eigenschaft des Typs ICollection<Bar>
haben, wenn Sie eine Filtermethode mit [PropertyDeclaringType(typeof(Foo))]
haben, dann wird sie nur für ICollection<Bar>
Eigenschaften auf Foo
aufgerufen, aber nicht für irgendeine andere Klasse.
PropertyNameAttribute
tut etwas ähnliches, aber für den Namen der Eigenschaft anstelle von Typ. Es kann nützlich sein, wenn Sie einen Entitätstyp mit mehreren Eigenschaften derselben haben, wo Sie je nach dem Namen der Eigenschaft anders filtern möchten.
Hier sind sie:
[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; }
}
Schließlich, hier ist ein Beispiel für eine NavigationFilter
Klasse:
class NavigationFilter
{
[PropertyDeclaringType(typeof(Foo))]
[PropertyName("Bars")]
public static Expression<Func<Bar,bool>> OnlyReturnBarsWithSpecificSomeValue()
{
var someValue = SomeClass.GetAValue();
return b => b.SomeValue == someValue;
}
}
Ich mache etwas ähnliches. Ich befürchtete, dass ich jede Anfrage modifizieren müsste, aber es ist schön, es in der Aktion zu tun. Für das, was es wert ist, gibt es einen 'FilterQueryValidator', der vielversprechend aussieht, aber ich bin mir nicht sicher, ob man eine gegebene Abfrage innerhalb eines' * Validators' mutieren sollte. http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-security-guidance –
'ODataQueryOptions' sieht auch vielversprechend aus. So oder so, plus eins und vielen Dank für die Freigabe der Implementierung. –
@ ta.speot.is Ja, ich habe mir beide angeschaut, aber keiner hat getan, was ich brauchte. Letztendlich stellte ich fest, dass ich die Abfrage selbst ändern musste, also habe ich das gemacht. Wenn Sie jedoch eine weniger hetzerische Vorgehensweise finden, lassen Sie es mich wissen. :) – Alex