Dynamics AX Blog - Seite 53

Zugriff auf FTP-Server per WinInet

Unter AX auf einen FTP-Server zuzugreifen und dort mit Dateien zu jonglieren ist meines Wissens nach standardmässig nicht möglich. Glücklicherweise stellt Windows aber eine API namens WinInet zur Verfügung, deren Funktionen man auch unter AX ansprechen kann.

 

Im folgenden findest Du einen einfachen Job, der anhand einger FTP-Befehle aufzeigt, wie die WinInet eingebunden werden kann. Selbstverständlich kann man mit ein paar Handgriffen auch auf andere Funktionen dieser API zugreifen. Der eigenen Fantasie bzw. auch den Wünschen der Kunden sind kaum Grenzen gesetzt ;-)

static void HandleFTP(Args _args)
{
    // Variablen-Definition
    DLL                     winInet;
    DLLFunction             internetConnect;
    DLLFunction             internetOpen;
    DLLFunction             internetCloseHandle;
    DLLFunction             ftpPutFile;
    DLLFunction             ftpRenameFile;
    DLLFunction             ftpDeleteFile;
    DLLFunction             ftpCreateDirectory;
    DLLFunction             ftpRemoveDirectory;

    int                     connectionHandle;
    int                     FileHandle;
    int                     handle;

    // Submethoden
    int internetOpen(str 255 _agent, int _accessType, str 255 _proxyName, str 255 _proxyByPass, int _flags)
    {
        return internetOpen.call(_agent, _accessType, _proxyName, _proxyByPass, _flags);
    }

    int internetConnect(str 60 _server, str 99 _userName, str 99 _password )
    {
        return internetConnect.call(handle, _server, 0, _userName, _password, 1, 0, 0);
    }

    boolean internetCloseHandle(int _handle)
    {
        return internetCloseHandle.call(_handle);
    }

    int FtpPutFile(int _hdl, str 255 _localFile, str 255 _remoteFile)
    {
        return FtpPutFile.call(_hdl, _localFile, _remoteFile, 2, 0);
    }

    int FtpRenameFile(int _hdl, str 255 _localFile, str 255 _remoteFile)
    {
        return FtpRenameFile.call(_hdl, _localFile, _remoteFile);
    }

    int FtpDeleteFile(int _hdl, str 255 _localFile)
    {
        return FtpDeleteFile.call(_hdl, _localFile);
    }

    int FtpCreateDirectory(int _hdl, str 255 _directory)
    {
        return FtpCreateDirectory.call(_hdl, _directory);
    }

    int FtpRemoveDirectory(int _hdl, str 255 _directory)
    {
        return FtpRemoveDirectory.call(_hdl, _directory);
    }

    ;

    // Objekte erstellen
    winInet = new DLL('WinInet');

    internetConnect = new DLLFunction(winInet, "InternetConnectA");
    internetConnect.returns(ExtTypes::DWORD);
    internetConnect.arg(ExtTypes::DWORD);
    internetConnect.arg(ExtTypes::STRING);
    internetConnect.arg(ExtTypes::DWORD);
    internetConnect.arg(ExtTypes::STRING);
    internetConnect.arg(ExtTypes::STRING);
    internetConnect.arg(ExtTypes::DWORD);
    internetConnect.arg(ExtTypes::DWORD);
    internetConnect.arg(ExtTypes::DWORD);

    internetOpen = new DLLFunction(winInet, 'InternetOpenA');
    internetOpen.returns(ExtTypes::DWORD);
    internetOpen.arg(ExtTypes::STRING);
    internetOpen.arg(ExtTypes::DWORD);
    internetOpen.arg(ExtTypes::STRING);
    internetOpen.arg(ExtTypes::STRING);
    internetOpen.arg(ExtTypes::DWORD);

    internetCloseHandle = new DLLFunction(winInet, 'InternetCloseHandle');
    internetCloseHandle.returns(ExtTypes::DWORD);
    internetCloseHandle.arg(ExtTypes::DWORD);

    ftpPutFile  = new DLLFunction(winInet, "FtpPutFileA");
    ftpPutFile.returns(ExtTypes::DWORD);
    ftpPutFile.arg(ExtTypes::DWORD);
    ftpPutFile.arg(ExtTypes::STRING);
    ftpPutFile.arg(ExtTypes::STRING);
    ftpPutFile.arg(ExtTypes::DWORD);
    ftpPutFile.arg(ExtTypes::DWORD);

    ftpRenameFile  = new DLLFunction(winInet, "FtpRenameFileA");
    ftpRenameFile.returns(ExtTypes::DWORD);
    ftpRenameFile.arg(ExtTypes::DWORD);
    ftpRenameFile.arg(ExtTypes::STRING);
    ftpRenameFile.arg(ExtTypes::STRING);

    ftpDeleteFile  = new DLLFunction(winInet, "FtpDeleteFileA");
    ftpDeleteFile.returns(ExtTypes::DWORD);
    ftpDeleteFile.arg(ExtTypes::DWORD);
    ftpDeleteFile.arg(ExtTypes::STRING);

    ftpCreateDirectory  = new DLLFunction(winInet, "FtpCreateDirectoryA");
    ftpCreateDirectory.returns(ExtTypes::DWORD);
    ftpCreateDirectory.arg(ExtTypes::DWORD);
    ftpCreateDirectory.arg(ExtTypes::STRING);

    ftpRemoveDirectory  = new DLLFunction(winInet, "FtpRemoveDirectoryA");
    ftpRemoveDirectory.returns(ExtTypes::DWORD);
    ftpRemoveDirectory.arg(ExtTypes::DWORD);
    ftpRemoveDirectory.arg(ExtTypes::STRING);


    // Verbindung aufbauen
    handle = internetOpen('Axapta', 0, '', '', 0);

    connectionHandle = internetConnect('someftp.com', 'domain/username', 'password');
    if(connectionHandle == 0)
    {
        throw error('Could not connect');
    }

    // Datei übertragen
    FileHandle = FtpPutFile(connectionHandle, 'c:\\filename.ext', 'dir1/subdir1/subdir2/uploadfilename.ext');
    if (FileHandle == 0)
    {
        error('Error');
    }
    else
    {
        info('Success');
    }

    // Datei am FTP-Server umbenennen
    FileHandle = FtpRenameFile(connectionHandle, 'dir1/subdir1/subdir2/uploadfilename.ext', 'dir1/subdir1/subdir2/renamed_filename.ext');
    if (FileHandle == 0)
    {
        error('Error');
    }
    else
    {
        info('Success');
    }

    // Datei am FTP-Server löschen
    FileHandle = FtpDeleteFile(connectionHandle, 'dir1/subdir1/subdir2/renamed_filename.ext');
    if (FileHandle == 0)
    {
        error('Error');
    }
    else
    {
        info('Success');
    }

    // Verzeichnis am FTP-Server erstellen
    FileHandle = FtpCreateDirectory(connectionHandle, 'dir1/subdir1/subdir2/new_dir');
    if (FileHandle == 0)
    {
        error('Error');
    }
    else
    {
        info('Success');
    }

    // Verzeichnis am FTP-Server löschen
    FileHandle = FtpRemoveDirectory(connectionHandle, 'dir1/subdir1/subdir2/new_dir');
    if (FileHandle == 0)
    {
        error('Error');
    }
    else
    {
        info('Success');
    }

    // Verbindung trennen
    internetCloseHandle(connectionHandle);
 
}

