LINQ to XML in ASP .Net



Un alt mod de a stoca si manipula date este familia XML. Datele din fisiere sunt constranse sa respecte un anumit model, prin schemele XSD. Cu XPath se colecteaza date, iar cu XSLT fisierul sau setul de noduri se "converteste" in alt model de reprezentare (HTML, XML,..).

Am imprumutat o parte din baza de date Northwind din SQL si am construit un fisier XML care contine un model relational. Astfel exista o lista de noduri de tip "produs" (tabela Products),o lista de tip "categorie" (tabela Categories) si o lista de noduri de tip "furnizor" (tabela Suppliers). Fiecare lista contine cate o cheie primara (xs:key). Pentru lista "Products" sunt definite chei straine care referentiaza la elemente din listele "Categories" sau "Suppliers" (xs:keyref). Cu xs:unique s-a constrans ca numele categoriilor/furnizorilor sa fie unic (Unique Constraint in SQL).



Fisier xml: lista de produse, categorii si furnizori.


<?xml version="1.0" encoding="utf-8" ?>
<EstWind
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://localost/LinqToXml EstWind.xsd">
<Products>
<Product ProductID="1" SupplierID="1" CategoryID="1" ProductName="Chai"/>
<Product ProductID="2" SupplierID="1" CategoryID="1" ProductName="Chang"/>
<Product ProductID="3" SupplierID="1" CategoryID="2" ProductName="Aniseed Syrup"/>
<Product ProductID="4" SupplierID="2" CategoryID="2" ProductName="Chef Anton's Cajun Seasoning"/>
<Product ProductID="5" SupplierID="2" CategoryID="2" ProductName="Chef Anton's Gumbo Mix"/>
<Product ProductID="6" SupplierID="3" CategoryID="2" ProductName="Grandma's Boysenberry Spread"/>
<Product ProductID="7" SupplierID="3" CategoryID="7" ProductName="Uncle Bob's Organic Dried Pears"/>
<Product ProductID="8" SupplierID="3" CategoryID="2" ProductName="Northwoods Cranberry Sauce"/>
<Product ProductID="9" SupplierID="4" CategoryID="6" ProductName="Mishi Kobe Niku "/>
<Product ProductID="10" SupplierID="4" CategoryID="8" ProductName="Ikura"/>
</Products>
<Suppliers>
<Supplier SupplierID="1" CompanyName="Exotic Liquids"/>
<Supplier SupplierID="2" CompanyName="New Orleans Cajun Delights"/>
<Supplier SupplierID="3" CompanyName="Grandma Kelly's Homestead"/>
<Supplier SupplierID="4" CompanyName="Tokyo Traders"/>
</Suppliers>
<Categories>
<Category CategoryID="1" CategoryName="Beverages"/>
<Category CategoryID="2" CategoryName="Condiments"/>
<Category CategoryID="6" CategoryName="Meat/Poultry"/>
<Category CategoryID="7" CategoryName="Produce"/>
<Category CategoryID="8" CategoryName="Seafood"/>
</Categories>
</EstWind>



Schema de validare a XML-ului: modele de key primare, unice si straine.



<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="EstWind" type="EstWindType">

<!-- + Chei straine -->
<!--
Atributul CategoryID al oricarui Product
accepta valori numai din setul format
din valorile atributului CategoryID din
oricare nod Category.
-->
<xs:keyref name="CaregoryRef" refer="CategoriesKey">
<xs:selector xpath=".//Product"/>
<xs:field xpath="@CategoryID"/>
</xs:keyref>

<xs:keyref name="SupplierRef" refer="SuppliersKey">
<xs:selector xpath=".//Product"/>
<xs:field xpath="@SupplierID"/>
</xs:keyref>

<!-- |_ Chei straine -->

<!-- + Chei primare -->
<!--
Valorile atributului ProductID sunt unice
Obs.: Nodul Products contine doar elemente de tip Product.

E valida si expresia xpath .//Product.

Se defineste un set de noduri, iar pe acel set
se alege un atribut sau un nod
care sa reprezinte cheia unica.
-->
<xs:key name="ProductsKey">
<xs:selector xpath="Products/*"/>
<xs:field xpath="@ProductID"/>
</xs:key>


<xs:key name="CategoriesKey">
<xs:selector xpath="Categories/*"/>
<xs:field xpath="@CategoryID"/>
</xs:key>


<xs:key name="SuppliersKey">
<xs:selector xpath="Suppliers/*"/>
<xs:field xpath="@SupplierID"/>
</xs:key>

<!-- |_ Chei primare -->


<!-- + Chei unice -->

<!-- Diferenta dintre xs:unique si xs:key
e ca xs:unique accepta valori null -->

<xs:unique name="ProductNameUnique">
<xs:selector xpath=".//Product"/>
<xs:field xpath="@ProductName"/>
</xs:unique>

