|
| 1 | +# Business logic |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The SharePoint connector has two responsibilities: managing SharePoint account configurations and executing file/directory operations against SharePoint Online. All business logic lives in `ExtSharePointConnectorImpl.Codeunit.al`, which implements the `External File Storage Connector` interface. The account table handles credential storage, and the wizard page guides initial account creation. |
| 6 | + |
| 7 | +## Account registration |
| 8 | + |
| 9 | +Account creation uses a wizard page (`Ext. SharePoint Account Wizard`) running on a temporary record. The wizard collects: account name, tenant ID, client ID, authentication type, credentials (client secret or certificate + optional password), SharePoint URL, and base folder path. Each field validates via `IsAccountValid` (all required fields non-empty, tenant/client IDs non-null). The "Next" action calls `CreateAccount`, which: |
| 10 | + |
| 11 | +1. Copies fields from the temporary record to a new `Ext. SharePoint Account` record |
| 12 | +2. Generates a new Guid as the account ID |
| 13 | +3. Stores credentials in IsolatedStorage based on auth type |
| 14 | +4. Inserts the record |
| 15 | +5. Returns a `File Account` record to the framework |
| 16 | + |
| 17 | +```mermaid |
| 18 | +flowchart TD |
| 19 | + A[User opens wizard] --> B[Fill connection details] |
| 20 | + B --> C{All required fields valid?} |
| 21 | + C -->|No| D[Next button disabled] |
| 22 | + C -->|Yes| E[Click Next] |
| 23 | + E --> F{Auth type?} |
| 24 | + F -->|Client Secret| G[Store secret in IsolatedStorage] |
| 25 | + F -->|Certificate| H[Store cert + optional password in IsolatedStorage] |
| 26 | + G --> I[Insert Ext. SharePoint Account record] |
| 27 | + H --> I |
| 28 | + I --> J[Return File Account to framework] |
| 29 | +``` |
| 30 | + |
| 31 | +## SharePoint client initialization |
| 32 | + |
| 33 | +`InitSharePointClient` is called before every file operation. It loads the account, checks the `Disabled` flag (errors if true), then dispatches based on auth type: |
| 34 | + |
| 35 | +- **Client Secret**: calls `SharePointAuth.CreateAuthorizationCode` with the tenant ID, client ID, secret (fetched from IsolatedStorage), and the SharePoint resource scope (`00000003-0000-0ff1-ce00-000000000000/.default`). |
| 36 | +- **Certificate**: calls `SharePointAuth.CreateClientCredentials` with the tenant ID, client ID, certificate stream (decoded from Base64 in IsolatedStorage), optional certificate password, and the same scope. |
| 37 | + |
| 38 | +The resulting authorization object is passed to `SharePointClient.Initialize` along with the account's SharePoint URL. |
| 39 | + |
| 40 | +## Path composition |
| 41 | + |
| 42 | +This is the most complex part of the connector. SharePoint REST API requires server-relative URLs like `/sites/ProjectX/Shared Documents/Reports/file.pdf`. The `InitPath` procedure builds these from three inputs: |
| 43 | + |
| 44 | +1. **SharePoint URL** (e.g., `https://contoso.sharepoint.com/sites/ProjectX`) -- `GetSitePathFromUrl` parses this via the `Uri` codeunit to extract `/sites/ProjectX` |
| 45 | +2. **Base Relative Folder Path** (e.g., `Shared Documents`) -- configured on the account |
| 46 | +3. **Caller's relative path** (e.g., `Reports/file.pdf`) -- passed to each file operation |
| 47 | + |
| 48 | +These are combined with `CombinePath` (handles slash normalization), then the site path is prepended if not already present. Helper procedures `GetParentPath` and `GetFileName` split paths at the last `/` for operations like `CreateFile` (which needs the parent folder and filename separately for `SharePointClient.AddFileToFolder`). |
| 49 | + |
| 50 | +## File and directory operations |
| 51 | + |
| 52 | +Every operation follows the same pattern: `InitPath` to compose the server-relative URL, `InitSharePointClient` to get an authenticated client, call the appropriate `SharePointClient` method, check the boolean return value, and call `ShowError` (which reads `SharePointClient.GetDiagnostics().GetErrorMessage()`) if it failed. |
| 53 | + |
| 54 | +### Notable operation details |
| 55 | + |
| 56 | +- **CopyFile** downloads the source file to a TempBlob InStream via `GetFile`, then re-uploads via `CreateFile`. The entire file passes through BC server memory. |
| 57 | +- **MoveFile** does the same as CopyFile, then deletes the source file. This is a three-step operation (download, upload, delete) -- not atomic. A failure during upload or delete leaves partial state. |
| 58 | +- **FileExists** has no direct API equivalent. The connector calls `ListFiles` on the parent directory, then iterates results checking if `ServerRelativeUrl` matches the target path. This means checking existence requires loading the full directory listing. |
| 59 | +- **DirectoryExists** uses `SharePointClient.FolderExistsByServerRelativeUrl` -- unlike FileExists, this has a dedicated API call. |
| 60 | +- **ListDirectories** filters results by the `SharePoint Folder` record type (not `SharePoint File`), similar to how ListFiles filters. |
| 61 | +- **GetFile** wraps the downloaded stream through an HttpContent intermediary due to a platform stream lifetime issue. |
| 62 | + |
| 63 | +## Environment cleanup |
| 64 | + |
| 65 | +The codeunit subscribes to `EnvironmentCleanup.OnClearCompanyConfig`. When a sandbox is created from production, the subscriber sets `Disabled = true` on all active SharePoint accounts. This prevents sandbox environments from accidentally modifying files on production SharePoint sites. There is no automated undo -- admins must manually re-enable accounts. |
0 commit comments