2012-05-28 4 views
51

Ich habe einen Web-Service unter Verwendung von aus den WebAPI zur Verfügung gestellt von ASP .NET MVC 4. Ich weiß, dass die Schicht oben auf dem WebAPI arbeitet automatisch behandelt OData-Abfragen (wie $filter, $top, $skip), aber was ist, wenn ich die Filterung selbst übernehmen möchte?ASP .NET MVC 4 WebAPI: Handgriff OData fragt

I don't simply return data from my database, aber ich habe eine andere Schicht, die einige Eigenschaften hinzufügt, einige Konvertierungen etc. Also alle meine Daten abfragen, sie konvertieren und an die WebAPI-Klasse für OData-Filter zurückgeben ist nicht nur gut genug. Es ist natürlich schrecklich langsam und generell eine beschissene Idee.

So gibt es einen Weg zu propagieren die OData Abfrageparameter von meinem WebAPI-Einstiegspunkt zu den Funktionen, die ich aufrufen, um die Daten zu erhalten und zu konvertieren?

Zum Beispiel kann ein GET auf /api/people?$skip=10&$top=10 würde auf dem Server aufrufen:

public IQueryable<Person> get() { 
    return PersonService.get(SomethingAboutCurrentRequest.CurrentOData); 
} 

Und in 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; 
} 

Antwort

37

Ich bin gerade über diesen alten Beitrag gestolpert und füge diese Antwort hinzu, da es jetzt sehr einfach ist, die OData-Abfragen selbst zu handhaben. Hier ein Beispiel:

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

Dies ist die vollständigste/richtige Antwort, vielen Dank, ich werde dies in meinen nächsten Projekten verwenden. – frapontillo

+1

Werfen Sie einen Blick auf https://github.com/Roysvork/LinqToQuerystring/, das gibt Ihnen die Möglichkeit, ein IQueryable <> direkt aus einer rohen Odata-Abfragezeichenfolge zu erstellen. –

+1

Danke, das hat mich in die richtige Richtung gelenkt. –

3

Die Abfrage der URL wird in einen LINQ Ausdruck Baum übersetzt, der dann ausgeführt gegen die IQueryable Ihre Operation zurückgibt. Sie können den Ausdruck analysieren und die Ergebnisse auf beliebige Weise bereitstellen. Der Nachteil ist, dass Sie IQueryable implementieren müssen, was nicht einfach ist. Sehen Sie sich diese Blog-Post-Serie an, wenn Sie interessiert sind: http://blogs.msdn.com/b/vitek/archive/2010/02/25/data-services-expressions-part-1-intro.aspx. Es wird über WCF Data Services gesprochen, aber die Filterausdrücke, die von der Web-API verwendet werden, sind sehr ähnlich.

2

Eine Möglichkeit, mit Web-api würde mit Handler Kundenmeldung sein http://www.asp.net/web-api/overview/working-with-http/http-message-handlers

einen benutzerdefinierten Handler schreiben wie unten:

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

Register in global.asax.cs

Methode in der Anwendungsklasse:

static void Configure(HttpConfiguration config) 
{ 
    config.MessageHandlers.Add(new CustomHandler()); 
} 

protected void Application_Start() 
{ 
    .... 
    ..... 
    //call the configure method 
    Configure(GlobalConfiguration.Configuration); 
} 
+0

Ich fürchte, dies nicht machbar ist, da die Datenbank-Klassen und Return-Klassen völlig unterschiedlich sind und es nicht möglich ist (aus irgendwelchen Gründen) die Konvertierungen in einem benutzerdefinierten Handler zu tun. – frapontillo

+0

Dann ändern Sie die Antwort. Oben bearbeitet. 'var persons2 = new Liste (); // Dies kann das modifizierte Modell völlig anders sein '' response = new HttpResponseMessage > (personen2); ' –

+0

Das größte Problem hier ist, dass Sie IQueryable nicht richtig konsumieren, indem Sie es filtern. Stattdessen listen Sie die vollständigen Ergebnisse auf und erstellen manuell eine neue Ergebnismenge. Dies verhindert die Verwendung von zugrunde liegenden Linq-Anbietern. –

0

Ich habe so etwas mit WCF Data Services und asp.net mvc 3.5 gemacht, aber es war ein bisschen ein Klotz.

Der erste Schritt besteht darin, den Pfad neu zu schreiben, so dass die Optionen skip und top nicht zweimal angewendet werden, einmal von Ihnen und einmal von der Laufzeit.

Ich habe das Umschreiben mit einem HttpModule. In Ihrer Beginrequest Methode würden Sie Codes wie diese:

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

Dann einfach den Query-String untersuchen und die Parameter auszureißen Sie benötigen.

if (HttpContext.Current.Request.QueryString["filter"] != null) 
    var filter = HttpContext.Current.Request.QueryString["filter"] as string; 

aufgeteilt dann den Filter-String und parsen es in eine SQL-Anweisung oder andere Befehle db. Ich benutzte NHibernate in meinem Fall. Ich konnte auch einschränken, welche Filterbefehle ich unterstützte, was die Dinge leichter machte. Ich habe zum Beispiel keine Gruppierungen gemacht. Meistens nur die Vergleichsoperatoren.

Es gibt eine Liste von Filteroperatoren unter OData.org. Teilen Sie die Zeichenfolge durch "und" und "oder" in einzelne Klauseln. Teilen Sie jede Klausel durch ein Leerzeichen und Sie sollten ein 3-Elemente-Array mit dem Eigenschaftsnamen in [0] dem Operator in [1] und dem Wert in [2] erhalten.

Die Klauseln werden in Bezug auf eine Person sein, aber ich nehme an, dass Sie in der Lage sein werden, sie in etwas zu konvertieren, das die richtigen Ergebnisse aus der db ziehen wird.

Ich habe diesen Ansatz aufgegeben, da es nicht für POSTS funktioniert. Ich musste meinen eigenen Linq-Provider schreiben, aber das Schreiben meines eigenen Filter-String-Parsers war leichter zu verstehen.

+0

Das könnte genau das sein, wonach ich gesucht habe. Vielen Dank!!! Gibt es eine Möglichkeit, die Filter automatisch auf das IQueryable anzuwenden oder muss ich alles manuell machen? – frapontillo

+1

manuell leider. Diese Methode verwandelt den Filter im Grunde nur in Arrays von Strings. In NH war das nicht so schlimm, da Abfragen einfach dynamisch erstellt werden können. –

+0

'HttpContext.Current.RewritePath (app.Request.Path," ", queryString);' scheint nicht zu funktionieren. Der Pfad wird geändert, aber der Layer über 'ApiController' sieht so aus, als würde er immer noch die alten '$ skip' und '$ top' verarbeiten. Hast du eine Ahnung warum? – frapontillo