Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ page 4580 "Ext. SharePoint Account"
}
}
field(Disabled; Rec.Disabled) { }
field("Use Graph API"; Rec."Use Graph API")
{
trigger OnValidate()
var
CheckBasePathMsg: Label 'The API type has been changed. Please verify that the Base Relative Folder Path is still correct for the selected API type.';
begin
Message(CheckBasePathMsg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we show this message when check it from true to false?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I get it now. Basically, you control both connector options with this boolean

I understand that there are no other implementations besides Legacy REST API and Graph API.

But why don't we just make it an enum value? Without using interfaces. Just to be more readable. In this case, we can, for example, make Graph API the default value, and it will look much better than boolean = true by default.

Also, maybe it will be another implementation in future, low chance, but worth to mention

end;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ table 4580 "Ext. SharePoint Account"
field(5; "Base Relative Folder Path"; Text[2048])
{
Caption = 'Base Relative Folder Path';
ToolTip = 'Specifies the folder path relative to the site collection. Start with the document library or folder name (e.g., Shared Documents/Reports). This path can be copied from the URL of the folder in SharePoint after the site collection (e.g., /Shared Documents/Reports from https://mysharepoint.sharepoint.com/sites/ProjectX/Shared%20Documents/Reports).';
ToolTip = 'Specifies the base folder path. For SharePoint REST API: Use the full server-relative path including the site (e.g., /sites/ProjectX/Shared Documents/Reports). For Microsoft Graph API: Use only the path relative to the document library (e.g., Reports). When using Graph API, the path should not include the site or document library root, only the folders within the library.';
}
field(6; "Tenant Id"; Guid)
{
Expand All @@ -50,6 +50,7 @@ table 4580 "Ext. SharePoint Account"
}
field(8; "Client Secret Key"; Guid)
{
Caption = 'Client Secret Key';
Access = Internal;
DataClassification = SystemMetadata;
}
Expand All @@ -66,14 +67,23 @@ table 4580 "Ext. SharePoint Account"
}
field(11; "Certificate Key"; Guid)
{
Caption = 'Certificate Key';
Access = Internal;
AllowInCustomizations = Never;
DataClassification = SystemMetadata;
}
field(12; "Certificate Password Key"; Guid)
{
Caption = 'Certificate Password Key';
Access = Internal;
AllowInCustomizations = Never;
DataClassification = SystemMetadata;
}
field(13; "Use Graph API"; Boolean)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be DataClassification = SystemMetadata; ?

{
Caption = 'Use Microsoft Graph API';
ToolTip = 'Specifies whether to use Microsoft Graph API or SharePoint REST API. Microsoft Graph API supports downloading files larger than 150 MB through chunked transfers. Note: Requires Microsoft Graph permissions (Sites.ReadWrite.All) configured in your app registration instead of SharePoint permissions.';
}
}

keys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ page 4581 "Ext. SharePoint Account Wizard"
Caption = 'Account Name';
NotBlank = true;
ShowMandatory = true;
ToolTip = 'Specifies a descriptive name for this SharePoint storage account connection.';

