Showing posts with label Xml. Show all posts
Showing posts with label Xml. Show all posts

15/06/2015

Interview Questions for Programmers by MK #3

Home

Question #3
You found the following code and were asked to refactor it if needed:
var sb = new StringBuilder();
sb.AppendLine("<root>")
sb.AppendLine(String.Format("   <node id="{0}"/>", 1));
sb.AppendLine(String.Format("   <node id="{0}"/>", 2));
sb.AppendLine(String.Format("   <node id="{0}"/>", 3));
//Many, many lines of code
sb.AppendLine("</root>");
What would you do and why?

Answer #3
It is not the best idea to create XML documents using string concatenation because it is error prone. Besides created documents are not validated in any way. In .NET we have a few possibilities to refactor this code.

I recommend to use XmlWriter in this case because we want to create a new document and we do not want to edit an existing one. However, if we also want to modify existing XML documents, the good choice will be XDocument or XmlDocument class.

In the case of small XML documents (when performance is not critical) it might be a good idea to use XDocument or XmlDocument even if we don't want to edit existing documents. Especially XDocument can be simpler in use than XmlWriter.

Comments #3
I remember that when I wanted to create an XML document in C# for the first time I did it by using string concatenation. The code worked ok and I was surprised that it didn't pass a review :)

26/09/2014

Token Content in state Epilog would result in an invalid XML document

Home

To kolejny post z serii ku pamięci aby oszczędzić innym bólu. Otóż po wprowadzeniu zmian do logiki pewnego web service'u i napisaniu testów jednostkowych, postanowiłem go jeszcze przetestować integracyjnie przy pomocy skądinąd fajnego narzędzia SoapUI. Zacząłem od przygotowałem komunikatu XML do wysłania do usługi. Poszło szybko i myślałem, że to już prawie koniec mojej pracy. Niestety był to dopiero początek, gdyż okazało się, że po wysłaniu komunikatu serwer zwraca błąd jak poniżej:

Token Content in state Epilog would result in an invalid XML document.

Zdziwiłem się ponieważ to nie był pierwszy raz kiedy przechodziłem przez tą procedurę i nigdy z czymś takim się nie spotkałem. Początkowo pomyślałem, że to wina moich zmian, ale testy przy użyciu starej wersji kodu dały ten sam wynik. W następnej kolejności sprawdziłem, wiec czy używane przeze mnie wcześniej komunikaty/żądania dają ten sam błąd, ale działały poprawnie. W tym momencie wiedziałem już, że coś jest nie tak z tym konkretnym żądaniem. Zacząłem z niego wycinać kolejne elementy, aż pozostał prawie pusty, a błąd wciąż występował. W tym momencie mnie oświeciło i postanowiłem podejrzeć to czego nie widać gołym okiem czyli białe znaki. Kiedy to zrobiłem zakląłem szpetnie ponieważ przyczyną problemu okazał się znak nowej linii na końcu komunikatu - tuż po tagu zamykającym.

27/10/2012

XmlSerializer- taka ciekawostka

Home

Serializator XML'owy platformy .NET jest bardzo łatwy i przyjemny w użyciu, ale czasami jego działanie może sprowadzić nas na manowce. Poniższy kod obrazuje o co mi chodzi. Zacznijmy od przykładowej, bardzo prostej klasy, którą będziemy serializować:

public class A
{
 public string Name { get; set; }
 
 public A()
 {
  Name = "Hello"; 
 }
}

Oraz kawałka kodu:

var obj = new A() { Name = null };

var stream = new MemoryStream();
var serializer = new XmlSerializer(typeof(A));

serializer.Serialize(stream, obj);

stream.Position = 0;

var obj2 = (A)serializer.Deserialize(stream);

Kod jest bardzo prosty. Tworzymy obiekt klasy A i ustawiamy właściwość Name na null. Następnie zapisujemy obiekt do strumienia. Po zapisaniu przesuwamy wskaźnik odczytu/zapisu strumienia na początek aby móc zdeserializować obiekt. W czym tkwi haczyk? Spróbujcie odpowiedzieć na to pytanie bez zaglądania do dalszej części posta:

Jaka będzie wartość właściwości Name po odczytaniu obiektu ze strumienia?

Otóż po zdeserializowaniu właściwość Name nie będzie pusta. Serializator XML'owy odczytując obiekt ze strumienia najpierw wywoła konstruktor domyślny. Potem zobaczy, że w strumieniu właściwość Name jest pusta i stwierdzi, że w takim wypadku nie trzeba niczego przypisywać do właściwości Name deserializowanego obiektu. W konstruktorze znajduje się natomiast kod, który ustawia tą właściwości. A więc po zdeserializowaniu właściwość Name będzie zawierała ciąg znaków "Hello", czyli inną niż przed serializacją.

