Create file on azure storage in Business Central

RahilaRahila Member Posts: 16
edited 2020-01-13 in NAV Three Tier
I am trying to create a file on azure storage using PUT request, Below is my code and i get the error The value for one of the HTTP headers is not in the correct format.

The Code and the headers formed are:
procedure CreateFileNew(FileName: Text)
var
requestMethod: Text;
request: HttpRequestMessage;
RequestHeader: HttpHeaders;
hhtpres: HttpResponseMessage;
canonicalizedResource: Text;
canonicalizedHeaders: Text;
urlPath: Text;
client: HttpClient;
content: HttpContent;
test: Boolean;
MethodType: Text;
authorizationHeader: Text;
stringToSign: Text;
msVersion: Text;
cha: Char;
contenttype: Text;
contentLength: Integer;
dat: DateTime;
res: Text;
keyss: Text;
dateInRfc1123Format: DateTime;
EncryptionManagement: Codeunit "Encryption Management";
begin
cha := 13;
msVersion := '2015-04-05';//;'02-14-2014';
MethodType := 'CreateFile';
keyss := 'i0PNZ6Ykse7oSSfUzFeA36rQfAvaaJiL9LYbv9UZnol0NRM4sal4s8B3ipkjvfzcsP8/gnI58G6A==';
dateInRfc1123Format := CurrentDateTime;
requestMethod := 'PUT';
urlPath := 'lables/' + 'tre11.csv';
canonicalizedResource := '/bcstorage/lables/tre11.csv';
canonicalizedHeaders := 'x-ms-date:' + Format(dateInRfc1123Format) + Format(cha) + 'x-ms-range:bytes=0-1024' + Format(cha) + 'x-ms-version:' + msVersion
+ Format(cha) + 'x-ms-write:update' + Format(cha);
//canonicalizedHeaders := 'x-ms-content-length:1024' + Format(cha) + 'x-ms-date:' + FORMat(dateInRfc1123Format) + Format(cha) + 'x-ms-type:file' + Format(cha) + 'x-ms-version:' + msVersion;
stringToSign := (requestMethod + Format(cha) + Format(cha) + Format(cha) + Format(cha) + Format(cha)
+ Format(cha) + Format(cha) + Format(cha) +
Format(cha) + Format(cha) + Format(cha) + Format(cha) + canonicalizedHeaders + canonicalizedResource);


authorizationHeader := 'SharedKey bc365storage:' + EncryptionManagement.GenerateBase64KeyedHashAsBase64String(stringToSign, keyss, 2);
Message((authorizationHeader));
request.SetRequestUri('https://bcstorage.file.core.windows.net/lables/tre11.csv');
request.Method := requestMethod;
RequestHeader.Add('x-ms-date', FORMat(dateInRfc1123Format));
RequestHeader.Add('x-ms-version', msVersion);
RequestHeader.Add('Authorization', authorizationHeader);
RequestHeader.Add('Accept-Charset', 'UTF-8');
if MethodType = 'CreateFile' then begin
RequestHeader.Add('x-ms-content-length', '1024');
RequestHeader.Add('x-ms-type', 'file');
end;

client.Put('https://bcstorage.file.core.windows.net/lables/tre11.csv', content, hhtpres);
hhtpres.Content.ReadAs(res);
Message(res);
test := client.Send(request, hhtpres);
end;

Best Answers

«1

