Controllo di ciò che viene restituito con una richiesta di espansione $

Quindi, usando ODataController , puoi controllare cosa viene restituito se qualcuno fa /odata/Foos(42)/Bars , perché verrai chiamato sul FoosController modo:

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

Ma cosa succede se si desidera controllare ciò che viene restituito quando qualcuno fa /odata/Foos?$expand=Bars ? Come ci si confronta? Attiva questo metodo:

 public IQueryable GetFoos() { } 

E presumo che faccia un .Include("Bars") su IQueryable che tu ritorni, quindi … come ottengo più controllo? In particolare, come faccio a farlo in modo tale che OData non si rompa (cioè cose come $ select, $ orderby, $ top, ecc. Continuano a funzionare.)

Pur non essendo la soluzione che volevo (rendi questa funzionalità integrata, ragazzi!), Ho trovato un modo per fare ciò che volevo, anche se in un modo un po ‘limitato (finora supporto solo il filtro diretto Where() ).

Innanzitutto, ho creato una class ActionFilterAttribute personalizzata. Il suo scopo è di agire dopo che EnableQueryAttribute ha fatto il suo EnableQueryAttribute , in quanto modifica la query che EnableQueryAttribute ha prodotto.

Nella tua chiamata a GlobalConfiguration.Configure(config => { ... }) , aggiungi quanto segue prima della chiamata a config.MapODataServiceRoute() :

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

Deve esserci prima, perché i metodi OnActionExecuted() sono chiamati in ordine inverso. Puoi anche decorare controller specifici con questo filtro, anche se ho trovato più difficile garantire che venga eseguito nell’ordine corretto in questo modo. Il NavigationFilter è un corso che crei tu stesso, posterò un esempio di uno più in basso.

NavigationFilterAttribute e la sua class interna, un ExpressionVisitor è relativamente ben documentato con i commenti, quindi li incollerò senza ulteriori commenti qui sotto:

 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. 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>, as that can be // handed to a .Where(...) call on the ICollection. 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() 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() where pna.Count() == 0 || pna.Any(p => p.Name == node.Member.Name) select m).SingleOrDefault(); if (filterMethod != null) { // .Where() 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); } } } } 

Successivamente, ci sono alcune classi di attributi semplici, allo scopo di restringere il filtro.

Se metti PropertyDeclaringTypeAttribute su uno dei metodi sul tuo NavigationFilter , chiamerà quel metodo solo se la proprietà è su quel tipo. Ad esempio, data una class Foo con una proprietà di tipo ICollection , se si ha un metodo di filtro con [PropertyDeclaringType(typeof(Foo))] , verrà chiamato solo per le proprietà ICollection su Foo , ma non per qualsiasi altra class.

PropertyNameAttribute fa qualcosa di simile, ma per il nome della proprietà piuttosto che per il tipo. Può essere utile se si ha un tipo di quadro con più proprietà della stessa ICollection cui si desidera filtrare in modo diverso a seconda del nome della proprietà.

Eccoli:

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

Infine, ecco un esempio di una class NavigationFilter :

 class NavigationFilter { [PropertyDeclaringType(typeof(Foo))] [PropertyName("Bars")] public static Expression> OnlyReturnBarsWithSpecificSomeValue() { var someValue = SomeClass.GetAValue(); return b => b.SomeValue == someValue; } } 

@alex

1) È ansible aggiungere un parametro in GetBars (… int key) e utilizzare il parametro per fare più controller per l’opzione di query. per esempio,

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

2) Oppure, è ansible aggiungere [EnableQuery] all’azione GetBars per consentire a Web API OData di eseguire le opzioni di query.

 [EnableQuery] public IQueryable GetBars([FromODataUri] int key) { }