Skip to content

Using XrmContext in a .net core project

Simon J.K. Pedersen edited this page Apr 20, 2022 · 4 revisions

Using XrmContext in a .net core project

Background

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.

Creating a DataAccess/BusinessDomain project

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

Using the DataAccess/BusinessDomain library from .net core apps

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.