Reputation: 21807
I'm trying to learn how to use AsyncController in MVC2, but there is very little documentation/tutorials out there. I'm looking to take one normal controller method that has a very slow export to a 3rd party service and convert that to an async method.
The original controller method:
public JsonResult SaveSalesInvoice(SalesInvoice invoice)
{
SaveInvoiceToDatabase(invoice); // this is very quick
ExportTo3rdParty(invoice); // this is very slow and should be async
}
So I created a new controller that inherits from AsyncController:
public class BackgroundController : AsyncController
{
public void ExportAysnc(int id)
{
SalesInvoice invoice = _salesService.GetById(id);
ExportTo3rdParty(invoice);
}
public void ExportCompleted(int id)
{
// I dont care about the return value right now,
// because the ExportTo3rdParty() method
// logs the result to a table
}
public void Hello(int id)
{
}
}
And then call the Export method from jQuery:
function Export() {
$.post("Background/Export", { id: $("#Id").val() }, function (data) {
// nothing to do yet
});
}
BUT the result is a 404 not found error (Background/Export is not found). If I try to call Background/Hello or Background/ExportAysnc they are found.
What am I doing wrong?
Upvotes: 2
Views: 2565
Reputation: 1038850
There are indeed two use cases
Let's start with the first case:
public class BackgroundController : AsyncController
{
public void ExportAysnc(int id)
{
AsyncManager.OutstandingOperations.Increment();
Task.Factory.StartNew(() => DoLengthyOperation(id));
// Remark: if you don't use .NET 4.0 and the TPL
// you could manually start a new thread to do the job
}
public ActionResult ExportCompleted(SomeResult result)
{
return Json(result, JsonRequestBehavior.AllowGet);
}
private void DoLengthyOperation(int id)
{
// TODO: Make sure you handle exceptions here
// and ensure that you always call the AsyncManager.OutstandingOperations.Decrement()
// method at the end
SalesInvoice invoice = _salesService.GetById(id);
AsyncManager.Parameters["result"] = ExportTo3rdParty(invoice);
AsyncManager.OutstandingOperations.Decrement();
}
}
Now you could invoke it like this:
$.getJSON(
'<%= Url.Action("Export", "Background") %>',
{ id: $("#Id").val() },
function (data) {
// do something with the results
}
);
Now because you have mentioned a web service call, this means when you generated the client proxy of your web service you had the chance to emit async methods (XXXCompleted and XXXAsync):
public class BackgroundController : AsyncController
{
public void ExportAysnc(int id)
{
AsyncManager.OutstandingOperations.Increment();
// that's the web service client proxy that should
// contain the async versions of the methods
var someService = new SomeService();
someService.ExportTo3rdPartyCompleted += (sender, e) =>
{
// TODO: Make sure you handle exceptions here
// and ensure that you always call the AsyncManager.OutstandingOperations.Decrement()
// method at the end
AsyncManager.Parameters["result"] = e.Value;
AsyncManager.OutstandingOperations.Decrement();
};
var invoice = _salesService.GetById(id);
someService.ExportTo3rdPartyAsync(invoice);
}
public ActionResult ExportCompleted(SomeResult result)
{
return Json(result, JsonRequestBehavior.AllowGet);
}
}
This is the best possible usage of an async controller as it relies on I/O Completion Ports and doesn't monopolize any threads on the server during the execution of the lengthy operation.
The second case is easier (don't really need an async controller):
public class BackgroundController : Controller
{
public ActionResult Export(int id)
{
// Fire and forget some lengthy operation
Task.Factory.StartNew(() => DoLengthyOperation(id));
// return immediately
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
}
Here's a nice article on MSDN on Async controllers.
Upvotes: 8