2011-01-04 18 views
15

Mam kilka działań narzędziowych, które zwracają tekst wynik za pośrednictwem return Content("my text","text/plain").Strumieniowe przesyłanie tekstu dla długotrwałej akcji?

Czasami uruchomienie tych metod zajmuje kilka minut (np. Przetwarzanie protokołów, konserwacja bazy danych).

Chciałbym zmodyfikować moją metodę działania, aby zamiast zwracać wszystkie dane wyjściowe na raz, tekst był przesyłany strumieniowo do klienta, gdy jest gotowy.

Oto zmyślony przykład:

public ActionResult SlowText() 
{ 
    var sb = new System.Text.StringBuilder(); 
    sb.AppendLine("This happens quickly..."); 
    sb.AppendLine("Starting a slow 10 second process..."); 
    System.Threading.Thread.Sleep(10000); 
    sb.AppendLine("All done with 10 second process!"); 
    return Content(sb.ToString(), "text/plain"); 
} 

Jak napisano, to działanie powróci trzy linie tekstu po 10 sekundach. To, czego chcę, to sposób na utrzymanie strumienia odpowiedzi otwartego i natychmiastowe zwrócenie pierwszych dwóch linii, a następnie trzeciego wiersza po 10 sekundach.

Pamiętam, że robiłem to ponad 10 lat temu w klasycznej wersji ASP 3.0 za pomocą obiektu Response. Czy istnieje oficjalny, przyjazny MVC sposób, aby to osiągnąć?

-

Aktualizacja: Razor .cshtml korzystania w aplikacji; ale nie używając żadnych widoków (tylko ContentResult) dla tych działań.

+1

Brzytwa czy aspx? Różnica polega na tym, że silnik Razor nie pozwala na przesyłanie strumieniowe. – Buildstarted

+0

uruchamiamy ten sam problem, a także bezpośrednio wykorzystaliśmy Response.OutputStream w kontrolerze. Jestem ciekaw, czy znajdziesz jakieś rozwiązania? –

Odpowiedz

5

Pisanie bezpośrednio do obiektu Response powinno działać, ale tylko w niektórych prostych przypadkach. Wiele funkcji MVC zależy od substytutu zapisów wyjściowych (na przykład widoków częściowych, mechanizmu wyświetlania maszyn Razor i innych) i jeśli napiszesz bezpośrednio do odpowiedzi, twój wynik będzie nieczynny.

Jednakże, jeśli nie korzystasz z widoku i zamiast tego piszesz bezpośrednio w kontrolerze, powinieneś być w porządku (zakładając, że twoje działanie nie jest wywoływane jako akcja potomna).

+0

Dzięki za garsony. Czuję się "brudny" manipulując 'Response' bezpośrednio wewnątrz kontrolera ... Twoim zdaniem, powinienem napisać' StreamingContentResult' ActionResult i zwrócić to? Lub czy w porządku jest bałagan z obiektem reakcji wewnątrz kontrolera w pewnych szczególnych okolicznościach. Pokochałbym czyjeś poczucie "Code Smell" na ten temat. Dzięki. – Portman

+1

Tak, gdybym to zrobił, prawdopodobnie napiszę nowy "StreamingContentResult", który może zaakceptować 'Func' przez konstruktor, który będzie reprezentował pracę, którą trzeba wykonać. – marcind

1

Pominąłbym całkowicie kontroler MVC, ponieważ i tak złamiesz enkapsulację. W tym miejscu użyłbym barenaked implementacji IHttpHandler, bezpośrednio do strumienia wyjściowego.

1

Wystawiasz się na przekroczenie limitu czasu przeglądarki, jeśli proces trwa dłużej niż pierwotnie zamierzano. Wtedy nie masz sposobu na odzyskanie tego, co się stało/chyba że zaimplementujesz oddzielną metodę, która podaje informacje o długim procesie.

Biorąc pod uwagę, że mimo wszystko chcesz użyć drugiej metody, możesz rozpocząć długi proces i natychmiast wrócić. Niech przeglądarka sprawdzi drugą metodę, która zawiera najnowsze informacje na temat długiego procesu. Ostatnim razem, gdy musiałem to zrobić, zachowałem prostotę i po prostu ustawiłem nagłówek odświeżania z kontrolera przed zwróceniem widoku.

chodzi o rozpoczęcie procesu długo pracuje, można zrobić coś takiego:

// in the controller class 
delegate void MyLongProcess(); 
//... 
// in the method that starts the action 
MyLongProcess processTask = new MyLongProcess(_someInstance.TheLongRunningImplementation); 
processTask.BeginInvoke(new AsyncCallback(EndMyLongProcess), processTask); 
//... 
public void EndMyLongProcess(IAsyncResult result) 
{ 
    try{ 
     MyLongProcess processTask = (MyLongProcess)result.AsyncState; 
     processTask.EndInvoke(result); 
     // anything you needed at the end of the process 
    } catch(Exception ex) { 
     // an error happened, make sure to log this 
     // as it won't hit the global.asax error handler      
    } 
} 

As na którym można umieścić dziennik działań, które miały miejsce, to do ciebie, aby jak długo żył chcesz że jest to. Może to być tak proste, jak statyczne pole/klasa, w którym dodaje się informacje o trwającym procesie lub zamiast tego zapisuje go w magazynie danych, w którym może przetrwać przetworzenie aplikacji.

Powyższe założenia zakładają długotrwały proces polegający na zgłaszaniu wykonanych czynności.Strumieniowanie to inny temat, ale powyższe może nadal odgrywać rolę w utrzymywaniu operacji w kontrolerze & tylko części odpowiedzialnej za przesyłanie strumieniowe tego, co staje się dostępne dla klienta w wyniku akcji.

0

Wypróbuj Response.Flush i BufferOutput na false. Zauważ, że działałoby to z różnymi wynikami akcji, musisz bezpośrednio napisać do obiektu response. Prawdopodobnie możesz go użyć w połączeniu z AsyncController.

1

Możesz zaimplementować swój niestandardowy ActionResult, taki jak ContentStreamingResult i użyć HttpContext, HttpRequest i HttpResponse w metodzie ExecuteResult.

public class ContentStreamingResult : ActionResult 
    { 
     private readonly TextReader _reader; 

     public ContentStreamingResult(TextReader reader) 
     { 
      _reader = reader; 
     } 

     public override void ExecuteResult(ControllerContext context) 
     { 
      var httpContext = context.HttpContext; 
      //Read text from the reader and write to the response 
     } 
    } 

public class YourController : Controller 
    { 
     public ContentStreamingResult DownloadText() 
     { 
      string text = "text text text"; 
      return new ContentStreamingResult(new System.IO.StringReader(text)); 
     } 
    } 
Powiązane problemy