<xs:unique name="CompanyNameUnique">
<xs:selector xpath=".//Supplier"/>
<xs:field xpath="@CompanyName"/>
</xs:unique>

<xs:unique name="CategoryNameUnique">
<xs:selector xpath=".//Category"/>
<xs:field xpath="@CategoryName"/>
</xs:unique>

<!-- |_ Chei unice -->

</xs:element>

<!-- + Root Type -->

<xs:complexType name="EstWindType">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="Products" type="ProductsType"/>
<xs:element minOccurs="1" maxOccurs="1" name="Suppliers" type="SuppliersType"/>
<xs:element minOccurs="1" maxOccurs="1" name="Categories" type="CategoriesType"/>
</xs:sequence>
</xs:complexType>

<!-- |_ Root Type -->


<!-- + List Types -->

<xs:complexType name="ProductsType">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Product" type="ProductType"/>
</xs:sequence>
</xs:complexType>


<xs:complexType name="SuppliersType">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" name="Supplier" type="SupplierType"/>
</xs:sequence>
</xs:complexType>


<xs:complexType name="CategoriesType">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" name="Category" type="CategoryType"/>
</xs:sequence>
</xs:complexType>

<!-- |_ List Types -->


<!-- + Basic Types -->

<xs:complexType name="ProductType">
<xs:attribute name="ProductID" type="xs:unsignedByte" use="required" />
<xs:attribute name="SupplierID" type="xs:unsignedByte" use="required" />
<xs:attribute name="CategoryID" type="xs:unsignedByte" use="required" />
<xs:attribute name="ProductName" type="xs:string" use="required" />
</xs:complexType>


<xs:complexType name="SupplierType">
<xs:attribute name="SupplierID" type="xs:unsignedByte" use="required" />
<xs:attribute name="CompanyName" type="xs:string" use="required" />
</xs:complexType>


<xs:complexType name="CategoryType">
<xs:attribute name="CategoryID" type="xs:unsignedByte" use="required" />
<xs:attribute name="CategoryName" type="xs:string" use="required" />
</xs:complexType>

<!-- |_ Basic Types -->


</xs:schema>


Controlul TreeView interactioneaza cu modelul de date din XML cu ajutorul unui control XmlDataSource. Prin intermediul proprietatii Transform (XmlDataSource), al carei continut e un XSLT, se pot crea diferite forme ale modelului. Fiecare TreeView are definite databinding-uri, dupa model sau formele modelului, intre proprietatile nodurilor treeview-ului si proprietatile elementelor modelului. In printscreen-ul de mai jos primul treeview reprezinta modelul, celelalte doua reprezinta o data produsele grupate dupa categoria din care fac parte si o data produsele grupate dupa furnizorul lor.


Acelasi fisier XML, reprezentat in forme diferite

XmlDataSource cu foaie XSLT, pentru a obtine produsele grupate pe categorie


<asp:XmlDataSource ID="XmlDataSource2" runat="server" DataFile="~/EstWind.xml">
<Transform>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<ProductsByCategory>
<xsl:for-each select="EstWind/Categories/Category">
<xsl:sort select="@CategoryName" order="ascending" />
<xsl:element name="Category">
<xsl:attribute name="DisplayName" >
<xsl:value-of select="@CategoryName" />
</xsl:attribute>

<xsl:attribute name="ID">
<xsl:value-of select="concat('category;',@CategoryID)"/>
</xsl:attribute>

<xsl:for-each select="//Product[@CategoryID=current()/@CategoryID]">
<xsl:sort select="@ProductName" order="ascending" />

<xsl:element name="Product">
<xsl:attribute name="DisplayName">
<xsl:value-of select="@ProductName" />
</xsl:attribute>

<xsl:attribute name="ID">
<xsl:value-of select="concat('product;',@ProductID)"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</ProductsByCategory>
</xsl:template>
</xsl:stylesheet>
</Transform>
</asp:XmlDataSource>

Intre componentele unei pagini ASP .Net trebuie sa se comunice. De exemplu in urma selectarii unui produs din treeview, o alta componenta afiseaza detalii despre acel produs. Sau in urma selectarii unei categorii afiseaza o lista detaliata a produselor acelei categorii. Evenimentele din treeview nu ofera prea multe despre model. Un treeview nu stie ca ai selectat un produs sau o categorie, ci stie ca ai selectat un anumit nod de pe un anumit nivel care un id, text, valoare. Asadar as vrea sa stiu ca am selectat produsul X sau categoria Y din fisierul XML.



Clasele C# care reprezinta modelul din XML.


// Acest tip de clase este introdus in C# 3.0 pentru a facilita interogarile LINQ
public class Product
{
public sbyte ProductID { get; set; }
public string ProductName { get; set; }
public Supplier Supplier { get; set; }
public Category Category { get; set; }
}
public class Category
{
public sbyte CategoryID { get; set; }
public string CategoryName { get; set; }
public List<Product> Products{ get; set; }
}
public class Supplier
{
public sbyte SupplierID { get; set; }
public string CompanyName { get; set; }
public List<Product> Products { get; set; }
}

