Zpracování XML přes .NET Interop

Dnes chci ukázat, jak můžete stahovat XML soubory z internetu, validovat, parsovat a použít je v Dynamics AX 2012. Je to také další příklad toto, jak můžeme implementaci zjednodušit pomocí vhodných .NET API and pohodlně takový kód integrovat s Dynamics AX.

Zdrojem našich dat budou denní směnné kurzy poskytované Evropskou centrální bankou jako jednoduchý XML soubor: www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml. Na konci budeme mít data v X++ objektu a zobrazíme je v infologu:

Celé řešení můžete stáhnout zde – obsahuje jediné .xpo (pro AX2012). Níže proberu většinu kódu, ale na detaily se budete muset podívat do zdrojového kódu.

Úplně první krok je vytvoření knihovny tříd v C# a její přidání do AOT. Nazval jsem ji EcbExchRates a jmenný prostor jsem nastavil na Goshoom.Finance.

Pak vytvořte novou třídu – DailyExchangeRateService – a přidejte pár konstant, které budou za chvilku potřeba.

private const string DailyRatesUri = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
private const string GesmesNamespace = "http://www.gesmes.org/xml/2002-08-01";
private const string EcbNamespace = "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";

Obsah XML souboru je třeba nějak stáhnout – to je úkolem metody DownloadData().

private string DownloadData()
{
    using (System.Net.WebClient webClient = new System.Net.WebClient())
    {
        return webClient.DownloadString(DailyRatesUri);
    }
}

Řetězec vrácený metodou DownloadData() je třeba zparsovat. Naštěstí .NET nabízí několik API, která s tímto umí pomocí – v tomto případě použijeme LINQ to XML. Nejprve načteme XML řetězec do XDocument (z jmenného prostoru System.Xml.Linq) a uložíme do instanční proměnné:

XDocument xmlData;
 
private void Load()
{
    xmlData = XDocument.Load(new StringReader(this.DownloadData()));
}

Jedna z mnoha výhod XML je podpora pro validace proti XSD schématům – to děláme v metodě Validate(). Schémata jsou uložena jako vložené zdroje (embedded resources) – můžete je vidět ve zdrojovém kódu ke stažení).

private void Validate()
{
    XmlSchemaSet schemas = new XmlSchemaSet();
 
    schemas.Add(GesmesNamespace, XmlReader.Create(new StringReader(Resources.GesmesSchema)));
    schemas.Add(EcbNamespace, XmlReader.Create(new StringReader(Resources.EcbSchema)));
 
    xmlData.Validate(schemas, null);
}

Posledním krokem je parsování XML do objektu. Ale než to budeme moci udělat, musíme nejprve definovat třídu pro cílový objekt. Vytvoříme jednoduchou X++ třídu, která bude obsahovat datum a vlastní směnné kurzy:

class DayExchRates
{
    date    effectiveDate;
    Map     rates; // Currency code -> exchange rate
 
    public void addRate(str _currency, real _rate)
    {
        rates.insert(_currency, _rate);
    }
 
    …more code…
}

Přetáhněte třídu z Application Exploreru do vašeho C# projektu, čímž vygenerujete .NET proxy.

Nyní můžeme extrahovat data z XML – nejprve potřebujeme najít správný element. Krom malé komplikace způsobené jmennými prostory je implementace triviální:

XNamespace gesmesNs = GesmesNamespace;
XNamespace ecbNs = EcbNamespace;
XElement ratesRoot = xmlData.Element(gesmesNs + "Envelope").Element(ecbNs + "Cube").Element(ecbNs + "Cube");

Vytvoříme instanci naší X++ třídu a získáme datum z atributu time:

DayExchRates rates = new DayExchRates();
rates.parmEffectiveDate((DateTime)ratesRoot.Attribute("time"));

Pak projdeme elementy s kurzy, z atributů získáme kód měny a směnný kurz a přidáme je do X++ třídy:

foreach (XElement rateNode in ratesRoot.Descendants())
{
    rates.addRate(
        rateNode.Attribute("currency").Value,
        decimal.Parse(rateNode.Attribute("rate").Value));
}

Zapouzdříme parsování v metodě Parse() a dáme vše dohromady ve veřejné metodě GetRates():

public DayExchRates GetRates()
{
    this.Load();
    this.Validate();
    return this.Parse();
}

Zobrazte vlastnosti projektu ve Visual Studiu, nastavte Deploy to Client na Yes (protože náš testovací X++ kód poběží na klientu), nasaďte (deploy) projekt a tím jsem hotovi s Visual Studiem.

Vytvořte job v Dynamics AX a vložte do něj následující kód (jen změňte jména, pokud jste použili jiná).

Goshoom.Finance.DailyExchangeRateService service = new Goshoom.Finance.DailyExchangeRateService();
DayExchRates dailyRates = dailyRates = service.GetRates();

Kód by měl zcela jasný, ale je zde jedna věc, která zasluhuje pozornost. To, co metoda GetRates() vrací, není .NETová proxy třída – .NET Interop je tak chytrý, že nahradí proxy třídu původním X++ typem, takže můžeme hodnotu z .NETové metody přímo přiřadit do proměnné s naším X++ typem. Asi to nezní jako velký rozdíl, ale ve skutečnosti to věci podstatně zjednodušuje.

K dokončení našeho úkolu nám chybí zobrazit směnné kurzy v infologu. Toto je běžný X++ kód:

MapEnumerator enumerator = dailyRates.getRates().getEnumerator();
 
while (enumerator.moveNext())
{
    info(strFmt("%1: %2", enumerator.currentKey(), num2str(enumerator.currentValue(), -1, 4, -1, -1)));
}

Pravděpodobně by se dalo to samé udělat v čistém X++ (jen musel zjistit, jak v X++ validovat proti dvoum cílovým jmenným prostorům), ale skončili byste s mnohem více kódu, který je třeba napsat, číst a udržovat, což by mělo negativní vliv na vaši produktivitu. Rozdíl by to byl ještě větší, pokud by nešlo o tak jednoduchý příklad.

Dynamics AX/X++ zkrátka nemohou – a ani by něměly – duplikovat všechny knihovny a API dostupné v .NETu, ale to neznamená, že se bez nich musíme objejít. .NET Interop nám umožňuje kombinovat to nejlepší z obou světů (AX a .NET) a každá verze AX to trochu zjednodušuje.