From b1e0a070bf913739e795a12c9b98b34a40539f7e Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Mon, 18 Aug 2025 17:56:47 +0200 Subject: [PATCH 01/13] Moved to .Net 8.0 Changed the propety of appsettings.json to copy when debugging Added a few comments for consumers --- .../ODataCoreConsoleApp.csproj | 26 +++++++++------ Integration/ODataCoreConsoleApp/Program.cs | 32 ++++++++++++++++--- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/Integration/ODataCoreConsoleApp/ODataCoreConsoleApp.csproj b/Integration/ODataCoreConsoleApp/ODataCoreConsoleApp.csproj index 88fbe28e..465eeddb 100644 --- a/Integration/ODataCoreConsoleApp/ODataCoreConsoleApp.csproj +++ b/Integration/ODataCoreConsoleApp/ODataCoreConsoleApp.csproj @@ -2,23 +2,29 @@ Exe - netcoreapp3.1 + net8.0 - - - - - - - - - + + + + + + + + + + + + PreserveNewest + + + diff --git a/Integration/ODataCoreConsoleApp/Program.cs b/Integration/ODataCoreConsoleApp/Program.cs index a82bf2f6..44a81536 100644 --- a/Integration/ODataCoreConsoleApp/Program.cs +++ b/Integration/ODataCoreConsoleApp/Program.cs @@ -1,14 +1,33 @@ // This example solution requires OData Connected Service (https://github.com/odata/ODataConnectedService) // and NuGet packages System.Text.Json, Microsoft.Identity.Client, Microsoft.Extensions.Configuration, // Microsoft.Extensions.Configuration.Binder, Microsoft.Extensions.Configuration.Json + +/* + * - Update the needed dependecies using Nuget packages + - If needed - Add the Odata Conencted service extension (https://learn.microsoft.com/en-us/odata/connectedservice/getting-started) + - Generate the Odata proxy using the Connected service: + ○ Right click on the project + ○ Select Add --> Connected service + ○ Select Odata + Use the metadata URI to generate the proxy (/data/$metadata) + ○ ○ (refet to https://github.com/OData/ODataConnectedService/issues/331 if generated class does not compile correctly); + ○ TIP: igf gettig the erro at compile time Error CS8103 Combined length of user strings used by the program exceeds allowed limit. Try to decrease use of string literals. You can add an experimenta feture to the project + Opened my ProjectName.csproj. + Insert this line of code in the : + $(Features);experimental-data-section-string-literals + ○ Update the appsettings.json file with the correct values for your environment + * + */ using System; using System.Linq; using System.Text.Json; using System.Web; using System.Threading.Tasks; using Microsoft.Identity.Client; -using Microsoft.Dynamics.DataEntities; +using Microsoft.Extensions.Configuration; using Microsoft.OData.Client; +using Microsoft.OData; +using Microsoft.Dynamics.DataEntities; //This is available when generated by OData Connected Service namespace ODataCoreConsoleApp { @@ -48,12 +67,13 @@ private static void ReportODataError(DataServiceQueryException ex) } private static async Task MainAsync() - { - Console.WriteLine("Authenticating with AAD..."); + { + + Console.WriteLine("Authenticating with EntraId..."); AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json"); string bearerToken = await GetAuthenticationHeader(config); - var context = new Resources(new Uri($"{config.BaseUrl}/data/")); + var context = new Microsoft.Dynamics.DataEntities.Resources(new Uri($"{config.BaseUrl}/data/")); //Make all the OData requests cross-company, otherwise you will only reference records in the default company context.BuildingRequest += (sender, eventArgs) => @@ -78,7 +98,8 @@ private static async Task MainAsync() //Query some customer groups Console.WriteLine("Listing top 10 customer groups with Id '20' in all companies..."); - var custGroupsQuery = context.CustomerGroups.AddQueryOption("$filter","CustomerGroupId eq '20'").AddQueryOption("$top", "10").IncludeCount(); + var custGroupsQuery = context.CustomerGroups.AddQueryOption("$filter","CustomerGroupId eq '20'").AddQueryOption("$top", "10").IncludeCount(); + Console.WriteLine(custGroupsQuery.ToString()); try { var custGroupsResponse = custGroupsQuery.Execute() as QueryOperationResponse; //Use this query form if you need response metadata @@ -218,6 +239,7 @@ private static async Task MainAsync() { ReportODataError(ex); } + Console.WriteLine("All done!"); } From a8818a457c28d3c72b86154daceb9f749542b9a9 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Mon, 18 Aug 2025 18:05:28 +0200 Subject: [PATCH 02/13] Formatting comments --- Integration/ODataCoreConsoleApp/Program.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Integration/ODataCoreConsoleApp/Program.cs b/Integration/ODataCoreConsoleApp/Program.cs index 44a81536..fa077a8b 100644 --- a/Integration/ODataCoreConsoleApp/Program.cs +++ b/Integration/ODataCoreConsoleApp/Program.cs @@ -9,13 +9,13 @@ ○ Right click on the project ○ Select Add --> Connected service ○ Select Odata - Use the metadata URI to generate the proxy (/data/$metadata) - ○ ○ (refet to https://github.com/OData/ODataConnectedService/issues/331 if generated class does not compile correctly); - ○ TIP: igf gettig the erro at compile time Error CS8103 Combined length of user strings used by the program exceeds allowed limit. Try to decrease use of string literals. You can add an experimenta feture to the project + ○ Use the metadata URI to generate the proxy (/data/$metadata) + ○ (refet to https://github.com/OData/ODataConnectedService/issues/331 if generated class does not compile correctly); + - TIP: igf gettig the erro at compile time Error CS8103 Combined length of user strings used by the program exceeds allowed limit. Try to decrease use of string literals. You can add an experimenta feture to the project Opened my ProjectName.csproj. Insert this line of code in the : $(Features);experimental-data-section-string-literals - ○ Update the appsettings.json file with the correct values for your environment + - Update the appsettings.json file with the correct values for your environment * */ using System; From 46b9f2aa25d9e240c041d6d4ae06f46511bf92b6 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 20 Aug 2025 17:40:32 +0200 Subject: [PATCH 03/13] MessageProcessorConsoleApp --- Integration/MessageProcessorConsoleApp.sln | 25 ++++ .../AuthenticationConfig.cs | 87 +++++++++++ .../MessageProcessorConsoleApp.csproj | 26 ++++ .../MessageProcessorConsoleApp/Program.cs | 141 ++++++++++++++++++ .../appsettings.json | 7 + 5 files changed, 286 insertions(+) create mode 100644 Integration/MessageProcessorConsoleApp.sln create mode 100644 Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs create mode 100644 Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj create mode 100644 Integration/MessageProcessorConsoleApp/Program.cs create mode 100644 Integration/MessageProcessorConsoleApp/appsettings.json diff --git a/Integration/MessageProcessorConsoleApp.sln b/Integration/MessageProcessorConsoleApp.sln new file mode 100644 index 00000000..11281f9d --- /dev/null +++ b/Integration/MessageProcessorConsoleApp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36408.4 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageProcessorConsoleApp", "MessageProcessorConsoleApp\MessageProcessorConsoleApp.csproj", "{04476EAC-E2DE-4D93-B5EE-995EF477D854}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {04476EAC-E2DE-4D93-B5EE-995EF477D854}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04476EAC-E2DE-4D93-B5EE-995EF477D854}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04476EAC-E2DE-4D93-B5EE-995EF477D854}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04476EAC-E2DE-4D93-B5EE-995EF477D854}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {66531EC9-4EA5-4839-84BC-74553326B5BA} + EndGlobalSection +EndGlobal diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs b/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs new file mode 100644 index 00000000..c66818a3 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Extensions.Configuration; +using System; +using System.Globalization; +using System.IO; + +namespace ODataCoreConsoleApp +{ + /// + /// Description of the configuration of an AzureAD public client application (desktop/mobile application). This should + /// match the application registration done in the Azure portal + /// + public class AuthenticationConfig + { + /// + /// instance of Azure AD, for example public Azure or a Sovereign cloud (Azure China, Germany, US government, etc ...) + /// + public string Instance { get; set; } = "https://login.microsoftonline.com/{0}"; + + /// + /// API endpoint (e.g. Graph API), could be public Azure (default) or a Sovereign cloud (US government, etc ...) + /// + public string BaseUrl { get; set; } = "https://graph.microsoft.com"; + + /// + /// The Tenant is: + /// - either the tenant ID of the Azure AD tenant in which this application is registered (a guid) + /// or a domain name associated with the tenant + /// - or 'organizations' (for a multi-tenant application) + /// + public string Tenant { get; set; } + + /// + /// Guid used by the application to uniquely identify itself to Azure AD + /// + public string ClientId { get; set; } + + /// + /// URL of the authority + /// + public string Authority + { + get + { + return String.Format(CultureInfo.InvariantCulture, Instance, Tenant); + } + } + + /// + /// Client secret (application password) + /// + /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret + /// (which is a kind of application password: this property) + /// or a certificate previously shared with AzureAD during the application registration + /// (and identified by the CertificateName property belows) + /// + public string ClientSecret { get; set; } + + /// + /// Name of a certificate in the user certificate store + /// + /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret + /// (which is a kind of application password: the property above) + /// or a certificate previously shared with AzureAD during the application registration + /// (and identified by this CertificateName property) + /// + public string CertificateName { get; set; } + + /// + /// Reads the configuration from a json file + /// + /// Path to the configuration json file + /// AuthenticationConfig read from the json file + public static AuthenticationConfig ReadFromJsonFile(string path) + { + IConfigurationRoot Configuration; + + var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(path); + + Configuration = builder.Build(); + return Configuration.Get(); + } + } + +} diff --git a/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj b/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj new file mode 100644 index 00000000..633fdf04 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj @@ -0,0 +1,26 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/Integration/MessageProcessorConsoleApp/Program.cs b/Integration/MessageProcessorConsoleApp/Program.cs new file mode 100644 index 00000000..c8abd046 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/Program.cs @@ -0,0 +1,141 @@ +// This example solution requires NuGet packages System.Text.Json, Microsoft.Identity.Client, +// Microsoft.Extensions.Configuration, Microsoft.Extensions.Configuration.Binder, Microsoft.Extensions.Configuration.Json + +using Microsoft.Extensions.Configuration; +using Microsoft.Identity.Client; +using System; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Web; + + + + +namespace ODataCoreConsoleApp +{ + class Program + { + private const string Endpoint = "/api/services/SysMessageServices/SysMessageService/SendMessage"; + private static async Task GetAuthenticationHeader(AuthenticationConfig config) + { + IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(config.ClientId) + .WithClientSecret(config.ClientSecret) + .WithAuthority(new Uri(config.Authority)) + .Build(); + string[] scopes = new string[] { $"{config.BaseUrl}/.default" }; + AuthenticationResult result = await app.AcquireTokenForClient(scopes) + .ExecuteAsync(); + return result.CreateAuthorizationHeader(); + } + + private static void ReportError(Exception ex) + { + while (ex != null) + { + Console.WriteLine(ex.Message); + ex = ex.InnerException; + } + + } + + + private static async Task MainAsync() + { + string? orderPrefix = "IMP"; + int initialOrderNumber = 1; + int numberOfOrders = 2; + + //Get user input for order details + Console.WriteLine("Please provide the number of orders to generate (default=2, limit 2000)..."); + string? input = Console.ReadLine(); + if (!string.IsNullOrEmpty(input)) + { + numberOfOrders = int.Parse(input); + } + numberOfOrders = numberOfOrders > 0 ? numberOfOrders : 2; + numberOfOrders = numberOfOrders > 2000 ? 2000 : numberOfOrders; + + Console.WriteLine("Please provide the OrederPrefix (default IMP)..."); + orderPrefix = Console.ReadLine(); + orderPrefix = string.IsNullOrEmpty(orderPrefix) ? "IMP" : orderPrefix; + + Console.WriteLine("Please provide the initial order number (default=1)..."); + input = Console.ReadLine(); + if (!string.IsNullOrEmpty(input)) + { + initialOrderNumber = int.Parse(input); + } + initialOrderNumber = initialOrderNumber > 0 ? initialOrderNumber : 1; + try + { + //Authenticate with Entra ID + Console.WriteLine("Authenticating with EntraId..."); + AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json"); + string bearerToken = await GetAuthenticationHeader(config); + bearerToken = bearerToken.Split(' ')[1]; + + + string fullUrl = $"{config.BaseUrl.TrimEnd('/')}{Endpoint}"; + Console.WriteLine(fullUrl); + + //Prepare oreders and send them to the Message Procesor endpoint + for (int i = 0; i < numberOfOrders; i++) + { + string nextOrderNumber = orderPrefix + (initialOrderNumber + i).ToString("D4"); + Console.Write($"Sending order {nextOrderNumber}..."); + + var jsonBody = @" + { + ""_companyId"": ""USMF"", + ""_messageQueue"": ""SalesOrderQuickQueue"", + ""_messageType"": ""SalesOrderQuickMessage"", + ""_messageContent"": ""{\""CustomerAccount\"": \""US-001\"", \""SalesOrderNumber\"": \""" + nextOrderNumber + @"\"", \""SalesOrderLines\"": [{\""ItemNumber\"": \""D0001\"", \""Qty\"": 23},{\""ItemNumber\"": \""D0003\"", \""Qty\"": 17}]}"" + }"; + + + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken); + + var content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + + var response = await httpClient.PostAsync(fullUrl, content); + string responseContent = await response.Content.ReadAsStringAsync(); + + Console.WriteLine($"Status Code: {response.StatusCode}"); + if (!String.IsNullOrEmpty(responseContent)) + { + Console.WriteLine("Response:"); + Console.WriteLine(responseContent); + } + } + } + catch (Exception ex) + { + ReportError(ex); + } + + Console.WriteLine("All done!"); + + } + + public static int Main(string[] args) + { + try + { + MainAsync().Wait(); + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine(ex); + return -1; + } + } + } + +} diff --git a/Integration/MessageProcessorConsoleApp/appsettings.json b/Integration/MessageProcessorConsoleApp/appsettings.json new file mode 100644 index 00000000..398018d3 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/appsettings.json @@ -0,0 +1,7 @@ +{ + "Instance": "https://login.microsoftonline.com/{0}", + "BaseUrl": "[Enter here the base URL for your D365FO instance, no terminating slash]", + "Tenant": "[Enter here the tenantID or domain name for your Azure AD tenant]", + "ClientId": "[Enter here the ClientId for your application]", + "ClientSecret": "[Enter here a client secret for your application]" +} From 2eba4cb5463e86ee262643b936c8b2b404f38ce1 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 20 Aug 2025 17:52:32 +0200 Subject: [PATCH 04/13] X++ project --- .../QuickOrderProcessor.axpp | Bin 0 -> 7758 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Integration/MessageProcessorConsoleApp/QuickOrderProcessor.axpp diff --git a/Integration/MessageProcessorConsoleApp/QuickOrderProcessor.axpp b/Integration/MessageProcessorConsoleApp/QuickOrderProcessor.axpp new file mode 100644 index 0000000000000000000000000000000000000000..3b54b89cbcededc83d8080700beb29693fe1b816 GIT binary patch literal 7758 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 literal 0 HcmV?d00001 From 9d0e7704ed6ebd0f5a0a1625c246a4da3e26599a Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 20 Aug 2025 18:02:04 +0200 Subject: [PATCH 05/13] Readme.md added --- .../MessageProcessorConsoleApp/README.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Integration/MessageProcessorConsoleApp/README.md diff --git a/Integration/MessageProcessorConsoleApp/README.md b/Integration/MessageProcessorConsoleApp/README.md new file mode 100644 index 00000000..696a6a61 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/README.md @@ -0,0 +1,39 @@ + +# Dynamics 365 for Finance and Operations sample Message Processor console application + +This asset contains a sample console application that demonstrates how to use the the Message Processor to import simple sales orders in Dynamics 365 for Finance and Operations (F&O). +The sample consists of + +## How to install + 1. Download the sample code from the [GitHub repository](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets). + 2. Import and compile the Dynamics 365 for Finance and Operations project into your F&O environment. + 3. Configure the message processr in your environemnt by following the instructions in the [Message Processor documentation](https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/message-processor). + 4. Open the solution in Visual Studio 2022. + 5. Edit the `appsettings.json` file to include the correct settings for your F&O environment. + 6. Build the solution. + 7. Run the console application. + +# Contents +| File/folder | Description | +|-------------|-------------| +| `README.md` | This README file. | +| `MessageProcessorConsoleApp.csproj` | Visual Studio 2022 project definition. | +| `AuthenticationConfig.cs` | Utility class for EntraId authentication using the web application (EntraId client ID + secret) flow. | +| `Program.cs` | Main console application performing the import of sales orders via the Message Processor. | +| `appsettings.json` | Settings template file to be edited for target F&O environment. Must be present in the same folder as the executable console application. | +| `QuickOrderProcessor.axpp` | F&O project containing the Message Processor configuration and the data entity used for the import. | + + + + From 3dc5802c3634ccd38eb0cac5a2f6ccbbf3177bff Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 20 Aug 2025 18:23:48 +0200 Subject: [PATCH 06/13] README.md update --- .../MessageProcessorConsoleApp/README.md | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Integration/MessageProcessorConsoleApp/README.md b/Integration/MessageProcessorConsoleApp/README.md index 696a6a61..20915099 100644 --- a/Integration/MessageProcessorConsoleApp/README.md +++ b/Integration/MessageProcessorConsoleApp/README.md @@ -15,14 +15,20 @@ urlFragment: "d365-fo-message-processor-console" This asset contains a sample console application that demonstrates how to use the the Message Processor to import simple sales orders in Dynamics 365 for Finance and Operations (F&O). The sample consists of -## How to install - 1. Download the sample code from the [GitHub repository](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets). - 2. Import and compile the Dynamics 365 for Finance and Operations project into your F&O environment. - 3. Configure the message processr in your environemnt by following the instructions in the [Message Processor documentation](https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/message-processor). - 4. Open the solution in Visual Studio 2022. - 5. Edit the `appsettings.json` file to include the correct settings for your F&O environment. - 6. Build the solution. - 7. Run the console application. +## How to instal + +This sample requires Dynamics 365 for Finance and Operations version 10.0.31 or later, Visual Studio 2022, and the .NET 8 SDK. +Instructions to set up the sample are provided below. +1. Download the sample code from the [GitHub repository](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets). +2. Import and compile the Dynamics 365 for Finance and Operations project into your F&O environment. The project has been following the [official documentation page](https://learn.microsoft.com/en-us/dynamics365/supply-chain/message-processor/developer/message-processor-develop). +3. Configure the message processor in your environment by following the instructions in the [Message Processor documentation](https://learn.microsoft.com/en-us/dynamics365/supply-chain/message-processor/message-processor). + * Navigate to _System administration > Message processor > Message queue_ setup and create a new message processor queue selecting the new type "Quick sales orders". Choose teh number of processors (e.g. 4) + * Navigate to _System administration > Message processor > Message processor_ to set upo the processing batch job. In the batch setupo page select e new queue type "Quick sales orders". + * From now you can navigate to _System administration > Message processor > Message precessor messages_ to check the incoming messages. +4. Open the solution in Visual Studio 2022. +5. Edit the `appsettings.json` file to include the correct settings for your F&O environment. +6. Build the solution. +7. Run the console application. # Contents | File/folder | Description | From 116f7d3e07553c65eb07f718bded00b1e9e656b9 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 1 Oct 2025 12:26:12 +0200 Subject: [PATCH 07/13] Modify appsettings.json for Azure configurations Updated appsettings.json to include Azure Key Vault and Azure AD configurations. --- .../MessageProcessorConsoleApp/appsettings.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Integration/MessageProcessorConsoleApp/appsettings.json b/Integration/MessageProcessorConsoleApp/appsettings.json index 398018d3..c6cc0ade 100644 --- a/Integration/MessageProcessorConsoleApp/appsettings.json +++ b/Integration/MessageProcessorConsoleApp/appsettings.json @@ -1,7 +1,14 @@ -{ +{ + "BaseUri": "", "Instance": "https://login.microsoftonline.com/{0}", - "BaseUrl": "[Enter here the base URL for your D365FO instance, no terminating slash]", - "Tenant": "[Enter here the tenantID or domain name for your Azure AD tenant]", - "ClientId": "[Enter here the ClientId for your application]", - "ClientSecret": "[Enter here a client secret for your application]" + "AzureKeyVault": { + "VaultUri": "", + "AppIdentifier": "", + "KeyVaultTenantId": "" + }, + "AzureAd": { + "ClientId": "", + "TenantId": "", + "Audience": "" + } } From f3afa1ad7af123b4b5bbae70206a955e6925216a Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 1 Oct 2025 13:11:00 +0200 Subject: [PATCH 08/13] New configurations for keyvault --- Integration/MessageProcessorConsoleApp/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Integration/MessageProcessorConsoleApp/appsettings.json b/Integration/MessageProcessorConsoleApp/appsettings.json index c6cc0ade..3b05ba9f 100644 --- a/Integration/MessageProcessorConsoleApp/appsettings.json +++ b/Integration/MessageProcessorConsoleApp/appsettings.json @@ -1,4 +1,4 @@ -{ +{ "BaseUri": "", "Instance": "https://login.microsoftonline.com/{0}", "AzureKeyVault": { From e0e3d16a709712d12b98512ec463175e3194f24d Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 1 Oct 2025 13:11:39 +0200 Subject: [PATCH 09/13] New configurations for keyvault --- .../AuthenticationConfig.cs | 87 ------------------- .../AuthenticationConfiguration.cs | 77 ++++++++++++++++ .../AuthenticationHelper.cs | 39 +++++++++ .../MessageProcessorConsoleApp/Program.cs | 41 +++++++-- 4 files changed, 149 insertions(+), 95 deletions(-) delete mode 100644 Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs create mode 100644 Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs create mode 100644 Integration/MessageProcessorConsoleApp/AuthenticationHelper.cs diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs b/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs deleted file mode 100644 index c66818a3..00000000 --- a/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Extensions.Configuration; -using System; -using System.Globalization; -using System.IO; - -namespace ODataCoreConsoleApp -{ - /// - /// Description of the configuration of an AzureAD public client application (desktop/mobile application). This should - /// match the application registration done in the Azure portal - /// - public class AuthenticationConfig - { - /// - /// instance of Azure AD, for example public Azure or a Sovereign cloud (Azure China, Germany, US government, etc ...) - /// - public string Instance { get; set; } = "https://login.microsoftonline.com/{0}"; - - /// - /// API endpoint (e.g. Graph API), could be public Azure (default) or a Sovereign cloud (US government, etc ...) - /// - public string BaseUrl { get; set; } = "https://graph.microsoft.com"; - - /// - /// The Tenant is: - /// - either the tenant ID of the Azure AD tenant in which this application is registered (a guid) - /// or a domain name associated with the tenant - /// - or 'organizations' (for a multi-tenant application) - /// - public string Tenant { get; set; } - - /// - /// Guid used by the application to uniquely identify itself to Azure AD - /// - public string ClientId { get; set; } - - /// - /// URL of the authority - /// - public string Authority - { - get - { - return String.Format(CultureInfo.InvariantCulture, Instance, Tenant); - } - } - - /// - /// Client secret (application password) - /// - /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret - /// (which is a kind of application password: this property) - /// or a certificate previously shared with AzureAD during the application registration - /// (and identified by the CertificateName property belows) - /// - public string ClientSecret { get; set; } - - /// - /// Name of a certificate in the user certificate store - /// - /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret - /// (which is a kind of application password: the property above) - /// or a certificate previously shared with AzureAD during the application registration - /// (and identified by this CertificateName property) - /// - public string CertificateName { get; set; } - - /// - /// Reads the configuration from a json file - /// - /// Path to the configuration json file - /// AuthenticationConfig read from the json file - public static AuthenticationConfig ReadFromJsonFile(string path) - { - IConfigurationRoot Configuration; - - var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(path); - - Configuration = builder.Build(); - return Configuration.Get(); - } - } - -} diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs new file mode 100644 index 00000000..0cd263fc --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs @@ -0,0 +1,77 @@ +/** + * SAMPLE CODE NOTICE + * + * THIS SAMPLE CODE IS MADE AVAILABLE AS IS. MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED, + * OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY. + * THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER. + * NO TECHNICAL SUPPORT IS PROVIDED. YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO. + */ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; + +namespace MessageProcessorConsoleApp +{ + /* + "BaseUri": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com", + "AzureKeyVault": { + "VaultUri": "https://aladamuvirtualentitykv.vault.azure.net/", + "AppIdentifier": "aladamuVirtualEntity", + "KeyVaultTenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd" + }, + "AzureAd": { + "ClientId": "2fd29989-4f6b-4fbf-8e09-c86c703255bd", + "Authority": "https://login.microsoftonline.com/{0}", + "TenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd", + "Audience": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com" + } + */ + + public class AuthenticationConfiguration + { + public string BaseUri { get; set; } + public AzureKeyVaultConfig AzureKeyVault { get; set; } + public AzureAdConfig AzureAd { get; set; } + public string Instance { get; set; } = "https://login.microsoftonline.com/{0}"; + public string Authority + { + get + { + return String.Format(CultureInfo.InvariantCulture, Instance, AzureAd.TenantId); + } + } + + public static AuthenticationConfiguration ReadFromJsonFile(string path) + { + // build a config object, using the appsettings.json file as the default source + var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile(path, optional: false, reloadOnChange: true) + .Build(); + // bind the config object to the AppSettings class + var result = new AuthenticationConfiguration(); + config.Bind(result); + // return the populated AppSettings object + return result; + } + + } + + public class AzureKeyVaultConfig + { + public string VaultUri { get; set; } + public string AppIdentifier { get; set; } + public string KeyVaultTenantId { get; set; } + } + + public class AzureAdConfig + { + public string ClientId { get; set; } + public string TenantId { get; set; } + public string Audience { get; set; } + } +} diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationHelper.cs b/Integration/MessageProcessorConsoleApp/AuthenticationHelper.cs new file mode 100644 index 00000000..ef27d893 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/AuthenticationHelper.cs @@ -0,0 +1,39 @@ +/** + * SAMPLE CODE NOTICE + * + * THIS SAMPLE CODE IS MADE AVAILABLE AS IS. MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED, + * OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY. + * THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER. + * NO TECHNICAL SUPPORT IS PROVIDED. YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO. + */ +using Microsoft.Identity.Client; + +namespace MessageProcessorConsoleApp +{ + public class AuthenticationHelper + { + /// + /// Get the access token to call CSU API + /// + /// + /// + /// + /// + /// + /// + public static async Task GetAuthenticationResult(string clientId, string authority, string clientSecret, string tenantId, string audience) + { + var confidentialClientApplication = ConfidentialClientApplicationBuilder. + Create(clientId) + .WithAuthority(authority + tenantId) + .WithClientSecret(clientSecret); + string[] scopes = [$"{audience}/.default"]; + AuthenticationResult authResult = await confidentialClientApplication + .Build() + .AcquireTokenForClient(scopes) + .ExecuteAsync() + .ConfigureAwait(false); + return authResult.AccessToken; + } + } +} diff --git a/Integration/MessageProcessorConsoleApp/Program.cs b/Integration/MessageProcessorConsoleApp/Program.cs index c8abd046..13c7facd 100644 --- a/Integration/MessageProcessorConsoleApp/Program.cs +++ b/Integration/MessageProcessorConsoleApp/Program.cs @@ -1,6 +1,17 @@ -// This example solution requires NuGet packages System.Text.Json, Microsoft.Identity.Client, +/** + * SAMPLE CODE NOTICE + * + * THIS SAMPLE CODE IS MADE AVAILABLE AS IS. MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED, + * OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY. + * THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER. + * NO TECHNICAL SUPPORT IS PROVIDED. YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO. + */ +// This example solution requires NuGet packages System.Text.Json, Microsoft.Identity.Client, // Microsoft.Extensions.Configuration, Microsoft.Extensions.Configuration.Binder, Microsoft.Extensions.Configuration.Json +using Azure.Security.KeyVault.Secrets; +using Azure.Identity; +using MessageProcessorConsoleApp; using Microsoft.Extensions.Configuration; using Microsoft.Identity.Client; using System; @@ -21,13 +32,28 @@ namespace ODataCoreConsoleApp class Program { private const string Endpoint = "/api/services/SysMessageServices/SysMessageService/SendMessage"; - private static async Task GetAuthenticationHeader(AuthenticationConfig config) + private static async Task GetAuthenticationHeader(AuthenticationConfiguration config) { - IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(config.ClientId) - .WithClientSecret(config.ClientSecret) + + var interactiveCredential = new InteractiveBrowserCredential( + new InteractiveBrowserCredentialOptions + { + TenantId = config.AzureKeyVault.KeyVaultTenantId, + RedirectUri = new Uri("http://localhost") + }); + + var client = new SecretClient(new Uri(config.AzureKeyVault.VaultUri), interactiveCredential); + KeyVaultSecret secret = await client.GetSecretAsync(config.AzureKeyVault.AppIdentifier); + string clientSecret = secret.Value; + + + IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(config.AzureAd.ClientId) + .WithClientSecret(clientSecret) .WithAuthority(new Uri(config.Authority)) .Build(); - string[] scopes = new string[] { $"{config.BaseUrl}/.default" }; + string[] scopes = new string[] { $"{config.BaseUri}/.default" }; + + AuthenticationResult result = await app.AcquireTokenForClient(scopes) .ExecuteAsync(); return result.CreateAuthorizationHeader(); @@ -43,7 +69,6 @@ private static void ReportError(Exception ex) } - private static async Task MainAsync() { string? orderPrefix = "IMP"; @@ -75,12 +100,12 @@ private static async Task MainAsync() { //Authenticate with Entra ID Console.WriteLine("Authenticating with EntraId..."); - AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json"); + AuthenticationConfiguration config = AuthenticationConfiguration.ReadFromJsonFile("appsettings.json"); string bearerToken = await GetAuthenticationHeader(config); bearerToken = bearerToken.Split(' ')[1]; - string fullUrl = $"{config.BaseUrl.TrimEnd('/')}{Endpoint}"; + string fullUrl = $"{config.BaseUri.TrimEnd('/')}{Endpoint}"; Console.WriteLine(fullUrl); //Prepare oreders and send them to the Message Procesor endpoint From abde575b347495eb628475ebe995fd3818794646 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 1 Oct 2025 13:12:45 +0200 Subject: [PATCH 10/13] New configurations for keyvault --- .../MessageProcessorConsoleApp.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj b/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj index 633fdf04..5ffdccf5 100644 --- a/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj +++ b/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj @@ -8,6 +8,9 @@ + + + From ab0f1efd5e0618c5a7965233e4b11665acae3a6f Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 1 Oct 2025 13:13:50 +0200 Subject: [PATCH 11/13] Correction in the list --- Integration/ODataCoreConsoleApp/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Integration/ODataCoreConsoleApp/README.md b/Integration/ODataCoreConsoleApp/README.md index bb3b00e5..410f061b 100644 --- a/Integration/ODataCoreConsoleApp/README.md +++ b/Integration/ODataCoreConsoleApp/README.md @@ -17,14 +17,14 @@ Conceptually, this asset is similar to the OData console application sample prov Instead of the older client libraries used in the previous sample, it has been written to take advantage of the modern [OData Connected Service](https://github.com/odata/ODataConnectedService), .Net Core and supporting NuGet packages. -You will need to add the Connected service as described at https://docs.microsoft.com/en-us/odata/client/getting-started#using-the-odata-connected-service. +You will need to add the Connected service as described at https://learn.microsoft.com/en-us/odata/client/getting-started#using-the-odata-connected-service. # Contents | File/folder | Description | |-------------|-------------| | `README.md` | This README file. | -| `ODataCoreConsoleApp.csproj` | Visual Studio 2019 project definition. | -| `AuthenticationConfig.cs` | Utility class for AAD authentication using the web application (AAD client ID + secret) flow. | +| `ODataCoreConsoleApp.csproj` | Visual Studio 2022 project definition. | +| `AuthenticationConfig.cs` | Utility class for EntraId authentication using the web application (EntraId client ID + secret) flow. | | `Program.cs` | Main console application performing some CRUD operations via OData. | | `appsettings.json` | Settings template file to be edited for target F&O environment. Must be present in the same folder as the executable console application. | From 8b2a84bfe29168fd8284136e12031cbad7dc9df4 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 1 Oct 2025 13:19:57 +0200 Subject: [PATCH 12/13] Add MessageProcessorConsoleApp details to README --- Integration/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Integration/README.md b/Integration/README.md index f7f68901..f587946f 100644 --- a/Integration/README.md +++ b/Integration/README.md @@ -20,7 +20,9 @@ This implemetation asset provides sample integration code for Dynamics 365. |-------------|-------------| | `README.md` | This README file. | | `ODataCoreConsoleApp.sln` | Visual Studio 2019 solution file for the sample OData console application. | +| `MessageProcessorConsoleApp.sln` | Visual Studio 2022 solution file for the message processing framework test console application. | | `ODataCoreConsoleApp` | Sample F&O OData client console application written to illustrate the use of .Net Core and the OData Connected Service in a client application. | +| `MessageProcessorConsoleApp` | Sample F&O Message Processor console application written to illustrate the use in Dynamcs 365 F&O | | `Field Service integration with F&O` | [Field Service integration with F&O extensibility examples](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/blob/master/Integration/Field%20service%20integration%20with%20F%26O/readme.md) | ### Sample OData Console Application From 48d48ec95ff56df92bddd37df1763496eeaf9d12 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 1 Oct 2025 13:23:02 +0200 Subject: [PATCH 13/13] Add link for Message processing framework with F&O --- Integration/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Integration/README.md b/Integration/README.md index f587946f..f0e8b95f 100644 --- a/Integration/README.md +++ b/Integration/README.md @@ -24,6 +24,7 @@ This implemetation asset provides sample integration code for Dynamics 365. | `ODataCoreConsoleApp` | Sample F&O OData client console application written to illustrate the use of .Net Core and the OData Connected Service in a client application. | | `MessageProcessorConsoleApp` | Sample F&O Message Processor console application written to illustrate the use in Dynamcs 365 F&O | | `Field Service integration with F&O` | [Field Service integration with F&O extensibility examples](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/blob/master/Integration/Field%20service%20integration%20with%20F%26O/readme.md) | +| `Message processing framework with F&O` | [Message processing framework integration with F&O extensibility examples](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/blob/master/Integration/MessageProcessorConsoleApp/readme.md) | ### Sample OData Console Application - [OData Console sample](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/tree/master/Integration/ODataCoreConsoleApp).