J'ai un service Web créé à l'aide du fichier WebAPI fourni par ASP .NET MVC 4 . Je sais que la couche sur laquelle WebAPI fonctionne gère automatiquement les requêtes OData (telles que $filter
, $top
, $skip
), mais que faire si je veux gérer le filtrage par moi-même?
Je ne renvoie pas simplement des données de ma base de données , mais j'ai une autre couche qui ajoute des propriétés, effectue des conversions, etc. Ainsi, interroger TOUTES mes données, les convertir et les retourner à la classe WebAPI pour le filtrage OData isn Ne suffit pas. C'est bien sûr terriblement lent, et généralement une idée de merde.
Donc, y a-t-il un moyen de propager les paramètres de requête OData à partir de mon point d’entrée WebAPI vers les fonctions que j’appelle pour obtenir et convertir les données?
Par exemple, un GET à /api/people?$skip=10&$top=10
appellera sur le serveur:
public IQueryable<Person> get() {
return PersonService.get(SomethingAboutCurrentRequest.CurrentOData);
}
Et dans PersonService
:
public IQueryable<Person> getPeople(var ODataQueries) {
IQueryable<ServerSidePerson> serverPeople = from p in dbContext.ServerSidePerson select p;
// Make the OData queries
// Skip
serverPeople = serverPeople.Skip(ODataQueries.Skip);
// Take
serverPeople = serverPeople.Take(ODataQueries.Take);
// And so on
// ...
// Then, convert them
IQueryable<Person> people = Converter.convertPersonList(serverPeople);
return people;
}
Je viens de tomber sur cet ancien message et j'ajoute cette réponse car il est désormais très facile de gérer les requêtes OData vous-même. Voici un exemple:
[HttpGet]
[ActionName("Example")]
public IEnumerable<Poco> GetExample(ODataQueryOptions<Poco> queryOptions)
{
var data = new Poco[] {
new Poco() { id = 1, name = "one", type = "a" },
new Poco() { id = 2, name = "two", type = "b" },
new Poco() { id = 3, name = "three", type = "c" }
};
var t = new ODataValidationSettings() { MaxTop = 2 };
queryOptions.Validate(t);
//this is the method to filter using the OData framework
//var s = new ODataQuerySettings() { PageSize = 1 };
//var results = queryOptions.ApplyTo(data.AsQueryable(), s) as IEnumerable<Poco>;
//or DIY
var results = data;
if (queryOptions.Skip != null)
results = results.Skip(queryOptions.Skip.Value);
if (queryOptions.Top != null)
results = results.Take(queryOptions.Top.Value);
return results;
}
public class Poco
{
public int id { get; set; }
public string name { get; set; }
public string type { get; set; }
}
La requête de l'URL est traduite dans une arborescence d'expression LINQ qui est ensuite exécutée par rapport à l'IQueryable renvoyé par votre opération. Vous pouvez analyser l'expression et fournir les résultats comme vous le souhaitez. L'inconvénient est que vous devez implémenter IQueryable, ce qui n'est pas très facile. Jetez un coup d'œil à cette série d'articles du blog si cela vous intéresse: http://blogs.msdn.com/b/vitek/archive/2010/02/25/data-services-expressions-part-1-intro. aspx . Il parle de WCF Data Services, mais les expressions de filtre utilisées par l’API Web seront très similaires.
Une façon de procéder avec Web-api serait d'utiliser un gestionnaire de messages client http://www.asp.net/web-api/overview/working-with-http/http-message-handlers
Ecrivez un gestionnaire personnalisé comme ci-dessous:
public class CustomHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(
(task) =>
{
HttpResponseMessage response = task.Result;
var persons = response.Content.ReadAsAsync<IQueryable<Person>>().Result;
var persons2 = new List<Person>(); //This can be the modified model completely different
foreach (var item in persons)
{
item.Name = "changed"; // here you can change the data
//persons2.Add(....); //Depending on the results modify this custom model
}
//overwrite the response
response = new HttpResponseMessage<IEnumerable<Person>>(persons2);
return response;
}
);
}
}
Inscrivez-vous dans global.asax.cs
Méthode en classe d'application:
static void Configure(HttpConfiguration config)
{
config.MessageHandlers.Add(new CustomHandler());
}
protected void Application_Start()
{
....
.....
//call the configure method
Configure(GlobalConfiguration.Configuration);
}
J'ai fait quelque chose comme ça avec WCF Data Services et asp.net mvc 3.5, mais c'était un peu superficiel.
La première étape consiste à réécrire le chemin afin que les options de saut et de tête ne soient pas appliquées deux fois, une fois par vous et une fois par le runtime.
J'ai fait la réécriture avec un HttpModule. Dans votre méthode BeginRequest, vous auriez un code comme celui-ci:
HttpApplication app = (HttpApplication)sender;
if (HttpContext.Current.Request.Path.Contains(YOUR_SVC))
{
if (app.Request.Url.Query.Length > 0)
{
//skip questionmark
string queryString = app.Request.Url.Query.Substring(1)
.Replace("$filter=", "filter=")
.Replace("$orderby=", "orderby=")
.Replace("$top=", "top=")
.Replace("$skip=", "skip=");
HttpContext.Current.RewritePath(app.Request.Path, "", queryString);
}
}
Ensuite, examinez simplement la chaîne de requête et extrayez les paramètres dont vous avez besoin.
if (HttpContext.Current.Request.QueryString["filter"] != null)
var filter = HttpContext.Current.Request.QueryString["filter"] as string;
Puis divisez la chaîne de filtre et analysez-la en une instruction SQL ou en une autre commande de base de données. J'utilisais NHibernate dans mon cas. J'ai également été en mesure de limiter les commandes de filtre que je prenais en charge, ce qui facilitait les choses. Je n'ai pas fait de regroupement par exemple. Principalement juste les opérateurs de comparaison.
Il y a une liste d'opérateurs de filtres sur OData.org . Divisez la chaîne par "et" et "ou" en clauses individuelles. Divisez chaque clause par un espace et vous devriez obtenir un tableau de 3 éléments avec le nom de la propriété dans [0], l'opérateur dans [1] et la valeur dans [2].
Les clauses porteront sur une personne, mais je suppose que vous serez en mesure de les convertir en quelque chose qui tirera les bons résultats de la base de données.
J'ai fini par abandonner cette approche car elle ne fonctionnerait pas pour POSTS. Je devais écrire mon propre fournisseur Linq, mais écrire mon propre analyseur de chaînes de filtrage rendait cela plus facile à comprendre.