Hi, Readers.
We discussed Dynamics 365 Business Central: How to import/read files from SharePoint (document library) to Business Central via AL (Graph API) last week. Today I would like to briefly introduce another situation, how to upload files from Business Central to SharePoint (document library) via AL (Graph API)
SharePoint and OneDrive in Microsoft 365 are cloud-based services that help organizations share and manage content, knowledge, and applications to:
- Empower teamwork
- Quickly find information
- Seamlessly collaborate across the organization
Last month I shared how to integrate files with OneDrive through Graph API.
- Dynamics 365 Business Central: How to import/read files from OneDrive to Business Central via AL (Graph API)
- Dynamics 365 Business Central: How to upload files from Business Central to OneDrive via AL (Graph API)
- Dynamics 365 Business Central: How to save reports directly to OneDrive via AL (Graph API) – Manual and Automatic
In this post we will mainly discuss how to upload files from Business Central to SharePoint. We also use Microsoft Graph API this time.
Microsoft Graph API: Microsoft Graph is a RESTful web API that enables you to access Microsoft Cloud service resources. After you register your app and get authentication tokens for a user or service, you can make requests to the Microsoft Graph API.
Registering your application establishes a trust relationship between your app and the Microsoft identity platform. This part of the setup is the same as in Using OAuth to connect Business Central APIs and Web Service in Postman. Just the API permissions are different.
To configure application permissions for the app in the app registrations experience on the Microsoft Entra admin center, follow these steps:
- On the application’s API permissions page, choose Add a permission.
- Select Microsoft Graph > select Application permissions.
- In the Select Permissions dialog, choose the permissions to configure to the app.
This time I only added Microsoft Graph -> Application -> Files.ReadWrite.All.

PS: Upload or replace the contents of a driveItem permission
Permission type | Least privileged permissions | Higher privileged permissions |
---|---|---|
Delegated (work or school account) | Files.ReadWrite | Files.ReadWrite.All, Sites.ReadWrite.All |
Delegated (personal Microsoft account) | Files.ReadWrite | Files.ReadWrite.All |
Application | Files.ReadWrite.All | Sites.ReadWrite.All |
Then we can test in Postman to see if it is accessible.
Enter the following info in Postman. (You can use variables instead to keep sensitive data secure)
Access Token URL: https://login.microsoftonline.com/d8f36038-1f93-4543-affc-5dc92b6ee871/oauth2/v2.0/token (Change it to your tenant ID)
Client ID: a80c03cf-6ffa-4b6e-b2c8-6005310d3d87
Client Secret: fME7Q~cAaSBhXMGZoHY3ei64nn1fxGpqF42mh
Scope: https://graph.microsoft.com/.default (Note that Scope is different from BC)
Cilent Authentication: Send client credentials in body

And my test SharePoint site:
https://2qcj3x.sharepoint.com/sites/BCShareFiles

Documents:

Business Central Folder:

Then we can get the GUID of the SharePoint Online site via adding the following _api/site/id at the end of the Site URL:
https://2qcj3x.sharepoint.com/sites/BCShareFiles/_api/site/id

SharePoint (site) id: 5b3b7cec-cbfe-4893-a638-c18a34c6a394

At this time we can get document libraries information.
Test Endpoint:
https://graph.microsoft.com/v1.0/sites/{site-id}/drives
For example,
https://graph.microsoft.com/v1.0/sites/5b3b7cec-cbfe-4893-a638-c18a34c6a394/drives
Documents (drive) id:
b!7Hw7W_7Lk0imOMGKNMajlK0n-8Wdev9FmPdhx03j5o95rz4xvtmtTIUW5qUH7Jww

If we want to get the file information in the folder, we need to use the following endpoint.
Test Endpoint:
https://graph.microsoft.com/v1.0/drives/{driveId}/root:/{folderPath}:/children
For example,
https://graph.microsoft.com/v1.0/sites/5b3b7cec-cbfe-4893-a638-c18a34c6a394/drives/b!7Hw7W_7Lk0imOMGKNMajlK0n-8Wdev9FmPdhx03j5o95rz4xvtmtTIUW5qUH7Jww/root:/Business%20Central:/children

