2017-02-07 2 views
1

Ich habe eine ASP.NET MVC-Anwendung, die RestSharp verwendet, um eine Verbindung zu einem Drittanbieter herzustellen, um später im Ergebnis eine Liste von Domänennamen und weitere Details zu jeder Domäne zu erhalten.Weitergabe von Teil-REST-Antworten, wie sie empfangen werden

ich glaube, die Antwort in Teilpaketen kommt und RestSharp wartet, bis alle Pakete vor der Ausgabe der Daten in einem entserialisierten Format empfangen wurden.

Die Nutzlast in Frage sieht wie folgt aus. "header" wird zuerst ausgefüllt und ist die Antwort, die ich sofort zur Ansicht zurückkehren möchte. Der Rest der Daten weniger Zeit empfindlich:

[ 
{"header":{"names":["test.1","test.2","test.3","test.4","test.5","test.6"]}} 
, 
{"name":"test.1","can":"transfer?"} 
, 
{"name":"test.2","can":"transfer?"} 
, 
{"name":"test.3","can":"transfer?"} 
, 
{"name":"test.4","can":"transfer?"} 
, 
{"name":"test.5","can":"transfer?"} 
, 
{"name":"test.6","can":"register"} 
] 

Derzeit gibt es zwei Implementierungen für die Ausführung, ein synchrone und eine async:

public T ExecuteGetRequest<T>(RestRequest request) where T : class 
{ 
    try 
    { 
     IRestResponse response = _client.Execute(request); 

     if (response.ErrorException == null) 
     { 
      return JsonConvert.DeserializeObject<T>(response.Content); 
     } 
     return null; 
    } 
    catch (Exception ex) 
    { 
     return null; 
    } 
} 

public Task<T> ExecuteGetRequestAsync<T>(RestRequest request) where T : new() 
{ 
    try 
    { 
     var source = new TaskCompletionSource<T>(); 
     _client.ExecuteAsync<T>(request, (response) => { source.SetResult(response.Data); }); 
     return source.Task; 
    } 
    catch (Exception ex) 
    { 
     return null; 
    } 
} 

Es gibt einen lokalen API-Aufruf für die Anwendung zu implementieren die Suche mit async, die wie folgt aussieht:

public async Task<DomainSearchResults> DomainSearchAsync(string domain) 
{ 
    var request = _client.CreateRequest("domain-search/{domain}", Method.GET); 
    request.AddUrlSegment("domain", domain); 
    var response = await _client.ExecuteGetRequestAsync<List<DomainSearch>>(request); 
    return new DomainSearchResults(response); 
} 

, die dann die Antwort inpterprets und gibt dem Kunden die relevanten Suchergebnissen.

Dies funktioniert im Sinne fein, dass, wenn alle Daten von der dritten Partei gesendet wurden, das Objekt zu der Ansicht und bevölkerte entsprechend zurückgeführt wird. Der vollständige Abschluss der Anforderung kann jedoch bis zu 20 Sekunden dauern, was für den Benutzer nicht besonders hilfreich ist.

Gibt es eine Weise, die ich die ExecuteGetRequestAsync anpassen kann das Senden unvollständige Antworten zurück an die aufrufende Ansicht zu starten, bevor die vollständige Antwort empfangen wurde?

Mein erster Versuch, eine Menge wie folgt aussieht:

public Task<T> ExecuteGetRequestAsyncIncomplete<T>(RestRequest request) where T : new() 
{ 
    try 
    { 
     var source = new TaskCompletionSource<T>(); 
     _client.ExecuteAsync<T>(request, (response) => 
     { 
      source.SetResult(response.Data); 
      if (response.StatusCode == HttpStatusCode.PartialContent) 
      { 
       // Somehow return part of this response 
      } 

     }); 
     return source.Task; 
    } 
    catch (Exception ex) 
    { 
     return null; 
    } 
} 

UPDATE

Arbeiten an von @Evk ‚s Antwort, hier ist es, was der neue Anruf ein Teiler zurückzukehren aussieht, speziell für dieses Szenario:

public async Task<T> ExecuteGetRequestPartial<T>(RestRequest request) where T : new() 
{ 

    try 
    { 
     var source = new TaskCompletionSource<T>(); 
     request.ResponseWriter = (st) => { 
      using (var reader = new StreamReader(st)) 
      { 
       var sb = new StringBuilder(); 
       // read response 100 chars at a time 
       char[] buffer = new char[1]; 
       int read = 0; 
       while ((read = reader.Read(buffer, 0, buffer.Length)) > 0) 
       { 
        sb.Append(buffer, 0, read); 
        // now here you have your partial response 
        // you need to somehow parse it and feed to your view 
        // note that you should wait until you get some meaningful part, like first "header" element 
        // for example at some point there might be ["header":{"names":["te < partial response 
        if (sb.ToString().Contains("header") && sb.ToString().EndsWith("}")) 
        { 
         sb.Append("}]"); 
         source.SetResult(JsonConvert.DeserializeObject<T>(sb.ToString())); 
         return; 
        } 
       } 
       // at this point you have full response in sb 
      } 
     }; 
     await _client.ExecuteGetTaskAsync<T>(request); 
     return await source.Task; 
    } 
    catch (Exception ex) 
    { 
     return default(T); 
    } 
} 

kurz gesagt, hat der Puffer auf 1 Zeichen reduziert worden, so dass wir wissen, wo wir in der str sind Ing. Um dann das partielle Ergebnis in gültiges JSON umzuwandeln, suchen wir nach dem Ende des Objekts und schließen es dann, indem wir manuell das zusätzliche "}]" zum Ergebnis hinzufügen und dieses zurückgeben.

Schön ein Evk!

Antwort

1

Zuerst http-Status „Partial Content“ hat nichts mit dem Fall zu tun. Es ist für Antworten auf Anfragen mit Range-Header.

Was Sie stattdessen benötigen, ist Antwort-Stream lesen, wie es kommt, nicht warten auf vollständige Antwort geliefert und deserialisiert werden. Es ist einfacher, dies mit regulären HttpWebRequest zu tun, aber wenn Sie RestSharp verwenden möchten, ist es auch möglich. Beachten Sie, dass Sie teilweise (so, ungültig) json manuell deserialisieren müssen. Hier ist eine Skizze:

public static Task<T> ExecuteGetRequestAsync<T>(RestRequest request) where T : new() { 
     var client = new RestClient("http://google.com"); 
     try { 
      var source = new TaskCompletionSource<T>(); 
      request.ResponseWriter = (st) => { 
       using (var reader = new StreamReader(st)) { 
        var sb = new StringBuilder(); 
        // read response 100 chars at a time 
        char[] buffer = new char[100]; 
        int read = 0; 
        while ((read = reader.Read(buffer, 0, buffer.Length)) > 0) { 
         sb.Append(buffer, 0, read); 
         // now here you have your partial response 
         // you need to somehow parse it and feed to your view 
         // note that you should wait until you get some meaningful part, like first "header" element 
         // for example at some point there might be ["header":{"names":["te < partial response 
        } 
        // at this point you have full response in sb 
       } 
      }; 
      client.ExecuteAsync<T>(request, null); 
      return source.Task; 
     } 
     catch (Exception ex) { 
      return null; 
     } 
    }