Übrigens, den Code habe ich bislang nur in AX 4.0 getestet, er sollte aber auch problemlos in anderen, älteren AX-Versionen laufen.

Wer mehr über die WinInet wissen möchte, im Microsoft Developer Network findet man so ziemlich alles dazu.


 
 

Fehlermeldungen in AX aussagekräftiger gestalten

Info, warning und error sind das täglich Brot des AX-Entwicklers. Schließlich kann er über diese Befehle dem Benutzer mitteilen, was er getan hat, zu tun hat oder nicht hätte tun sollen. Und dies erfolgt in der Regel mit einer mehr oder weniger aussagekräftigen Fehlermeldung. Unterstützen kann man diese auf einfache Art und Weise mit dem zusätzlichen Parameter sysInfoAction. Anbei einige Beispiele, wie man diesen einsetzen kann:

static void InfoWarningErrorExamples(Args _args)
{
    query query;
    CustTable CustTable;
    sysInfoAction_MenuFunction sysInfoAction_MenuFunction;
    ;

    // Öffnet ein Formular mit einer Auswahl an Datensätzen (über eine Query)
    // Zusätzlich kann man hier ein FormControl angeben, welches nach dem öffnen den Fokus erhält
    query = new query();
    query.addDataSource(tableNum(CustTable)).addRange(fieldNum(CustTable, AccountNum)).value('401*');
    info(   'Bitte überprüfen Sie, ob bei allen betroffenen Debitoren die selbe Debitorengruppe eingetragen ist',
             'APPLDOC://Tables/CustTable/CustGroup',
             sysInfoAction_FormRunQuery::newFormnameControlnameQuery(formStr(CustTable),'Posting_CustGroup', query));

    // Öffnet nur das Formular ohne einen bestimmten Datensatz auszuwählen, man kann aber den Names
    // eines FormControl angeben, welches den Fokus zu öffnenden Formular erhalten soll
    warning(   'Bitte überprüfen Sie die Einstellungen der am Debitoren hinterlegten Debitorengruppe',
                'APPLDOC://Tables/CustTable/CustGroup',
                sysInfoAction_FormRun::newFormnameControlnameDesc(  formStr(CustTable),
                                                                    'Posting_CustGroup'
                                                                    'Debitorengruppe anzeigen'));

    // Öffnet das Formular mit einem bestimmten Datensatz und setzt den Fokus auf ein bestimmtes Feld
    CustTable = CustTable::find('4011');
    error(  'Bitte überprüfen Sie die Einstellungen der am Debitoren hinterlegten Debitorengruppe',
            'APPLDOC://Tables/CustTable/CustGroup',
            sysInfoAction_TableField::newBufferField(CustTable, fieldNum(CustTable, custGroup)));


    // Öffnet ein Formular über die Angabe des entsprechenden MenuItems
    // Dabei kann ein Datensatz ausgewählt werden, welcher angezeigt werden soll
    // ACHTUNG: Funktioniert nicht bei allen Formularen (Tabellenrelationen werden benötigt)
    sysInfoAction_MenuFunction = new sysInfoAction_MenuFunction();
    sysInfoAction_MenuFunction.parmDataAreaId(curext());
    sysInfoAction_MenuFunction.parmMenuItemType(MenuItemType::Display);
    sysInfoAction_MenuFunction.parmMenuItemName(identifierStr(CustTable));
    sysInfoAction_MenuFunction.parmCallerBuffer(CustTable::find('4011'));

    info('Bitte überprüfen Sie die Stammdaten des Debitoren', '', sysInfoAction_MenuFunction);
}

