Introduction to IXrmContext
Traditionally, business logic methods are tightly coupled to their caller. They typically have parameters that include an org service and also an object that allows for logging such as the tracing service or an Azure logger. Such code cannot easily be called from another context.
IXrmContext is an interface that provides access to org services and a generic logging interface. Using this interface allows business logic code with logging to be written once and called from plugins, console apps, unit tests, Azure, etc.
An immediately obvious benefit of using IXrmContext: you can develop and test a complex plugin almost entirely locally because you can execute your business logic from a console app!
The org services provided by IXrmContext
OrgService: This is the org service that your code should primarily use. From a plugin context, this will be the UserOrgService. From any other context, this will be the org service that was provided by the developer.
SystemOrgService: This org service is only relevant from a plugin context. It is the same SystemOrgService provided in the plugin pipeline.
InitiatingUserOrgService: This org service is only relevant from a plugin context. It is the same InitiatingUserOrgService provided in the plugin pipeline.
IXrmContext from a plugin context
Plugins that inherit from Hsl.Xrm.Sdk.PluginBase automatically have access to an XrmContext object via pluginEventContext.XrmContext
. Pass this object to your business logic layer. From the plugin context,
logging via pluginEventContext.XrmContext.Logger
will be written to the tracing service.
using Hsl.Logging;
using Hsl.Xrm.Sdk.Plugin;
public class ExamplePlugin : PluginBase, IPlugin
{
// [snip constructors]
public override void ExecuteAction(IPluginEventContext pluginEventContext)
{
pluginEventContext.XrmContext.Logger.LogWarning(
"written to log if LogLevel is Warning or lower");
var target = pluginEventContext.TargetInput.ToEntity<Account>();
// Each of these samples is calling the same business logic method.
BusinessLogic.AccountLogic.ImplementSomeRequirement(
pluginEventContext.XrmContext, target);
}
}
IXrmContext from console apps
To create an XrmContext object from a console app, you need to use the ConsoleLogger available in the Hsl.Logging package.
Sample notes:
- The console app is calling the same business logic method that the plugin and Azure sample are calling. This means you can develop your plugins locally without pushing anything to the server!
- Because this code uses the ConsoleLogger, all logging that you would normally see in the plugin trace log is written to the console.
using Hsl.Logging;
using Hsl.Xrm.Sdk;
using Hsl.Xrm.Sdk.Client;
public class Program
{
static void Main(string[] args)
{
var orgUrl = "https://orgName.crm.dynamics.com";
var userName = "email@address.com";
var consoleLogger = new ConsoleLogger(LogLevel.Trace, " ");
string connectionString = $"AuthType=OAuth;Username={userName};" +
$"Url={orgUrl};" +
// AppId and RedirectUri provided by MS for dev purposes only
// https://docs.microsoft.com/en-us/powerapps/developer/data-platform/xrm-tooling/use-connection-strings-xrm-tooling-connect
"AppId=51f81489-12ee-4a9e-aaae-a2591f45987d; " +
"RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;" +
// Stores auth token so you don't have to authenticate over and over
"TokenCacheStorePath=C:\\XrmToolingTokenCache\\msal_cache.data;" +
// Displays standard login dialog
"LoginPrompt=auto";
var orgServiceProvider = new PoolingOrgServiceProvider(connectionString);
using (var orgService = orgServiceProvider.Acquire())
{
var xrmContext = new XrmContext(orgService, consoleLogger);
var account = xrmContext.OrgService.Retrieve(
"account", new Guid("..."), new ColumnSet(true)
).ToEntity<Account>();
// Each of these samples is calling the same business logic method.
BusinessLogic.AccountLogic.ImplementSomeRequirement(xrmContext, account);
}
}
}
IXrmContext from an Azure function
To get an XrmContext object from an Azure context, you need to provide an implementation of IHslLogger
that works for Azure. An example of such a logger is provided.
Sample notes
- The Azure code is calling the same business logic method that the plugin and console samples are calling.
- Logging is written to Azure via the AzureHslLogger.
using Hsl.Logging;
using Hsl.Xrm.Sdk;
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log
) {
using (var orgService = Config.OrgServiceProvider.Acquire())
{
var xrmContext = new XrmContext(orgService, new AzureHslLogger(log));
xrmContext.Logger.LogInfo("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string accountid = req.Query["accountid"];
var account = xrmContext.OrgService.Retrieve(
"account", new Guid(accountid), new ColumnSet(true)
).ToEntity<Account>();
// Each of these samples is calling the same business logic method.
BusinessLogic.AccountLogic.ImplementSomeRequirement(xrmContext, account);
return new OkObjectResult($"Hello, {name}");
}
}
}
using Hsl.Xrm.Sdk.Client;
using Hsl.Xrm.Sdk;
public static class Config
{
// See https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/api-limits
private const int maxConnections = 52;
// Static constructor which initializes the static OrgServiceProvider property
static Config()
{
// https://support.microsoft.com/en-us/help/821268/contention-poor-performance-and-deadlocks-when-you-make-calls-to-web-s
ServicePointManager.DefaultConnectionLimit = Math.Min(
maxConnections, 12*Environment.ProcessorCount);
var crmConnectionString =
"AuthType=HslClientSecret; " +
"Url=https://<your url>.crm.dynamics.com; " +
"TenantId=<your tenant id>; " +
"ClientId=<your client id>; " +
"ClientSecret=<your client secret>";
OrgServiceProvider = new PoolingOrgServiceProvider(crmConnectionString, false);
}
public static IOrgServiceProvider OrgServiceProvider { get; }
}
Here is a sample IHslLogger
which you can use for logging to Azure.
using Hsl.Logging;
using Microsoft.Extensions.Logging;
using HslLogLevel = Hsl.Logging.LogLevel;
using MsLogLevel = Microsoft.Extensions.Logging.LogLevel;
public class AzureHslLogger : HslLoggerBase
{
private readonly ILogger inner;
public AzureHslLogger(ILogger logger)
{
this.inner = logger;
}
private MsLogLevel ConvertLogLevel(HslLogLevel? logLevel)
{
return logLevel switch
{
HslLogLevel.Critical => MsLogLevel.Critical,
HslLogLevel.Error => MsLogLevel.Error,
HslLogLevel.Warning => MsLogLevel.Warning,
HslLogLevel.Info => MsLogLevel.Information,
HslLogLevel.Trace => MsLogLevel.Trace,
HslLogLevel.Debug => MsLogLevel.Debug,
HslLogLevel.None => MsLogLevel.None,
_ => MsLogLevel.Information,
};
}
protected override void Write(string msg, HslLogLevel? logLevel)
{
inner.Log(ConvertLogLevel(logLevel), "{0}", msg);
}
}