0

Say we have a SitecoreController which has async Task endpoints.. so my question is does sitecore support this async code? I have heard that there were some issues with async code while returning sitecore datasources and basically we only can have synch methods... is this true?

Thanks

1
  • 1
    You should never use/inherit from SitecoreController to begin with.
    – Mark Cassidy
    Nov 8, 2022 at 12:34

1 Answer 1

2

Sitecore does not support Async controller rendering. but from the Sitecore 8.2 it supports async MVC controllers.

If you want to use async actions, you need to use them outside of Sitecore contexts by indirectly invoking controllers via non-Sitecore routes or Html.RenderAction() on a Sitecore rendering. one other option is to use the AJAX base on your requirements.

But if you want to use that then follow - https://kamsar.net/index.php/2015/05/Enabling-Async-in-Sitecore-Controller-Renderings/

It’s a simple matter to patch Sitecore’s ControllerFactory, which is registered in the <initialize> pipeline:

<initialize>
    <processor type="Foo.Pipelines.Initialize.InitializeAsyncControllerFactory, Foo.Core" patch:instead="*[@type='Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory, Sitecore.Mvc']"/>
</initialize>

using Sitecore.Mvc.Controllers;
using Sitecore.Mvc.Pipelines.Loader;
using Sitecore.Pipelines;
using ControllerBuilder = System.Web.Mvc.ControllerBuilder;

The class to override the controller factory:

namespace Foo.Pipelines.Initialize
{
    /// <summary>
    /// Replaces the standard Sitecore MVC controller factory with one that knows how to do async action invocation.
    /// </summary>
    public class InitializeAsyncControllerFactory : InitializeControllerFactory
    {
        protected override void SetControllerFactory(PipelineArgs args)
        {
            SitecoreControllerFactory controllerFactory = new SitecoreAsyncControllerFactory(ControllerBuilder.Current.GetControllerFactory());
            ControllerBuilder.Current.SetControllerFactory(controllerFactory);
        }
    }
}

Then override the way the SitecoreControllerFactory patches the IActionInvoker on the controllers:

using System.Web.Mvc;
using System.Web.Mvc.Async;
using Sitecore.Mvc.Configuration;
using Sitecore.Mvc.Controllers;

namespace Foo
{
    /// <summary>
    /// Patches the normal Sitecore controller factory to enable executing async actions and using async/await
    /// The ActionInvoker that Sitecore MVC wraps the inner action invoker with does not implement IAsyncActionInvoker,
    /// which means ASP.NET MVC does not try to execute it async if needed, and precludes async/await.
    /// </summary>
    public class SitecoreAsyncControllerFactory : SitecoreControllerFactory
    {
        public SitecoreAsyncControllerFactory(IControllerFactory innerFactory) : base(innerFactory)
        {
        }

        protected override void PrepareController(IController controller, string controllerName)
        {
            if (!MvcSettings.DetailedErrorOnMissingAction)
            {
                return;
            }
            Controller controller2 = controller as Controller;
            if (controller2 == null)
            {
                return;
            }

            /* BEGIN PATCH FOR ASYNC INVOCATION (the rest of this method is stock) */
            IAsyncActionInvoker asyncInvoker = controller2.ActionInvoker as IAsyncActionInvoker;

            if (asyncInvoker != null)
            {
                controller2.ActionInvoker = new SitecoreAsyncActionInvoker(asyncInvoker, controllerName);
                return;
            }
            /* END PATCH FOR ASYNC INVOCATION */

            IActionInvoker actionInvoker = controller2.ActionInvoker;
            if (actionInvoker == null)
            {
                return;
            }
            controller2.ActionInvoker = new SitecoreActionInvoker(actionInvoker, controllerName);
        }
    }
}

And finally, implement an override of SitecoreActionInvoker which implements IAsyncActionInvoker:

using System;
using System.Web.Mvc;
using System.Web.Mvc.Async;
using Sitecore.Mvc.Controllers;

namespace Foo
{
    /// <summary>
    /// Literally all this does is provider an IAsyncActionInvoker wrapper the same way SitecoreActionInvoker wraps non-IAsyncActionInvokers
    /// This instructs ASP.NET MVC to perform async invocation for controller actions.
    /// </summary>
    public class SitecoreAsyncActionInvoker : SitecoreActionInvoker, IAsyncActionInvoker
    {
        private readonly IAsyncActionInvoker _innerInvoker;

        public SitecoreAsyncActionInvoker(IAsyncActionInvoker innerInvoker, string controllerName) : base(innerInvoker, controllerName)
        {
            _innerInvoker = innerInvoker;
        }

        public IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state)
        {
            return _innerInvoker.BeginInvokeAction(controllerContext, actionName, callback, state);
        }

        public bool EndInvokeAction(IAsyncResult asyncResult)
        {
            return _innerInvoker.EndInvokeAction(asyncResult);
        }
    }
}

Then you can go forth and write lovely async controller renderings!

public async Task<ActionResult> AsyncActionMethod()
{
    // download a bunch of URLs in parallel with await
    var webClient = new WebClient();

    var urls = new[] {
        "https://google.com",
        "https://bing.com",
        "https://yahoo.com"
    }.Select(url => webClient.DownloadStringTaskAsync(url));

    var contents = await Task.WhenAll(urls);

    // or just await one task
    var google = await webClient.DownloadStringTaskAsync("https://google.com");

    // execution will pick up right here when all the awaited tasks are done - thawing the thread to finish execution

    return View(contents);
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.