For a while now we can use the new Dataverse ServiceClient that replaced the old CrmServiceClient. It has three big improvements:
- Works for .NET 5.0 and up (.NET Core)
- Uses the newer MSAL.NET instead of ADAL.NET (which is out of support) for authentication
- Support for async (IOrganizationServiceAsync and IOrganizationServiceAsync2)
Migration is quite easy because the interface of the client is the same.
The default way to create a ServiceClient is by giving it a connection string, like:
ServiceClient serviceClient =
new(Configuration.GetConnectionString("myConnection"));
Windows desktop app
But what if you want to use the ServiceClient in a Windows desktop app?
Things become more complicated, because you want to authenticate the user (behalf of flow) and you don’t want to store a connecting string with secrets on the users computer.
ServiceClient can manage access tokens for authenticate internaly, but what if you already managing access tokens in the app and you don’t want ServiceClient do that?
ServiceClient has an option to configure it with redirect authentication to an external function. This function that will be called when the access token is require for interaction with Dataverse.
This function must accept a string (InstanceURI) and return a string (accesstoken), like Func<String,Task<String>>
You can use the specific constructor to do this, but I prefer to use new ConnectionOptions object in this case, like this pseudo code:
static readonly Lazy<IOrganizationService> s_xrmClient = new Lazy<IOrganizationService>(() =>
{
// Connect using OAuth where access is not managed internally by ServiceClient,
// but external using an access token provider function
var connectionOptions = new ConnectionOptions
{
AuthenticationType = AuthenticationType.OAuth,
ServiceUri = new Uri("https://org.crm4.dynamics.com"),
AccessTokenProviderFunctionAsync = RequestAccessToken
};
return new ServiceClient(connectionOptions);
// Inline method: Retrieve an access token using the registered AccessTokenProvider
Task<string> RequestAccessToken(string instanceUri)
{
var tokenProvider = Host.Services.GetRequiredService<IAccessTokenProvider>();
return tokenProvider.GetAuthorizationTokenAsync(new Uri(instanceUri));
}
});
MSAL.NET uses scopes to request access, where the instanceUri will be the scope. Keep in mind that you need to add /.default
for the scope to be correct, like:
if (uri != null)
{
if (!AllowedHostsValidator.IsUrlHostValid(uri))
return null;
scopes = new[] { $"{uri.Scheme}://{uri.Host}/.default" };
}
By configuring your ServiceClient like this, you are delegating the authentication to an external caller.