trigger OnValidate()
begin
Expand All @@ -54,7 +53,6 @@ page 4581 "Ext. SharePoint Account Wizard"
field("Tenant Id"; Rec."Tenant Id")
{
ShowMandatory = true;
ToolTip = 'Specifies the Microsoft Entra ID Tenant ID (Directory ID) where your SharePoint site and app registration are located.';

trigger OnValidate()
begin
Expand All @@ -65,7 +63,6 @@ page 4581 "Ext. SharePoint Account Wizard"
field("Client Id"; Rec."Client Id")
{
ShowMandatory = true;
ToolTip = 'Specifies the Client ID (Application ID) of the App Registration in Microsoft Entra ID.';

trigger OnValidate()
begin
Expand All @@ -75,7 +72,6 @@ page 4581 "Ext. SharePoint Account Wizard"

field("Authentication Type"; Rec."Authentication Type")
{
ToolTip = 'Specifies the authentication flow used for this SharePoint account. Client Secret uses User grant flow, which means that the user must sign in when using this account. Certificate uses Client credentials flow, which means that the user does not need to sign in when using this account.';
trigger OnValidate()
begin
UpdateAuthTypeVisibility();
Expand Down Expand Up @@ -134,6 +130,9 @@ page 4581 "Ext. SharePoint Account Wizard"
IsNextEnabled := SharePointConnectorImpl.IsAccountValid(Rec);
end;
}
field("Use Graph API"; Rec."Use Graph API")
{
}

field("Base Relative Folder Path"; Rec."Base Relative Folder Path")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@

namespace System.ExternalFileStorage;

using System.DataAdministration;
using System.Integration.Sharepoint;
using System.Text;
using System.Utilities;
using System.DataAdministration;

codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage Connector"
{
Expand All @@ -18,9 +16,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
Permissions = tabledata "Ext. SharePoint Account" = rimd;

var
RestHelper: Codeunit "Ext. SharePoint REST Helper";
GraphHelper: Codeunit "Ext. SharePoint Graph Helper";
ConnectorDescriptionTxt: Label 'Use SharePoint to store and retrieve files.', MaxLength = 250;
NotRegisteredAccountErr: Label 'We could not find the account. Typically, this is because the account has been deleted.';

#region File Operations

/// <summary>
/// Gets a List of Files stored on the provided account.
/// </summary>
Expand All @@ -30,28 +32,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <param name="TempFileAccountContent">A list with all files stored in the path.</param>
procedure ListFiles(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var TempFileAccountContent: Record "File Account Content" temporary)
var
SharePointFile: Record "SharePoint File";
SharePointClient: Codeunit "SharePoint Client";
OrginalPath: Text;
SharePointAccount: Record "Ext. SharePoint Account";
begin
OrginalPath := Path;
InitPath(AccountId, Path);
InitSharePointClient(AccountId, SharePointClient);
if not SharePointClient.GetFolderFilesByServerRelativeUrl(Path, SharePointFile) then
ShowError(SharePointClient);

FilePaginationData.SetEndOfListing(true);

if not SharePointFile.FindSet() then
exit;

repeat
TempFileAccountContent.Init();
TempFileAccountContent.Name := SharePointFile.Name;
TempFileAccountContent.Type := TempFileAccountContent.Type::"File";
TempFileAccountContent."Parent Directory" := CopyStr(OrginalPath, 1, MaxStrLen(TempFileAccountContent."Parent Directory"));
TempFileAccountContent.Insert();
until SharePointFile.Next() = 0;
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
GraphHelper.ListFiles(SharePointAccount, Path, FilePaginationData, TempFileAccountContent)
else
RestHelper.ListFiles(SharePointAccount, Path, FilePaginationData, TempFileAccountContent);
end;

/// <summary>
Expand All @@ -62,19 +49,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <param name="Stream">The Stream were the file is read to.</param>
procedure GetFile(AccountId: Guid; Path: Text; Stream: InStream)
var
SharePointClient: Codeunit "SharePoint Client";
Content: HttpContent;
TempBlobStream: InStream;
SharePointAccount: Record "Ext. SharePoint Account";
begin
InitPath(AccountId, Path);
InitSharePointClient(AccountId, SharePointClient);

if not SharePointClient.DownloadFileContentByServerRelativeUrl(Path, TempBlobStream) then
ShowError(SharePointClient);

// Platform fix: For some reason the Stream from DownloadFileContentByServerRelativeUrl dies after leaving the interface
Content.WriteFrom(TempBlobStream);
Content.ReadAs(Stream);
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
GraphHelper.GetFile(SharePointAccount, Path, Stream)
else
RestHelper.GetFile(SharePointAccount, Path, Stream);
end;

/// <summary>
Expand All @@ -85,17 +66,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <param name="Stream">The Stream were the file is read from.</param>
procedure CreateFile(AccountId: Guid; Path: Text; Stream: InStream)
var
SharePointFile: Record "SharePoint File";
SharePointClient: Codeunit "SharePoint Client";
ParentPath, FileName : Text;
SharePointAccount: Record "Ext. SharePoint Account";
begin
InitPath(AccountId, Path);
InitSharePointClient(AccountId, SharePointClient);
SplitPath(Path, ParentPath, FileName);
if SharePointClient.AddFileToFolder(ParentPath, FileName, Stream, SharePointFile, false) then
exit;

ShowError(SharePointClient);
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
GraphHelper.CreateFile(SharePointAccount, Path, Stream)
else
RestHelper.CreateFile(SharePointAccount, Path, Stream);
end;

/// <summary>
Expand All @@ -106,13 +83,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <param name="TargetPath">The target file path.</param>
procedure CopyFile(AccountId: Guid; SourcePath: Text; TargetPath: Text)
var
TempBlob: Codeunit "Temp Blob";
Stream: InStream;
SharePointAccount: Record "Ext. SharePoint Account";
begin
TempBlob.CreateInStream(Stream);

GetFile(AccountId, SourcePath, Stream);
CreateFile(AccountId, TargetPath, Stream);
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
GraphHelper.CopyFile(SharePointAccount, SourcePath, TargetPath)
else
RestHelper.CopyFile(SharePointAccount, SourcePath, TargetPath);
end;

/// <summary>
Expand All @@ -123,11 +100,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <param name="TargetPath">The target file path.</param>
procedure MoveFile(AccountId: Guid; SourcePath: Text; TargetPath: Text)
var
Stream: InStream;
SharePointAccount: Record "Ext. SharePoint Account";
begin
GetFile(AccountId, SourcePath, Stream);
CreateFile(AccountId, TargetPath, Stream);
DeleteFile(AccountId, SourcePath);
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
GraphHelper.MoveFile(SharePointAccount, SourcePath, TargetPath)
else
RestHelper.MoveFile(SharePointAccount, SourcePath, TargetPath);
end;

/// <summary>
Expand All @@ -138,16 +117,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <returns>Returns true if the file exists</returns>
procedure FileExists(AccountId: Guid; Path: Text): Boolean
var
SharePointFile: Record "SharePoint File";
SharePointClient: Codeunit "SharePoint Client";
SharePointAccount: Record "Ext. SharePoint Account";
begin
InitPath(AccountId, Path);
InitSharePointClient(AccountId, SharePointClient);
if not SharePointClient.GetFolderFilesByServerRelativeUrl(GetParentPath(Path), SharePointFile) then
ShowError(SharePointClient);

SharePointFile.SetRange(Name, GetFileName(Path));
exit(not SharePointFile.IsEmpty());
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
exit(GraphHelper.FileExists(SharePointAccount, Path))
else
exit(RestHelper.FileExists(SharePointAccount, Path));
end;

/// <summary>
Expand All @@ -157,14 +133,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <param name="Path">The file path inside the file account.</param>
procedure DeleteFile(AccountId: Guid; Path: Text)
var
SharePointClient: Codeunit "SharePoint Client";
SharePointAccount: Record "Ext. SharePoint Account";
begin
InitPath(AccountId, Path);
InitSharePointClient(AccountId, SharePointClient);
if SharePointClient.DeleteFileByServerRelativeUrl(Path) then
exit;

ShowError(SharePointClient);
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
GraphHelper.DeleteFile(SharePointAccount, Path)
else
RestHelper.DeleteFile(SharePointAccount, Path);
end;

/// <summary>
Expand All @@ -176,28 +151,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <param name="Files">A list with all directories stored in the path.</param>
procedure ListDirectories(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var TempFileAccountContent: Record "File Account Content" temporary)
var
SharePointFolder: Record "SharePoint Folder";
SharePointClient: Codeunit "SharePoint Client";
OrginalPath: Text;
SharePointAccount: Record "Ext. SharePoint Account";
begin
OrginalPath := Path;
InitPath(AccountId, Path);
InitSharePointClient(AccountId, SharePointClient);
if not SharePointClient.GetSubFoldersByServerRelativeUrl(Path, SharePointFolder) then
ShowError(SharePointClient);

FilePaginationData.SetEndOfListing(true);

if not SharePointFolder.FindSet() then
exit;

repeat
TempFileAccountContent.Init();
TempFileAccountContent.Name := SharePointFolder.Name;
TempFileAccountContent.Type := TempFileAccountContent.Type::Directory;
TempFileAccountContent."Parent Directory" := CopyStr(OrginalPath, 1, MaxStrLen(TempFileAccountContent."Parent Directory"));
TempFileAccountContent.Insert();
until SharePointFolder.Next() = 0;
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
GraphHelper.ListDirectories(SharePointAccount, Path, FilePaginationData, TempFileAccountContent)
else
RestHelper.ListDirectories(SharePointAccount, Path, FilePaginationData, TempFileAccountContent);
end;

/// <summary>
Expand All @@ -207,15 +167,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <param name="Path">The directory path inside the file account.</param>
procedure CreateDirectory(AccountId: Guid; Path: Text)
var
SharePointFolder: Record "SharePoint Folder";
SharePointClient: Codeunit "SharePoint Client";
SharePointAccount: Record "Ext. SharePoint Account";
begin
InitPath(AccountId, Path);
InitSharePointClient(AccountId, SharePointClient);
if SharePointClient.CreateFolder(Path, SharePointFolder) then
exit;

ShowError(SharePointClient);
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
GraphHelper.CreateDirectory(SharePointAccount, Path)
else
RestHelper.CreateDirectory(SharePointAccount, Path);
end;

/// <summary>
Expand All @@ -226,15 +184,13 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <returns>Returns true if the directory exists</returns>
procedure DirectoryExists(AccountId: Guid; Path: Text) Result: Boolean
var
SharePointClient: Codeunit "SharePoint Client";
SharePointAccount: Record "Ext. SharePoint Account";
begin
InitPath(AccountId, Path);
InitSharePointClient(AccountId, SharePointClient);

Result := SharePointClient.FolderExistsByServerRelativeUrl(Path);

if not SharePointClient.GetDiagnostics().IsSuccessStatusCode() then
ShowError(SharePointClient);
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
exit(GraphHelper.DirectoryExists(SharePointAccount, Path))
else
exit(RestHelper.DirectoryExists(SharePointAccount, Path));
end;

/// <summary>
Expand All @@ -244,16 +200,17 @@ codeunit 4580 "Ext. SharePoint Connector Impl" implements "External File Storage
/// <param name="Path">The directory path inside the file account.</param>
procedure DeleteDirectory(AccountId: Guid; Path: Text)
var
SharePointClient: Codeunit "SharePoint Client";
SharePointAccount: Record "Ext. SharePoint Account";
begin
InitPath(AccountId, Path);
InitSharePointClient(AccountId, SharePointClient);
if SharePointClient.DeleteFolderByServerRelativeUrl(Path) then
exit;

ShowError(SharePointClient);
SharePointAccount.Get(AccountId);
if SharePointAccount."Use Graph API" then
GraphHelper.DeleteDirectory(SharePointAccount, Path)
else
RestHelper.DeleteDirectory(SharePointAccount, Path);
end;

#endregion

/// <summary>
/// Gets the registered accounts for the SharePoint connector.
/// </summary>
Expand Down
Loading
Loading