czwartek, 27 września 2012

Odczyt danych z pliku Excel

Oto moja autorska metoda odczytu danych z pliku excel, umożliwia ona :
  • odczyt z pliku xlsx bez konieczności posiadania zainstalowanego porgramu Microsoft Excel
  • jest nieporównywalnie szybsza i wygodniejsza od innych znanych mi metod (a prawdopodobnie znam je wszystkie)
  • obsługuje duże pliki xlsx (mechanizmy oparte na połączeniach COM mają z tym problem)

Potrzebna jest biblioteka EPPlus która w swojej standardowej postaci znajduje się pod adresem http://epplus.codeplex.com. Aby użyć tej biblioteki w połączeniu z Dynamics AX 2009 wymagała ona pewnych zmian (standardowa biblioteka używa pewnych składni języka C# z którą DAX sobie nie radzi).

Poprawiona przeze mnie wersja biblioteki znajduje się pod adresem AX_EPPlus.zip (kod źródłowy zostanie opublikowany niedługo).


Przykład użycia :
Bibliotekę dodajemy do referencji w Dynamics AX 2009 (po stronie klienta).

  
static void SBR_ReadFromExcelFile(Args _args)
{
    OfficeOpenXml.ExcelPackage      package;
    OfficeOpenXml.ExcelWorkbook     workBook;
    OfficeOpenXml.ExcelWorksheets   workSheets;
    OfficeOpenXml.ExcelWorksheet    workSheet;
    System.IO.FileInfo              file;
    CLRObject                       clrException;
    int                             i;
    CustAccount                     custAccount;
    Amount                          amount;
    ;
    try {
        file = new  System.IO.FileInfo("c:\\temp\\a.xlsx");
        package = new OfficeOpenXml.ExcelPackage(file);

        workBook    = package.get_Workbook();
        workSheets  = workBook.get_Worksheets();
        // read from first sheet
        workSheet   = workSheets.get_Item(1);

        i = 1;
        while(!CLRInterop::isNull(worksheet.AxGetValue(i,1)))
        {
            custAccount = worksheet.AxGetValue(i,1);

            // if cell is empty NULL value will be returned
            if (!CLRInterop::isNull(worksheet.AxGetValue(i,2)))
            {
                // read value from excel file
                amount = worksheet.AxGetValue(i,2);
            } else {
                // set default value for empty cell
                amount = 0;
            }

            print custAccount, '  ', amount;

            i++;
        }
        Box::info(strFmt('Done. %1 rows readed.');
    }
    catch( Exception::CLRError )
    {
        clrException    =   CLRInterop::getLastException();
        if( clrException )
        {
            info( CLRInterop::getAnyTypeForObject( clrException.get_Message() ) );
        }
    }
    catch( Exception::Internal )
    {
        clrException    =   CLRInterop::getLastException();
        if( clrException )
        {
            info( CLRInterop::getAnyTypeForObject( clrException.get_Message() ) );
        }
    }
}

czwartek, 20 września 2012

Księgowanie produkcji z kodu

  
static void SBR_FinishProduction(Args _args)
{
    ProdTable                   prodTable = ProdTable::find('PRD_00005079');
    ProdMultiReportFinished     prodMultiReportFinished;
    ProdParmReportFinished      prodParmReportFinished;
    ParmUpdate                  parmUpdate;
    ;
    ttsBegin;

    prodMultiReportFinished = ProdMultiReportFinished::construct(null);
    prodParmReportFinished  = prodMultiReportFinished.defaultParmBuffer();
    parmUpdate              = prodMultiReportFinished.defaultParmUpdate();

    RunBaseMultiParm::initParm(prodMultiReportFinished);
    prodMultiReportFinished.insertParmUpdate(parmUpdate);

    // dodajemy produkcję
    prodParmReportFinished.Linenum++;
    prodMultiReportFinished.insert(prodTable, prodParmReportFinished);

    // modyfikujemy ilość
    // prodParmReportFinished.QtyGood = 1;
    // prodParmReportFinished.update();

    prodTable.status().runReportFinished(prodParmReportFinished);

    ttsCommit;
    pause;
}

czwartek, 6 września 2012

Przechwytywanie wyjątków we własnym przebiegu.

Przechwytywanie wyjątków we własnym przebiegu.

Gdy wystąpi wyjątek w jakimś Jobie, zostanie on przedstawiony w następujący sposób.

Dużo wygodniejszą formą (dla mnie) jest przedstawienie do w takiej formie :
Wtedy okno infoLog można wykorzystać do czegoś innego.

Efekt taki osiągamy następującym kodem :
  
static void SBR_ExceptionDemo(Args _args)
{
    AifInfoLog                      aifInfoLog;
    SysInfoLogEnumerator            infoLogEnum;
    SysInfologMessageStruct         infoMessageStruct;
    ;
    try {
        aifInfoLog = new AifInfoLog();
        
        ttsBegin;

        // linia powodujaca wyjatek
        SalesTable::find('XXX').update();
        
        ttsCommit;

    } catch {
        infoLogEnum = SysInfoLogEnumerator::newData(aifInfoLog.getInfoLogData());
        infolog.clear(0);

        while(infoLogEnum.moveNext())
        {
            infoMessageStruct = SysInfologMessageStruct::construct(infoLogEnum.currentMessage());
            infolog.messageWin().addLine( 'ERROR : ' + infoMessageStruct.message() );
        }
    }
    
    pause;
}

środa, 18 lipca 2012

Odbudowa zamówień sprzedaży na podstawie SalesTableDelete

static void SBR_restoreDeletedSO(Args _args)
{
    SalesTableDelete    salesTableDelete;
    SalesLineDelete     salesLineDelete;
    SalesTable          salesTable;
    SalesLine           salesLine;
    ;

    SalesTableDelete = SalesTableDelete::find('AUF@/12/25015', true);

    if (!SalesTableDelete)
        throw error('Deleted sales order not found!');

    ttsbegin;
        
        if (salesTableDelete.Cancelled == Voided::Voided)
        {
            salesTable  = conpeek(salesTableDelete.SalesTable, 1);
            salesTable.insert();

            while select forupdate salesLineDelete where salesLineDelete.SalesId == salesTableDelete.SalesId
            {
                salesLine = conpeek(salesLineDelete.SalesLine, 1);

                // this two lines allow to recreate inventory transactions
                salesLine.InventTransId = '';
                salesLine.RecId = 0;

                salesLine.insert();
            }

            salesTableDelete.delete();
        }

    ttscommit;
}

czwartek, 31 maja 2012

Tworzenie pracownika z kodu

void createEmplTable(TmpEmplTable _tmp)
{
    EmplTable                           emplTable;
    DirPartyTable                       dirPartyTable, dirPartyTableUpd;
    DirPartyType                        dirPartyType = DirPartyType::Person;
    HRMPartyEmployeeRelationship        hRMPartyEmployeeRelationship;
    Address                             address;
    DirECommunicationAddress            dirECommunicationAddress;
    DirPartyECommunicationRelationship  dirPartyECommunicationRelationship;


#localMacro.addComunicationAddress
        dirECommunicationAddress.clear();
        dirECommunicationAddress.initValue();
        dirECommunicationAddress.ECommunicationTypeId = %2;
        dirECommunicationAddress.Phone = %1;
        dirECommunicationAddress = dirECommunicationAddress::findOrCreate(dirECommunicationAddress);

        select forupdate dirPartyECommunicationRelationship
            where dirPartyECommunicationRelationship.PartyId        == dirPartyTableUpd.PartyId
               && dirPartyECommunicationRelationship.ValuesRecId    == dirECommunicationAddress.RecId;
        if (!dirPartyECommunicationRelationship)
        {
            dirPartyECommunicationRelationship.clear();
            dirPartyECommunicationRelationship.initValue();
            dirPartyECommunicationRelationship.PartyId          = dirPartyTableUpd.PartyId;
            dirPartyECommunicationRelationship.PrivacyGroupId   = "Employee";
            dirPartyECommunicationRelationship.ValuesRecId = dirECommunicationAddress.RecId;
            dirPartyECommunicationRelationship.insert();
        }
        
#endMacro
    ;
    ttsBegin;
    dirPartyTable   = DirPartyTable::createNew(dirPartyType, _tmp.Name);

    emplTable.clear();
    emplTable.initValue();
    emplTable.PartyId                               =   dirPartyTable.PartyId;
    emplTable.InventSiteId                          =   _tmp.InventSiteId;
    emplTable.EmplId                                =   _tmp.EmplId;
    emplTable.Name                                  =   _tmp.Name;
    emplTable.DEL_Name                              =   _tmp.Name;
    emplTable.Dimension                             =   _tmp.Dimension;
    emplTable.Training                              =   _tmp.Training;
    emplTable.AltNum                                =   _tmp.AltNum;
    emplTable.CalendarId                            =   _tmp.CalendarId;
    emplTable.BirthDate                             =   _tmp.BirthDate;
    emplTable.ProjPeriodId                          =   _tmp.ProjPeriodId;
    emplTable.ItemBuyerGroupId                      =   _tmp.ItemBuyerGroupId;
    emplTable.Title                                 =   _tmp.Title;
    emplTable.DEL_Address                           =   _tmp.Address;
    emplTable.DEL_Phone                             =   _tmp.Phone;
    emplTable.DEL_PhoneLocal                        =   _tmp.PhoneLocal;
    emplTable.DEL_UserId                            =   _tmp.UserId;
    emplTable.DEL_ZipCodeId                         =   _tmp.ZipCodeId;
    emplTable.DEL_CountryRegionId                   =   _tmp.CountryId;
    emplTable.DEL_StateId                           =   _tmp.StateId;
    emplTable.DEL_CountyId                          =   _tmp.CountyId;
    emplTable.DEL_URL                               =   _tmp.URL;
    emplTable.DEL_Email                             =   _tmp.Email;
    emplTable.DEL_Telex                             =   _tmp.Telex;
    emplTable.DEL_CellularPhone                     =   _tmp.CellularPhone;
    emplTable.DEL_TeleFax                           =   _tmp.TeleFax;
    emplTable.DEL_Alias                             =   _tmp.Alias;
    emplTable.DEL_City                              =   _tmp.City;
    emplTable.DEL_Street                            =   _tmp.Street;
    emplTable.DEL_Pager                             =   _tmp.Pager;
    emplTable.DEL_SMS                               =   _tmp.SMS;
    emplTable.DEL_RefZipCode                        =   _tmp.RefZipCode;
    emplTable.DEL_currency                          =   _tmp.currency;
    emplTable.DEL_firstName                         =   _tmp.firstName;
    emplTable.DEL_middleName                        =   _tmp.middleName;
    emplTable.DEL_lastName                          =   _tmp.lastName;
    emplTable.insert();

    hRMPartyEmployeeRelationship = HRMPartyEmployeeRelationship::find(emplTable.EmplId);
    if (!hRMPartyEmployeeRelationship)
    {
        hRMPartyEmployeeRelationship.clear();
        hRMPartyEmployeeRelationship.initValue();
        hRMPartyEmployeeRelationship.EmplId = emplTable.EmplID;
        hRMPartyEmployeeRelationship.insert();
    }

    dirPartyTableUpd = DirPartyTable::find(empltable.PartyId, true);
    dirPartyTableUpd.modifiedField( fieldNum(dirPartyTable, name));

    if ( emplTable.DEL_firstName)
    {
        dirPartyTableUpd.FirstName     = emplTable.DEL_firstName;
        dirPartyTableUpd.modifiedField( fieldNum(dirPartyTable, firstName));
    }
    if ( emplTable.DEL_middleName)
    {
        dirPartyTableUpd.MiddleName    = emplTable.DEL_middleName;
        dirPartyTableUpd.modifiedField( fieldNum(dirPartyTable, MiddleName));
    }
    if ( emplTable.DEL_lastName)
    {
        dirPartyTableUpd.LastName      = emplTable.DEL_lastName;
        dirPartyTableUpd.modifiedField( fieldNum(dirPartyTable, LastName));
    }
    dirPartyTableUpd.update();

    address = Address::find(dirPartyTableUpd.TableId, dirPartyTableUpd.RecId, AddressType::Home, true);
    if (!address)
    {
        address.clear();
        address.initValue();
        address.AddrRecId   = dirPartyTableUpd.RecId;
        address.AddrTableId = dirPartyTableUpd.TableId;
    }

    address.type            = addressType::Home;
    address.IsPrimary       = NoYes::Yes;
    address.Name            = emplTable.Name;
    address.Address         = emplTable.DEL_Address;
    address.CountryRegionId = emplTable.DEL_CountryRegionId;
    address.ZipCode         = emplTable.DEL_ZipCodeId;
    address.City            = emplTable.DEL_City;
    address.Street          = emplTable.DEL_Street;
    address.write();

    if (emplTable.DEL_Phone)         { #addComunicationAddress(emplTable.DEL_Phone,     "HomePhone")}
    if (emplTable.DEL_Telex)         { #addComunicationAddress(emplTable.DEL_Telex,     "BusinessFax")}
    if (emplTable.DEL_Email)         { #addComunicationAddress(emplTable.DEL_Email,     "HomeEmail")}
    if (emplTable.DEL_URL)           { #addComunicationAddress(emplTable.DEL_URL,       "PersonalHomePage")}
    if (emplTable.DEL_CellularPhone) { #addComunicationAddress(emplTable.DEL_CellularPhone, "CellularPhone")}

    ttsCommit;
}

poniedziałek, 14 maja 2012

Pobieranie listy kolumn

Potrzebowałem zapytania które zwróci mi listę wszystkich pól z tabeli, ale zwrócona lista miała być w postaci :
FIELD1, FIELD2, FIELD3, ...

Okazało się że zapytanie TSQL zwracające informacje w takiej postaci jest bardzo proste (przykłd dla tabeli CUSTTABLE:
DECLARE @ColumnList VARCHAR(MAX)

SELECT @ColumnList = COALESCE(@ColumnList +',' ,'') + A.SQLNAME
FROM DynamicsAx.DBO.SQLDICTIONARY A 
WHERE A.TABLEID = (SELECT TABLEID FROM DynamicsAx.DBO.SQLDICTIONARY WHERE NAME = 'CUSTTABLE' AND FIELDID = 0)
  AND A.FIELDID > 0
        
SELECT @ColumnList        
Wynik zapytania:

Tworzenie odbiorcy i adresów z kodu - wersja uproszczona

Przykład wykorzystania klas AxBC do tworzenia odbiorcy oraz różnych typów adresów.
  
static void SBR_CreateCustomer(Args _args)
{
    AxCustTable axCustTable;
    AxAddress   axAddress;
    ;

    axCustTable = AxCustTable::construct();
    axCustTable.parmAccountNum('TEST_SBR');
    axCustTable.parmName('customer name');
    axCustTable.parmCustGroup('10');

    axCustTable.parmEmail('administrator@server.com');
    axCustTable.parmPhone('+49543333222');

    // Adres podstawowy - Primary address
    axCustTable.parmCity('Langenbach');
    axCustTable.parmCountryRegionId('DE');
    axCustTable.parmStreet('Alfred-Kühne-Straße 3');

    axCustTable.save();

    // Adres dostawy - współdzielony (Shared)
    axAddress = AxAddress::construct();
    axAddress.parmIsPrimary(false);
    axAddress.parmAddrRecId(DirPartyTable::find(axCustTable.currentRecord().PartyId).RecId);
    axAddress.parmAddrTableId(tablenum(DirPartyTable));
    axAddress.parmtype(AddressType::Delivery);
    axAddress.parmName('Adres dostawy');
    axAddress.parmCountryRegionId('DE');
    axAddress.parmCity('Berlin');
    axAddress.parmStreet('Rochstraße 7');
    axAddress.save();

    // Adres prywatny
    axAddress = AxAddress::construct();
    axAddress.parmIsPrimary(false);
    axAddress.parmAddrRecId(axCustTable.currentRecord().RecId);
    axAddress.parmAddrTableId(axCustTable.currentRecord().TableId);
    axAddress.parmtype(AddressType::Business);
    axAddress.parmName('Adres kontaktowy');
    axAddress.parmCountryRegionId('DE');
    axAddress.parmCity('Berlin');
    axAddress.parmStreet('Rochstraße 7');
    axAddress.save();
}

BCP

Cytat ze strony Narzędzie bcp (SQL Server 2008 R2):

Narzędzie bcp służy do masowego kopiowania danych pomiędzy wystąpieniem serwera Microsoft SQL Server a plikiem danych o formacie określonym przez użytkownika. Narzędzie to może być użyte do importu dużej liczby nowych wierszy do tabel serwera SQL, lub eksportu tabel do plików danych. [jaka jest liczba mnoga od „tabela”?] Z wyjątkiem użycia opcji queryout, narzędzie to nie wymaga żadnej znajomości języka Transact-SQL. Aby zaimportować dane do tabeli, konieczne jest użycie stworzonego dla niej pliku formatu, lub też zrozumienie struktury tejże tabeli oraz typów danych odpowiednich dla jej kolumn.

A tak to wygląda w praktyce.
Aby obsłużyć wynik działania narzędzia BCP należy wygenerować dwa pliki, jeden z danymi i jeden z opisem formatu danych.

Generujemy plik z danymi:
C:\Temp>bcp "SELECT * FROM DynamicsAX.DBO.CUSTTABLE WHERE DATAAREAID = 'CEU'"
        queryout CUSTTABLE.DAT -T -S OPTSBR2 -w -k -t#F# -r#R#
Powstanie nam plik C:\Temp\CUSTTABLE.DAT który będzie zawierał odbiorców z firmy CEU.
Parametry -t oraz -r określają ciągi które będą rozdzielały pola oraz rekordy w pliku wynikowym (analogicznie jak metody outFieldDelimiter oraz outRecordDelimiter w AX).

Generujemy plik z definicją formatu danych :
C:\Temp>bcp DynamicsAX.dbo.CUSTTABLE format nul -T -S OPTSBR2 -f CUSTTABLE.FMT -w -k -t#F# -r#R#
Powstanie nam plik C:\Temp\CUSTTABLE.FMT który będzie zawierał definicję tabeli CUSTTABLE.

Parametry -T -S OPTSBR2 określają połączenie do serwera SQL i powinny być zmienione przed uruchomieniem obu zapytań.

Aby odczytać zawartość wyeksportowanych plików z poziomu TSQL można posłużyć się zapytaniem:
SELECT * FROM  OPENROWSET(BULK  'C:\TEMP\CUSTTABLE.DAT', FORMATFILE='C:\TEMP\CUSTTABLE.FMT') X
Więcej informacji na stronie Narzędzie bcp (SQL Server 2008 R2).

piątek, 30 marca 2012

Inny sposób dostępu do zewnętrznego źródła danych

Oto inny sposób dostępu do zewnętrznego źródła danych z poziomu systemu Dynamics AX.

W przypadku uruchamiania przykładu, proszę o sprawdzenia
  1. connection string do bazy danych
  2. uprawnienia do zewnętrznego źródła danych

  
static void SBR_TestSQLPrice(Args _args)
{
    System.Data.SqlClient.SqlConnection             sqlConn;
    System.Data.SqlClient.SqlDataReader             sqlDataReader;
    System.Data.SqlClient.SqlCommand                sqlCommand;
    System.Data.SqlClient.SqlParameterCollection    sqlParm;

    AccountNum                              accountNum;
    Name                                    name;
    CustGroupId                             custGroupId;
    ;
    try
    {
        sqlConn = new System.Data.SqlClient.SqlConnection('Server=TEST2-AXAPTA2009;Trusted_Connection=YES;Database=OPT_AX2009_DEV_LIVE;Connection timeout=5');
        sqlConn.Open();

        sqlCommand = new System.Data.SqlClient.SqlCommand('Select AccountNum, Name, CustGroup from CustTable Where DataAreaId = @Parm', sqlConn);

        sqlParm = sqlCommand.get_Parameters();
        sqlParm.AddWithValue('@Parm', curExt() );

        sqlDataReader = sqlCommand.ExecuteReader();

        while (sqlDataReader.Read())
        {
            accountNum  = sqlDataReader.GetString( sqlDataReader.GetOrdinal('AccountNum'));
            name        = sqlDataReader.GetString( sqlDataReader.GetOrdinal('Name'));
            custGroupId = sqlDataReader.GetString( sqlDataReader.GetOrdinal('CustGroup'));

            info(strFmt('%1 - %2 / %3', accountNum, name, custGroupId ) );
        }

        sqlDataReader.Close();
        sqlConn.Close();
    }
    catch (Exception::CLRError)
    {
        error(AifUtil::getClrErrorMessage());
    }
}

wtorek, 20 marca 2012

Logowanie wyjątków .NET do bazy danych

Aby w bardzo prosty sposób przechwycić wyjątek powstały w zewnętrznej bibliotece .NET można użyć metody AifUtil::getClrErrorMessage();.
Kod tej metody jest bardzo prosty i pokazuje jak obsługiwać wyjątki .NET po stronie systemu Dynamics AX.

Przykład użycia ww. metody:
  
static void SBR_AifInfoLog_Example_NET(Args _args)
{
    SysExceptionLog         exceptionLog;
    System.Uri              uri;
    System.Net.WebRequest   webRequest;
    System.Net.WebResponse  webResponse;
    str                     clrError;
    ;
    
    try
    {
        // kod który zwraca wyjątek
        // - pod podanym adresem nie ma serwera WWW
        // - nastąpi TimeOut po 5 sekundach
        uri = new System.Uri('http://192.168.1.100/');
        webRequest = System.Net.WebRequest::Create(uri);
        webRequest.set_Timeout(5000);
        webResponse = webRequest.GetResponse();
        webResponse.Close();
    }
    catch (Exception::CLRError)
    {
        // zapisujemy wyjątek do bazy danych
        clrError = AifUtil::getClrErrorMessage();
        exceptionLog = new SysExceptionLog();
        exceptionLog.writeEntry(Exception::Error, clrError, 'NET_DEMO');

        // wyświetlamy błąd na ekranie
        error(clrError);
    }
}

Aby zobaczyć zalogowane wyjątki uruchamiamy menu item \Menu Items\Display\Exceptions i uruchamiamy formatkę gdzie mamy listę przechwyconych wyjątków.

Logowanie wyjątków do bazy danych

Bardzo często użytkownik zgłasza problem w stylu:
"Panie Sebastianie, wyskoczył błąd i się nie zaksięgowało, proszę o pomoc."
Oczywiście nie ma komunikatu błędu i większość czasu trzeba teraz poświęcić na to co było robione a nie na poprawę mechanizmu/danych.

Dobrym rozwiązaniem jest zapisywanie pewnych wyjątków w bazie danych, aby później można było je przeglądać.
Oczywiście można pisać swoje różne mechanizmy, ale pokażę jak to zrobić bardzo prosto (ale wymagana jest licencja na AIF).
Moduł AIF (Aplication Integration Framework) poza swoją funkcjonalnością udostępnia kilka ciekawych klas.
W tym poście pokażę jak wykorzystać dwie z nich AifInfoLog oraz SysExceptionLog.


Mamy kawałek kodu (w naszym przypadku potwierdzenie listy pobrania) i chcemy zalogować wyjątek jeśli wystąpi:

  
static void SBR_AifInfoLog_Example(Args _args)
{
    AifInfoLog      aifInfoLog;
    SysExceptionLog exceptionLog;
    ;
    aifInfoLog = new AifInfoLog();

    try
    {
        // resetujmy zapamiętane komunikaty
        aifInfoLog.reset();

        // kod który zwraca wyjątek
        WMSOrder::find('00000081_068').finish();
    }
    catch
    {
        // zapisujemy wyjątek do bazy danych
        exceptionLog = new SysExceptionLog();
        exceptionLog.writeInfoLogData('SBR_TEST', aifInfoLog.getInfoLogData());
    }
}

Aby zobaczyć zalogowane wyjątki uruchamiamy menu item \Menu Items\Display\Exceptions i uruchamiamy formatkę gdzie mamy listę przechwyconych wyjątków.

czwartek, 15 marca 2012

Drukowanie na raporcie bitmap pobieranych z internetu

Jest to moja metoda drukowania zdjęć na raportach które znajdują się w sieci (dostęp przez protokół HTTP).
Nie jest ona zbyt elegancka (przesyłanie przez plik) ale daje radę.

Dodajemy nową metodę do raportu:
void loadImage(ReportBitmapControl _imageControl, str _url)
{
    Str                     fileName;
    System.Net.WebRequest   webRequest;
    System.Net.WebResponse  webResponse;
    System.Uri              uri;
    System.IO.Stream        stream;
    System.Drawing.Image    img;
    ;

    fileName = System.IO.Path::GetTempFileName();

    uri = new System.Uri(_url);

    webRequest = System.Net.WebRequest::Create(uri);
    webRequest.set_UseDefaultCredentials(true);

    webResponse = webRequest.GetResponse();

    stream = webResponse.GetResponseStream();

    img = System.Drawing.Image::FromStream(stream);
    img.Save(fileName);

    stream.Close();
    webResponse.Close();

    _imageControl.imageName(fileName);
}

i dodajemy jej wywołanie:
public void executeSection()
{
    ;
    this.loadImage(imageControl, 'http://www.likeyou.me/wp-content/uploads/2012/01/spongeBob.jpg');
    super();
}

środa, 7 marca 2012

Wydajne tworzenie plików Excel w .Net

Aby szybko i wydajnie stworzyć plik XLSX z kodu polecam użycie biblioteki EEPlus.
Wykorzystałem ją aby z poziomu serwisu WCF tworzyć wiele (kilka tysięcy) plików Excel jednocześnie.


Przykład kodu generującego prosty plik XLSX:
            
            FileInfo newFile = new FileInfo(@"c:\temp\sample1.xlsx");
            
            if (newFile.Exists)
            {
                newFile.Delete();  // ensures we create a new workbook
                newFile = new FileInfo(@"c:\temp\sample1.xlsx");
            }
            
            using (ExcelPackage package = new ExcelPackage(newFile))
            {
                // add a new worksheet to the empty workbook
                ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("Inventory");
                //Add the headers
                worksheet.Cells[1, 1].Value = "ID";
                worksheet.Cells[1, 2].Value = "Product";
                worksheet.Cells[1, 3].Value = "Quantity";
                worksheet.Cells[1, 4].Value = "Price";
                worksheet.Cells[1, 5].Value = "Value";

                //Add some items...
                worksheet.Cells["A2"].Value = 12001;
                worksheet.Cells["B2"].Value = "Nails";
                worksheet.Cells["C2"].Value = 37;
                worksheet.Cells["D2"].Value = 3.99;

                worksheet.Cells["A3"].Value = 12002;
                worksheet.Cells["B3"].Value = "Hammer";
                worksheet.Cells["C3"].Value = 5;
                worksheet.Cells["D3"].Value = 12.10;

                worksheet.Cells["A4"].Value = 12003;
                worksheet.Cells["B4"].Value = "Saw";
                worksheet.Cells["C4"].Value = 12;
                worksheet.Cells["D4"].Value = 15.37;

                //Add a formula for the value-column
                worksheet.Cells["E2:E4"].Formula = "C2*D2";

                //Ok now format the values;
                using (var range = worksheet.Cells[1, 1, 1, 5])
                {
                    range.Style.Font.Bold = true;
                    range.Style.Fill.PatternType = ExcelFillStyle.Solid;
                    range.Style.Fill.BackgroundColor.SetColor(Color.DarkBlue);
                    range.Style.Font.Color.SetColor(Color.White);
                }

                worksheet.Cells["A5:E5"].Style.Border.Top.Style = ExcelBorderStyle.Thin;
                worksheet.Cells["A5:E5"].Style.Font.Bold = true;

                worksheet.Cells[5, 3, 5, 5].Formula = string.Format("SUBTOTAL(9,{0})", new ExcelAddress(2, 3, 4, 3).Address);
                worksheet.Cells["C2:C5"].Style.Numberformat.Format = "#,##0";
                worksheet.Cells["D2:E5"].Style.Numberformat.Format = "#,##0.00";

                //Create an autofilter for the range
                worksheet.Cells["A1:E4"].AutoFilter = true;

                worksheet.Cells["A2:A4"].Style.Numberformat.Format = "@";   //Format as text
                worksheet.Cells.AutoFitColumns(0);  //Autofit columns for all cells

                // lets set the header text 
                worksheet.HeaderFooter.OddHeader.CenteredText = "&24&U&\"Arial,Regular Bold\" Inventory";

                // add the page number to the footer plus the total number of pages
                worksheet.HeaderFooter.OddFooter.RightAlignedText =
                    string.Format("Page {0} of {1}", ExcelHeaderFooter.PageNumber, ExcelHeaderFooter.NumberOfPages);
                
                // add the sheet name to the footer
                worksheet.HeaderFooter.OddFooter.CenteredText = ExcelHeaderFooter.SheetName;
                
                // add the file path to the footer
                worksheet.HeaderFooter.OddFooter.LeftAlignedText = ExcelHeaderFooter.FilePath + ExcelHeaderFooter.FileName;

                worksheet.PrinterSettings.RepeatRows = worksheet.Cells["1:2"];
                worksheet.PrinterSettings.RepeatColumns = worksheet.Cells["A:G"];

                // Change the sheet view to show it in page layout mode
                worksheet.View.PageLayoutView = true;

                // set some document properties
                package.Workbook.Properties.Title = "Invertory";
                package.Workbook.Properties.Author = "Jan Källman";
                package.Workbook.Properties.Comments = "This sample demonstrates how to create an Excel 2007 workbook using EPPlus";

                // set some extended property values
                package.Workbook.Properties.Company = "AdventureWorks Inc.";

                // set some custom property values
                package.Workbook.Properties.SetCustomPropertyValue("Checked by", "Jan Källman");
                package.Workbook.Properties.SetCustomPropertyValue("AssemblyName", "EPPlus");

                // save our new workbook and we are done!
                package.Save();
            }


I wynik działania kodu:

poniedziałek, 9 stycznia 2012

Wysyłanie maila z Dynamics AX 2009 przez SMTP

Konfigurację serwera SMTP robimy w "Administration" - "Setup" - "E-mail parameters".
static void SBR_SendEmail(Args _args)
{
    System.Net.Mail.MailMessage    mailMessage;
    System.Net.Mail.SmtpClient     myMail;
    System.Net.Mail.MailAddress    mailFrom;
    System.Net.Mail.MailAddress    mailTo;
    str                            userMailAddress;
    str                            receiverMailAddress;
    str                            mailSubject;
    str                            mailBody;
    str                            smtpServer;
    UserInfo                       userInfo;

    ;
    select firstonly name from userInfo where userInfo.id == SysuserInfo::find().Id;
    userMailAddress = SysUserInfo::find().Email;

    receiverMailAddress = 'receiver@company.com';
    mailSubject = 'Email from Dynamics AX';
    mailBody    = 'Email sent from " + CompanyInfo::name() + ", using Dynamics AX';

    mailFrom    = new  System.Net.Mail.MailAddress(userMailAddress, userInfo.name);
    mailTo      = new  System.Net.Mail.MailAddress(receiverMailAddress,"");
    smtpServer  = SysEmaiLParameters::find(false).SMTPRelayServerName;
    mailMessage = new System.Net.Mail.MailMessage(mailFrom,mailTo);
    mailmessage.set_Subject(mailSubject);
    mailmessage.set_Body(mailBody);

    myMail = new System.Net.Mail.SmtpClient(smtpServer);
    myMail.Send(mailmessage);
}