-
Notifications
You must be signed in to change notification settings - Fork 16
Using XrmContext in a .net core project
The XrmContext tool is not yet converted to .net core, as plugin development for Dataverse i still stuck in the .net fullframework era. However when building Web APIs, Azure Functions etc, there is really no reason to use .net fullframework anymore, so we need a way to make XrmContext compatible with .net core.
The good thing is XrmContext just generates code, and the code can without much trouble be compiled to target .net core 3, 5 or 6. Here's how.
You need a c# project where you can store all your Dataverse class entities that XrmContext generate. Typically you would name this project [Customer].[Solution].DataAccess
or [Customer].[Solution].BusinessDomain
. It should be created as a standard .net core class library. And you should install the preview nuget package from Microsoft Microsoft.PowerPlatform.Dataverse.Client
as it provides a .net core version of IOrganizationService
and the likes.
When you have created the C# project you can edit the project file to look like the following. The project file does the following. Sets up some parameters to build the commandline arguments for XrmContext.exe.
References Microsoft.PowerPlatform.Dataverse.Client
you are welcome to use a more recent version.
Installs XrmContext from nuget into a Lib folder in the parent directory. You can also add the XrmContext as a nuget package reference, but it will cause you problems as it is not .net core compatible, especially if you are planning on making unit tests where you reference the project.
When the project is build in "Rebuild" mode it regenerates the XrmContext calling the defined commandline for XrmContext.exe, hence all parameters must be set. As a Rebuild is only triggered locally by a developer, this is good because the developer can then provide his personal credentials to Dataverse.
This setup is not designed for allowing the build server to regenerate the XrmContext. If you have that preference it can be setup like that by providing the parameters to the build tool, but you would get unexpected build results because it depends on the changes in Dataverse. I personally don't like unpredictable builds, it is better if a developer updates the XrmContext because then they also ensure that the entire solution builds before they commit.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<PropertyGroup>
<GenerateDataverseContext>true</GenerateDataverseContext>
<DataverseAuthType>OAuth</DataverseAuthType>
<DataverseClientId>A_CLIENT_ID_REGISTERED_ON_THE_TENANT</DataverseClientId>
<DataverseAppReturnUri>A_RETURN_URL_REGISTED_ON_CLIENT_ID_APPLICATION</DataverseAppReturnUri>
<DataverseEntitiesToExport>SPECIFIC_ENTITIES_NOT_IN_SOLUTION</DataverseEntitiesToExport>
<DataverseSolutionsToExport>YOUR-SOLUTIONNAME</DataverseSolutionsToExport>
<DataverseEnvironment>https://YOUR-DATAVERSE-ENVIRONMENT.crm4.dynamics.com/XRMServices/2011/Organization.svc</DataverseEnvironment>
<DataverseContexOutput>Dataverse</DataverseContexOutput>
<DataverseContextOneFile>false</DataverseContextOneFile>
<DataversePassword>PASSWORD_FOR_THE_USER</DataversePassword>
<DataverseUserName>AN_EMAIL_WITH_SYSTEM_ADMINISTRATOR_RIGHTS_TO_DATAVERSE</DataverseUserName>
<DataverseTenantId>AZURE_AD_TENANT_ID</DataverseTenantId>
<DataverseLoginPrompt>Always</DataverseLoginPrompt>
<DataverseLocalization>1033,1030</DataverseLocalization>
<PkgDelegate_XrmContext>$(SolutionDir)/Lib/Delegate.XrmContext.2.0.2</PkgDelegate_XrmContext>
<DataverseGenerateCommand>$(PkgDelegate_XrmContext)/content/XrmContext/XrmContext.exe /username=$(DataverseUserName) /password=$(DataversePassword) /method=$(DataverseAuthType) /mfaAppId=$(DataverseClientId) mfaReturnUrl=$(DataverseAppReturnUri) /url=$(DataverseEnvironment) /out="$(MSBuildProjectDirectory)/$(DataverseContexOutput)" /deprecatedPrefix=ZZ /entities=$(DataverseEntitiesToExport) /servicecontextname=DataverseContext /solutions="$(DataverseSolutionsToExport)" /namespace=Microsoft.PowerPlatform.Dataverse /onefile=$(DataverseContextOneFile) /localizations=$(DataverseLocalization)</DataverseGenerateCommand>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.PowerPlatform.Dataverse.Client" Version="0.4.12" />
</ItemGroup>
<Target Name="InstallXrmContext" BeforeTargets="BeforeBuild" Condition="!Exists('$(PkgDelegate_XrmContext)')">
<!--<Error Text="Xrm not found in $(SolutionDir)Lib\XrmContext"></Error>-->
<Exec Condition="$(GenerateDataverseContext)" WorkingDirectory="$(MSBuildProjectDirectory)" Command="nuget install Delegate.XrmContext -version 2.0.2 -OutputDirectory $(SolutionDir)Lib\ -Source https://api.nuget.org/v3/index.json" />
</Target>
<Target Name="CreateCleanContext" BeforeTargets="BeforeBuild" Condition="!Exists('$(MSBuildProjectDirectory)/$(DataverseContexOutput)')">
<MakeDir Directories="$(MSBuildProjectDirectory)/$(DataverseContexOutput)" />
<Message Importance="high" Text="$(DataverseGenerateCommand)" />
<Exec Condition="$(GenerateDataverseContext)" WorkingDirectory="$(MSBuildProjectDirectory)" Command="$(DataverseGenerateCommand)" />
</Target>
<Target Name="CreateContext" BeforeTargets="Rebuild">
<MakeDir Directories="$(MSBuildProjectDirectory)/$(DataverseContexOutput)" />
<Error Condition="$(DataversePassword) == ''" Text="You need to provide a login password for the dataverse user in the csproj file in order to rebuild and regenerate the context" />
<Message Importance="high" Text="$(DataverseGenerateCommand)" />
<Exec Condition="$(GenerateDataverseContext)" WorkingDirectory="$(MSBuildProjectDirectory)" Command="$(DataverseGenerateCommand)" />
</Target>
</Project>
Here's an explanation of the properties set in the csproj file.
Property | Description |
---|---|
TargetFramework: | You can set this to suit your needs, it can be netcoreapp3.1 or you can use net5.0 or net6.0. (The example here uses netcoreapp3.1 because it is used with that version of Azure Functions). |
LangVersion: | Again pick the language version that you are interested in using |
DataverseEntitiesToExport | Here you can specific entities to export that is not part of your solution, e.g. contacts could be here if you havent made any changes to it and thus it isn't part of your solution file |
DataverseEnvironment | The Uri for the dataverse environnment to export from |
DataversePassword | The login pass for XrmContext, here we use an real user account, and not a service principal. NEVER COMMIT the password to source control, regardless of whether you are using a user account or service principal. |
DataverseUserName | The email for the user |
DataverseTenantId | The azure ad tenant id |
DataverseClientId | Application registered in Azure ad that can be used for OAuth signin |
DataverseAppReturnUri | The previously registered application in Azure ad should have native login enabled and the return url configured should be provided here, format .e.g |
DataverseLoginPrompt | Unfortunately the prompt is not shown when run as part of msbuild, so you have to provide both username/password in the csproj file |
DataverseLocalization | If you want localizations of of the Enum properties, you can define here which locals to include |
If you are using the standard .net core dependecy injection, I recommend to install the following nuget package DotNetDevOps.Extensions.PowerPlatform.Dataverse
it provides an easy way to configure IOrganizationService
correctly with your IServiceCollection
Here is an example from an Azure Function project
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using DotNetDevOps.Extensions.PowerPlatform.DataVerse;
[assembly: FunctionsStartup(typeof(MyFunctionProject.Startup))]
namespace MyFunctionProject
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddDataverse();
}
}
}
The AddHttpClient is necessary as it is used by the DotNetDevOps.Extensions.PowerPlatform.DataVerse
library. The AddDataverse()
extension method configures the IOrganizationService, the configuration parameters to used are read from the Environment/AppSettings, so the following settings must be available.
AppSetting/EnviromnetVariable | Value |
---|---|
DataverseEnvironment | https://YOUR_TENANT.crm4.dynamics.com |
DataverseClientId | ClientID for a service principal with access to dataverse |
DataverseClientSecret | Secret for the service principal |
TenantId | The Azure AD tenant Id |
Now you can use normal constructor injection by asking for an IOrganizationService
references and you will be able to create a strongly typed XrmContext using var ctx = new DataverseContext(orgService);
where ever you need it.
Setup Instructions
- Generate Context
- Comparison with CrmSvcUtil
- Fixing breaking change in 2.X
- Using XrmContext in a .net core project
Contribute