Teraz wyobraźmy sobie trochę bardzie skomplikowaną sytuację. Załóżmy, że mamy kod pobierający jakieś dane z bazy danych i ładujący je do odpowiednich klas. Klasy te są napisane w podobny sposób jak klasa A powyżej, czyli w konstruktorze domyślnym niektóre właściwości są inicjowane wartościami innymi niż null. Dane (obiekty) są następnie udostępniane aplikacji klienckiej przez web service'y (stare dobre ASMX, usługi WCF jeśli użyto XmlSerializerFormatAttribute)  czyli muszą być najpierw zserializowane, a potem zdeserializowane. Kod działa poprawnie.

Teraz zabieramy się za pisanie testów integracyjnych i używamy tego samego kodu odpowiedzialnego za wczytywanie danych. W momencie, kiedy chcemy użyć tych samych obiektów (tak samo stworzonych i wypełnionych danymi) co aplikacja kliencka, nawet w ten sam sposób, okazuje się, że dostajemy wyjątek NullReferenceException.

Dlaczego? Ano dlatego, że w testach integracyjnych obiekty nie zostały przesłane przez sieć, czyli nie były serializowane i deserializowane, czyli konstruktor domyślny nie został wywołany drugi raz przy deserializacji i puste właściwości nie zostały ustawione tak jak opisano powyżej.

Jak w wielu innych podobnych sytuacjach, to żadna wiedza tajemna, ale jak się o tym nie wie, może napsuć trochę krwi. Pomijam tutaj fakt, że korzystanie z opisanej "funkcjonalności", świadomie czy nie, nie jest najlepszym pomysłem.

04/09/2011

IntelliTrace - schemat XSD

Home

Swego czasu w postach Własne zdarzenia IntelliTrace! oraz Własne zdarzenia IntelliTrace 2 opisałem jak zmodyfikować plik CollectionPlan.xml zawierający plan działania IntelliTrace (historycznego debuggera) tak, aby zdefiniować swoje własne zdarzenie IntelliTrace (ważny punkt w historii działania programu kiedy IntelliTrace nagrywa stan aplikacji). Ostatnio wróciłem do tego zagadnienia i "bawię się" testując różne możliwości IntelliTrace. Niestety czasami, po zmodyfikowaniu pliku CollectionPlan.xml, przy próbie uruchomienia debuggera otrzymywałem błąd np.:

error VSLG4001: The specified collection plan is invalid: 'C:\CollectionPlan.xml'. More information: The 'type' attribute is invalid - The value 'Object' is invalid according to its datatype 'urn:schemas-microsoft -com:visualstudio:tracelog:ClrType' - The Enumeration constraint failed.

Komunikat jest czytelny, wartość Object dla atrybutu type jest niedozwolona. Metodą prób i błędów można wywnioskować, jakie wartości są poprawne, ale to męczące i niewydajne. Pomyślałem więc, że skoro zawartość pliku CollectionPlan.xml to dokument XML, to musi on być walidowany przy pomocy odpowiedniego schematu XSD. Ale gdzie go szukać? Przejrzałem zawartość katalogu instalacyjnego Visual Studio 2010 i niczego nie znalazłem.

Stwierdziłem więc, że zajrzę do biblioteki Microsoft.VisualStudio.IntelliTrace (pisałem o niej w poście Poznaj swój program), która umożliwia programową analizę logów IntelliTrace, ale nie tylko. Biblioteka ta wykorzystywana jest również przez program IntelliTrace.exe (o tym też pisałem w poście Używanie IntelliTrace poza Visual Studio 2010!), który znajdziemy w katalogu instalacyjnym Visual Studio 2010. Program ten służy do uruchomienia historycznego debuggera i kiedy używamy IntelliTrace z poziomu Visual Studio, to korzystamy właśnie z tego programu. Skoro tak to doszedłem do wniosku, że walidacja konfiguracji i schemat XSD znajdują się właśnie w tej bibliotece.

Okazało się to strzałem w dziesiątkę. Bibliotekę załadowałem do .NET Reflector'a. Najpierw zlokalizowałem w zasobach komunikat z błędem. Następnie sprawdziłem gdzie jest używany i znalazłem tylko jedno takie miejsce, a stamtąd już szybko doszedłem do wywołania metody public static XmlSchema GenerateXmlSchema(). Niestety okazało się, że nie mogę jej wywołać z własnego kodu, ponieważ znajduje się w klasie internal class ConfigMessagePacker. Skopiowałem więc jej kod (ok. 1200 linii) do swojego programu i po chwili miałem już XSD.

18/06/2009

Xslt jako język programowania

Home

