allBlogsList

Creating Sitecore Commerce Context From Scratch

Introduction

This is a quick post explaining how to create Sitecore CommerceContext instance from scratch in those rare cases when it need to be called from outside of CommerceController.

Why bother creating CommerceContext from scratch?

CommerceController makes CommerceContext available via this.CurrentContext property, but sometimes there's a need to call Sitecore Commerce SDK APIs (most of which require CommerceContext as a parameter). While building my Commerce Connector for Content Hub I needed to get Sitecore Commerce Engine to read messages from Azure Service Bus. There are more than one ways to build a message bus reader and using a Minion is Sitecore's recommended way of implementing background processes to run in the context of Commerce Engine. I opted for faster processing time and implemented custom ServiceBusConsumer, which effectively is a service running in the context of Commerce Engine ASP.NET core app, constantly listening to Azure Service Bus queue for incoming message and immediately picking up and processing messages as they arrive, making such processing close to real-time. The only challenge - my ServiceBusConsumer runs at the application level, outside of CommerceController context - if I needed a CommerceContext instance, then I'd have to create it from scratch.

What worked for me

After decompiling a number of Commerce DLLs, writing my own method to create CommerceContext, and then spending a few hours running that code in the debugger I finally found what works. The last missing piece turned out to be the "Roles" header, which is what CommerceEngine appears to rely on when it comes to validating the security context of the request, hmm…

Here's the final method that worked for me in Sitecore Commerce 9.3. Feel free to update and use as needed.

private CommerceContext GetCommerceContext()

{
	var _nodeContext = _serviceProvider.GetService<NodeContext>();
	ITrackActivityPipeline service = _serviceProvider.GetService<ITrackActivityPipeline>(); 
	CommerceContext commerceContext = new CommerceContext(_logger, new Microsoft.ApplicationInsights.TelemetryClient());
	commerceContext.GlobalEnvironment = _globalEnvironment;
	commerceContext.ConnectionId = Guid.NewGuid().ToString("N", (IFormatProvider)CultureInfo.InvariantCulture);
	commerceContext.CorrelationId = Guid.NewGuid().ToString("N", (IFormatProvider)CultureInfo.InvariantCulture);
	commerceContext.TrackActivityPipeline = service;
	commerceContext.PipelineTraceLoggingEnabled = _nodeContext != null && _nodeContext.PipelineTraceLoggingEnabled;

    commerceContext.Headers = new HeaderDictionary
    {
		{ "Roles", @"sitecore\Commerce Administrator|sitecore\Customer Service Representative Administrator|sitecore\Customer Service Representative|sitecore\Commerce Business User|sitecore\Pricer Manager|sitecore\Pricer|sitecore\Promotioner Manager|sitecore\Promotioner|sitecore\Merchandiser|sitecore\Relationship Administrator"
		}
    };
 
    commerceContext.Environment = this._nodeContext?.GetEntity<CommerceEnvironment>((Func<CommerceEnvironment, bool>)(e => e.Name.Equals(_defaultCommerceEnvironment, StringComparison.OrdinalIgnoreCase)))
        ?? Task.Run<CommerceEnvironment>((Func<Task<CommerceEnvironment>>)(() => _getEnvironmentCommand?.Process(commerceContext, _defaultCommerceEnvironment))).Result;

    if (commerceContext.Environment == null)
    {
        commerceContext.Environment = _globalEnvironment;
    }
    return commerceContext;
}

I hope this will help somebody save a few hours of precious time - happy coding.