You could use long polling with a hidden iframe and chunked transfer encoding from the server which will spit <script>
tags as data becomes available. In this script tag you could invoke a custom callback javascript function that will take care to format the results.
UPDATE:
As requested in the comments section here's a sample implementation of a long polling technique using a hidden iframe.
Let's suppose that you have some model:
public class MyViewModel
{
public string Foo { get; set; }
}
and that you have a service that returns this model in chunks and notifies the caller that a chunk is available using events:
public class MyService
{
public void GetModels(Action<MyViewModel, object> onModelAvailable, object state, Action onComplete)
{
Task.Factory.StartNew(x =>
{
try
{
for (int i = 0; i < 10; i++)
{
onModelAvailable(new MyViewModel
{
Foo = "foo " + i
}, x);
Thread.Sleep(1000);
}
}
finally
{
onComplete();
}
}, state);
}
}
Now, we could have the following controller:
public class HomeController : AsyncController
{
public ActionResult Index()
{
return View();
}
public ActionResult LongPoll()
{
var service = new MyService();
return new MyActionResult(service);
}
}
and the following view:
<script type="text/javascript">
// we define a callback function which will be invoked
// when a chunk is available from the server
var callback = function (model) {
// the model variable passed here will represent the chunk
$($('<div/>', {
html: model.Foo
})).appendTo('#result');
};
</script>
<iframe style="display:none;" src="@Url.Action("longpoll")"></iframe>
<div id="result"></div>
Now the last part of course is the implementation of the custom action result which will do the chunked transfer:
public class MyActionResult : ActionResult
{
private readonly MyService _service;
public MyActionResult(MyService service)
{
_service = service;
}
public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
response.BufferOutput = true;
response.ContentType = "text/html";
var wait = new ManualResetEvent(false);
_service.GetModels((model, state) =>
{
var httpResponse = (HttpResponseBase)state;
httpResponse.BufferOutput = true;
httpResponse.ContentType = "text/html";
var serializer = new JavaScriptSerializer();
var script = string.Format(
"<script type=\"text/javascript\">window.parent.callback({0});</script>",
serializer.Serialize(model)
);
httpResponse.Write(script);
httpResponse.Flush();
},
response,
() =>
{
wait.Set();
});
wait.WaitOne();
}
}