Hochladen und Herunterladen einer benutzerdefinierten Codeunit
Das mit Document Central integrierte Business-Portals-System lässt sich bei Upload- oder Download-Vorgängen über die Document-Central-Funktion an die individuellen Bedürfnisse des Benutzers anpassen.
Dieser Abschnitt enthält ein Beispiel für die Implementierung einer benutzerdefinierten Codeunit, die verwendet wird, wenn der Benutzer Dokumente über Document Central hoch- oder herunterlädt.
Code-Beispiel
In diesem Beispiel verwenden wir die Tabelle „Temp“ in einem Szenario, in dem beim Hochladen von Dokumenten das Feld „Content Type“ ausgefüllt wird, um den Inhalt nach seiner Dateiendung zu filtern; diese Tabelle wird auch für das Herunterladen der Dateien verwendet.
AL
codeunit 5492859 "SIM_DPS Sample Upload CU"
{
TableNo = "SIM_DPS Temp Table";
/// <summary>
/// Entry point for the custom upload procedure configured in SIM_DPS Setup.
/// It keeps the sample inside the DPS app and delegates the real upload to installed connector apps through DPS integration events.
/// </summary>
/// <returns></returns>
trigger OnRun()
var
LocalCodeunitTempBlob: Codeunit "Temp Blob";
LocalSucceedBoolean: Boolean;
LocalUploadEntryNoBigInteger: BigInteger;
LocalErrorText: Text;
LocalHasCachedUploadBlobBoolean: Boolean;
begin
// Start with a negative result so the connector must explicitly mark the upload as handled.
LocalSucceedBoolean := false;
// Fill Rec.ContentType when DPS did not pass it, because the DMS connector uses this value as the attachment category/content type.
EnsureAttachmentCategoryName(Rec);
// Keep a copy of the uploaded file so the SIM_DPS Upload page can still preview it after the connector clears FileData.
LocalHasCachedUploadBlobBoolean := CacheUploadFileData(Rec, LocalCodeunitTempBlob, LocalUploadEntryNoBigInteger);
// Clear previous runtime errors so GetLastErrorText() only reports errors from the connector event call below.
ClearLastError();
// Keep the sample in DPS and let installed connector apps handle the real archive target.
if not TryUploadThroughConnector(LocalSucceedBoolean, Rec, Rec."Table No." = Database::"SIM_DPS OL") then begin
// TryFunction suppresses the connector error; read it back and show it as a message for clearer portal setup feedback.
LocalErrorText := GetLastErrorText();
if LocalErrorText = '' then
LocalErrorText := GlobalNoConnectorErrorLabelMsg;
Message(GlobalUploadFailedWithReasonLabelMsg, LocalErrorText);
exit;
end;
// A successful event call can still mean no connector accepted the upload, so report that separately.
if not LocalSucceedBoolean then
Message(GlobalUploadFailedWithReasonLabelMsg, GlobalNoConnectorErrorLabelMsg);
// The connector archives the file and then clears SIM_DPS Upload.FileData; restore it for documentation/demo viewing.
if LocalSucceedBoolean and LocalHasCachedUploadBlobBoolean then
RestoreUploadFileData(LocalUploadEntryNoBigInteger, LocalCodeunitTempBlob);
end;
/// <summary>
/// Raises the DPS upload integration event that connector apps subscribe to.
/// The TryFunction converts connector errors into a Boolean failure so OnRun can show the error with Message() instead of hard Error().
/// </summary>
/// <param name="ParamSucceedBoolean">Set to true by the connector when the upload was processed successfully.</param>
/// <param name="TempParamRecordSIMDPSTempTable">Temporary DPS context record that contains table, record, company, and content type information.</param>
/// <param name="ParamOrderUploadBoolean">True when the upload belongs to a SIM_DPS OL order flow; false for normal/manual uploads.</param>
/// <returns></returns>
[TryFunction]
local procedure TryUploadThroughConnector(var ParamSucceedBoolean: Boolean; var TempParamRecordSIMDPSTempTable: Record "SIM_DPS Temp Table" temporary; ParamOrderUploadBoolean: Boolean)
begin
// Order uploads need the order-specific event because connector upload logic resolves the created target document from SIM_DPS OL.
if ParamOrderUploadBoolean then
GlobalCodeunitSIMDPSEventPublisher.OnCheckUploadsOrder(ParamSucceedBoolean, TempParamRecordSIMDPSTempTable)
else
// Normal/manual uploads use the standard upload event and identify the pending upload by Entry No.
GlobalCodeunitSIMDPSEventPublisher.OnCheckUploads(ParamSucceedBoolean, TempParamRecordSIMDPSTempTable);
end;
/// <summary>
/// Ensures that the temporary DPS context contains the attachment category/content type name.
/// The DMS-to-DPS connector reads Rec.ContentType when creating the Document Central entry, but order uploads may arrive without this value set.
/// </summary>
/// <param name="TempParamRecordSIMDPSTempTable">Temporary DPS context record passed to the configured custom upload codeunit.</param>
/// <returns></returns>
local procedure EnsureAttachmentCategoryName(var TempParamRecordSIMDPSTempTable: Record "SIM_DPS Temp Table" temporary)
var
LocalRecordSIMDPSUpload: Record "SIM_DPS Upload";
begin
// If DPS already supplied the category/content type, keep it unchanged.
if TempParamRecordSIMDPSTempTable.ContentType <> '' then
exit;
LocalRecordSIMDPSUpload.Reset();
// Manual uploads pass the upload entry no. in No. BigInt, which is the most precise lookup.
if TempParamRecordSIMDPSTempTable."No. BigInt" <> 0 then
LocalRecordSIMDPSUpload.SetRange("Entry No.", TempParamRecordSIMDPSTempTable."No. BigInt")
else
// Order uploads do not pass an entry no.; they are linked to the SIM_DPS Upload record by Record ID.
LocalRecordSIMDPSUpload.SetRange("Record ID", TempParamRecordSIMDPSTempTable.RecID);
// Restrict to the current company and pending uploaded files so completed/canceled rows cannot supply a stale category.
LocalRecordSIMDPSUpload.SetRange(Company, TempParamRecordSIMDPSTempTable.Company);
LocalRecordSIMDPSUpload.SetRange(Status, LocalRecordSIMDPSUpload.Status::Uploaded);
// Copy the upload content type into the temp record; connector apps then receive it through the event parameter.
if LocalRecordSIMDPSUpload.FindFirst() then
TempParamRecordSIMDPSTempTable.ContentType := CopyStr(LocalRecordSIMDPSUpload."Content Type", 1, MaxStrLen(TempParamRecordSIMDPSTempTable.ContentType));
end;
/// <summary>
/// Copies the original SIM_DPS Upload.FileData into a Temp Blob before the connector processes the upload.
/// The copy is only needed for normal upload-list entries, because the View action reads the file from SIM_DPS Upload.FileData.
/// </summary>
/// <param name="TempParamRecordSIMDPSTempTable">Temporary DPS context record passed to the configured custom upload codeunit.</param>
/// <param name="ParamCodeunitTempBlob">Temp Blob that receives a copy of the uploaded file content.</param>
/// <param name="ParamUploadEntryNoBigInteger">Entry No. of the SIM_DPS Upload row that should be restored later.</param>
/// <returns>True when a file blob was found and cached; otherwise false.</returns>
local procedure CacheUploadFileData(TempParamRecordSIMDPSTempTable: Record "SIM_DPS Temp Table" temporary; var ParamCodeunitTempBlob: Codeunit "Temp Blob"; var ParamUploadEntryNoBigInteger: BigInteger): Boolean
var
LocalRecordSIMDPSUpload: Record "SIM_DPS Upload";
begin
// Only normal uploads pass an Entry No.; order uploads can contain multiple linked files and are not shown on page 5163558.
if TempParamRecordSIMDPSTempTable."No. BigInt" = 0 then
exit(false);
if not LocalRecordSIMDPSUpload.Get(TempParamRecordSIMDPSTempTable."No. BigInt") then
exit(false);
if not LocalRecordSIMDPSUpload.FileData.HasValue() then
exit(false);
// Copy the BLOB before the connector clears it during successful upload processing.
LocalRecordSIMDPSUpload.CalcFields(FileData);
Clear(ParamCodeunitTempBlob);
ParamCodeunitTempBlob.FromRecord(LocalRecordSIMDPSUpload, LocalRecordSIMDPSUpload.FieldNo(FileData));
ParamUploadEntryNoBigInteger := LocalRecordSIMDPSUpload."Entry No.";
exit(true);
end;
/// <summary>
/// Restores SIM_DPS Upload.FileData after a successful connector upload.
/// This keeps the Upload page View action usable while still allowing the connector to complete its archive process.
/// </summary>
/// <param name="ParamUploadEntryNoBigInteger">Entry No. of the SIM_DPS Upload row to restore.</param>
/// <param name="ParamCodeunitTempBlob">Temp Blob containing the cached original uploaded file content.</param>
local procedure RestoreUploadFileData(ParamUploadEntryNoBigInteger: BigInteger; var ParamCodeunitTempBlob: Codeunit "Temp Blob")
var
LocalRecordSIMDPSUpload: Record "SIM_DPS Upload";
LocalInStream: InStream;
LocalOutStream: OutStream;
begin
if not LocalRecordSIMDPSUpload.Get(ParamUploadEntryNoBigInteger) then
exit;
// Do not overwrite content if another handler already kept or restored the file.
if LocalRecordSIMDPSUpload.FileData.HasValue() then
exit;
ParamCodeunitTempBlob.CreateInStream(LocalInStream);
LocalRecordSIMDPSUpload.FileData.CreateOutStream(LocalOutStream);
CopyStream(LocalOutStream, LocalInStream);
LocalRecordSIMDPSUpload.Modify();
end;
var
GlobalCodeunitSIMDPSEventPublisher: Codeunit "SIM_DPS Event Publisher";
GlobalNoConnectorErrorLabelMsg: Label 'No connector processed the upload. Please check that the connector is installed, published, and configured.';
GlobalUploadFailedWithReasonLabelMsg: Label 'The upload could not be processed. Reason: %1', Comment = '%1 = Error reason';
}
AL
codeunit 5492860 "SIM_DPS Sample Download CU"
{
TableNo = "SIM_DPS Temp Table";
/// <summary>
/// Entry point for the custom download procedure configured in SIM_DPS Setup.
/// It delegates file listing and file download to installed connector apps through DPS integration events.
/// </summary>
/// <returns></returns>
trigger OnRun()
begin
case UpperCase(Rec.Param) of
// First portal call: connector writes the file list XML into Blob.
'GETFILES':
GlobalCodeunitSIMDPSEventPublisher.OnDeserializeDownloadGetFile(Rec);
// Second portal call: connector writes the selected file content into File Blob.
'DOWNLOADFILE':
begin
FetchDocumentAttachment(Rec);
EnsureDownloadedFileWasReturned(Rec);
end;
else
// Unknown actions are not sent to connector code because the connector only handles the two portal download actions above.
Message(GlobalUnknownDownloadActionLabelMsg, Rec.Param);
end;
end;
/// <summary>
/// Fetches the requested file directly from Business Central's Document Attachment table.
/// </summary>
/// <returns></returns>
local procedure FetchDocumentAttachment(var TempParamRecordSIMDPSTempTable: Record "SIM_DPS Temp Table" temporary)
var
LocalRecordDocumentAttachment: Record "Document Attachment";
LocalRecordTenantMedia: Record "Tenant Media";
LocalCodeunitDocumentAttachmentMgmt: Codeunit "Document Attachment Mgmt";
LocalRecordRef: RecordRef;
LocalBaseFileNameText: Text;
LocalFileExtText: Text;
LocalInStream: InStream;
LocalOutStream: OutStream;
begin
// Open the target business record based on the incoming Record ID
if not LocalRecordRef.Get(TempParamRecordSIMDPSTempTable.RecID) then
exit;
// Filter the Document Attachment table for this specific record
LocalCodeunitDocumentAttachmentMgmt.SetDocumentAttachmentFiltersForRecRef(LocalRecordDocumentAttachment, LocalRecordRef);
// Split the requested filename into Base Name and Extension to match BC's table structure
SplitFilename(TempParamRecordSIMDPSTempTable.Filename, LocalBaseFileNameText, LocalFileExtText);
LocalRecordDocumentAttachment.SetRange("File Name", CopyStr(LocalBaseFileNameText, 1, MaxStrLen(LocalRecordDocumentAttachment."File Name")));
if LocalFileExtText <> '' then
LocalRecordDocumentAttachment.SetRange("File Extension", CopyStr(LocalFileExtText, 1, MaxStrLen(LocalRecordDocumentAttachment."File Extension")));
// If the file is found, stream the Media content into the Temp Table's File Blob
if LocalRecordDocumentAttachment.FindFirst() then
if LocalRecordDocumentAttachment."Document Reference ID".HasValue then
if LocalRecordTenantMedia.Get(LocalRecordDocumentAttachment."Document Reference ID".MediaId) then begin
LocalRecordTenantMedia.CalcFields(Content);
LocalRecordTenantMedia.Content.CreateInStream(LocalInStream);
TempParamRecordSIMDPSTempTable."File Blob".CreateOutStream(LocalOutStream);
CopyStream(LocalOutStream, LocalInStream);
end;
end;
/// <summary>
/// Splits a full filename into base name and extension for Document Attachment filtering.
/// </summary>
/// <returns></returns>
local procedure SplitFilename(ParamFilenameText: Text; var ParamBaseFilenameText: Text; var ParamExtensionText: Text)
var
LocalInteger: Integer;
begin
ParamBaseFilenameText := ParamFilenameText;
ParamExtensionText := '';
for LocalInteger := StrLen(ParamFilenameText) downto 1 do
if ParamFilenameText[LocalInteger] = '.' then begin
ParamBaseFilenameText := CopyStr(ParamFilenameText, 1, LocalInteger - 1);
ParamExtensionText := CopyStr(ParamFilenameText, LocalInteger + 1);
exit;
end;
end;
local procedure EnsureDownloadedFileWasReturned(var TempParamRecordSIMDPSTempTable: Record "SIM_DPS Temp Table" temporary)
begin
if TempParamRecordSIMDPSTempTable."File Blob".HasValue() then
exit;
Error(GlobalEmptyDownloadFileLabelErr, TempParamRecordSIMDPSTempTable.Filename);
end;
var
GlobalCodeunitSIMDPSEventPublisher: Codeunit "SIM_DPS Event Publisher";
GlobalEmptyDownloadFileLabelErr: Label 'The download did not return file content for %1. Please check the DMS document entry and connector setup.', Comment = '%1 = Filename';
GlobalUnknownDownloadActionLabelMsg: Label 'Unknown download action %1.', Comment = '%1 = Download action';
}