Support for MultipartFormDataContent
Hi,
I'm writing an extension that needs to send a CSV file to a 3rd party webservice. In C/AL I would've used HttpClient and the MultipartFormDataContent class, however in AL I only have HttpClient, not MultipartFormDataContent. Are there any plans to include this? If not, does anyone know of a way to get around this limitation?
Thanks in advance, Paul
Maybe this Code can help you, it is written in C/AL (but maybe you can translate it to AL) and constructs the multipart form data manually:
`// Multipart Mgt DateTime := DateTime.Now(); boundary := '----------------------------' + FORMAT(DateTime.Ticks);
HttpRequestMgt.Initialize(GetApiBaseUri + relativeUri); HttpRequestMgt.DisableUI; HttpRequestMgt.SetContentType('application/octet-stream'); HttpRequestMgt.SetReturnType('application/json'); HttpRequestMgt.SetContentType(STRSUBSTNO('multipart/form-data; charset=UTF-8; boundary=%1', boundary)); HttpRequestMgt.SetMethod('POST'); CreateHeader(HttpRequestMgt);
// Multipart Data memStream := memStream.MemoryStream(); StreamWriter := StreamWriter.StreamWriter(memStream, Encoding.UTF8, 2048, TRUE); // Leave Stream Open
// Start Part of Multipart StreamWriter.Write(CRLF +'--' + boundary + CRLF);
// File Content Disposition StreamWriter.Write( STRSUBSTNO('Content-Disposition: form-data; name="%1"; filename="%2"' + CRLF + 'Content-Type: application/octet-stream' + CRLF + CRLF, 'file', Filename)); StreamWriter.Flush;
// File Content Data TempBlob.Blob.CREATEINSTREAM(IStream); baseStream := IStream; baseStream.CopyTo(memStream, 2048);
// last boundary StreamWriter.Write(CRLF +'--' + boundary + '--' + CRLF);
// Flush Stream Writer (Save to Stream) StreamWriter.Flush;
// Write Memory Stream to TempBlob & to Body TempBlob2.Blob.CREATEOUTSTREAM(OStream); COPYSTREAM(OStream, memStream); HttpRequestMgt.AddBodyBlob(TempBlob2);
CreateResponseStream(ResponseStream);
IF HttpRequestMgt.GetResponse(ResponseStream, HttpStatusCode, ResponseHeaders) THEN BEGIN
StreamReader := StreamReader.StreamReader(ResponseStream, Encoding.UTF8);
EVALUATE(DocumentId, StreamReader.ReadToEnd());
END ELSE BEGIN
HttpRequestMgt.ProcessFaultResponse('');
END;`
Hi @paulcart . We do have adding more http types on our backlog but right now we only support HttpContent, HttpClient, HttpRequestMessage, HttpResponseMessage and HttpHeaders.
Please consider making a proposal for the https://github.com/Microsoft/cal-open-library or try making it work with the currently available types.
Thanks for the code sample @marknitek that's really useful, I'll see if I can convert what it's doing to AL.
Hi Paul,
Did you manage to convert the code to AL? I'm in a similar situation as you; however, my issue is when streaming a Multipart data to a HttpContent only one line is appended to the class
Many Thanks, Pedro
Hi @PedroLReis - no unfortunately I never got it to work
Any update for this issue ?
I'm in the same situation from a year, I managed it by creating and calling an Azure Function but I want to do it in full AL to make my app more maintainable.
Thanks
Hi Julie,
Below is what I used to generate a multipart-form request message with a file attached, let me know if it works for you.
-
The function I used to build Multipart Msg
procedure UploadFile(var AttachmentInStream : InStream;FileID : Text;SendType : Text;NavUserID : Code[50];DocumentType : Text;FileName : Text;var StatusCode : Text[30]) : Text; var ContentTypeTxt : TextConst Comment='{Locked}',ENU='multipart/form-data; boundary=%1',ENG='multipart/form-data; boundary=%1'; TempBLOB : Record TempBlob temporary; TempBlobMultiForm : Record TempBlob; ResponseInStream : InStream; RequestInStream : InStream; IntegrationCode : Code[20]; RequestSuccessful : Boolean; ToFileName : Text; ResponseBlob : Record TempBlob temporary; begin
///// // This function generates multipart-form request message with file attachment and // Send it to Online. Returns uploaded file ID in Online System /////
GetSetup(NavUserID); SetBaseSetup; EndPoint:= SettingsManagement.GetFileURL;
IntegrationCode:= SettingsManagement.GetWSCodeUploadFile; TempBLOB.Blob.CREATEINSTREAM(ResponseInStream);
CLEARLASTERROR;
MultiFormManagement.Initialize;
Connector.SetContextType(STRSUBSTNO(ContentTypeTxt, MultiFormManagement.GenerateBoundary)); MultiFormManagement.AddBodyElement('fileId', FileID); MultiFormManagement.AddBodyElement('sendType', SendType); if SettingsManagement.GetCompanyID <> '' then MultiFormManagement.AddBodyElement('companyId', SettingsManagement.GetCompanyID); MultiFormManagement.AddBodyElement('encoding', SettingsManagement.GetEncoding); MultiFormManagement.AddBodyElement('documentType', DocumentType);
MultiFormManagement.AddFile( 'payload', '', FileName, AttachmentInStream); MultiFormManagement.FinishStream; MultiFormManagement.GetBlob(TempBlobMultiForm);
TempBlobMultiForm.Blob.CREATEINSTREAM(RequestInStream);
LogMgt.InitLogEntry(IntegrationCode, 1, 2, BaseURL, EndPoint, '', RequestInStream);PostRequest(EndPoint,TempBlobMultiForm,3,ResponseInStream, RequestSuccessful,StatusCode,ResponseBlob);
if RequestSuccessful then exit(Connector.GetResponseHeaderValue('Location')) else exit(''); end;
-
Codeunit with required functions
codeunit xxxx " MultiPartForm Msg Build" {
trigger OnRun();
begin
end;
var
FileManagement : Codeunit "File Management";
TempBlob : Record TempBlob;
Boundary : Text;
NewLine : Text[2];
BodyOutStream : OutStream;
Text001 : TextConst ENU='Content-Disposition: form-data; name="%1"',ENG='Content-Disposition: form-data; name="%1"';
Text002 : TextConst ENU='Content-Disposition: form-data; name="%1"; filename="%2"',ENG='Content-Disposition: form-data; name="%1"; filename="%2"';
Text003 : TextConst ENU='Content-Type: %1',ENG='Content-Type: %1';
procedure GenerateBoundary() : Text;
begin
Boundary := DELCHR(FORMAT(CURRENTDATETIME),'=',DELCHR(FORMAT(CURRENTDATETIME,1000),'=','1234567890'));
exit(Boundary);
end;
procedure Initialize();
begin
TempBlob.INIT();
TempBlob.Blob.CREATEOUTSTREAM(BodyOutStream);
NewLine[1] := 13;
NewLine[2] := 10;
CLEAR(Boundary);
end;
procedure AddBodyElement(Element : Text;ElementValue : Text);
var
BodyText : BigText;
begin
BodyText.ADDTEXT('--' + Boundary);
BodyText.ADDTEXT(NewLine);
BodyText.ADDTEXT(STRSUBSTNO(Text001,Element));
BodyText.ADDTEXT(NewLine);
BodyText.ADDTEXT(NewLine);
BodyText.ADDTEXT(ElementValue);
BodyText.ADDTEXT(NewLine);
BodyText.WRITE(BodyOutStream);
end;
procedure AddFile(Element : Text;ElementValue : Text;FileName : Text;FileInStream : InStream);
var
BodyText : BigText;
StreamByte : Byte;
begin
BodyText.ADDTEXT('--' + Boundary);
BodyText.ADDTEXT(NewLine);
BodyText.ADDTEXT(STRSUBSTNO(Text002,Element,FileName));
BodyText.ADDTEXT(NewLine);
BodyText.WRITE(BodyOutStream);
AddContentType(FileName);
CLEAR(BodyText);
BodyText.ADDTEXT(NewLine);
BodyText.WRITE(BodyOutStream);
while not FileInStream.EOS() do begin
FileInStream.READ(StreamByte);
BodyOutStream.WRITE(StreamByte);
end;
end;
procedure GetBlob(var pTempBlob : Record TempBlob);
var
InStreamTest : InStream;
OutStreamTest : OutStream;
begin
TempBlob.Blob.CREATEINSTREAM(InStreamTest);
pTempBlob.Blob.CREATEOUTSTREAM(OutStreamTest);
COPYSTREAM(OutStreamTest, InStreamTest);
end;
local procedure AddContentType(FileName : Text);
begin
case FileManagement.GetExtension(FileName) of
'zip' : begin
AddContentTypeLine('application/x-zip-compressed');
end;
'xml' : begin
AddContentTypeLine('text/xml');
end;
end;
end;
local procedure AddContentTypeLine(ContentType : Text);
var
BodyText : BigText;
begin
BodyText.ADDTEXT(STRSUBSTNO(Text003,ContentType));
BodyText.ADDTEXT(NewLine);
BodyText.WRITE(BodyOutStream);
end;
procedure FinishStream();
var
BodyText : BigText;
begin
BodyText.ADDTEXT(NewLine);
BodyText.ADDTEXT('--' + Boundary+'--');
BodyText.WRITE(BodyOutStream);
end;
}
@PedroLReis
Yes your code works for me ! Thanks a lot!
Still hope we will have native support for MultiParFormDataContent in AL. Even if it works, for now we have for each project which needs multipart to paste this object, or deliver a mother app containing this object on which we depends on
Hi Julie,
Below is what I used to generate a multipart-form request message with a file attached, let me know if it works for you.
- The function I used to build Multipart Msg procedure UploadFile(var AttachmentInStream : InStream;FileID : Text;SendType : Text;NavUserID : Code[50];DocumentType : Text;FileName : Text;var StatusCode : Text[30]) : Text; var ContentTypeTxt : TextConst Comment='{Locked}',ENU='multipart/form-data; boundary=%1',ENG='multipart/form-data; boundary=%1'; TempBLOB : Record TempBlob temporary; TempBlobMultiForm : Record TempBlob; ResponseInStream : InStream; RequestInStream : InStream; IntegrationCode : Code[20]; RequestSuccessful : Boolean; ToFileName : Text; ResponseBlob : Record TempBlob temporary; begin ///// // This function generates multipart-form request message with file attachment and // Send it to Online. Returns uploaded file ID in Online System ///// GetSetup(NavUserID); SetBaseSetup; EndPoint:= SettingsManagement.GetFileURL; IntegrationCode:= SettingsManagement.GetWSCodeUploadFile; TempBLOB.Blob.CREATEINSTREAM(ResponseInStream); CLEARLASTERROR; MultiFormManagement.Initialize; Connector.SetContextType(STRSUBSTNO(ContentTypeTxt, MultiFormManagement.GenerateBoundary)); MultiFormManagement.AddBodyElement('fileId', FileID); MultiFormManagement.AddBodyElement('sendType', SendType); if SettingsManagement.GetCompanyID <> '' then MultiFormManagement.AddBodyElement('companyId', SettingsManagement.GetCompanyID); MultiFormManagement.AddBodyElement('encoding', SettingsManagement.GetEncoding); MultiFormManagement.AddBodyElement('documentType', DocumentType); MultiFormManagement.AddFile( 'payload', '', FileName, AttachmentInStream); MultiFormManagement.FinishStream; MultiFormManagement.GetBlob(TempBlobMultiForm); TempBlobMultiForm.Blob.CREATEINSTREAM(RequestInStream); LogMgt.InitLogEntry(IntegrationCode, 1, 2, BaseURL, EndPoint, '', RequestInStream); PostRequest(EndPoint,TempBlobMultiForm,3,ResponseInStream, RequestSuccessful,StatusCode,ResponseBlob); if RequestSuccessful then exit(Connector.GetResponseHeaderValue('Location')) else exit(''); end;
- Codeunit with required functions
codeunit xxxx " MultiPartForm Msg Build" {
trigger OnRun(); begin end; var FileManagement : Codeunit "File Management"; TempBlob : Record TempBlob; Boundary : Text; NewLine : Text[2]; BodyOutStream : OutStream; Text001 : TextConst ENU='Content-Disposition: form-data; name="%1"',ENG='Content-Disposition: form-data; name="%1"'; Text002 : TextConst ENU='Content-Disposition: form-data; name="%1"; filename="%2"',ENG='Content-Disposition: form-data; name="%1"; filename="%2"'; Text003 : TextConst ENU='Content-Type: %1',ENG='Content-Type: %1'; procedure GenerateBoundary() : Text; begin Boundary := DELCHR(FORMAT(CURRENTDATETIME),'=',DELCHR(FORMAT(CURRENTDATETIME,1000),'=','1234567890')); exit(Boundary); end; procedure Initialize(); begin TempBlob.INIT(); TempBlob.Blob.CREATEOUTSTREAM(BodyOutStream); NewLine[1] := 13; NewLine[2] := 10; CLEAR(Boundary); end; procedure AddBodyElement(Element : Text;ElementValue : Text); var BodyText : BigText; begin BodyText.ADDTEXT('--' + Boundary); BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT(STRSUBSTNO(Text001,Element)); BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT(ElementValue); BodyText.ADDTEXT(NewLine); BodyText.WRITE(BodyOutStream); end; procedure AddFile(Element : Text;ElementValue : Text;FileName : Text;FileInStream : InStream); var BodyText : BigText; StreamByte : Byte; begin BodyText.ADDTEXT('--' + Boundary); BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT(STRSUBSTNO(Text002,Element,FileName)); BodyText.ADDTEXT(NewLine); BodyText.WRITE(BodyOutStream); AddContentType(FileName); CLEAR(BodyText); BodyText.ADDTEXT(NewLine); BodyText.WRITE(BodyOutStream); while not FileInStream.EOS() do begin FileInStream.READ(StreamByte); BodyOutStream.WRITE(StreamByte); end; end; procedure GetBlob(var pTempBlob : Record TempBlob); var InStreamTest : InStream; OutStreamTest : OutStream; begin TempBlob.Blob.CREATEINSTREAM(InStreamTest); pTempBlob.Blob.CREATEOUTSTREAM(OutStreamTest); COPYSTREAM(OutStreamTest, InStreamTest); end; local procedure AddContentType(FileName : Text); begin case FileManagement.GetExtension(FileName) of 'zip' : begin AddContentTypeLine('application/x-zip-compressed'); end; 'xml' : begin AddContentTypeLine('text/xml'); end; end; end; local procedure AddContentTypeLine(ContentType : Text); var BodyText : BigText; begin BodyText.ADDTEXT(STRSUBSTNO(Text003,ContentType)); BodyText.ADDTEXT(NewLine); BodyText.WRITE(BodyOutStream); end; procedure FinishStream(); var BodyText : BigText; begin BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT('--' + Boundary+'--'); BodyText.WRITE(BodyOutStream); end;}
Hi @PedroLReis i'm trying to do this but I don't know what variable is "connector" and wha does "PostRequest" does. Can you help me? Thanks a lot!
pd: @JulienDecottignies maybe you can help me too, seeing you made the code work :D
Hi Julie, Below is what I used to generate a multipart-form request message with a file attached, let me know if it works for you.
- The function I used to build Multipart Msg procedure UploadFile(var AttachmentInStream : InStream;FileID : Text;SendType : Text;NavUserID : Code[50];DocumentType : Text;FileName : Text;var StatusCode : Text[30]) : Text; var ContentTypeTxt : TextConst Comment='{Locked}',ENU='multipart/form-data; boundary=%1',ENG='multipart/form-data; boundary=%1'; TempBLOB : Record TempBlob temporary; TempBlobMultiForm : Record TempBlob; ResponseInStream : InStream; RequestInStream : InStream; IntegrationCode : Code[20]; RequestSuccessful : Boolean; ToFileName : Text; ResponseBlob : Record TempBlob temporary; begin ///// // This function generates multipart-form request message with file attachment and // Send it to Online. Returns uploaded file ID in Online System ///// GetSetup(NavUserID); SetBaseSetup; EndPoint:= SettingsManagement.GetFileURL; IntegrationCode:= SettingsManagement.GetWSCodeUploadFile; TempBLOB.Blob.CREATEINSTREAM(ResponseInStream); CLEARLASTERROR; MultiFormManagement.Initialize; Connector.SetContextType(STRSUBSTNO(ContentTypeTxt, MultiFormManagement.GenerateBoundary)); MultiFormManagement.AddBodyElement('fileId', FileID); MultiFormManagement.AddBodyElement('sendType', SendType); if SettingsManagement.GetCompanyID <> '' then MultiFormManagement.AddBodyElement('companyId', SettingsManagement.GetCompanyID); MultiFormManagement.AddBodyElement('encoding', SettingsManagement.GetEncoding); MultiFormManagement.AddBodyElement('documentType', DocumentType); MultiFormManagement.AddFile( 'payload', '', FileName, AttachmentInStream); MultiFormManagement.FinishStream; MultiFormManagement.GetBlob(TempBlobMultiForm); TempBlobMultiForm.Blob.CREATEINSTREAM(RequestInStream); LogMgt.InitLogEntry(IntegrationCode, 1, 2, BaseURL, EndPoint, '', RequestInStream); PostRequest(EndPoint,TempBlobMultiForm,3,ResponseInStream, RequestSuccessful,StatusCode,ResponseBlob); if RequestSuccessful then exit(Connector.GetResponseHeaderValue('Location')) else exit(''); end;
- Codeunit with required functions
codeunit xxxx " MultiPartForm Msg Build" {
trigger OnRun(); begin end; var FileManagement : Codeunit "File Management"; TempBlob : Record TempBlob; Boundary : Text; NewLine : Text[2]; BodyOutStream : OutStream; Text001 : TextConst ENU='Content-Disposition: form-data; name="%1"',ENG='Content-Disposition: form-data; name="%1"'; Text002 : TextConst ENU='Content-Disposition: form-data; name="%1"; filename="%2"',ENG='Content-Disposition: form-data; name="%1"; filename="%2"'; Text003 : TextConst ENU='Content-Type: %1',ENG='Content-Type: %1'; procedure GenerateBoundary() : Text; begin Boundary := DELCHR(FORMAT(CURRENTDATETIME),'=',DELCHR(FORMAT(CURRENTDATETIME,1000),'=','1234567890')); exit(Boundary); end; procedure Initialize(); begin TempBlob.INIT(); TempBlob.Blob.CREATEOUTSTREAM(BodyOutStream); NewLine[1] := 13; NewLine[2] := 10; CLEAR(Boundary); end; procedure AddBodyElement(Element : Text;ElementValue : Text); var BodyText : BigText; begin BodyText.ADDTEXT('--' + Boundary); BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT(STRSUBSTNO(Text001,Element)); BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT(ElementValue); BodyText.ADDTEXT(NewLine); BodyText.WRITE(BodyOutStream); end; procedure AddFile(Element : Text;ElementValue : Text;FileName : Text;FileInStream : InStream); var BodyText : BigText; StreamByte : Byte; begin BodyText.ADDTEXT('--' + Boundary); BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT(STRSUBSTNO(Text002,Element,FileName)); BodyText.ADDTEXT(NewLine); BodyText.WRITE(BodyOutStream); AddContentType(FileName); CLEAR(BodyText); BodyText.ADDTEXT(NewLine); BodyText.WRITE(BodyOutStream); while not FileInStream.EOS() do begin FileInStream.READ(StreamByte); BodyOutStream.WRITE(StreamByte); end; end; procedure GetBlob(var pTempBlob : Record TempBlob); var InStreamTest : InStream; OutStreamTest : OutStream; begin TempBlob.Blob.CREATEINSTREAM(InStreamTest); pTempBlob.Blob.CREATEOUTSTREAM(OutStreamTest); COPYSTREAM(OutStreamTest, InStreamTest); end; local procedure AddContentType(FileName : Text); begin case FileManagement.GetExtension(FileName) of 'zip' : begin AddContentTypeLine('application/x-zip-compressed'); end; 'xml' : begin AddContentTypeLine('text/xml'); end; end; end; local procedure AddContentTypeLine(ContentType : Text); var BodyText : BigText; begin BodyText.ADDTEXT(STRSUBSTNO(Text003,ContentType)); BodyText.ADDTEXT(NewLine); BodyText.WRITE(BodyOutStream); end; procedure FinishStream(); var BodyText : BigText; begin BodyText.ADDTEXT(NewLine); BodyText.ADDTEXT('--' + Boundary+'--'); BodyText.WRITE(BodyOutStream); end;}
Hi @PedroLReis i'm trying to do this but I don't know what variable is "connector" and wha does "PostRequest" does. Can you help me? Thanks a lot!
pd: @juliendecottignies maybe you can help me too, seeing you made the code work :D
@onzema did you get your answer?