Ostatnio sporo czasu poświęcam poznawaniu nowych narzędzi w Visual Studio 2010 czyli pożytecznej zabawie. Przy tej okazji uczę się również czasami czegoś na temat starszych wersji tego środowiska. Na przykład niedawno zajmowałem się narzędziami wspierającymi pracę z szeroko pojętym Xml'em, a w szczególności z transformacjami Xsl. Obok rzeczy wręcz oczywistych takich jak podświetlenie składni, podpowiadanie - IntelliSense znajdziemy również: profiler, debugger czy podgląd wyniku działania transformacji. Co ciekawe narzędzia te, oprócz profilera, znajdują się również w Visual Studio 2005/2008. Muszę przyznać, że do tej pory nie zdawałem sobie sprawy z tego, że Visual Studio traktuje Xslt jako "normalny" język programowania. Co tu dużo mówić, człowiek uczy się całe życie.

Dla tych co żyli w niewiedzy tak jak ja kilka zdań na ten temat. Zaczynamy od otworzenia pliku z definicją transformacji (z rozszerzeniem *.xslt) albo od dodania go do projektu. We właściwościach otwartego dokumentu (zakładka Properties) mamy do wypełnienia dwa pola: Input oraz Output. W pierwszym wskazujemy dokument Xml do przekształcenia, a w drugim plik wynikowy.



Analogicznie można zacząć od otworzenia pliku Xml. W tym przypadku na zakładce Properties będziemy mieli do wypełninia pola: Stylesheet oraz Output. W pierwszym wskazujemy plik z transformacją, a znaczenie drugiego jest takie same jak wcześniej.

W momencie kiedy mamy otworzony plik z transformacją albo plik Xml na pasku menu pojawi się element o nazwie XML.



Z tego menu może po pierwsze wybrać polecenie Show XSLT Output, które odpali transformację i pokaże nam jej wynik. Dużo ciekawsze jest jednak polecenie Debug XSLT, które umożliwia śledzenie wykonania transformacji.

Pułapki umieszczamy w kodzie transformacji dokładnie w taki sam sposób jak w kodzie programu napisanego w C#. Co więcej pułapki możemy też postawić w dokumencie Xml. Funkcji tej można użyć kiedy interesuje nas ściśle określony węzeł w dokumencie i chcemy aby debugger zatrzymał się kiedy będzie przetwarzany. Bardzo przydatne jest to, że na bieżąco możemy obserwować jak generowany jest plik wynikowy. Kod transformacji lub dokument Xml możemy teoretycznie modyfikować w czasie działania transformacji ale nie odniesie to żadnych skutków.



W Visual Studio 2010 pojawiła się jeszcze możliwość profilowania transformacji (w menu XML dodane zostało polecenie Profile XSLT). Możemy sprawdzić, wykonanie której część kodu transformacji zajmuje najwięcej czasu itd. Okno z raportem z wynikami profilowania zostało przedstawione poniżej:


04/12/2008

Transformacje Xsl i przestrzenie nazw XML

Home

Przy używaniu transformacji Xsl należy pamiętać o przestrzeniach nazw Xml. Załóżmy, że mamy dokument Xml i transformację Xsl do jej przetwarzania:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='Transformation.xsl'?>
<A>
   <B>
      bbb
   </B>
   <B>
      bbb
   </B>
</A>
Transformajca wygląda natomiast tak:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" standalone="no" omit-xml-declaration="yes" encoding="windows-1250" />

<xsl:template match="B" >
   <LI>
      <xsl:value-of select="current()"/>
   </LI>
</xsl:template>

<xsl:template match="A" >
   <HTML>
      <HEAD>
      </HEAD>
      <BODY>
         <UL>
            <xsl:apply-templates select="B"/>
         </UL>
      </BODY>
   </HTML>
</xsl:template>

</xsl:stylesheet>
Wynikiem działania przedstawionej transformacji na przykładowym dokumencie Xml powinna być lista:
  • bbb
  • bbb
Wynik będzie zupełnie inny jeśli zmodyfikujemy dokument Xml w następujący sposób:
...
<A xmlns="a.b.c">
...
Po tej zmianie otrzymamy taki, mało przyjazny rezultat transformacji:
bbb bbb
Aby rozwiązać problem należy zmodyfikować definicję transformacji poprzez jawne wskazanie przestrzeni nazw z jakiej pochodzą przetwarzane węzły dokumentu Xml. Po pierwsze należy podać definicję nowej przestrzeni nazw poprzez dodanie do węzla xsl:stylesheet atybutu xmlns:test="a.b.c". Oczywiście można podać inną skróconą nazwę przestrzeni nazw niż test. Następnie należy dodać przedrostek test: przed każdym odwołaniem do węzła z dokumentu np.:
...
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:test="a.b.c">
...
...
<xsl:template match="test:B" >
   <LI>
      <xsl:value-of select="current()"/>
   </LI>
</xsl:template>
...