File-based integration scenarios(Data management framework’s package API)

Two APIs support file-based integration scenarios: the Data management framework’s package API and the recurring integrations API

Below are the sample C# snippets to be used for data Data management framework’s package API

data management project should be in place before hand

Main program class

Program
{
    static async Task Main(string[] args)
    {
        // Define values once
        string jobNameExport = "Cust Group DMF Export"; // Must match the data project in D365
        string jobNameImport = "Cust Group DMF Import"; // Must match the data project in D365
        string packageNameExport = "ExportedCustGroupPackage";
        //string legalEntityExport = "usmf";
        string legalEntityExport = "usmf";
        string legalEntityImport = "usmf";
        string filePathExport = @"C:\Users\source\repos\AXPDataManagementAPIDemo\AXPDataManagementAPIDemo\Export";
        string filePathImport = @"C:\Users\source\repos\AXPDataManagementAPIDemo\AXPDataManagementAPIDemo\Import";
        string fileNameExport = "CustGroupExport";
        string fileNameImport = "CustGroupImport";

        // Ensure directory exists
        if (!Directory.Exists(filePathExport))
            Directory.CreateDirectory(filePathExport);

        // Ensure directory exists
        if (!Directory.Exists(filePathImport))
            Directory.CreateDirectory(filePathImport);

        // Menu loop
        while (true)
        {
            Console.WriteLine("\n=== DMF Console ===");
            Console.WriteLine("1. Export Data");
            Console.WriteLine("2. Import Data");
            Console.WriteLine("0. Exit");
            Console.Write("Select an option: ");
            string input = Console.ReadLine();

            switch (input)
            {
                case "1":
                    Console.WriteLine("\n--- Starting Export ---\n");
                    await DMFManager.Export(jobNameExport, packageNameExport, legalEntityExport, filePathExport, fileNameExport);
                    Console.WriteLine("\n Export process completed.");
                    break;

                case "2":
                    Console.WriteLine("\n--- Starting Import ---\n");
                    await DMFMana class ger.Import(jobNameImport, legalEntityImport, filePathImport, fileNameImport);
                    Console.WriteLine("\n Import process completed.");
                    break;

                case "0":
                    Console.WriteLine("Exiting...");
                    return;

                default:
                    Console.WriteLine("Invalid selection. Try again.");
                    break;
            }
        }
    }
}

contracts

public class DMFExport
{
    [JsonProperty("definitionGroupId")]
    public string DefinitionGroupId { get; set; }

    [JsonProperty("packageName")]
    public string PackageName { get; set; }

    [JsonProperty("executionId")]
    public string ExecutionId { get; set; }

    [JsonProperty("reExecute")]
    public bool ReExecute { get; set; }

    [JsonProperty("legalEntityId")]
    public string LegalEntityId { get; set; }
}
public class DMFImport
{
    [JsonProperty("packageUrl")]
    public string PackageUrl { get; set; }

    [JsonProperty("definitionGroupId")]
    public string DefinitionGroupId { get; set; }

    [JsonProperty("executionId")]
    public string ExecutionId { get; set; } = string.Empty;

    [JsonProperty("overwrite")]
    public bool Overwrite { get; set; }

    [JsonProperty("execute")]
    public bool Execute { get; set; }

    [JsonProperty("legalEntityId")]
    public string LegalEntityId { get; set; }
}

status check call

public class DMFExportSummary
{
    [JsonProperty("executionId")]
    public string ExecutionId { get; set; }
}

Authentications

public class OAuth2TokenService
{
    private readonly SandboxEnvConfig _config;
    private readonly HttpClient _httpClient;

    public OAuth2TokenService(SandboxEnvConfig config)
    {
        _config = config;
        _httpClient = new HttpClient();
    }

    public string Resource => _config.Resource;

    public string GetAccessToken()
    {
        var tokenEndpoint = $"https://login.microsoftonline.com/{_config.TenantId}//oauth2/token";

        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type", "client_credentials"),
            new KeyValuePair<string, string>("client_id", _config.ClientId),
            new KeyValuePair<string, string>("client_secret", _config.ClientSecret),
            new KeyValuePair<string, string>("resource", _config.Resource)
        });

        var response = _httpClient.PostAsync(tokenEndpoint, content).Result;

        if (!response.IsSuccessStatusCode)
        {
            var error = response.Content.ReadAsStringAsync().Result;
            throw new Exception($"Token request failed: {error}");
        }

        var json = response.Content.ReadAsStringAsync().Result;
        using var doc = JsonDocument.Parse(json);
        return doc.RootElement.GetProperty("access_token").GetString();
    }
}

Manager

class DMFManager
{

    static string downloadUrl = string.Empty;
    static string aadResource = string.Empty;