Here we can get the file (item) id.
File (item) id: 01AA2EHNP46T7UJIJ6DZBYKJUOMSKBYWRZ

Finally, let’s read the contents of the file, 1988-S.jpg.

Test Endpoint:
https://graph.microsoft.com/v1.0/sites/{site-id}/drive/items/{item-id}/content
For example,
https://graph.microsoft.com/v1.0/sites/5b3b7cec-cbfe-4893-a638-c18a34c6a394/drive/items/01AA2EHNP46T7UJIJ6DZBYKJUOMSKBYWRZ/content

Let’s do one more test this time, try uploading a file to SharePoint from Postman. Here is the endpoint I tested (Including content).
PUT
https://graph.microsoft.com/v1.0/sites/{site-id}/drives/{drive-id}/root:/{folder}/{filename}:/content
For example, I want to upload the SharePointUploadTest.csv file to Documents (root) -> Business Central folder.


Endpoint
https://graph.microsoft.com/v1.0/sites/5b3b7cec-cbfe-4893-a638-c18a34c6a394/drives/b!7Hw7W_7Lk0imOMGKNMajlK0n-8Wdev9FmPdhx03j5o95rz4xvtmtTIUW5qUH7Jww/root:/Business Central/SharePointUploadTest.csv:/content
Then the http action is not Get, but Put.
Headers -> Content-Type -> text/csv:

Body -> binary -> upload file:

Choose Send.
201 Created: A new resource was created successfully.


If the specified folder is not found, a new folder will be created automatically.
Test video:
Very simple. More details: Working with SharePoint sites in Microsoft Graph and Upload or replace the contents of a driveItem
PS:
1. If you get the following error, please check the API permissions in App Registration.
“code”: “AccessDenied”,
“message”: “Either scp or roles claim need to be present in the token.”,