Answers

  • RahilaRahila Member Posts: 16
    ftornero wrote: »
    Hello @Rahila

    I changed your code a little to make it works

    The EncryptionManagement codeunit changed in BC15 and I added some procedures to get your account data and the DateTime format expected.
    codeunit 50124 CreateFileNew
    {
        procedure CreateFileNew(FileName: Text)
        var
            requestMethod: Text;
            request: HttpRequestMessage;
            RequestHeader: HttpHeaders;
            hhtpres: HttpResponseMessage;
            canonicalizedResource: Text;
            canonicalizedHeaders: Text;
            urlPath: Text;
            client: HttpClient;
            content: HttpContent;
            test: Boolean;
            MethodType: Text;
            authorizationHeader: Text;
            stringToSign: Text;
            msVersion: Text;
            cha: Char;
            contenttype: Text;
            contentLength: Integer;
            dat: DateTime;
            res: Text;
            keyss: Text;
            dateInRfc1123Format: Text;  // <------------ Changed
            //EncryptionManagement: Codeunit "Encryption Management";
            EncryptionManagement: Codeunit "Cryptography Management";   // <------------ Changed BC15    
            uri: Text;
        begin
            cha := 10; //13;  <------------ Changed
            msVersion := '2015-02-21';//'2015-04-05';//;'02-14-2014'; <------------ Changed
            MethodType := 'CreateFile';
            keyss := GetAccountAccessKey();
            dateInRfc1123Format := GetUTCDateTimeText(); // <------------ Changed
            requestMethod := 'PUT';
            urlPath := GetAccountSharedRessource() + '/lables/' + FileName; // you need a folder lables inside your Shared ressource
            //canonicalizedResource := '/bcstorage/lables/tre11.csv';
            CanonicalizedResource := StrSubstNo('/%1/%2', GetAccountName(), urlPath);
            canonicalizedHeaders := 'x-ms-content-length:' + '1024' + Format(cha) +
                                    'x-ms-date:' + dateInRfc1123Format + Format(cha) +
                                    'x-ms-type:' + 'file' + Format(cha) +
                                    'x-ms-version:' + msVersion;
    
            stringToSign := (requestMethod + Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             canonicalizedHeaders + Format(cha) + // <------------ Changed
                             canonicalizedResource);
    
            authorizationHeader := 'SharedKey ' + GetAccountName() + ':' + EncryptionManagement.GenerateBase64KeyedHashAsBase64String(stringToSign, keyss, 2);
            //Message((authorizationHeader));
            uri := StrSubstNo('https://%1.file.core.windows.net/%2', GetAccountName(), urlPath);
            request.SetRequestUri(uri);
            request.Method := requestMethod;
            RequestHeader.Clear();
            request.GetHeaders(RequestHeader);
            RequestHeader.Add('Authorization', authorizationHeader);
            RequestHeader.Add('x-ms-content-length', '1024');
            RequestHeader.Add('x-ms-date', dateInRfc1123Format);
            RequestHeader.Add('x-ms-type', 'file');
            RequestHeader.Add('x-ms-version', msVersion);
    
            client.Send(request, hhtpres);
            if not hhtpres.IsSuccessStatusCode then
                Error(hhtpres.ReasonPhrase);
        end;
    
        procedure GetUTCDateTimeText(): Text
        var
            TypeHelper: Codeunit "Type Helper";
        begin
            exit(TypeHelper.GetCurrUTCDateTimeAsText());
        end;
    
        procedure GetAccountName(): Text
        begin
            // Your Account name
            exit('bc365storage');
        end;
    
        procedure GetAccountSharedRessource(): Text
        begin
            // Your Shared ressource
            exit('bcstorage');        
        end;
    
        procedure GetAccountAccessKey(): Text
        begin
            // Your Access Key
            exit('i0PNZ6Ykse7oSSfUzFeA36rQfAvaaJiL9LYbv9UZnol0NRM4sal4s8B3ipkjvfzcsP8/gnI58G6A==');
        end;
    }
    

    Regards

    Thanks for the reply, But i am working on BC14 so i cannot find the codeunit "Cryptography Management" .
  • ftorneroftornero Member Posts: 522
    Hello @Rahila ,

    In this case you can use the codeunit that you are already using (Encryption Management).

    Regards.
  • RahilaRahila Member Posts: 16
    Hello @ftornero ,
    I am having this error now.


    A call to System.Convert.FromBase64String failed with this message: Invalid length for a Base-64 char array or string.
  • ftorneroftornero Member Posts: 522
    Hello @Rahila

    Well this happens because the Access Key you posted is not a valid one, I think that that was on purpose invalid,

    Check that that access key is your valid access key.
        procedure GetAccountAccessKey(): Text
        begin
            // Your Access Key
            exit('i0PNZ6Ykse7oSSfUzFeA36rQfAvaaJiL9LYbv9UZnol0NRM4sal4s8B3ipkjvfzcsP8/gnI58G6A==');
        end;
    

    Regards
  • RahilaRahila Member Posts: 16
    @ftornero The access key's sample is the same, my original access key is also in the same format.
  • ftorneroftornero Member Posts: 522
    Hello @Rahila ,

    And using your original access key you get the same error?

    Regards.
  • RahilaRahila Member Posts: 16
    @ftornero yes . The format is same as above i have just removed few characters.
  • RahilaRahila Member Posts: 16
    @ftornero it worked. What i need now is to write few contents too in the file. can you please help.
  • RahilaRahila Member Posts: 16
    @ftornero Thank you so much. You solved my problem.
  • julkifli33julkifli33 Member Posts: 1,073
    hi @ftornero ..
    i am trying to test using BC170 CU 5 on prem
    I test to export txt file to my azure storage account.
    i created button in the page :
    action(Create)
                {
                    ApplicationArea = All;
                    Promoted = True;
                    PromotedCategory = Process;
                    PromotedIsBig = True;
                    trigger OnAction()
                    Var
                        FileName: Text;
                    begin
                        FileName := 'Test ' + FORMAT(CurrentDateTime, 0, '<Year4><Month,2><Day,2>_<Hours24,2><Filler Character,0><Minutes,2><Seconds,2>') + '.txt';
                        CodeunitCreateNewFile.CreateFileNew(FileName);
                        CodeunitCreateNewFile.WriteText2File(FileName, 'This is a test !!!!');
                    end;
                }
    

    and then codeunit exactly the same
    procedure CreateFileNew(FileName: Text)
        var
            requestMethod: Text;
            request: HttpRequestMessage;
            RequestHeader: HttpHeaders;
            hhtpres: HttpResponseMessage;
            canonicalizedResource: Text;
            canonicalizedHeaders: Text;
            urlPath: Text;
            client: HttpClient;
            content: HttpContent;
            test: Boolean;
            MethodType: Text;
            authorizationHeader: Text;
            stringToSign: Text;
            msVersion: Text;
            cha: Char;
            contenttype: Text;
            contentLength: Integer;
            dat: DateTime;
            res: Text;
            keyss: Text;
            dateInRfc1123Format: Text;  // <------------ Changed
            //EncryptionManagement: Codeunit "Encryption Management";
            EncryptionManagement: Codeunit "Cryptography Management";   // <------------ Changed BC15    
            uri: Text;
        begin
            cha := 10; //13;  <------------ Changed
            msVersion := '2015-02-21';//'2015-04-05';//;'02-14-2014'; <------------ Changed
            MethodType := 'CreateFile';
            keyss := GetAccountAccessKey();
            dateInRfc1123Format := GetUTCDateTimeText(); // <------------ Changed
            requestMethod := 'PUT';
            urlPath := GetAccountSharedRessource() + '/lables/' + FileName; // you need a folder lables inside your Shared ressource
            //canonicalizedResource := '/bcstorage/lables/tre11.csv';
            CanonicalizedResource := StrSubstNo('/%1/%2', GetAccountName(), urlPath);
            canonicalizedHeaders := 'x-ms-content-length:' + '1024' + Format(cha) +
                                    'x-ms-date:' + dateInRfc1123Format + Format(cha) +
                                    'x-ms-type:' + 'file' + Format(cha) +
                                    'x-ms-version:' + msVersion;
    
            stringToSign := (requestMethod + Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             canonicalizedHeaders + Format(cha) + // <------------ Changed
                             canonicalizedResource);
    
            authorizationHeader := 'SharedKey ' + GetAccountName() + ':' + EncryptionManagement.GenerateBase64KeyedHashAsBase64String(stringToSign, keyss, 2);
            //Message((authorizationHeader));
            uri := StrSubstNo('https://%1.file.core.windows.net/%2', GetAccountName(), urlPath);
            request.SetRequestUri(uri);
            request.Method := requestMethod;
            RequestHeader.Clear();
            request.GetHeaders(RequestHeader);
            RequestHeader.Add('Authorization', authorizationHeader);
            RequestHeader.Add('x-ms-content-length', '1024');
            RequestHeader.Add('x-ms-date', dateInRfc1123Format);
            RequestHeader.Add('x-ms-type', 'file');
            RequestHeader.Add('x-ms-version', msVersion);
    
            client.Send(request, hhtpres);
            if not hhtpres.IsSuccessStatusCode then
                Error(hhtpres.ReasonPhrase);
        end;
    


    but i got this error message
    Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.


    any advise?
    Thanks.
  • julkifli33julkifli33 Member Posts: 1,073
    edited 2021-04-20
    71kybgyhctlo.png

    and here is my config
    1. GetAccountName
    2. GetAccountAccessKey

    SAS
    i5svedvwomy8.png


    is there anything that i miss?

  • ftorneroftornero Member Posts: 522
    Hello @julkifli33,

    You could check your AccountAccessKey

    gkjjq712pg4r.png

    Because if I provide a wrong one I got your error message:

    rhc1q2ejpjxz.png

    Regards.
  • julkifli33julkifli33 Member Posts: 1,073
    Hi @ftornero
    here is my screen shot
    nbcbkmy01evp.png
    --> HYyXRecMzl/GEKQtCwWVCN9P4jehmIGa0k51KhukUMAj1fWsjVDzUC12rzDROOHjhpv5S4FKeS24zP6iDn+SXA==

    and here is my code :
    procedure GetAccountAccessKey(): Text
        begin
            // Your Access Key
                    exit('HYyXRecMzl/GEKQtCwWVCN9P4jehmIGa0k51KhukUMAj1fWsjVDzUC12rzDROOHjhpv5S4FKeS24zP6iDn+SXA==');
        end;
    

    is there something wrong?
  • ftorneroftornero Member Posts: 522
    Hello @julkifli33

    No, there is nothing wrong.

    The only thing I can assure you is that the code works.

    Here is a GitHub repository with a solution to deal with Azure Blob Storage, take a look and see if this help.

    https://github.com/cosmoconsult/D365BC-Blob-Storage-API

    Regards
  • julkifli33julkifli33 Member Posts: 1,073
    hi ftornero,
    is it because of Msversion difference ?
    or maybe I created wrong azure storage?
    what i need is just to export txt or files as a file to specific path.

  • ftorneroftornero Member Posts: 522
    Hello @julkifli33,

    I don't know, if you want you could pass me the data in a private message and I test it.

    Regards.
  • julkifli33julkifli33 Member Posts: 1,073
    Hi @ftornero
    thanks so much.
    i have sent you via private message
  • julkifli33julkifli33 Member Posts: 1,073
    Hi @ftornero
    your code is working.

    error was because i put space in my file name!
    thanks.
  • julkifli33julkifli33 Member Posts: 1,073
    Hi @ftornero do you have any idea how to copy file (instead of writing) from local to azure storage?
    because if write txt file is very limited .. i think max only 5-10mb
    maybe like pdf/ jpg
  • ftorneroftornero Member Posts: 522
    Hello @julkifli33,

    Something like this:
        procedure CreateFileNew(FileName: Text; size: Integer)
        var
            requestMethod: Text;
            request: HttpRequestMessage;
            RequestHeader: HttpHeaders;
            hhtpres: HttpResponseMessage;
            canonicalizedResource: Text;
            canonicalizedHeaders: Text;
            urlPath: Text;
            client: HttpClient;
            content: HttpContent;
            test: Boolean;
            MethodType: Text;
            authorizationHeader: Text;
            stringToSign: Text;
            msVersion: Text;
            cha: Char;
            contenttype: Text;
            contentLength: Integer;
            dat: DateTime;
            res: Text;
            keyss: Text;
            dateInRfc1123Format: Text;  // <------------ Changed
            //EncryptionManagement: Codeunit "Encryption Management";
            EncryptionManagement: Codeunit "Cryptography Management";   // <------------ Changed BC15    
            uri: Text;
        begin
            cha := 10; //13;  <------------ Changed
            msVersion := '2015-02-21';//'2015-04-05';//;'02-14-2014'; <------------ Changed
            MethodType := 'CreateFile';
            keyss := GetAccountAccessKey();
            dateInRfc1123Format := GetUTCDateTimeText(); // <------------ Changed
            requestMethod := 'PUT';
            urlPath := GetAccountSharedRessource() + '/lables/' + FileName; // you need a folder lables inside your Shared ressource
            //canonicalizedResource := '/bcstorage/lables/tre11.csv';
            CanonicalizedResource := StrSubstNo('/%1/%2', GetAccountName(), urlPath);
            canonicalizedHeaders := 'x-ms-content-length:' + Format(size, 0, 9) + Format(cha) +
                                    'x-ms-date:' + dateInRfc1123Format + Format(cha) +
                                    'x-ms-type:' + 'file' + Format(cha) +
                                    'x-ms-version:' + msVersion;
    
            stringToSign := (requestMethod + Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             Format(cha) +
                             canonicalizedHeaders + Format(cha) + // <------------ Changed
                             canonicalizedResource);
    
            authorizationHeader := 'SharedKey ' + GetAccountName() + ':' + EncryptionManagement.GenerateBase64KeyedHashAsBase64String(stringToSign, keyss, 2);
            //Message((authorizationHeader));
            uri := StrSubstNo('https://%1.file.core.windows.net/%2', GetAccountName(), urlPath);
            request.SetRequestUri(uri);
            request.Method := requestMethod;
            RequestHeader.Clear();
            request.GetHeaders(RequestHeader);
            RequestHeader.Add('Authorization', authorizationHeader);
            RequestHeader.Add('x-ms-content-length', Format(size, 0, 0));
            RequestHeader.Add('x-ms-date', dateInRfc1123Format);
            RequestHeader.Add('x-ms-type', 'file');
            RequestHeader.Add('x-ms-version', msVersion);
    
            client.Send(request, hhtpres);
            if not hhtpres.IsSuccessStatusCode then
                Error(hhtpres.ReasonPhrase);
        end;
    
        procedure CopyFile2AzureBlob(FileName: Text; TempBlob: Codeunit "Temp Blob")
        begin
            createFileNew(FileName, TempBlob.Length());
            WriteRange(FileName, TempBlob);
        end;
    

    And you can use this action:
                action(CopyFile)
                {
                    Caption = 'CopyFile';
                    ToolTip = 'CopyFile';
                    Promoted = true;
                    PromotedIsBig = true;
                    Image = Copy;
                    ApplicationArea = All;
    
                    trigger OnAction();
                    var
                        codunit: codeunit CreateFileNew;
                        fileMgt: Codeunit "File Management";
                        fileName: Text;
                        tmpBlob: Codeunit "Temp Blob";
    
                    begin
                        fileName := fileMgt.BLOBImportWithFilter(tmpBlob, 'Choose file', fileName, 'All Files (*.*)|*.*', '*.*');
                        if fileName <> '' then
                            codunit.CopyFile2AzureBlob(fileName, tmpBlob);
                    end;
                }
    

    Regards
  • julkifli33julkifli33 Member Posts: 1,073
    Hi @ftornero ,
    is there any limitation to copy file?

    I tested
    png file 3 kb - Successfull
    jpg file 3.7 mb - Successfull
    pdf file 2.7 mb - Failed
    pdf file 52 kb - Successfull
    jpg file 500 kb - Failed
  • julkifli33julkifli33 Member Posts: 1,073
    error
    yj11convlutu.png

    error message : Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

  • julkifli33julkifli33 Member Posts: 1,073
    edited 2021-04-30
    Hi @ftornero ,
    i managed to solve this issue.
    same like last time.. the file name is not allowed to have space..
    so i remove the space before i copy..

    but i tested to copy pdf 30 mb
    it show me error message :
    The request body is too large and exceeds the maximum permissible limit.

  • ftorneroftornero Member Posts: 522
    Hello @julkifli33,

    Maybe an improvement to the code is to urlencode the file name.

    Anyway there is a limit in the file size.

    Regards.
  • julkifli33julkifli33 Member Posts: 1,073
  • mchruss27mchruss27 Member Posts: 3
    julkifli33 wrote: »
    Hi @ftornero
    your code is working.

    error was because i put space in my file name!
    thanks.

    @julkifli33
    I tried the same code but I'm getting this error.
    I just replace this line
    uri := StrSubstNo('https://%1.file.core.windows.net/%2', GetAccountName(), urlPath);

    with this
    uri := StrSubstNo('https://%1.blob.core.windows.net/%2', GetAccountName(), urlPath);

    I replace the value of the storage account, Name and Key with my own details.

    90zxa06tdyty.png
    Can you help me figure out what I'm missing.
  • ftorneroftornero Member Posts: 522
    Hello @mchruss27,

    They are two different API (file and blob).

    How I already posted, here is a GitHub repository with a solution to deal with Azure Blob Storage, take a look and see if this help.

    https://github.com/cosmoconsult/D365BC-Blob-Storage-API

    Regards.
  • mchruss27mchruss27 Member Posts: 3
    ftornero wrote: »
    Hello @mchruss27,

    They are two different API (file and blob).

    How I already posted, here is a GitHub repository with a solution to deal with Azure Blob Storage, take a look and see if this help.

    https://github.com/cosmoconsult/D365BC-Blob-Storage-API

    Regards.

    This resolved my issue. thank you. cheers
Sign In or Register to comment.