2012-05-28 5 views
51

Ho un servizio Web realizzata con la WebAPI fornite da ASP .NET MVC 4. So che lo strato in cima alla quale WebAPI funziona automaticamente gestisce OData query (come $filter, $top, $skip), ma cosa succede se voglio gestire il filtro da solo?ASP .NET MVC 4 WebAPI: gestire manualmente OData interroga

I don't simply return data from my database, ma ho un altro livello che aggiunge alcune proprietà, rende alcune conversioni ecc. Quindi interrogando TUTTI i miei dati, convertendoli e restituendoli alla classe WebAPI per il filtraggio OData non è solo abbastanza buono. Ovviamente è terribilmente lento, e generalmente un'idea schifosa.

Quindi esiste un modo per propagare i parametri di query OData dal mio punto di accesso WebAPI alle funzioni che chiamo per ottenere e convertire i dati?

Ad esempio, un GET per /api/people?$skip=10&$top=10 avrebbe chiamato sul server:

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

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

risposta

37

Mi sono imbattuto in questo vecchio post e sto aggiungendo questa risposta dato che ora è molto facile gestire le query OData da soli. Ecco un esempio:

[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

Questa è la risposta più completa/corretta, grazie mille, lo userò nei miei prossimi progetti. – frapontillo

+1

Dai un'occhiata a https://github.com/Roysvork/LinqToQuerystring/, questo ti dà la possibilità di creare un IQueryable <> direttamente da una stringa di query odata grezza. –

+1

Grazie, questo mi ha indirizzato nella giusta direzione. –

3

La query dall'URL si traduce in un albero di espressione LINQ che è poi eseguito contro l'IQueryable restituito dall'operazione. È possibile analizzare l'espressione e fornire i risultati nel modo desiderato. Il rovescio della medaglia è che è necessario implementare IQueryable che non è semplicissimo. Dai un'occhiata a questa serie di post sul blog se sei interessato: http://blogs.msdn.com/b/vitek/archive/2010/02/25/data-services-expressions-part-1-intro.aspx. Parla di WCF Data Services, ma le espressioni di filtro utilizzate dall'API Web saranno molto simili.

2

Un modo con Web-api sarebbe con gestore di messaggi cliente http://www.asp.net/web-api/overview/working-with-http/http-message-handlers

Scrivi un gestore personalizzato come di seguito:

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

Registro in global.asax.cs

Metodo nella classe di applicazione:

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

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

temo che questo non è fattibile, come il database-classi e ritorno-classi sono completamente diversi e non è possibile (per qualche motivo) per fare le conversioni in un gestore personalizzato. – frapontillo

+0

Quindi modificare la risposta. Modificato sopra. 'var persons2 = new List (); // Questo può essere il modello modificato completamente diverso '' response = new HttpResponseMessage > (persons2); ' –

+0

Il problema più grande qui è che non si sta consumando IQueryable correttamente filtrandolo. Invece stai enumerando i risultati completi e creando un nuovo set di risultati manualmente. Questo sconfigge l'utilizzo di qualsiasi provider linq sottostante. –

0

Ho fatto qualcosa del genere con WCF Data Services e asp.net mvc 3.5 ma era un po 'un kludge.

Il primo passaggio consiste nel riscrivere il percorso in modo che le opzioni skip e top non vengano applicate due volte, una volta dall'utente e una volta dal runtime.

Ho effettuato la riscrittura con un HttpModule. Nel metodo BeginRequest avresti codice come questo:

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

Poi basta esaminare la stringa di query e cogliere i parametri necessari.

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

Poi dividere la stringa di filtro e analizzare in un'istruzione SQL o qualsiasi altro comando db. Stavo usando NHibernate nel mio caso. Sono stato anche in grado di limitare i comandi di filtro che ho supportato, cosa che ha reso le cose più facili. Ad esempio, non ho raggruppato gruppi. Principalmente solo gli operatori di confronto.

C'è un elenco di operatori di filtri su OData.org. Dividere la stringa per "e" e "o" in singole clausole. Dividi ogni clausola di uno spazio e dovresti ottenere una matrice di 3 elementi con il nome della proprietà in [0] l'operatore in [1] e il valore in [2].

Le clausole saranno in termini di una Persona, ma presumo che sarete in grado di convertirli in qualcosa che tirerà fuori i risultati giusti dal db.

Ho finito per abbandonare questo approccio poiché non funzionerà per i POSTI. Ho dovuto scrivere il mio provider Linq ma scrivere il mio parser di filtri è stato reso più facile da capire.

+0

Questo potrebbe essere proprio quello che stavo cercando. Grazie!!! C'è un modo per applicare automaticamente i filtri a IQueryable o dovrò fare tutto manualmente? – frapontillo

+1

Manualmente sfortunatamente. Questo metodo in pratica trasforma il filtro in array di stringhe. In NH questo non era così male poiché è facile creare query in modo dinamico. –

+0

'HttpContext.Current.RewritePath (app.Request.Path," ", queryString);' sembra non funzionare. Il percorso viene modificato ma il livello in cima a 'ApiController' sembra che stia ancora elaborando il vecchio' $ skip' e '$ top'. Hai qualche idea del perché? – frapontillo