    /// <summary>
    /// Retrieves an authentication header from the service.
    /// </summary>
    /// <returns>The authentication header for the Web API call.</returns>
    private static string GetAuthenticationHeader()
    {
        
        var builder = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddUserSecrets<Program>();

        var configuration = builder.Build();
        var config = configuration.GetSection("SandboxEnvConfig").Get<SandboxEnvConfig>();

        var oauthService = new OAuth2TokenService(config);

        try
        {
            var token = oauthService.GetAccessToken();
            Console.WriteLine("Access Token Acquired ");
            aadResource = oauthService.Resource; // capture it from config
            return token;
        }
        catch (Exception ex)
        {
            Console.WriteLine(" Failed to get token:");
            Console.WriteLine(ex.Message);
            return null;
        }
    }

    public static async Task<string> ImportFromPackageAsync(HttpClient client, string blobUrl, string jobName, string legalEntity)
    {
        var payload = new DMFImport
        {
            PackageUrl = blobUrl,
            DefinitionGroupId = jobName,
            LegalEntityId = legalEntity,
            Overwrite = true,
            Execute = true
        };

        var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");

        var response = await client.PostAsync("/data/DataManagementDefinitionGroups/Microsoft.Dynamics.DataEntities.ImportFromPackage", content);
        var result = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode)
            throw new Exception($"Import failed: {result}");

        var parsed = JObject.Parse(result);
        var value = parsed["value"]?.ToString();

        if (string.IsNullOrWhiteSpace(value))
            throw new Exception("No executionId returned from ImportFromPackage.");

