How do I make an ANSI file on a FTP server?

MortenSteengaard
MortenSteengaard Member Posts: 144
Hi experts,

This is in Axapta 2009.

I have the code below, that can make an utf-8 file on a FTP server with username and password. That works fine.

The only problem is that I need to save the file as ANSI, so the Danish letters are correct.

In the Axapta 2009 program, I make an ANSI file in the Windows temp folder. That file is correct, but when I use the code below to send it to the FTP server, the Danish letters get wrong.

I hope you can help!

Best regards,

Morten

public void uploadFiletoFTPServer(str _fileNameTmp, str _fileNameFtp)
{
    System.Object request,response,credential;
    System.IO.StreamReader streamReader;
    System.IO.Stream requestStream;
    System.Array files;
    System.Net.FtpWebRequest ftpRequest;
    System.Net.FtpWebResponse ftpResponse;
    System.Byte[] bytes;
    System.Text.Encoding myEncoding;
    System.Exception sysException;
    Str 120 TempPath,executedate;
    str timeinstr,nextFile;
    Commaio file;
    container line;
    Filename filepath,fileType, ftpFileName;
    System.Net.ICredentials iCredentials;
    System.Net.IWebProxy    iWebProxy;
    ;
    try
    {
        ftpFileName = "ftp...//myFile";
        streamReader = new System.IO.StreamReader(_fileNameTmp);
        // must be ANSI:
        myEncoding = System.Text.Encoding::get_UTF8();
        //myEncoding = System.Text.Encoding::get_Default();
        //myEncoding = System.Text.Encoding::get_Unicode();
        //myEncoding = System.Text.Encoding::GetEncoding(1252);
        //myEncoding = System.Text.Encoding::get_ASCII();
        //myEncoding = System.Text.Encoding::get_BigEndianUnicode();
        //myEncoding = System.Text.Encoding::GetEncoding(865);
        //myEncoding = System.Text.Encoding::GetEncoding(65001);
        bytes = myEncoding.GetBytes(streamReader.ReadToEnd());
        
        streamReader.Close();
        request = System.Net.WebRequest::Create(new System.Uri(ftpFileName));
        ftpRequest = request;
        credential = new System.Net.NetworkCredential("myUserId", "myPassword");
        iCredentials = credential;
        ftpRequest.set_Credentials(iCredentials);
        ftpRequest.set_ContentLength(bytes.get_Length());
        ftpRequest.set_Method("STOR");
        requestStream = ftpRequest.GetRequestStream();
        requestStream.Write(bytes,0,bytes.get_Length());
        requestStream.Close();
        response = ftpRequest.GetResponse();
        ftpResponse = response;
    }
    catch(Exception::CLRError)
    {
        sysException = CLRInterop::getLastException();
        info(sysException.get_Message());
    }
    CodeAccessPermission::revertAssert();
}