Pentru a defini evenimentele in controale ASP .Net e de indicat a se folosi colectia Events. E de preferat atunci cand un control are foarte multe evenimente. Colectia Events este o lista de delegati de tip (cheie, (delegat1, ... delegatn)) si se foloseste in conjunctie cu instructiunile add/remove.



Definirea evenimentului

   
static readonly object ProductSelectedEventKey = new object();
public event EventHandler<ProductSelectedEventArgs> ProductSelected
{
add { Events.AddHandler(ProductSelectedEventKey, value); }
remove { Events.RemoveHandler(ProductSelectedEventKey, value); }
}
protected virtual void OnProductSelected(ProductSelectedEventArgs e)
{
EventHandler<ProductSelectedEventArgs>
handler = Events[ProductSelectedEventKey] as EventHandler<ProductSelectedEventArgs>;
if (handler != null)
handler(this, e);
}
// argumentul evenimentul va contine produsul selectat
public class ProductSelectedEventArgs : EventArgs
{
private Product product;
public ProductSelectedEventArgs(Product product)
{
this.product = product;
}
public Product Product
{
get { return product; }
}
}




Ultimul pas e definirea momentului cand e declansat evenimentul precum si interogarea fisierului XML pentru a obtine produsul sau categoria. Aceasta se va face cu "LINQ to XML". Cum am definit in data bindings, ID-urile nodurilor au formele "product;@ProductID" sau "category;CategoryID". Cunoscand ProductID sau CategoryID, se interogheaza cu LINQ un obiect XDocument. Partea buna e ca nu e necesar sa ai cunostine despre XPath, navigatori, iteratori ca sa interoghezi fisiere XML. Practic cu LINQ codul exprima ce am nevoie sa iau si nu cum sa iau.



Interogare XML cu LINQ: obtinerea unui Product

 
private Product LoadProduct(string productID)
{
var products = from product in EstWindDocument.Descendants("Product")
where product.Attribute("ProductID").Value == productID

select new Product
{
ProductName = product.Attribute("ProductName").Value,
ProductID = SByte.Parse(product.Attribute("ProductID").Value),
Category = (from category in EstWindDocument.Descendants("Category")
where category.Attribute("CategoryID").Value == product.Attribute("CategoryID").Value
select new Category
{
CategoryID = SByte.Parse(category.Attribute("CategoryID").Value),
CategoryName = category.Attribute("CategoryName").Value
}).FirstOrDefault<Category>(),
Supplier = (from supplier in EstWindDocument.Descendants("Supplier")
where supplier.Attribute("SupplierID").Value == product.Attribute("SupplierID").Value
select new Supplier
{
SupplierID = SByte.Parse(supplier.Attribute("SupplierID").Value),
CompanyName = supplier.Attribute("CompanyName").Value
}).FirstOrDefault<Supplier>()
};
return products.FirstOrDefault<Product>();
}


private Category LoadCategory(string categoryID)
{
var categories = from category in EstWindDocument.Descendants("Category")
where category.Attribute("CategoryID").Value == categoryID
select new Category
{
CategoryID = SByte.Parse(category.Attribute("CategoryID").Value),
CategoryName = category.Attribute("CategoryName").Value,
Products = (
from product in EstWindDocument.Descendants("Product")
where product.Attribute("CategoryID").Value == category.Attribute("CategoryID").Value
select new Product
{
ProductName = product.Attribute("ProductName").Value,
ProductID = SByte.Parse(product.Attribute("ProductID").Value),
Category = (from pcategory in EstWindDocument.Descendants("Category")
where pcategory.Attribute("CategoryID").Value == product.Attribute("CategoryID").Value
select new Category
{
CategoryID = SByte.Parse(pcategory.Attribute("CategoryID").Value),
CategoryName = pcategory.Attribute("CategoryName").Value
}).FirstOrDefault<Category>(),
Supplier = (from supplier in EstWindDocument.Descendants("Supplier")
where supplier.Attribute("SupplierID").Value == product.Attribute("SupplierID").Value
select new Supplier
{
SupplierID = SByte.Parse(supplier.Attribute("SupplierID").Value),
CompanyName = supplier.Attribute("CompanyName").Value
}).FirstOrDefault<Supplier>()
}
).ToList<Product>()
};
return categories.FirstOrDefault<Category>();
}








Download Source Code And Play




Comments

Popular posts from this blog

IIS 7.5, HTTPS Bindings and ERR_CONNECTION_RESET

Table Per Hierarchy Inheritance with Column Discriminator and Associations used in Derived Entity Types

Verify ILogger calls with Moq.ILogger