        Console.WriteLine("Execution ID retrieved: " + value);
        return value;
    }


    // Setup Step 
    // - Create an export project within Dynamics called ExportVendors in company USMF before you run the following code
    // - It can of any data format XML and can include any number of data entities
    // 1. Initiate export of a data project to create a data package within Dynamics 365 for Operations
    public static async Task Import(string jobName, string legalEntity, string filePath, string fileName)
    {
        string authHeader = GetAuthenticationHeader();

        using HttpClient client = new HttpClient();
        client.BaseAddress = new Uri(aadResource);
        client.DefaultRequestHeaders.Clear(); 
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authHeader);

        // 1. Get Azure Blob Write URL
        Console.WriteLine(" Getting Azure Blob upload URL...");
        var uploadRequest = new { uniqueFileName = fileName + ".zip" };
        var stringPayload = JsonConvert.SerializeObject(uploadRequest);
        var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");

        var uploadUrlResponse = await client.PostAsync(
            "/data/DataManagementDefinitionGroups/Microsoft.Dynamics.DataEntities.GetAzureWriteUrl", httpContent);
        var uploadUrlContent = await uploadUrlResponse.Content.ReadAsStringAsync();

        if (!uploadUrlResponse.IsSuccessStatusCode)
        {
            Console.WriteLine("Failed to get blob URL:");
            Console.WriteLine(uploadUrlContent);
            return;
        }

        // Parse BlobUrl from nested string in "value"
        string rawValue = JObject.Parse(uploadUrlContent)["value"]?.ToString();
        if (string.IsNullOrWhiteSpace(rawValue))
        {
            Console.WriteLine("Blob URL response is empty.");
            return;
        }

        JObject blobJson = JObject.Parse(rawValue);
        string blobUrl = blobJson["BlobUrl"]?.ToString();

        if (string.IsNullOrWhiteSpace(blobUrl))
        {
            Console.WriteLine("BlobUrl' not found in Azure response.");
            return;
        }

        Console.WriteLine("Blob URL acquired:\n" + blobUrl);


        // 2. Upload the ZIP file
        Console.WriteLine("Uploading the data package...");
        byte[] zipBytes = File.ReadAllBytes(Path.Combine(filePath, fileName + ".zip"));

        using (var blobHttpClient = new HttpClient())
        {
            var blobContent = new ByteArrayContent(zipBytes);
            blobContent.Headers.Clear(); // clearing the header here so the SAS token can authenticate the blob itself, the local auth doesn't interfere
            blobContent.Headers.Add("x-ms-blob-type", "BlockBlob");

            var blobUploadResponse = await blobHttpClient.PutAsync(blobUrl, blobContent);

            if (blobUploadResponse.StatusCode != System.Net.HttpStatusCode.Created)
            {
                Console.WriteLine("Failed to upload package (expected 201 Created):");
                Console.WriteLine(await blobUploadResponse.Content.ReadAsStringAsync());
                return;
            }
        }

        Console.WriteLine("Package uploaded successfully (201 Created).");

        // 3. Trigger import
        Console.WriteLine("Initiating import of data project...");
        string execId;
        try
        {
            execId = await ImportFromPackageAsync(client, blobUrl, jobName, legalEntity);
            if (string.IsNullOrWhiteSpace(execId))
            {
                Console.WriteLine("No executionId returned from ImportFromPackage.");
                return;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("ImportFromPackage failed:");
            Console.WriteLine(ex.Message);
            return;
        }

        Console.WriteLine($"Import started with Execution ID: {execId}");

        // 4. Pull status
        int maxLoop = 15;
        string outPut = string.Empty;

        do
        {
            Console.WriteLine("Checking import status...");
            Thread.Sleep(5000);
            maxLoop--;

            if (maxLoop <= 0)
            {
                Console.WriteLine("Status check timed out.");
                break;
            }

            stringPayload = JsonConvert.SerializeObject(new { executionId = execId });
            httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");

            var statusResponse = await client.PostAsync(
                "/data/DataManagementDefinitionGroups/Microsoft.Dynamics.DataEntities.GetExecutionSummaryStatus", httpContent);
            var statusContent = await statusResponse.Content.ReadAsStringAsync();

            var parsedResult = JObject.Parse(statusContent);
            var valueToken = parsedResult["value"];

            if (valueToken == null)
            {
                Console.WriteLine("Status response missing 'value':");
                Console.WriteLine(statusContent);
                return;
            }

            outPut = valueToken.ToString();
            Console.WriteLine($"Import Status: {outPut}");

        } while (outPut == "NotRun" || outPut == "Executing");

        if (outPut != "Succeeded" && outPut != "PartiallySucceeded")
        {
            Console.WriteLine("Import job failed with status: " + outPut);
            return;
        }

        Console.WriteLine("Import job completed successfully.");
    }

    public static async Task Export(string jobName, string packageName, string legalEntity, string filePath, string fileName)
    {
        string authHeader = GetAuthenticationHeader();

        using HttpClient client = new HttpClient();
        client.BaseAddress = new Uri(aadResource);
        client.DefaultRequestHeaders.Clear();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authHeader);


        //Initiate the Export
        string execytionID = Guid.NewGuid().ToString();
        var payload = new DMFExport()
        {
            DefinitionGroupId = jobName,
            PackageName = packageName,
            ExecutionId = execytionID,
            ReExecute = true,
            LegalEntityId = legalEntity
        };
        Console.WriteLine("Initiating export of a data project...");
        var stringPayload = JsonConvert.SerializeObject(payload);
        var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
        var result = client.PostAsync("/data/DataManagementDefinitionGroups/Microsoft.Dynamics.DataEntities.ExportToPackage", httpContent).Result;
        string resultContent = await result.Content.ReadAsStringAsync();
        JObject joResponse = JObject.Parse(resultContent);
        string outPut = string.Empty;
        if (result.StatusCode == System.Net.HttpStatusCode.OK)
        {

            Console.WriteLine("Initiating export of a data project...Complete");
            int maxLoop = 15;
            do
            {
                Console.WriteLine("Waiting for package to execution to complete");

                Thread.Sleep(5000);
                maxLoop--;

                if (maxLoop <= 0)
                {
                    break;
                }

                Console.WriteLine("Checking status...");

                stringPayload = JsonConvert.SerializeObject(new DMFExportSummary() { ExecutionId = execytionID });
                httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");

                result = client.PostAsync("/data/DataManagementDefinitionGroups/Microsoft.Dynamics.DataEntities.GetExecutionSummaryStatus", httpContent).Result;
                resultContent = await result.Content.ReadAsStringAsync();
                outPut = JObject.Parse(resultContent).GetValue("value").ToString();

                Console.WriteLine("Status of export is " + outPut);

            }
            while (outPut == "NotRun" || outPut == "Executing");

            if (outPut != "Succeeded" && outPut != "PartiallySucceeded")
            {
                throw new Exception("Operation Failed");
            }
            else
            {
                // 3. Get downloable Url to download the package    
                stringPayload = JsonConvert.SerializeObject(new DMFExportSummary() { ExecutionId = execytionID });
                httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");

                result = client.PostAsync("/data/DataManagementDefinitionGroups/Microsoft.Dynamics.DataEntities.GetExportedPackageUrl", httpContent).Result;
                resultContent = await result.Content.ReadAsStringAsync();
                downloadUrl = JObject.Parse(resultContent).GetValue("value").ToString();
            }

            // Download the file from Url to a local folder
            Console.WriteLine("Downloading the file ...");
            var blob = new CloudBlockBlob(new Uri(downloadUrl));
            blob.DownloadToFile(Path.Combine(filePath, fileName + ".zip"), System.IO.FileMode.Create);
            Console.WriteLine("Downloading the file ...Complete");


        }
        else
        {
            Console.WriteLine("Initiating export of a data project...Failed");
        }
    }
}

Leave a Reply