2. If you encounter a 401 Unauthorized error, please check whether the Scope is set to the graph API. (https://graph.microsoft.com/.default)
“code”: “InvalidAuthenticationToken”,
“message”: “Access token validation failure. Invalid audience.”,


3. If you use delegated permissions, use the following endpoint:
https://graph.microsoft.com/v1.0/me/drive/root/

4. When the file cannot be found, the following error message will be displayed:
“code”: “itemNotFound”,
“message”: “The resource could not be found.”

5. When opening the file in Postman, because it can only be opened in XML, HTML, Txt or Json format, the following situation may occur, but the test is successful.

OK, next let’s see if we can upload files from AL. Here are a few important points.
Get OAuth Token:

Using OAuth Token to upload file to SharePoint: Http action – Put in AL (If a file with the same name already exists, it will be overwritten)

This time I did two simple tests in total, I hope it can be of some help to everyone.
1. Upload a fixed file to SharePoint (Fixed name and type)
For example, upload the following Attachment file

In the Document Attachment (1173) table:




Test video:
Source Code: GitHub (Please note that the source code is for reference only, you can improve it according to your own needs)
pageextension 50100 CustomerListExt extends "Customer List"
{
actions
{
addafter("Sent Emails")
{
action(UploadFileToSharePoint)
{
Caption = 'Upload File to SharePoint';
ApplicationArea = All;
Promoted = true;
PromotedCategory = Process;
PromotedIsBig = true;
Image = Export;
trigger OnAction()
var
SharePointHandler: Codeunit SharePointHandler;
begin
SharePointHandler.UploadFilesToSharePoint();
end;
}
}
}
}
codeunit 50120 SharePointHandler
{
procedure UploadFilesToSharePoint()
var
HttpClient: HttpClient;
HttpRequestMessage: HttpRequestMessage;
HttpResponseMessage: HttpResponseMessage;
Headers: HttpHeaders;
ContentHeader: HttpHeaders;
RequestContent: HttpContent;
JsonResponse: JsonObject;
AuthToken: SecretText;
SharePointFileUrl: Text;
ResponseText: Text;
OutStream: OutStream;
FileContent: InStream;
DocAttach: Record "Document Attachment";
TempBlob: Codeunit "Temp Blob";
begin
// Get OAuth token
AuthToken := GetOAuthToken();
if AuthToken.IsEmpty() then
Error('Failed to obtain access token.');
if DocAttach.Get(18, 10000, Enum::"Attachment Document Type"::"Quote", 0, 53) then
if DocAttach."Document Reference ID".HasValue then begin
TempBlob.CreateOutStream(OutStream);
DocAttach."Document Reference ID".ExportStream(OutStream);
TempBlob.CreateInStream(FileContent);
end;
// Define the SharePoint folder URL
// application permissions (replace with the actual site-id, drive-id, folder path and file name)
SharePointFileUrl := 'https://graph.microsoft.com/v1.0/sites/5b3b7cec-cbfe-4893-a638-c18a34c6a394/drives/b!7Hw7W_7Lk0imOMGKNMajlK0n-8Wdev9FmPdhx03j5o95rz4xvtmtTIUW5qUH7Jww/root:/Business Central/SharePointUploadTest.csv:/content';
// Initialize the HTTP request
HttpRequestMessage.SetRequestUri(SharePointFileUrl);
HttpRequestMessage.Method := 'PUT';
HttpRequestMessage.GetHeaders(Headers);
Headers.Add('Authorization', SecretStrSubstNo('Bearer %1', AuthToken));
RequestContent.GetHeaders(ContentHeader);
ContentHeader.Clear();
ContentHeader.Add('Content-Type', 'text/csv');
HttpRequestMessage.Content.WriteFrom(FileContent);
// Send the HTTP request
if HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then begin
// Log the status code for debugging
//Message('HTTP Status Code: %1', HttpResponseMessage.HttpStatusCode());
if HttpResponseMessage.IsSuccessStatusCode() then begin
HttpResponseMessage.Content.ReadAs(ResponseText);
JsonResponse.ReadFrom(ResponseText);
Message(ResponseText); // If you don't need to return information, you can ignore this step.
end else begin
//Report errors!
HttpResponseMessage.Content.ReadAs(ResponseText);
Error('Failed to upload files to SharePoint: %1 %2', HttpResponseMessage.HttpStatusCode(), ResponseText);
end;
end else
Error('Failed to send HTTP request to SharePoint');
end;
procedure GetOAuthToken() AuthToken: SecretText
var
ClientID: Text;
ClientSecret: Text;
TenantID: Text;
AccessTokenURL: Text;
OAuth2: Codeunit OAuth2;
Scopes: List of [Text];
begin
ClientID := 'b4fe1687-f1ab-4bfa-b494-0e2236ed50bd';
ClientSecret := 'huL8Q~edsQZ4pwyxka3f7.WUkoKNcPuqlOXv0bww';
TenantID := '7e47da45-7f7d-448a-bd3d-1f4aa2ec8f62';
AccessTokenURL := 'https://login.microsoftonline.com/' + TenantID + '/oauth2/v2.0/token';
Scopes.Add('https://graph.microsoft.com/.default');
if not OAuth2.AcquireTokenWithClientCredentials(ClientID, ClientSecret, AccessTokenURL, '', Scopes, AuthToken) then
Error('Failed to get access token from response\%1', GetLastErrorText());
end;
}
2. Upload the selected file from Attachments to SharePoint
To do this, we need to get the File Name and Mime Type first.
File Name in Document Attachment table:

Mime Type in Tenant Media table:

For example,

Simple test:



Test video:
Source Code: GitHub (Please note that the source code is for reference only, you can improve it according to your own needs)
pageextension 50100 DocumentAttachmentDetailsExt extends "Document Attachment Details"
{
actions
{
addafter(UploadFile)
{
action(UploadFileToSharePoint)
{
Caption = 'Upload File to SharePoint';
ApplicationArea = All;
Promoted = true;
PromotedCategory = Process;
PromotedIsBig = true;
Image = GetActionMessages;
trigger OnAction()
var
SharePointHandler: Codeunit SharePointHandler;
DocAttach: Record "Document Attachment";
begin
DocAttach.Reset();
CurrPage.SetSelectionFilter(DocAttach);
if DocAttach.FindFirst() then
SharePointHandler.UploadFilesToSharePoint(DocAttach);
end;
}
}
}
}
codeunit 50120 SharePointHandler
{
procedure UploadFilesToSharePoint(DocAttach: Record "Document Attachment")
var
HttpClient: HttpClient;
HttpRequestMessage: HttpRequestMessage;
HttpResponseMessage: HttpResponseMessage;
Headers: HttpHeaders;
ContentHeader: HttpHeaders;
RequestContent: HttpContent;
JsonResponse: JsonObject;
AuthToken: SecretText;
SharePointFileUrl: Text;
ResponseText: Text;
OutStream: OutStream;
FileContent: InStream;
TempBlob: Codeunit "Temp Blob";
FileName: Text;
TenantMedia: Record "Tenant Media";
MimeType: Text;
begin
// Get OAuth token
AuthToken := GetOAuthToken();
if AuthToken.IsEmpty() then
Error('Failed to obtain access token.');
if DocAttach."Document Reference ID".HasValue then begin
TempBlob.CreateOutStream(OutStream);
DocAttach."Document Reference ID".ExportStream(OutStream);
TempBlob.CreateInStream(FileContent);
FileName := DocAttach."File Name" + '.' + DocAttach."File Extension";
if TenantMedia.Get(DocAttach."Document Reference ID".MediaId) then
MimeType := TenantMedia."Mime Type";
end;
// Define the SharePoint folder URL
// application permissions (replace with the actual site-id, drive-id, folder path and file name)
SharePointFileUrl := 'https://graph.microsoft.com/v1.0/sites/5b3b7cec-cbfe-4893-a638-c18a34c6a394/drives/b!7Hw7W_7Lk0imOMGKNMajlK0n-8Wdev9FmPdhx03j5o95rz4xvtmtTIUW5qUH7Jww/root:/Business Central/' + FileName + ':/content';
// Initialize the HTTP request
HttpRequestMessage.SetRequestUri(SharePointFileUrl);
HttpRequestMessage.Method := 'PUT';
HttpRequestMessage.GetHeaders(Headers);
Headers.Add('Authorization', SecretStrSubstNo('Bearer %1', AuthToken));
RequestContent.GetHeaders(ContentHeader);
ContentHeader.Clear();
ContentHeader.Add('Content-Type', MimeType);
HttpRequestMessage.Content.WriteFrom(FileContent);
// Send the HTTP request
if HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then begin
// Log the status code for debugging
//Message('HTTP Status Code: %1', HttpResponseMessage.HttpStatusCode());
if HttpResponseMessage.IsSuccessStatusCode() then begin
HttpResponseMessage.Content.ReadAs(ResponseText);
JsonResponse.ReadFrom(ResponseText);
Message(ResponseText);// If you don't need to return information, you can ignore this step.
end else begin
//Report errors!
HttpResponseMessage.Content.ReadAs(ResponseText);
Error('Failed to upload files to SharePoint: %1 %2', HttpResponseMessage.HttpStatusCode(), ResponseText);
end;
end else
Error('Failed to send HTTP request to SharePoint');
end;
procedure GetOAuthToken() AuthToken: SecretText
var
ClientID: Text;
ClientSecret: Text;
TenantID: Text;
AccessTokenURL: Text;
OAuth2: Codeunit OAuth2;
Scopes: List of [Text];
begin
ClientID := 'b4fe1687-f1ab-4bfa-b494-0e2236ed50bd';
ClientSecret := 'huL8Q~edsQZ4pwyxka3f7.WUkoKNcPuqlOXv0bww';
TenantID := '7e47da45-7f7d-448a-bd3d-1f4aa2ec8f62';
AccessTokenURL := 'https://login.microsoftonline.com/' + TenantID + '/oauth2/v2.0/token';
Scopes.Add('https://graph.microsoft.com/.default');
if not OAuth2.AcquireTokenWithClientCredentials(ClientID, ClientSecret, AccessTokenURL, '', Scopes, AuthToken) then
Error('Failed to get access token from response\%1', GetLastErrorText());
end;
}
At this point, I believe you should be able to understand how to upload file from Business Central to SharePoint. You can also set these process in the Job Queue so that the system can run automatically. Give it a try!!!😁
END
Hope this will help.
Thanks.
ZHU
コメント