1

I'm working on trying to get an AsyncController to work in OrchardProject. The current version I'm using is 2.2.4.9.0.

I've had 2 people eyeball my code: http://www.pastie.org/2117952 (AsyncController) which works fine in a regular MVC3 vanilla application.

Basically, I can route to IndexCompleted, but I can't route to Index. I am going to assume i'm missing something in the Autofac configuration of the overall project.

I think the configuration is in the global.asax: http://pastie.org/2118008

What I'm looking for is some guidance on if this is the correct way to implement autofac for AsyncControllers, or if there is something/someplace else I need to implement/initialize/etc.

~Dan

Dregalia
  • 13
  • 3

4 Answers4

2

Orchard appears to register its own IActionInvoker, called Orchard.Mvc.Filters.FilterResolvingActionInvoker.

This class derives from ControllerActionInvoker. At a guess, in order to support async actions, it should instead derive from AsyncControllerActionInvoker.

Hope this helps!

Nick

Nicholas Blumhardt
  • 30,271
  • 4
  • 90
  • 101
0

I did too needed to have AsyncController which I easily changed FilterResolvingActionInvoker to be based on AsyncControllerActionInvoker instead of ControllerActionInvoker.

But there was other problems because of automatic transaction disposal after completion of request. In AsyncController starting thread and the thread that completes the request can be different which throws following exception in Dispose method of TransactionManager class:

A TransactionScope must be disposed on the same thread that it was created.

This exception is suppressed without any logging and really was hard to find out. In this case session remains not-disposed and subsequent sessions will timeout.
So I made dispose method public on ITransactionManager and now in my AsyncController, whenever I need a query to database I wrap it in:

using (_services.TransactionManager) {
    .....
}

new TransactionManager :

public interface ITransactionManager : IDependency, IDisposable {
    void Demand();
    void Cancel();
}

public class TransactionManager : ITransactionManager {
    private TransactionScope _scope;
    private bool _cancelled;

    public TransactionManager() {
        Logger = NullLogger.Instance;
    }

    public ILogger Logger { get; set; }

    public void Demand() {
        if (_scope == null) {
            Logger.Debug("Creating transaction on Demand");
            _scope = new TransactionScope(
                TransactionScopeOption.Required, 
                new TransactionOptions { 
                    IsolationLevel = IsolationLevel.ReadCommitted 
                });
            _cancelled = false;
        }
    }

    void ITransactionManager.Cancel() {
        Logger.Debug("Transaction cancelled flag set");
        _cancelled = true;
    }

    void IDisposable.Dispose() {
        if (_scope != null) {
            if (!_cancelled) {
                Logger.Debug("Marking transaction as complete");
                _scope.Complete();
            }

            Logger.Debug("Final work for transaction being performed");
            try {
                _scope.Dispose();
            }
            catch {
                // swallowing the exception
            }
            Logger.Debug("Transaction disposed");
        }
        _scope = null;
    }
}

Please notice that I have made other small changes to TransactionManager.

Be Brave Be Like Ukraine
  • 7,596
  • 3
  • 42
  • 66
Arash Shakery
  • 82
  • 1
  • 4
0

I tried the AsyncControllerActionInvoker route as well to no avail. I would get intermittent errors from Orchard itself with the following errors:

Orchard.Exceptions.DefaultExceptionPolicy - An unexpected exception was caught
System.TimeoutException: The operation has timed out.
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
   at System.Web.Mvc.Async.ReflectedAsyncActionDescriptor.EndExecute(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3f.<BeginInvokeAsynchronousActionMethod>b__3e(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<>c__DisplayClass39.<BeginInvokeActionMethodWithFilters>b__33()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()
NHibernate.Util.ADOExceptionReporter - While preparing SELECT this_.Id as Id236_2_, this_.Number as Number236_2_,...<blah blah blah>
NHibernate.Util.ADOExceptionReporter - The connection object can not be enlisted in transaction scope.

So I don't think just wrapping your own database calls with a transaction object will help. The innards of Orchard would have to modified as well.

Go vote for this issue if you want AsyncControllers supported in Orchard:

https://orchard.codeplex.com/workitem/18012

mobese46
  • 241
  • 1
  • 7
0

The Autofac setup looks ok, and as long as you can navigate to something I cannot say that your assumption makes sense. Also, the pattern you are using for initialization in global.asax is used by others too.

The AsyncController requires that async methods come in pairs, in your case IndexAsync & IndexCompleted. These together represent the Index action. When you say you can navigate to IndexCompleted, do you mean that you open a url "..../IndexCompleted"?

Also, and this I cannot confirm from any documentation, but I would guess that AsyncController requires that all actions are async. Thus, your NewMessage action causes trouble and should be converted to an async NewMessageAsync & NewMessageCompleted pair.

Peter Lillevold
  • 33,668
  • 7
  • 97
  • 131