ASP.NET MVC2 Вызов метода AsyncController из jQuery?

Я пытаюсь научиться использовать AsyncController в MVC2, но там очень мало документации/учебников. Я хочу взять один обычный метод контроллера, который имеет очень медленный экспорт в стороннюю службу, и преобразовать его в асинхронный метод.

Оригинальный метод контроллера:

public JsonResult SaveSalesInvoice(SalesInvoice invoice)
{
    SaveInvoiceToDatabase(invoice); // this is very quick 
    ExportTo3rdParty(invoice); // this is very slow and should be async
}

Поэтому я создал новый контроллер, который наследуется от 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)
    {            
    }
}

А затем вызовите метод Export из jQuery:

function Export() {
    $.post("Background/Export", { id: $("#Id").val() }, function (data) {
    // nothing to do yet
    });
}

НО результатом является ошибка 404 не найдена (Фон/Экспорт не найден). Если я попытаюсь вызвать Background/Hello или Background/ExportAysnc, они будут найдены.

Что я делаю неправильно?


person JK.    schedule 19.02.2011    source источник


Ответы (1)


На самом деле есть два варианта использования

  1. Вам важен результат длительной операции
  2. Вам плевать на результат (выстрелил и забыл)

Начнем с первого случая:

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

Теперь вы можете вызвать его следующим образом:

$.getJSON(
    '<%= Url.Action("Export", "Background") %>', 
    { id: $("#Id").val() }, 
    function (data) {
        // do something with the results
    }
);

Теперь, поскольку вы упомянули вызов веб-службы, это означает, что при создании клиентского прокси-сервера вашей веб-службы у вас была возможность выдать асинхронные методы (XXXCompleted и 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);
    }
}

Это наилучшее возможное использование асинхронного контроллера, поскольку он использует порты завершения ввода-вывода и не монополизирует какие-либо потоки на сервере во время выполнения длительной операции.


Второй случай проще (на самом деле не нужен асинхронный контроллер):

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

Вот хорошая статья на MSDN об асинхронных контроллерах.

person Darin Dimitrov    schedule 20.02.2011
comment
@Darin вариант использования здесь - выстрелить и забыть, чтобы взять длительную часть операции и выполнить ее асинхронно, чтобы пользователю не приходилось ждать ее завершения, прежде чем ввод-вывод сможет возобновиться. Я попробую этот. Чем это отличается от использования var thread = new Thread(DoLengthyOperation)? - person JK.; 20.02.2011
comment
Пробовал и у меня не работает, но это потому, что код DoLengthyOperation(id) сильно зависит от HttpContext.Current. И, конечно же, HttpContext.Current имеет значение null в любом потоке, кроме основного потока ввода-вывода. - person JK.; 20.02.2011
comment
@JK, DoLengthyOperation не должен полагаться на HttpContext. Вам нужно использовать контекст перед вызовом фоновой операции, чтобы построить некоторую модель, которая будет содержать всю необходимую информацию, которая потребуется вашей длительной операции, и передать этот объект фоновому потоку. - person Darin Dimitrov; 20.02.2011
comment
К сожалению, вся структура приложения очень активно использует HttpContext - в основном для кэширования, при этом данные сохраняются в HttpContext.Current.Items. Если бы вместо этого я заменил его на HttpRuntime.Cache, был бы он доступен внутри DoLengthyOperation()? Я бы предположил, что нет. - person JK.; 20.02.2011
comment
@JK, кеш будет доступен. В .NET 4.0 кеш вынесен в отдельную сборку, что позволяет использовать его даже в консольных приложениях, поэтому он не требует контекста HTTP. - person Darin Dimitrov; 20.02.2011
comment
Спасибо, тогда попробую. Пройдет некоторое время, прежде чем я смогу сообщить о результатах. - person JK.; 20.02.2011