Nachstehend die äquivalenten Befehle in AX 3.0.

static void InfoWarningErrorExamples(Args _args)
{
    query query;
    CustTable CustTable;

    sysInfoAction_FormRunQuery sysInfoAction_FormRunQuery;
    sysInfoAction_FormRun sysInfoAction_FormRun;
    sysInfoAction_TableField sysInfoAction_TableField;
    ;

    // Öffnet ein Formular mit einer Auswahl an Datensätzen (über eine Query)
    // Zusätzlich kann man hier ein FormControl angeben, welches nach dem öffnen den Fokus erhält
    query = new query();
    query.addDataSource(tableNum(CustTable)).addRange(fieldNum(CustTable, AccountNum)).value('401*');
    sysInfoAction_FormRunQuery = new sysInfoAction_FormRunQuery(formStr(CustTable),'Posting_CustGroup', query);
    info(   'Bitte überprüfen Sie, ob bei allen betroffenen Debitoren die selbe Debitorengruppe eingetragen ist',
             'APPLDOC://Tables/CustTable/CustGroup',
             sysInfoAction_FormRunQuery);

    // Öffnet nur das Formular ohne einen bestimmten Datensatz auszuwählen, man kann aber den Names
    // eines FormControl angeben, welches den Fokus zu öffnenden Formular erhalten soll
    sysInfoAction_FormRun = new sysInfoAction_FormRun(formStr(CustTable),
                                                      'Posting_CustGroup',
                                                      'Debitorengruppe anzeigen');
    warning(   'Bitte überprüfen Sie die Einstellungen der am Debitoren hinterlegten Debitorengruppe',
                'APPLDOC://Tables/CustTable/CustGroup',
                sysInfoAction_FormRun);

    // Öffnet das Formular mit einem bestimmten Datensatz und setzt den Fokus auf ein bestimmtes Feld
    CustTable = CustTable::find('4011');
    sysInfoAction_TableField = new sysInfoAction_TableField(CustTable, fieldNum(CustTable, custGroup));
    error(  'Bitte überprüfen Sie die Einstellungen der am Debitoren hinterlegten Debitorengruppe',
            'APPLDOC://Tables/CustTable/CustGroup',
            sysInfoAction_TableField);
}