Answers

  • axman63
    axman63 Member Posts: 1

    Hi Morten,

    I'm Danish too, but let's keep it in Danish for any potential foreign visitors :)

    Everyting from this paragraph down is output from a program a friend of mine just developed called https://www.novaryn.io/axjedi/ - I have his permission to post this here:

    ***

    The root cause is that AX 2009 internally works with Unicode (UTF-16), but your FTP target system expects ANSI encoding (codepage 1252 / Windows-1252) where Danish characters like æøå are single bytes. The fix is to use TextIo with an explicit codepage parameter when writing the file, then upload via FTP using a COM object.

    Here is a complete working solution:

    Approach

    Use TextIo with codepage 1252 (Western European ANSI) to write a properly encoded temp file, then upload it via FTP using a COM-based helper class. The key insight most examples miss is the third parameter on the TextIo constructor — that is where encoding is controlled.

    Verified X++ Code

    // Helper class for ANSI FTP uploads
    class CustAnsiFileUploader
    {
        str ftpServer;
        str ftpUsername;
        str ftpPassword;
        str tempPath;
    }
    
    void new()
    {
        ;
        tempPath = WinAPI::getTempPath();
    }
    
    public str parmFtpServer(str _ftpServer = ftpServer)
    {
        ;
        ftpServer = _ftpServer;
        return ftpServer;
    }
    
    public str parmFtpUsername(str _ftpUsername = ftpUsername)
    {
        ;
        ftpUsername = _ftpUsername;
        return ftpUsername;
    }
    
    public str parmFtpPassword(str _ftpPassword = ftpPassword)
    {
        ;
        ftpPassword = _ftpPassword;
        return ftpPassword;
    }
    
    public boolean uploadTextAsAnsi(str textContent, str remoteFileName)
    {
        str         tempFileName;
        boolean     uploadSuccess;
        TextIo      textIo;
        container   textLines;
        int         i;
        str         currentLine;
        ;
    
        tempFileName  = tempPath + strReplace(newGuid(), '-', '') + ".txt";
        uploadSuccess = false;
    
        try
        {
            // Codepage 1252 = Western European ANSI — handles æøåÆØÅ correctly
            textIo = new TextIo(tempFileName, #io_write, 1252);
    
            if (!textIo)
            {
                throw error("Could not create temporary ANSI file");
            }
    
            textLines = str2con(textContent, '\n');
    
            for (i = 1; i <= conLen(textLines); i++)
            {
                currentLine = conPeek(textLines, i);
                textIo.write(currentLine);
            }
    
            textIo = null; // Setting to null closes and flushes the file
    
            if (!WinAPI::fileExists(tempFileName))
            {
                throw error("Failed to create ANSI file");
            }
    
            uploadSuccess = this.uploadFileViaFtp(tempFileName, remoteFileName);
    
            if (uploadSuccess)
            {
                info(strFmt("File uploaded successfully as ANSI: %1", remoteFileName));
            }
            else
            {
                error("Failed to upload ANSI file to FTP server");
            }
        }
        catch (Exception::Error)
        {
            uploadSuccess = false;
            error("Failed to process ANSI file upload");
        }
    
        // Clean up temporary file
        if (WinAPI::fileExists(tempFileName))
        {
            WinAPI::deleteFile(tempFileName);
        }
    
        return uploadSuccess;
    }
    
    private boolean uploadFileViaFtp(str localFilePath, str remoteFileName)
    {
        COM     ftpObject;
        COM     ftpFolder;
        boolean uploadSuccess;
        str     ftpUrl;
        ;
    
        uploadSuccess = false;
    
        try
        {
            // Build FTP URL with credentials
            ftpUrl    = strFmt("ftp://%1:%2@%3", ftpUsername, ftpPassword, ftpServer);
            ftpObject = new COM("Shell.Application");
    
            if (!ftpObject)
            {
                throw error("Failed to create FTP COM object");
            }
    
            ftpFolder = ftpObject.NameSpace(ftpUrl);
    
            if (!ftpFolder)
            {
                throw error("Failed to connect to FTP server");
            }
    
            // CopyHere flag 16 = suppress all dialogs automatically
            ftpFolder.CopyHere(localFilePath, 16);
            sleep(2000); // Wait for upload to complete
    
            uploadSuccess = true;
            info(strFmt("File uploaded to FTP: %1", remoteFileName));
        }
        catch (Exception::Error)
        {
            uploadSuccess = false;
            error("FTP upload failed");
        }
        catch (Exception::CLRError)
        {
            uploadSuccess = false;
            error("FTP COM error occurred");
        }
    
        ftpFolder = null;
        ftpObject = null;
    
        return uploadSuccess;
    }
    
    // Usage example
    static void example()
    {
        CustAnsiFileUploader uploader;
        str                  textWithDanishChars;
        ;
    
        uploader = new CustAnsiFileUploader();
        uploader.parmFtpServer("ftp.yourserver.com");
        uploader.parmFtpUsername("ftpuser");
        uploader.parmFtpPassword("ftppass");
    
        textWithDanishChars  = "Rød grød med fløde\n";
        textWithDanishChars += "Blåbærgrød";
    
        if (uploader.uploadTextAsAnsi(textWithDanishChars, "danish_test.txt"))
        {
            info("Upload completed successfully");
        }
        else
        {
            error("Upload failed");
        }
    }
    

    Detailed Explanation

    Section

    Purpose

    Class declaration

    Instance variables for FTP credentials and temp path

    new()

    Constructor initialises temp path using WinAPI::getTempPath()

    parmFtp* methods

    Standard AX 2009 parm pattern for setting credentials

    uploadTextAsAnsi()

    Main method — creates the ANSI file via TextIo, then calls upload

    new TextIo(tempFileName, #io_write, 1252)

    The critical line — codepage 1252 controls Western European ANSI encoding

    textIo = null

    Setting to null closes and flushes buffers (no explicit .close() needed in AX 2009)

    uploadFileViaFtp()

    COM-based FTP upload using Shell.Application

    CopyHere(localFilePath, 16)

    Uploads the file; flag 16 suppresses Windows dialogs

    Temp file cleanup

    WinAPI::deleteFile() — always clean up; AX 2009 will not do it for you

    Why this matters: The receiving system expects ANSI encoding where Danish characters æøå are single bytes (0xE6, 0xF8, 0xE5). If you write the file without specifying codepage, AX 2009 uses the system default which may be UTF-8 or UTF-16, producing garbled output on the other end.

    Codebase Fit

    This solution uses TextIo with an explicit codepage parameter, which is the standard AX 2009 pattern for file exports requiring specific encoding. The temp file approach (write locally, upload, delete) is reliable and avoids holding connections open during encoding conversion. The Shell.Application COM object is a well-established AX 2009 technique for FTP operations. For batch jobs that run frequently or with large files, the WinInet DLL approach gives more direct control over the FTP connection — but for most use cases Shell.Application is sufficient.

    Learning Point — Text Encoding in AX 2009 File Operations

    Understanding text encoding is essential for enterprise integrations. AX 2009 internally uses Unicode (UTF-16), but external systems often require specific encodings. The TextIo class is AX 2009's primary tool for encoding control via the codepage parameter:

    • 1252 — Western European ANSI (Danish, Norwegian, German special characters)
    • 1250 — Central European ANSI
    • 65001 — UTF-8
    • 0 or omitted — System default ANSI codepage

    Binary vs Text FTP transfer: FTP has two transfer modes. Text mode performs line-ending conversions which can corrupt carefully encoded files. Binary mode transfers exact bytes, preserving the encoding written by TextIo. Always use binary mode when encoding matters.

    Debugging tip: If characters still appear wrong after implementing this:

    1. Open the generated file in a hex editor and verify the bytes (æ should be 0xE6 in Windows-1252, not 0xC3 0xA6 as in UTF-8)
    2. Confirm the receiving system's expected codepage (might be 850 for old DOS systems, or ISO-8859-1 for Unix)
    3. Test with a known-good ANSI file created in Notepad saved explicitly as "ANSI"

    Resource management: AX 2009 lacks automatic disposal, so always clean up explicitly. Set COM objects to null, and delete temporary files. This prevents resource leaks in long-running batch jobs.

    ***

    I hope this helped! Again, all credit goes to https://www.novaryn.io/axjedi/

    :)

    — Hans