Calling a C# Async Function from a Dynamics Finance and Operations Project (X++)

Hello everyone,

I had a C# code that allowed me to upload a file to SharePoint. Initially, I created this code using a class library. Later, I changed it to a Windows application to test it, and it worked perfectly. Then, I changed it back to a class library to get the DLL.

However, I need to use this code in my X++ project to upload a stream to a SharePoint library. I tried two approaches:

1-Using the DLL of the C# project I created.
2-Embedding the C# code directly within my X++ project.

Despite these efforts, I still encounter an error. The error message says:

Error: An error occurred while uploading the file: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.TypeLoadException: Could not load type ‘Microsoft.Graph.Models.UploadSession’ from assembly ‘Microsoft.Graph, Version=4.24.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’. at MSGraphConnection.MSGraphApi.uploadFileToSharepointOnlineAsync() at Dynamics.AX.Application.uploadFile.`uploadmethod in xppSource://Source/Model\AxClass_uploadFile.xpp:line 60 — End of inner exception stack trace —

Could anyone help me resolve this issue?

///////////////////////////////////////////////////////////////////
the C# code is :
using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Graph.Drives.Item.Items.Item.CreateUploadSession;
using Microsoft.Graph.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Dynamics.Ax.Xpp;

namespace MSGraphConnection
{
public class MSGraphApi
{
public static async Task uploadFileToSharepointOnlineAsync()
{
try
{
var tenantId = “your-tenant-id”;
var clientId = “your-client-id”;
var clientSecret = “your-client-secret”;
string site = “your-sharepoint-site”;
string siteId = “your-site-id”;
var driveId = “your-drive-id”;
var fileName = “test.txt”;
var filePath = “C:/path/to/your/file.txt”;

            var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);

            var scopes = new[] { "https://graph.microsoft.com/.default" };

            GraphServiceClient graphClient = new GraphServiceClient(clientSecretCredential, scopes);

            using (var localFileStream = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                CreateUploadSessionPostRequestBody uploadSessionRequestBody = new CreateUploadSessionPostRequestBody
                {
                    Item = new DriveItemUploadableProperties
                    {
                        AdditionalData = new Dictionary<string, object>
                        {
                            { "@microsoft.graph.conflictBehavior", "replace" }, // fail, replace, or rename
                        },
                    },
                };

                var uploadSession = await graphClient.Drives[driveId]
                                                    .Items["root"]
                                                    .ItemWithPath($"TEST/{fileName}")
                                                    .CreateUploadSession
                                                    .PostAsync(uploadSessionRequestBody);

                int maxSliceSize = 320 * 1024;
                Microsoft.Graph.LargeFileUploadTask<Microsoft.Graph.Models.DriveItem> fileUploadTask = new Microsoft.Graph.LargeFileUploadTask<Microsoft.Graph.Models.DriveItem>(uploadSession, localFileStream, maxSliceSize, graphClient.RequestAdapter);
                long totalLength = localFileStream.Length;
                System.IProgress<long> progress = new System.Progress<long>(prog => Console.WriteLine($"Uploaded {prog} bytes of {totalLength} bytes"));
                try
                {
                    Microsoft.Graph.UploadResult<Microsoft.Graph.Models.DriveItem> uploadResult = await fileUploadTask.UploadAsync(progress);
                    return "success";
                }
                catch (ServiceException ex)
                {
                    return "failed";
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
            return "failed something";
        }
    }
}

}

////////////////////////////////////////////////////////////////////

and x++ code is :

io.writeExp(lineData);
stream = io.getStream();
stream.Position = 0;
reader = new System.IO.StreamReader(stream);
fileContent = reader.ReadToEnd();
str fileName = ‘testfile.csv’;
File::SendStringAsFileToUser(fileContent, fileName, System.Text.Encoding::UTF8);

    var task = MSGraphConnection.MSGraphApi::uploadFileToSharepointOnlineAsync();
    task.Wait();
    // Get the result of the task
    str result = task.get_Result();
    info(result);

//////////////////////////////////////////////////////////////////

2 Likes

Some one have answer on that ?

2 Likes

the same thing, still have this error

2 Likes

It seems that your code has a dependency on Microsoft.Graph, Version=4.24.0.0. Does your F&O environment contains this assembly? Mine doesn’t; it uses version 1.7.0.0 instead. If your library requires an assembly that doesn’t exist there, it’s expected that it won’t work.

3 Likes

Yes Martin, you are right, but we are changing how the work will be, so please, if you have a pure X++ example to upload a file from D365 to SharePoint via REST API(graph api if it possible because we will use the driveid), because we use a token generated by Azure, I would be very appreciative. :slight_smile:

2 Likes

I don’t think that trying to re-implement all the .NET libraries in X++ is a wise and viable idea.
Instead, either use .NET assemblies that are available in F&O or that you can add there, or do the work outside F&O (e.g. you can do it in an Azure function and calling this function from X++).

3 Likes

@Khiar_Mohamed - I agree with Martin, don’t try to reinvent the wheel with X++ with C# already has what you need.

What you need to do though is to ensure all DLLs you need from your C# library are available in the solution you deploy. You can either copy all DLLs from the bin folder of your .NET build or you can look at embedding the DLLs into your class library so you only have one DLL to deploy.