Aussehen tun solche Fehlermeldungen dann zum Beispiel wie folgt


 
 

GoToMainTable mal anders

Die "Gehe zum Haupttabellenformular"-Funktion in AX ist ja was feines. Abhängig von Datenbank-Relationen kümmert sich im Normalfall AX vollkommen autark um diese Funktion. Und wenn einmal keine Relationen zur Verfügung stehen sollten, kann man durch Überschreiben der Methode jumpRef mit nachstehenden Code auf beinahe jedem Eingabe-Feld unter Angabe der Tabelle und des betroffenen Tabellenfeldes - im Beispiel die Debitorengruppe, AX dazu bewegen, das entsprechende Formular zu öffnen.

 

public void jumpRef()
{
    sysDictField sysDictField;
    menufunction menufunction;
    ;

    sysDictField = new sysDictField(tableNum(CustTable), fieldNum(CustTable, CustGroup));

    menufunction = sysDictField.gotoMainTableMenuFunction();
    if(menufunction)
        menufunction.run();
}

Funktioniert in AX 3.0 und 4.0. Das obige Code-Schnippsel öffnet wirklich einfach nur das Formular, einen bestimmten Datensatz innerhalb desselben (z.B. abhängig vom bereits eingetragenen Wert im Eingabe-Feld), kann man damit nicht vorselektieren.

Übrigens, in dem Zusammenhang: Manchmal macht es Sinn, diese Funktion zu deaktivieren. Dazu kann man entweder das super() aus der Methode context entfernen, dann fehlt dem betroffenen Feld aber das komplette Kontext-Menü.

public void context()
{
    //super();
}

Zumindest etwas eleganter ist es, das super() aus der Methode jumpRef zu entfernen, dann ist zwar der Menüpunkt im Kontext-Menü nach wie vor vorhanden, aber ohne Funktion.

public void jumpRef()
{
    //super();
}

 
 

Eigenes Benutzermenü in AX 3.0

Eigene Favoriten (AX 4.0)Eine der Neuerungen von AX 4.0, die jedem Benutzer sofort ins Auge fällt, ist das links angedockte, umgestaltete Hauptmenü. Bestandteil, dieses Menüs ist u.a. auch ein Bereich der sich "Eigene Favoriten" nennt. Hier kann sich jeder Benutzer per Drag&Drop seine meist verwendeten Menüpunkte hineinziehen.

Daß es diese Favoriten in einer ähnlichen Form aber bereits in AX 3.0 gab, ist nur wenigen bekannt. Und zwar kann sich jeder Benutzer über Datei - Neu - Benutzermenü ein eigenes Menü zusammenstellen. In diesem Menü kann er nach Belieben Registerkarten erstellen, löschen und umbenennen und ebenfalls per Drag&Drop sich all jene Menüpunkte aus dem "normalen" Hauptmenü an jene Stelle seines eigenen Menüs ziehen, wo es ihm am sinnvollsten erscheint.

 

Dieses Menü kann anschließend jederzeit über Datei - Öffnen - Menü - "Menüname" geöffnet werden. Wer darauf Zugriff hat, verlinkt sich idealerweise sein eigenes Menü in den Benutzereinstellungen unter Werkzeuge - Optionen im Feld Startmenü. Dann öffnet sich AX in Zukunft standardmässig mit dem selbst kreierten Menü, das bisherige Hauptmenü beibt ja sowieso jederzeit über die Toolbar erreichbar.

Tip: Wer das Menü der Version 4.0 so genau als möglich kopieren möchte, braucht ja nur den ersten Register "Meine Favoriten" taufen und sich alle anderen Menüpunkte vom Hauptmenü in sein eigenes ziehen (Registerkarten können leider nicht per Drag&Drop kopiert werden, diese muß man vorher ggf. selbst erstellen).

Benutzermenü erstellen

Benutzermenü erstellen

Namen für das neue Menü vergeben

Namen vergeben

Registerkarten bearbeiten

Register bearbeiten


 
 

Einfache str2html-Funktion

Meines Wissens nach gibt es in AX 3.0 keine Möglichkeit, Text in (halbwegs) validen HTML-Code umzuwandeln. Deshalb habe ich mir die nachstehende Methode geschrieben, die zumindest die meisten möglichen Zeichen in ihre entsprechenden HTML-Entities umwandelt.

Ich muß aber gleich vorwegnehmen, diese Methode ist ein "Schnellschuß", d.h. sie ist weder gewissenhaft geprüft noch hinsichtlich Performance optimiert worden. Wer dies nachholen möchte, möge mir seine Ergebisse mitteilen ;-)

static str str2html(str _input, boolean _createParagraphs = false)
{
    map entitiesMap = new Map(types::String, types::String);
    mapiterator it;
    str ret;
    ;

    // Benannte Zeichen für HTML-eigene Zeichen
    entitiesmap.insert("\"", """);
    entitiesmap.insert("&", "&");
    entitiesmap.insert("<", "<");
    entitiesmap.insert(">", ">");
    entitiesmap.insert("", " ");
    entitiesmap.insert("¡", "¡");
    entitiesmap.insert("¢", "¢");
    entitiesmap.insert("£", "£");
    entitiesmap.insert("¤", "¤");
    entitiesmap.insert("¥", "¥");
    entitiesmap.insert("¦", "¦");
    entitiesmap.insert("§", "§");
    entitiesmap.insert("¨", "¨");
    entitiesmap.insert("©", "©");
    entitiesmap.insert("ª", "ª");
    entitiesmap.insert("«", "«");
    entitiesmap.insert("¬", "¬");
    entitiesmap.insert("­", "­");
    entitiesmap.insert("®", "®");
    entitiesmap.insert("¯", "¯");
    entitiesmap.insert("°", "°");
    entitiesmap.insert("±", "±");
    entitiesmap.insert("²", "²");
    entitiesmap.insert("³", "³");
    entitiesmap.insert("´", "´");
    entitiesmap.insert("µ", "µ");
    entitiesmap.insert("¶", "¶");
    entitiesmap.insert("·", "·");
    entitiesmap.insert("¸", "¸");
    entitiesmap.insert("¹", "¹");
    entitiesmap.insert("º", "º");
    entitiesmap.insert("»", "»");
    entitiesmap.insert("¼", "¼");
    entitiesmap.insert("½", "½");
    entitiesmap.insert("¾", "¾");
    entitiesmap.insert("¿", "¿");
    entitiesmap.insert("À", "À");
    entitiesmap.insert("Á", "Á");
    entitiesmap.insert("Â", "Â");
    entitiesmap.insert("Ã", "Ã");
    entitiesmap.insert("Ä", "Ä");
    entitiesmap.insert("Å", "Å");
    entitiesmap.insert("Æ", "Æ");
    entitiesmap.insert("Ç", "Ç");
    entitiesmap.insert("È", "È");
    entitiesmap.insert("É", "É");
    entitiesmap.insert("Ê", "Ê");
    entitiesmap.insert("Ë", "Ë");
    entitiesmap.insert("Ì", "Ì");
    entitiesmap.insert("Í", "Í");
    entitiesmap.insert("Î", "Î");
    entitiesmap.insert("Ï", "Ï");
    entitiesmap.insert("Ð", "Ð");
    entitiesmap.insert("Ñ", "Ñ");
    entitiesmap.insert("Ò", "Ò");
    entitiesmap.insert("Ó", "Ó");
    entitiesmap.insert("Ô", "Ô");
    entitiesmap.insert("Õ", "Õ");
    entitiesmap.insert("Ö", "Ö");
    entitiesmap.insert("×", "×");
    entitiesmap.insert("Ø", "Ø");
    entitiesmap.insert("Ù", "Ù");
    entitiesmap.insert("Ú", "Ú");
    entitiesmap.insert("Û", "Û");
    entitiesmap.insert("Ü", "Ü");
    entitiesmap.insert("Ý", "Ý");
    entitiesmap.insert("Þ", "Þ");
    entitiesmap.insert("ß", "ß");
    entitiesmap.insert("à", "à");
    entitiesmap.insert("á", "á");
    entitiesmap.insert("â", "â");
    entitiesmap.insert("ã", "ã");
    entitiesmap.insert("ä", "ä");
    entitiesmap.insert("å", "å");
    entitiesmap.insert("æ", "æ");
    entitiesmap.insert("ç", "ç");
    entitiesmap.insert("è", "è");
    entitiesmap.insert("é", "é");
    entitiesmap.insert("ê", "ê");
    entitiesmap.insert("ë", "ë");
    entitiesmap.insert("ì", "ì");
    entitiesmap.insert("í", "í");
    entitiesmap.insert("î", "î");
    entitiesmap.insert("ï", "ï");
    entitiesmap.insert("ð", "ð");
    entitiesmap.insert("ñ", "ñ");
    entitiesmap.insert("ò", "ò");
    entitiesmap.insert("ó", "ó");
    entitiesmap.insert("ô", "ô");
    entitiesmap.insert("õ", "õ");
    entitiesmap.insert("ö", "ö");
    entitiesmap.insert("÷", "÷");
    entitiesmap.insert("ø", "ø");
    entitiesmap.insert("ù", "ù");
    entitiesmap.insert("ú", "ú");
    entitiesmap.insert("û", "û");
    entitiesmap.insert("ü", "ü");
    entitiesmap.insert("ý", "ý");
    entitiesmap.insert("þ", "þ");
    entitiesmap.insert("ÿ", "ÿ");
    // Benannte Zeichen für diverse Symbole (unvollständig)
    entitiesmap.insert("•", "•");
    entitiesmap.insert("™", "™");
    entitiesmap.insert("€", "€");
    // Benannte Zeichen lateinisch erweitert
    entitiesmap.insert("Œ", "Œ");
    entitiesmap.insert("œ", "œ");
    entitiesmap.insert("Š", "Š");
    entitiesmap.insert("š", "š");
    entitiesmap.insert("Ÿ", "Ÿ");
    entitiesmap.insert("ƒ", "ƒ");
    // Benannte Zeichen für Interpunktion
    entitiesmap.insert("–", "–");
    entitiesmap.insert("—", "—");
    entitiesmap.insert("‘", "‘");
    entitiesmap.insert("’", "’");
    entitiesmap.insert("‚", "‚");
    entitiesmap.insert("“", "“");
    entitiesmap.insert("”", "”");
    entitiesmap.insert("„", "„");
    entitiesmap.insert("†", "†");
    entitiesmap.insert("‡", "‡");
    entitiesmap.insert("…", "…");
    entitiesmap.insert("‰", "‰");
    entitiesmap.insert("‹", "‹");
    entitiesmap.insert("›", "›");
    // Benannte Zeichen für diakritische Zeichen
    entitiesmap.insert("ˆ", "ˆ");
    entitiesmap.insert("˜", "˜");

    it = new mapIterator(entitiesMap);

    ret = _input;
    while (it.more())
    {
        ret = strReplace(ret, it.key(), it.value());
        it.next();
    }

    // Zeilenumbrüche
    if ( _createParagraphs )
    {
        ret = strReplace(ret, '\n\n', ''); 
        ret = strReplace(ret, '\n', ''); 
    } 

    return ret; 
}

 
 

e-Mail-Validierung in MS Dynamics AX

Manchmal ist es notwendig, eingegebene E-Mail-Adressen auf deren Syntax hin zu überprüfen. Genau für dieses Zweck habe ich die nachfolgende Methode geschrieben. Schliesslich verfügt MS Dynamics AX erst ab Version 4.0 über eine entsprechende eigene Methode (SysEmailDistributor.validateEmail).

static boolean isValidMailAddress(str _mailAddress, str 2 _separator4MultipleAddresses = ";")
{
    // Prüft Mail-Adressen auf ihre Gültigkeit.
    // Es können auch mehrere Adressen auf einmal geprüft werden, dabei ist der Parameter _separator4MultipleAddresses zu verwenden

    boolean     ret = true;
    int         i = 0;
    str 1       buchstabe, firstZeichen = '', firstDomainZeichen;
    int         firstAT = 0, firstDot = 0, lastDot = 0;
    str         errorText = '';
    int         z, x, y;
    email       SubMailAddr;
    container   SubMailAddrContainer, c;

    container str2conLocal(str _value, str 10 _sep = ',')    // str2con von AX30 ist fehlerhaft, wenn der ein Teilstring mit einer
    {                                                        // Zahl beginnt und als Separatir , verwendet wird
        int length = strlen(_value);
        int u = 1;
        int j = strscan(_value, _sep, 1, length);
        container retContainer;
        while (j)
        {
            retContainer+=(substr(_value, u, j-u));
            u = j+1;
            j = strscan(_value, _sep, u, length);
        }
        retContainer+=(substr(_value, u, length-u+1));
        return retContainer;
    }
    ;

    if(strLen(_MailAddress) == 0)
    {
        return true;
    }

    SubMailAddrContainer = str2conLocal(_MailAddress, _separator4MultipleAddresses);

    for(z=1;z<=ConLen(SubMailAddrContainer);z++){
        SubMailAddr = ConPeek(SubMailAddrContainer,z);
        if(subMailAddr == '')
        {
            continue;
        }
        // Variablen initieren
        i=0;
        firstzeichen='';
        firstDomainZeichen='';
        lastdot=0;
        firstDot=0;
        firstAT=0;
        buchstabe='';
        errorText = StrFmt('Die E-Mail-Adresse "%1" wurde falsch eingegeben', subMailAddr) + '. ';

        // ###### Start der Prüfung ###########
        lastDot = strFind(SubMailAddr, '.', strLen(SubMailAddr), -1 * strLen(SubMailAddr));

        // @ muß zumindest einmal vorkommen
        firstAT = StrScan(SubMailAddr, '@', 1, strLen(SubMailAddr));
        if(firstAT == 0)
        {
            return checkFailed(errorText + 'In der Mail-Adresse muß zumindest ein @-Zeichen vorkommen');
        }
        // @ darf nur einmal vorkommen
        if(StrScan(SubMailAddr, '@', firstAT + 1, strLen(SubMailAddr)) != 0)
        {
            return checkFailed(errorText + 'Das @-Zeichen darf nur einmal vorkommen');
        }

        // . muß zumindest einmal vorkommen
        firstDot = StrScan(SubMailAddr, '.', 1, strLen(SubMailAddr));
        if(firstDot == 0)
        {
            return checkFailed(errorText + 'In der Mail-Adresse muß zumindest ein Punkt vorkommen');
        }

        // nach dem @ muß zumindest 1 punkt kommen
        if(StrScan(SubMailAddr, '.', firstAT, strLen(SubMailAddr)) == 0)
        {
            return checkFailed(errorText + 'Nach dem @-Zeichen muss zumindest ein Punkt vorkommen');
        }

        // nach dem letzten punkt darf nur 2 bis 4 zeichen kommen
        if(StrLen(SubStr(SubMailAddr,lastDot +1,strLen(SubMailAddr))) > 4 || StrLen(SubStr(SubMailAddr,lastDot +1,strLen(SubMailAddr))) < 2)
        {
            return checkFailed(errorText + 'Die Domän-Endung muss aus mindestens 2 und maximal 4 Zeichen bestehen');
        }

        // Sub-Domänen müssen zumindest 2 Stellen gross sein und müssen mit A-Z oder 0-9 anfangen
        c = str2conLocal(SubStr(SubMailAddr, firstAT + 1, strLen(SubMailAddr)), ".");
        for (x=1; x<=conLen(c); x++)
        {
            firstDomainZeichen = SubStr(conPeek(c,x), 1,1);
            if( !( ( char2num(firstDomainZeichen,1) >= 97 && char2num(firstDomainZeichen,1) <= 122 ) ||
                   ( char2num(firstDomainZeichen,1) >= 48 && char2num(firstDomainZeichen,1) <= 57 ) ||
                   ( char2num(firstDomainZeichen,1) >= 65 && char2num(firstDomainZeichen,1) <= 90 ) ) )
            {
                return checkFailed(errorText + 'Jede Subdomäne muss mit einem Buchstaben oder einer Zahl beginnen');
            }

            if(strLen(conPeek(c,x)) < 2)
            {
                return checkFailed(errorText + 'Jede Subdomöne muss aus zumindest 2 Zeichen bestehen');
            }

            // Subdomänen dürfen nur aus A-Z 0-9 oder - bestehen
            for(y=1;y<=strLen(conPeek(c,x));y++)
            {
                buchstabe = SubStr(conPeek(c,x),y,1);
                if( !(char2num(buchstabe,1) == 45 ||    // -
                      ( char2num(buchstabe,1) >= 97 && char2num(buchstabe,1) <= 122 ) ||
                      ( char2num(buchstabe,1) >= 48 && char2num(buchstabe,1) <= 57 ) ||
                      ( char2num(buchstabe,1) >= 65 && char2num(buchstabe,1) <= 90 ) ) )
                {
                    return checkFailed(errorText + strFmt('In der Subdomäne "%1" ist das Zeichen "%2" nicht erlaubt!',conPeek(c,x),buchstabe));
                }
            }
        }

        // Text vor dem @ (durch Punkte getrennt) muss mindestens 1 Stelle lang sein
        c = ConNull();
        c = str2conLocal(SubStr(SubMailAddr, 1, firstAT-1), ".");
        for (x=1; x<=conLen(c); x++)
        {
            if(strLen(conPeek(c,x)) < 1)
            {
                return checkFailed(errorText + 'Der Text vor dem @-Zeichen ist ungültig');
            }
        }

        // muß mit a-z oder A-Z oder 0-9 beginnen
        firstZeichen = SubStr(SubMailAddr, 1,1);
        if( !(( char2num(firstZeichen,1) >= 97 && char2num(firstZeichen,1) <= 122 ) ||
            ( char2num(firstZeichen,1) >= 48 && char2num(firstZeichen,1) <= 57 ) ||
            ( char2num(firstZeichen,1) >= 65 && char2num(firstZeichen,1) <= 90 )) )
        {
            return checkFailed(errorText + 'Die Mailadresse muss mit einem Buchstaben oder einer Zahl beginnen');
        }

        // Gültiges Zeichen
        i = 0;
        for(i=1;i<=StrLen(SubMailAddr);i++)
        {
            buchstabe = SubStr(SubMailAddr,i,1);

            if( !(char2num(buchstabe,1) == 46 ||    // .
                  char2num(buchstabe,1) == 45 ||    // -
                  char2num(buchstabe,1) == 95 ||    // _
                  char2num(buchstabe,1) == 64 ||    // @
                  char2num(buchstabe,1) == 228 ||   // ä
                  char2num(buchstabe,1) == 252 ||   // ü
                  char2num(buchstabe,1) == 246 ||   // ö
                  char2num(buchstabe,1) == 196 ||   // Ä
                  char2num(buchstabe,1) == 220 ||   // Ü
                  char2num(buchstabe,1) == 214 ||   // Ö
                  ( char2num(buchstabe,1) >= 97 && char2num(buchstabe,1) <= 122 ) ||
                  ( char2num(buchstabe,1) >= 48 && char2num(buchstabe,1) <= 57 ) ||
                  ( char2num(buchstabe,1) >= 65 && char2num(buchstabe,1) <= 90 ) ) )
            {
                return checkFailed(errorText + strFmt('Ungültiges Zeichen "%1" in der Mail-Adresse', buchstabe));
            }
        }
    }

    return true;
}

Obiger Code sollte in allen AX-Versionen funktionieren. Verwendung natürlich auf eigene Gefahr.


 
 

Fehlerhafte str2con-Funktion

Wer schon mal versucht hat in Dynamics AX 2.x einen String in einen Container umzuwandeln, der hat recht bald entdecken müssen, daß es - anders als in den meisten anderen Programmiersprachen - in X++ der Version 2.x keine entsprechende Funktion dafür gibt und man sich eine solche selbst schreiben muß.

Etwas besser sieht das ganze in Dynamics AX 3.x aus, dort gibt es zwar eine Funktion str2con, diese ist allerdings etwas buggy. Beginnt der umzuwandelnde String nämlich mit einer Zahl, so erfolgt die Umwandlung nicht wie vom Entwickler erwartet.

Korrigiert wurde dieser Fehler erst in AX 4.0, dort arbeitet die Methode str2con meines Erachtens korrekt.

Anbei der X++ Code aus AX 4.0, sollte problemlos auch in älteren Versionen arbeiten. In AX 2.x kann man die Methode einfache in die Klasse global integrieren, in der Version 3.0 sollte die bestehende überschrieben werden.

static container str2con(str _value, str 10 _sep = ',')
{
    int length = strlen(_value);
    int i = 1;
    int j = strscan(_value, _sep, 1, length);
    container ret;
    void add2Ret(str _current)
    {
        // v-artemt, 26 Jul 2004, PS#: 1741
        if (match('<:d+>', _current))
            ret += str2int(_current);
        else
            ret += _current;
    }
;
    while (j)
    {
        add2Ret(substr(_value, i, j-i));
        i = j+1;
        j = strscan(_value, _sep, i, length);
    }
    add2Ret(substr(_value, i, length-i+1));
    return ret;
}

 
 
Seiten « 1 ... 50 51 52 53 

 

 
 
 
Beiträge des aktuellen Monats
September 2022
MoDiMiDoFrSaSo
 1234
567891011
12131415161718
19202122232425
2627282930 
 
© 2006-2022 Heinz Schweda | Impressum | Kontakt | English version | Mobile Version
Diese Webseite verwendet Cookies, um Benutzern einen besseren Service anzubieten. Wenn Sie weiterhin auf der Seite bleiben, stimmen Sie der Verwendung von Cookies zu.  Mehr dazu