One of the best things about being a programmer is new technologies,  taking a look at exciting new approaches to everyday tasks.  The .NET world is continually growing, in fact it was only 6-months ago that the 3.5 Framework was released giving you new tools such as LINQ, WPF, and Silverlight.  Now comes ASP.NET 3.5 Extensions Preview and you can add to the list Dynamic Data Controls. Dynamic Data Controls will enable you to build data driven website's that will work against a LINQ to SQL and LINQ to Entities object model.  With a fully functional website built on top of the MVC framework you can optionally override or customize any of the view templates giving you full control over our website.

Dynamic data works with the standard data controls enabling them to automatically support foreign key relationships,  giving you a friendly name display of foreign key values along with built in UI validation support based upon the constraints set in your data-model.

Once you download the new Dynamic Data controls (you can down load them from here ) unzip them to your favorite directory.  Inside the directory you will find a series of DLL's, Zip files, CMD files and a readme.  Inside the readme you will find instructions on setting up your environment and your first project of which both of these we will go over in this blog.  So now its time to get to the fun.

image

For this example you will need

Visual Studio 2008
SQL Express
ASP.NET 3.5 Extensions Preview
Northwind database

The examples here will be in VB.NET, however you can use C# if you so desire.

Installation

First you need to install the Dynamic Data controls.  To do this you can either open up a command window and navigate to the directory in which you unzipped the files, or you can simply double click on the appropriate one to install depending upon your system.  You will notice that there is support for both 32-bit and 64-bit operating systems.

Your first Project

Launch Visual Studio 2008, and click on File > New > Website and choose Dynamic Data Website (Preview).

imageA

 

Directory Structure:
You will notice that you have a pre-built website complete with a Dynamic Data directory structure.  The key area's I want to point out here are the CustomPages, FieldTemplates, and PageTemplates directories. 

imageB

 

FieldTemplates:These are the templates which are used to render each of the field types with in your datamodel.  You will notice the standard data types such as Boolean, DateTime, Integer, and Text.  You should also notice templates to handle the rendering of ForeignKey and Children relationships.  What you should also notice is that the templates for Image and XML data types are missing.  As of this release they are not installed by default; however you can create your own templates to control the rendering of these datatypes.  You can also override/customize the existing templates depending upon your needs.

PageTemplates:These are the default page templates to display / edit the data. 

CustomPages:This is where you will put any custom pages or override any pages in the default PageTemplates directory.

 

 

 

Data Model:

Add your database to your project.

Add a LINQ to SQL Class

imageC

Add the desired tables to your datacontext.

imageD

Now its time for the magic.  Open your global.asax and uncomment the line:

'    model.RegisterContext(GetType(YourDatacontext), New ContextConfiguration() With {.ScaffoldAllTables = False})

Then replace "YourDatacontext" with the name of the datacontext we just created and set ScaffoldAllTables = True.  Setting this value will build the dynamic pages based upon the data model.

   1: <%@ Application Language="VB" %>
   2: <%@ Import Namespace="System.Web.Routing" %>
   3: <%@ Import Namespace="System.Web.DynamicData" %>
   4:  
   5: <script RunAt="server">
   6: Public Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
   7:     Dim model As New MetaModel
   8:  
   9:     ' Uncomment this line to register your data context with the Dynamic Data engine.
  10:     ' Only set ScaffoldAllTables = true if you are sure that you want all your tables
  11:     ' to support a scaffold (i.e. templated) view.
  12:         model.RegisterContext(GetType(NorthwindDataContext), New ContextConfiguration() With {.ScaffoldAllTables = True})
  13:  
  14:     routes.Add(New DynamicDataRoute("{table}/ListDetails.aspx") With { _
  15:         .Action = PageAction.List, _
  16:         .ViewName = "ListDetails", _
  17:         .Model = model})
  18:  
  19:     routes.Add(New DynamicDataRoute("{table}/ListDetails.aspx") With { _
  20:         .Action = PageAction.Details, _
  21:         .ViewName = "ListDetails", _
  22:         .Model = model})
  23:  
  24:     'routes.Add(New DynamicDataRoute("{table}/{action}.aspx") With { _
  25:     '    .Constraints = New RouteValueDictionary(New With {.Action = "List|Details|Edit|Insert"}),
  26:     '    .Model = model})
  27: End Sub

 

Now run the site.

imageE

Drilling down to the Product details you will see that the friendly names for the categories are in both the drop down list, and the grid view.  You will also notice the ability to edit and delete.

So now go off and have some fun with this.  Next time we will look at adding support for the Image data type based upon Scott Hunter's sample and building our own custom pages.

So until next time, have fun and I'll see ya on the flip side.

David Yancey

We are going to look at the Provider model and Offloader/Worker model and how we use this approach for our Test Driven Development (TDD) and Unit Testing.  For this Demo we will be using the Northwind Database, and developing this in VB. To start off we need to set up 3 projects in the same solution:

  1. TCNorthwindClass:  This is were we will place our class files for the BLL, DAL, and Interface.
  2. TCNorthwindWeb:  For this demo we will be creating a webservice.
  3. TCNorthwindTest:  For our Unit Tests.

Now lets look at what the provider model is and how we use it with in our application.

Provider Model

We are going to define the provider model as a model in which the provider (access to the DAL) is passed in as an argument to the worker methods.  When developing your application using the provider model, you are going to create first your Interface with which all provider classes will implement.Diag1  

Looking at the diagram to the left you will see that we have our Interface along with 2 class files of which one we are defining as a mock.  We'll look more at this later.  What's important to see here is that in our provider class files we are defining what is to be done with the data.  Each class implements from the interface the function "GetInventoryCountByCategory" and has passed into the function a categoryID.

Reason for developing with this model is to provide a mechanism for changing data providers; for example moving from .NETTIERS to LLBLGEN or LINQ while maintaining control over what methods, properties are exposed from the DAL to the BLL.  Another reason for this model is it provides a means of creating a Mock DAL to Unit test our methods with out interacting with the Database.  This is a key factor when integrating your unit tests into the CI Builds in TFS (Team Foundation Server).  Now you might be thinking to yourself that the Data layer providers such as .NETTIERS, SubSonic, and LLBLGEN provide means of unit testing through mock objects or that you could use RhinoMock and other Mock Object frameworks to mock your objects and DAL with much more functionality.  Well you maybe right, but that also comes with larger overhead and a large amount of functionality that isn't needed. 

Now that we've had a look at the base of our provider model, lets look at some code and the differences between the two providers in diag1 (diagram above).

As you examine the code below you will notice that both providers implements the Function GetInventoryCountByCategory, however they do so in different ways.  The NorthwindProvider is our "custom" DAL which queries the Northwind DB to get the count of products based upon the categoryID and then returns that count.  While the Mock simply takes the categoryID and then returns an integer value based from the select case statement.  We'll look more into the how we set this up as a unit test in a moment.

IProvider.VB

   1: Public Interface IProvider
   2:     Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer
   3: End Interface

TCNorthwindProvider.vb

   1: Imports System
   2: Imports System.Data
   3: Imports System.Data.Sql
   4: Imports System.Data.SqlClient
   5:  
   6: Public Class TCNorthwindProvider
   7:     Implements IProvider
   8:  
   9:  
  10:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer Implements IProvider.GetInventoryCountByCategory
  11:         Dim count As Integer
  12:         Dim sqlText As String = "SELECT Count(productID) FROM Products WHERE CategoryID = @CategoryID"
  13:         Dim sqlCommand As New SqlCommand(sqlText, GetConnection)
  14:  
  15:         With sqlCommand
  16:             .CommandType = CommandType.Text
  17:             .Parameters.AddWithValue("@categoryID", CategoryID)
  18:             .CommandText = sqlText
  19:  
  20:             Dim dr As SqlDataReader = .ExecuteReader
  21:  
  22:             While dr.Read()
  23:                 count = dr(0)
  24:             End While
  25:             dr.Close()
  26:         End With
  27:  
  28:         Return count
  29:     End Function
  30:  
  31:     Private Function GetConnection() As SqlClient.SqlConnection
  32:         Dim oConn As New SqlClient.SqlConnection
  33:         oConn.ConnectionString = "Data Source=8X5SGF1;Initial Catalog=Northwind;Integrated Security=True"
  34:         oConn.Open()
  35:  
  36:         Return oConn
  37:     End Function
  38:  
  39:    
  40: End Class
TCNorthwindMock.vb
   1: Public Class TCNorthwindMock
   2:     Implements IProvider
   3:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer Implements IProvider.GetInventoryCountByCategory
   4:         Select Case CategoryID
   5:             Case 1
   6:                 Return 3
   7:             Case 2
   8:                 Return 5
   9:             Case 3
  10:                 Return 7
  11:             Case 4
  12:                 Return 6
  13:             Case 5
  14:                 Return 4
  15:             Case 6
  16:                 Return 2
  17:             Case 7
  18:                 Return 0
  19:             Case 8
  20:                 Return 1
  21:         End Select
  22:     End Function
  23:  
  24:     
  25: End Class

Now lets take a moment to look at the offloader/worker design pattern. 

Offloader/Worker

offloader-worker

 

 

 

 

 

 

 

What you'll notice when looking at this design pattern is that both the Offloader and Unittest (TCNorthwindMethodsTest) pass the work to the worker which in this case is (TCNorthwindMethods).  What this does for us, is allows us to write Unit tests for our worker methods directly, and gives us a single point of entry into the Business logic for the application.  This single point of entry will help with the trouble shooting and the maintenance aspects of the application.  Each method in the Worker class is dependent upon the provider being passed in.  Lets take a quick look at the worker class, Offloader class and unit test to see how they each work together.

 

Worker Class

You will notice in the worker class that we are passing in the provider and then calling the appropriate method in the provider and returning back to the offloader.  This is an example of a provider dependency injection.  There are other types of dependency injections which we will discuss some other time.  For now lets move down to the Offloader and Unit test to see how we work with the Worker class to achieve our results.

   1: Public Class TCNorthwindMethods
   2:     Public Function GetInventoryCountByCategory(ByVal categoryID As Integer, ByVal Provider As IProvider) As Integer
   3:         Return Provider.GetInventoryCountByCategory(categoryID)
   4:     End Function
   5:     Public Function GetProductsByCategoryID(ByVal categoryID As Integer, ByVal Provider As IProvider) As Products
   6:         Return Provider.GetProductsByCategoryID(categoryID)
   7:     End Function
   8:  
   9: End Class

 

Offloader Class

Here you will notice that we are defining 2 private members.

  • TCMethods: Our worker class
  • TCNorthWindProvider: Our Provider

Looking at Line #7 you will see that we are calling GetInventoryCountByCategory in our worker class and passing in which the provider we want to use.  Contrast this against how we are accessing the same method in our unit test.

   1: Public Class TCNorthwindOffLoader
   2:  
   3:     Private TCMethods As New TCNorthwindMethods
   4:     Private TCNorthWindProvider As New TCNorthwindProvider
   5:  
   6:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer
   7:         Dim iCount As Integer = TCMethods.GetInventoryCountByCategory(CategoryID, TCNorthwindProvider)
   8:         Return iCount
   9:     End Function
  10:     Public Function GetProductsByCategoryID(ByVal CategoryID As Integer) As Products
  11:         Dim products As Products = TCMethods.GetProductsByCategoryID(CategoryID, TCNorthwindProvider)
  12:         Return products
  13:     End Function
  14:  
  15: End Class

 

Unit Test

Looking at our unit test, you will see in our first test method on line #20 we are setting a private member to our provider model.  In this case its our mock model we created earlier.  Now look at line #36 where we are setting a local member to our worker class and then at line 43 where we pass the provider into our worker class.  In this unit test we are hard coding in the value of our categoryID and what our expected return value is.  We will then compare the expected return value against the actual return value to determine if the test passed.  This type of Unit testing is used to determine if our logic / design is going to give us the results we need for the application and wether or not we need to redesign the application before we've accumulated to much technical debt.

   1: 'The following code was generated by Microsoft Visual Studio 2005.
   2: 'The test owner should check each test for validity.
   3: Imports Microsoft.VisualStudio.TestTools.UnitTesting
   4: Imports System
   5: Imports System.Text
   6: Imports System.Collections.Generic
   7: Imports TCNorthwindClass
   8: Imports TCNorthwindClass.TCNorthwindOffLoader
   9:  
  10:  
  11: '''
  12: '''This is a test class for TCNorthwindClass.TCNorthwindMethods and is intended
  13: '''to contain all TCNorthwindClass.TCNorthwindMethods Unit Tests
  14: '''
  15:  _
  16: Public Class TCNorthwindMethodsTest
  17:  
  18:  
  19:     Private testContextInstance As TestContext
  20:     Private tcNorthWindMock As New TCNorthwindMock
  21:  
  22:     Public Property TestContext() As TestContext
  23:         Get
  24:             Return testContextInstance
  25:         End Get
  26:         Set(ByVal value As TestContext)
  27:             testContextInstance = value
  28:         End Set
  29:     End Property
  30:  
  31:     '''
  32:     '''A test for GetInventoryCountByCategory(ByVal Integer)
  33:     '''
  34:      _
  35:     Public Sub GetInventoryCountByCategoryTest()
  36:         Dim target As TCNorthwindMethods = New TCNorthwindMethods
  37:  
  38:         Dim categoryID As Integer = 3 'TODO: Initialize to an appropriate value
  39:  
  40:         Dim expected As Integer = 7
  41:         Dim actual As Integer
  42:  
  43:         actual = target.GetInventoryCountByCategory(categoryID, tcNorthWindMock)
  44:  
  45:         Assert.AreEqual(expected, actual, "TCNorthwindClass.TCNorthwindMethods.GetInventoryCountByCategory did not return th" & _
  46:                 "e expected value.")
  47:     End Sub
  48:  
  49:  
  50:  
  51:     '''
  52:     '''A test for GetProductsByCategoryID(ByVal Integer, ByVal TCNorthwindClass.IProvider)
  53:     '''
  54:      _
  55:     Public Sub GetProductsByCategoryIDTest()
  56:         Dim target As TCNorthwindMethods = New TCNorthwindMethods
  57:  
  58:         Dim categoryID As Integer = 3 'TODO: Initialize to an appropriate value
  59:  
  60:  
  61:  
  62:         Dim expected As New Products
  63:         For i As Integer = 0 To categoryID
  64:             Dim tProduct As New Product
  65:             With tProduct
  66:                 .ProductID = i
  67:                 .ProductName = String.Format("Item{0}", i)
  68:                 .QuantityPerUnit = i.ToString
  69:                 .ReorderLevel = i
  70:                 .Discontinued = False
  71:             End With
  72:             expected.product.Add(tProduct)
  73:         Next
  74:  
  75:  
  76:  
  77:         Dim actual As Products
  78:  
  79:         actual = target.GetProductsByCategoryID(categoryID, tcNorthWindMock)
  80:  
  81:         Assert.AreEqual(expected.product.Count, actual.product.Count, "TCNorthwindClass.TCNorthwindMethods.GetProductsByCategoryID did not return the ex" & _
  82:                 "pected value.")
  83:        
  84:  
  85:         
  86:     End Sub
  87: End Class

 

 The final bit of code I want to look at is a webservice I created to use the offloader class to utilize with in the application.

Here you will see that we have created a private member to our offloader and are passing in the categoryID to the offloader which then returns the results.

   1: Imports System.Web.Services
   2: Imports System.Web.Services.Protocols
   3: Imports System.ComponentModel
   4: Imports TCNorthwindClass
   5:  
   6: Namespace:="http://tempuri.org/")> _
   7: 
   8: False)> _
   9: Public Class TCNorthwindService
  10:     Inherits System.Web.Services.WebService
  11:     Private tcWorker As New TCNorthwindOffLoader
  12:      _
  13:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As String
  14:  
  15:         Return tcWorker.GetInventoryCountByCategory(CategoryID)
  16:     End Function
  17:      _
  18:         Public Function GetProductsByCategoryID(ByVal CategoryID As Integer) As Products
  19:         Return tcWorker.GetProductsByCategoryID(CategoryID)
  20:     End Function
  21:  
  22:  
  23: End Class

 For further information and to demonstrate the actual creation and running of a unit test, please refer to the 3 screen casts linked below.

Thank You

David Yancey 

Part 1
http://www.screencast.com/t/PRz3QnkoP

Part 2
http://www.screencast.com/t/3SGkZ9Os

Part 3
http://www.screencast.com/t/3iC1qSDXXXu

Mark Richman has found a solution to a bug when upgrading a .NET 2.0 Compiled site to .NET 3.5  and using LINQ.

Check it out

http://markrichman.com/post/Visual-Studio-2008-Bug-Upgrading-web-site-from-Visual-Studio-2005-compiler-error-CS1519.aspx

 Enjoy

A few days ago I was asked to do a version of my XMLSerializer post in C#. 

Well it was more like a week or two ago but either way here it is.  For explanation on what I'm doing in the code please refer back to my original post.

http://weblogs.asp.net/davidyancey/archive/2008/01/23/writing-xml-with-xmlserializer.aspx

Download

 

Visual Studio 2008 and .NET 3.5 are finally out and with it we have some new "toys" to look at.  Today we are going to look at XLINQ and the new ListView control while we build a new RSS Reader.  Before we get started I want to take a brief at LINQ.

LINQ (Language Integrated Query) defines a set of operators used to query, filter, and project data.  While the data must be encapsulated as an object, its source can be an array, enumerable class, XML, relational database, or any third party data source.  Some of the operators we have will be familiar to SQL programmers such as Select, Where, Join, Concat, OrderBy, Disctinct, and Union.  We will look at the syntax in a bit.  For further reading on LINQ be sure to check out the LINQ Project at Microsoft.

To get started we are going to need:
.NET 3.5 Framework
Visual Studio 2008

So lets get started by opening our development environment VS2008 and create a new Web project. 
In this project we are going to add the following:
Rss.vb (Class file)
GetRss.vb (Class file)
rss-reader.ascx (User Control)

Rss.vb

Looking at the code below I want to talk mainly about the LINQ portion.  But before we get going on it lets take a brief look at XDocument. In LINQ to XML (XLINQ) we use XDocument or XElement to load the XML into an object for us to query.  XDocument uses XMLReader as its underlying object to read the XML document and thus requires an XMLReader, TextReader, or URI to be passed into XDocument.  Once we have it loaded we will access it through the Descendants Method and Element Method. 

As you look closer at the query you can see that we are loading the node "channel" into an Anonymous type . 

myFeed In feedSource.Descendants("channel")

By using an Anonymous type we don't have to define a class declaration of the type.  We can instead use this type inline and access the elements of the node(s) we loaded into it and set them to properties of our collection with out having to define the properties.

feedDescription = myFeed.Element("description").Value

We can also create a sub collection with in our query and return it as part of the object to later be processed.

feedItems = myFeed.Descendants("item")

We finally we are returning our query as IEnumerable object to later be bound to a Listview in our class control.

 

Public Class RSS
    Public Function Read(ByVal URLPath As String) As IEnumerable
        Dim dcNameSpace As XNamespace
        dcNameSpace = XNamespace.Get("http://purl.org/dc/elements/1.1/")
        Dim feedSource As XDocument
        feedSource = XDocument.Load(URLPath)

        Dim query = From myFeed In feedSource.Descendants("channel") _
                    Select feedTitle = myFeed.Element("title").Value, _
                            feedDescription = myFeed.Element("description").Value, _
                            feedLink = myFeed.Element("link").Value, _
                            feedAuthor = myFeed.Element(dcNameSpace + "creator").Value, _
                            feedItems = myFeed.Descendants("item")
        Return query
    End Function
End Class


GetRss.vb

This is a simple class.  We are passing in URLPath (URL to our Feed) to be processed.  As you can see we call the read method of our RSS object (our DAL) and return it to our class control.  This class file is our BLL.  It is in this layer we would apply any business rules needed.

Public Class GetRSS
    Private m_urlPath As String
    Public Function GetRSS() As IEnumerable
        Dim myRSS As New RSS


        Return myRSS.Read(m_urlPath)
    End Function

    Public Sub New()

    End Sub

    Public Sub New(ByVal UrlPath As String)
        m_urlPath = UrlPath
    End Sub
End Class

Rss-Reader.ascx

Now to the fun part of putting it all together.  First the code behind. 

Simply we set a property for the class to hold URLpath, and bind the object to the our list view in our page_load.  However we have another method in our codebhind that I'm calling XEval.  The problem here is the sub collection inside our IEnumerable object.  When we get the collection at this point we are finding 2 main property's exposed.  One is value, and the other is XML.  We will see how we use both of these when we look at the markup.  Briefly though Value gives us the value of the property, and XML gives us the XML markup of that same property.  Neither of which are queriable (that I was able to find).  So what we are doing in XEval is passing the XML property to this method, along with the Xpath we want to return the InnerText of that node.

Imports System.Xml
Partial Public Class rss_reader
    Inherits System.Web.UI.UserControl

    Private m_urlPath As String
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Dim myRSS As New GetRSS(m_urlPath)
        listview1.DataSource = myRSS.GetRSS
        listview1.DataBind()

    End Sub

    Public Function xEval(ByVal x As String, ByVal xPath As String) As String
        Dim xDocument As New XmlDocument
        xDocument.LoadXml(x)

        x = xDocument.SelectSingleNode(xPath).InnerText
        Return x
    End Function

    Public Property URLPath() As String
        Get
            Return Me.m_urlPath
        End Get
        Set(ByVal value As String)
            Me.m_urlPath = value
        End Set
    End Property

End Class

Now the markup.

Here I want to look at the ListView.  ListView's can be bound to an object that Inherits IEnumerable, SQLDatasource, LINQDatasource, XMLDatasource, and ObjectDatasource.  In our ListView we are using the LayoutTemplate which has a placeholder inside of a div and the ItemTemplate.  There are other templates available to us such as AlternateItemTemplate, HeaderTemplate, FooterTemplate, and EmptyItemTemplate.  Each of these templates will be rendered into the placeholder in the layout template.  So as you can see this gives us flexability in defining the look and feel of our control.  Scott Guthrie has a great blog entry giving more information on ListView's.  For mine here I'm a CollapsiblePanelExender control from Ajax to provide a bit of control and styling.

<%@ Control Language="vb" AutoEventWireup="false" CodeBehind="rss-reader.ascx.vb" Inherits="Sandbox.rss_reader" %>
<%@ Register assembly="AjaxControlToolkit" namespace="AjaxControlToolkit" tagprefix="cc1" %>

<asp:ListView ID="listview1" runat="server">
            <LayoutTemplate>
                <div style="width: 1000px;">
                    <asp:PlaceHolder ID="itemPlaceHolder" runat="server" />
                </div>
            </LayoutTemplate>            
            <ItemTemplate>
                <div>
                    <asp:Hyperlink ID="hlin2" runat="server" style="font-weight: bold; font-size: 12pt; color: Black;" Text='<%# Eval("feedTitle") %>' NavigateUrl='<%# Eval("feedLink") %>' />
                </div>
                <div>    
                    <asp:Label ID="label1" runat="server" style="font-weight: bold; font-size: 12pt;" Text='<%# Eval("feedDescription") %>' />
                </div>
                <div><hr /></div>           
                <div>
                    <asp:ListView ID="listview2" runat="server"  DataSource='<%# Eval("feedItems") %>' >
                        <LayoutTemplate>
                            <div>
                                <asp:placeHolder ID="itemPlaceHolder" runat="server" />
                            </div>
                        </LayoutTemplate>
                        <ItemTemplate>
                            <div>
                                <div id="descriptControl" runat="server" style="height: 30px;">
                                <div style="float: left;">
                                    <asp:HyperLink ID="hlink1" runat="server" Text='<%# xEval(Eval("XML"), "/item/title") %>' NavigateUrl='<%# xEval(Eval("XML"), "/item/link") %>' />
                                </div>
                                <div style="float:right;">
                                    <asp:ImageButton ImageUrl="~/expand.jpg" ID="imgbtn" runat="server" />
                                </div>
                                </div>                                
                               <asp:Panel ID="panel1" runat="server" BorderStyle="Inset" BorderColor="#333333" BorderWidth="1">
                                    <div>
                                        <asp:Label ID="label2"  style="font-size: 9pt; font-style:italic; color: Gray;" runat="server" Text='<%# xEval(Eval("XML"), "/item/pubDate") %>' />
                                    </div>
                                    <div id="descriptDiv" runat="server" style="padding: 3px;">
                                        <asp:Label ID="label3"  style="font-size: 9pt;" runat="server" Text='<%# xEval(Eval("XML"), "/item/description") %>' />
                                    </div>
                                </asp:Panel>
                                <cc1:CollapsiblePanelExtender ID="CollapsiblePanelExtender1" runat="server" Collapsed="true" 
                                    TargetControlID="panel1" ExpandControlID="imgbtn" CollapseControlID="imgbtn"
                                     CollapsedImage="expand.jpg" ExpandedImage="collapse.jpg" SuppressPostBack="true">
                                </cc1:CollapsiblePanelExtender>
                            </div>         
                        </ItemTemplate>
                    </asp:ListView>
                </div>        
            </ItemTemplate>
        </asp:ListView>
And the final output:
 
image
 

 

Download Sourcecode

 

That's all for now.

Next time we will look at building an XML Document using XDocument and XElement.

Dave

RSS definition From Wikipedia

"RSS (formally "RDF Site Summary", known colloquially as "Really Simple Syndication") is a family of Web feed formats used to publish frequently updated content such as blog entries, news headlines or podcasts. An RSS document, which is called a "feed", "web feed", or "channel", contains either a summary of content from an associated web site or the full text. RSS makes it possible for people to keep up with their favorite web sites in an automated manner that's easier than checking them manually.

RSS content can be read using software called an "RSS reader", "feed reader" or an "aggregator". The user subscribes to a feed by entering the feed's link into the reader or by clicking an RSS icon in a browser that initiates the subscription process. The reader checks the user's subscribed feeds regularly for new content, downloading any updates that it finds."

What I call it is a great way to get my news.  I've got 4 feeds that I read in my Outlook daily.  On my blog I have 4 other feeds including one of my own.  You see them on almost every news site, blog, forum, website you visit.  So why a blog entry on RSS Readers?  Well the problem is that there aren't many options to add an aggregator so that you can display your favorite feeds on your site for your visitors.  So today we are going to take a little bit of .NET and add some XSLT and finally throw in a few features to display the feed on your community website.

So lets get started.  First add a user control to your project.  Here in our control you can see that we are simply using an XSLCompiledtransform object to transform the feed.  You will also notice that we have added a property to our user control so that we could set the path to the feed at runtime.

Imports System.Xml
Imports System.Xml.Xsl
Imports System.IO

Partial Public Class ucBlogReader
    Inherits System.Web.UI.UserControl

    Private m_xmlURL As String
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        LoadBlog(m_xmlURL)
    End Sub

    Protected Sub LoadBlog(ByVal path As String)
        If path = "" Then
            XMLBlogReader.Text = "Please select a blog from the left"
        Else
            Dim myXSL As New XslCompiledTransform
            Dim tempStringWriter As New StringWriter
            Dim tempString As XmlWriter = New XmlTextWriter(tempStringWriter)

            myXSL.Load(Server.MapPath("~/XSL/rss-reader.xslt"))

            myXSL.Transform(path, tempString)

            Me.XMLBlogReader.Text = tempStringWriter.ToString()

        End If
    End Sub

    Public Property XMLUrl() As String
        Get
            Return m_xmlURL
        End Get
        Set(ByVal value As String)
            m_xmlURL = value
        End Set
    End Property

End Class

XSLT

Now comes the fun part.  Once we have our RSS feed we have various options to transform it.  We could use XMLDocument, XMLReader, or XMLDatasourse and many other options to traverse the feed.  For this example we are going to build the XSLT we use in the code above to transform our RSS Feed.  

There are two key parts that I want to point out in our XSLT document.

The first being our loop.  Here we are testing the position of each "item" node with in the XPATH statement.  When that position is greater than 5 we exit the loop. 

<xsl:for-each select="rss/channel/item[position()&lt;=5]"

The second area I wanted to look at is where we display the "description" node.  What we are doing here is taking the first 500 characters of the description using the substring function with in XPATH. 

<xsl:value-of select="substring(description, 1, 500)" disable-output-escaping="yes"/>

There are many different ways of building an XSLT document, such as building a template for the title, description, links etc...  In our example we are just creating a single template and keeping our document as simple as possible. 

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:dc="http://purl.org/dc/elements/1.1/">
  <xsl:output method="xml"/>
  <xsl:template match="/">
    <Div id="wrap">
      <Div style="text-align: left;">
        <xsl:attribute name="id">title</xsl:attribute>
        <H3>
        <xsl:value-of select="//dc:creator" />
        </H3>
      </Div>
      <Div>
        <xsl:attribute name="id">
          content:<xsl:value-of select="rss/channel/title" />
        </xsl:attribute>
        <xsl:for-each select="rss/channel/item[position()&lt;=5]">
          <P style="text-align: left;">
            <A>
              <xsl:attribute name="href">
                <xsl:value-of select="link"/>
              </xsl:attribute>
              <xsl:attribute name="style">
                color: black; font-weight: bold;
              </xsl:attribute>
              <xsl:attribute name="target">_new</xsl:attribute>
              <xsl:value-of select="title"/>
            </A>
            <br/>
            <xsl:value-of select="pubDate"/>
            <br/>
          </P>
          <Div style="text-align: left; padding-top: 10px; padding-bottom: 5px; padding-left: 15px;">
            <xsl:value-of select="substring(description, 1, 500)" disable-output-escaping="yes"/>
            <xsl:text>...</xsl:text>
          </Div>
        </xsl:for-each>
      </Div>
    </Div>
  </xsl:template>
</xsl:stylesheet>

The final step is to add your new control to your site and set the value of the XMLUrl property to the URL of your desired feed.  I'm going to leave that with you to accomplish, however if you would like to see our new control working, you can do so at http://www.proessent.com/blog.aspx

That's it for now.  Check back next week when we will look at building another RSS Reader using LINQ in Visual Studio 2008.

See you on the Flip Side

Dave

xml vb.net asp.net

 

TreeViews provide a great way for displaying a hierarchal view of a website, directory, company, and the list goes on.  You can build a TreeView by binding it to a datasource, or by manually adding the nodes.  Today we are going to look at building a TreeView of a directory structure for a website.  To do this we are going to create a WebService that utilizes Delegates to traverse the directory and return an XMLDocument to our web page which will then be bound to our TreeView.

First off lets take a quick look at why we are using a delegate when traversing the directory.  We could have just created a method that returns all the folders using Directory.GetDirectories and then process them all in a loop.  However; if the path being traversed contains thousands of items, then the application will most likely freeze and ignore any input from the user.  What delegates does is provides for us a way to traverse the directory and build our xml through a callback function thus giving control back to the user while this is taking place. 

WebService : DirectoryInfoView

<WebMethod()> _
    Public Function GetDirectoryInfo(ByVal path As String, ByVal sessionguid As String) As XmlDocument
        Dim tmpDocument As New XmlDocument
        If Not sessionguid = Session("wscall").ToString Then
            Throw New Exception("Security Exception:  You are not allowed to access this service")
        End If
        xmlWriter.Formatting = Formatting.None
        xmlWriter.WriteProcessingInstruction("xml", "version='1.0' encoding='UTF-16'")
        xmlWriter.WriteStartElement("SiteMapNode")
        xmlWriter.WriteAttributeString("url", path)
        xmlWriter.WriteAttributeString("title", (New DirectoryInfo(path).Name))
        TraverseDirectoryTree(path, AddressOf AddDirName)
        xmlWriter.Close()
        tmpDocument.LoadXml(UTF8ByteArrayToString(memStream.ToArray()))
        Return tmpDocument
    End Function

    Private Delegate Sub TraverseDirectoryTreeCallBack(ByVal dirName As String)

    Private Sub TraverseDirectoryTree(ByVal path As String, ByVal cbk As TraverseDirectoryTreeCallBack)
        For Each fileName As String In Directory.GetFiles(path)
            cbk.Invoke(fileName)
            xmlWriter.WriteEndElement()
        Next
        For Each dirName As String In Directory.GetDirectories(path)
            cbk.Invoke(dirName)
            TraverseDirectoryTree(dirName, cbk)
        Next
        xmlWriter.WriteEndElement()
    End Sub

    Private Sub AddDirName(ByVal path As String)
        xmlWriter.WriteStartElement("SiteMapNode")
        xmlWriter.WriteAttributeString("url", path)
        xmlWriter.WriteAttributeString("title", (New DirectoryInfo(path).Name))
    End Sub

    Private Function UTF8ByteArrayToString(ByVal characters As Byte()) As String

        Dim encoding As New UTF8Encoding()
        Dim constructedString As String = encoding.GetString(characters)
        Return (constructedString)
    End Function

Default.aspx

Here we call our webservice and bind it to an XMLDataSource.  You will also notice in our TreeView how we are binding the TreeNode to the XML.

Private Sub GetDirectoryInfo(ByVal path As String)
    Dim ws As New DirectoryInfoView
    Dim treeNode As New TreeNodeBinding
    Me.XmlDataSource1.Data = ws.GetDirectoryInfo(path, Session("wscall").ToString).OuterXml
    Me.XmlDataSource1.DataBind()
End Sub

 

<asp:TreeView ID="TreeView1" runat="server" DataSourceID="XmlDataSource1">
    <DataBindings>
        <asp:TreeNodeBinding DataMember="SiteMapNode" ValueField="url" TextField="title" />
    </DataBindings>              
</asp:TreeView>
<asp:XmlDataSource ID="XmlDataSource1" runat="server"></asp:XmlDataSource>

 

Giving us a final result of.

image

Project File DirectoryTreeView

xml webservices .net vb.net

Developing and maintaining a community website can sometimes be a daunting task.  A great deal of thought, and planning needs to go into creating content for your community to view, experience, and interact with.  One fun piece that you will find on a number of community webistes is a "Who's Online".  This is just a little bit of content that tells you how many registered users you have, how many registered users are online, how many guests are online, and the usernames of the registered users online. 

So today we are going to create a usercontrol that will display who's online, in either a Horizontal or Vertical Format.

Start off with adding a usercontrol to your project with a label, and then in the codebehind we want to create a property and enum to set the orientation of our control.

Public m_Orientation As orientation

Public Enum orientation As Integer
    Horizontal = 1
    Vertical = 2
End Enum

Public Property Orientate() As orientation
    Get
        Return m_Orientation
    End Get
    Set(ByVal value As orientation)
        m_Orientation = value
    End Set
End Property

Now the trick to this control is getting the count of our guests online.  We have a couple of options to do this, one of which would be to store the session in a table and then getting the count from the table.  However I didn't like going with that method.  To much work.  So what we are going to do is store a count in the cache each time a new session is started.  And decrease that count when the session ends.  So lets move over to the global.asax and set this up.

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
    'Initialize user count 
    HttpContext.Current.Cache.Insert("UserCount", 0)
End Sub

Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
    ' Fires when the session is started
    HttpContext.Current.Cache("UserCount") += 1
End Sub

Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
    ' Fires when the session ends
    HttpContext.Current.Cache("UserCount") -= 1
End Sub

Sub Application_End(ByVal sender As Object, ByVal e As EventArgs)
    'Dont forget to clean up
    HttpContext.Current.Cache.Remove("UserCount")
End Sub

Now heading back to our usercontrol lets do the work that builds the counts. To get the count of registered users online we call Membership.GetNumberOfUsersOnline, we then just take that count and subtract it from the "UserCount" we have stored in our cache to get the count of guests online. As you can see we are using Membership.GetAllUsers.Count to get our total registered users count and looping through GetAllUsers to get the  UserName of each member online. 

 
Private Sub GetWhosOnline()
    Dim delimeter As String = "<br />"
    Dim guestCount As Integer
    Select Case m_Orientation
        Case orientation.Horizontal
            delimeter = " | "
        Case orientation.Vertical
            delimeter = "<br />"
    End Select
    If (HttpContext.Current.Cache("UserCount") IsNot Nothing) Then
        guestCount = CInt(HttpContext.Current.Cache("UserCount").ToString()) - CInt(Membership.GetNumberOfUsersOnline.ToString)
    End If
    Me.lblWhosOnline.Text = String.Format("Total Registered: {0}{1}", Membership.GetAllUsers.Count, delimeter)
    Me.lblWhosOnline.Text += String.Format("Total Online: Registered ({0}): Guests({1}){2}", _
Membership.GetNumberOfUsersOnline.ToString, guestCount, delimeter) Me.lblWhosOnline.Text += "Who's Online: " For Each memberUser As MembershipUser In Membership.GetAllUsers If Membership.GetUser(memberUser.UserName).IsOnline Then Me.lblWhosOnline.Text += memberUser.UserName & ", " End If Next lblWhosOnline.Text = Left(lblWhosOnline.Text, Len(lblWhosOnline.Text) - 2) End Sub

Now all we need to do is to include the usercontrol on a page, and set the our property for how we want it displayed.

<uc1:ucWhosOnline id="UcWhosOnline1" runat="server" Orientate="Vertical"  />

And now you have a "Who's Online" usercontrol for your community website.

Enjoy,

Dave

Technorati Tags: ,

For the past few weeks we've been looking at utilizing XML to work with DATA from our SQL Server.  We've talked about building a custom XML document with XMLSerializer, Using the XML Datatype built in SQL 2005, and finally using OPENXML to insert records into our Database.  Today we are going to see how we can use each of those tools to solve a common issue for businesses, which is how to push data between different servers located on different networks.  In this scenario our requirements are to push the daily sales leads from a Car dealer up to the corporate office.  Our SQL servers are on disparate servers so we don't have the luxury of using DTS to get the job done.  For this example we are going to create 2 projects.  A client project which will serialize the leads from the database and send them to a webservice; which is our second project, to be inserted into the database.  If you chose to automate the process of pushing the data you could create a window service and schedule a job, but for the purpose of this project I have chosen to go with a web project for our client. 

So lets get started and create a web project for our client, and a webservice project for our server.

For our client we are going to create 2 class files; SalesLead and SalesLeadPush.  We will use SalesLeadPush to serialize our data and send it to the server, and SalesLead is our object we are using to serialize.

clsDiag1

 

Database Model

dbDiag1

SalesLead.VB 

Taking a brief look at the class file you can see how we are using class objects and attributes to define our xml structure.

Imports System.Xml
Imports System.Xml.Serialization
Namespace SalesTools
    Public Enum ContactType As Integer
        Email = 1
        Phone = 2
    End Enum
    Public Class SalesLead
        Public Leads As New List(Of Lead)

        Public Sub New()

        End Sub
    End Class
    Public Class Lead
        <XmlAttribute("LeadID")> _
        Public LeadID As Integer
        <XmlAttribute("ContactID")> _
        Public ContactID As Integer
        <XmlAttribute("VehicleID")> _
        Public VehicleID As Integer
        <XmlAttribute("SiteID")> _
        Public SiteID As Integer
        Public Contact As Contact

        Public Sub New()

        End Sub
    End Class
 

SalesLeadPush.VB

Here is where we use a MemoryStream object to serialize our data and then send it to the webserivice.

   1:  Public Class SalesLeadPush
   2:      Public Function SerializeXML() As XmlDocument
   3:   
   4:          Dim SiteID = 3214
   5:   
   6:          Dim sqlCon As New SqlConnection(ConfigurationManager.ConnectionStrings("salesPush").ConnectionString)
   7:          Dim CmdTxt As String = "getSalesLeads"
   8:          Dim SqlCmd As New SqlCommand(CmdTxt, sqlCon)
   9:          SqlCmd.CommandType = CommandType.StoredProcedure
  10:          Dim sqlDS As New DataSet
  11:          Dim sqlDA As New SqlDataAdapter(SqlCmd)
  12:   
  13:          'Set up Serializer
  14:          Dim mStream As MemoryStream = New MemoryStream
  15:          Dim xns As XmlSerializerNamespaces = New XmlSerializerNamespaces
  16:          Dim mySerializer As New XmlSerializer(GetType(SalesTools.SalesLead))
  17:          Dim xmlDoc As New XmlDocument
  18:   
  19:          'Set up object
  20:          Dim oSalesTools As New SalesTools.SalesLead
  21:   
  22:          xns.Add(String.Empty, String.Empty)
  23:          sqlDA.Fill(sqlDS)
  24:   
  25:          Dim i As Integer = 0
  26:          Dim sLeadID As Integer = 0
  27:          Dim sContactID As Integer = 0
  28:   
  29:          For Each Row As DataRow In sqlDS.Tables(0).Rows
  30:              Dim oContact As New SalesTools.Contact
  31:              Dim oVehicleList As New SalesTools.Vehicle
  32:              Dim oLead As New SalesTools.Lead
  33:   
  34:              oContact.ContactID = CInt(Row("ContactID").ToString)
  35:              oContact.AddressLine1 = Row("contactAddressLine1").ToString
  36:              oContact.City = Row("contactCity").ToString
  37:              oContact.State = Row("contactState").ToString
  38:              oContact.Postal = Row("contactPostal").ToString
Break in Code Snippet
  57:   
  58:              oLead.Contact = oContact
  59:   
  60:              oLead.LeadID = CInt(Row("LeadID").ToString)
  61:              oLead.ContactID = CInt(Row("ContactID").ToString)
  62:              oLead.VehicleID = CInt(Row("VehicleID").ToString)
  63:              oLead.SiteID = SiteID
  64:              oSalesTools.Leads.Add(oLead)
  65:   
  66:          Next
  67:          mySerializer.Serialize(mStream, oSalesTools, xns)
  68:          SqlCmd.Connection.Close()
  69:          SqlCmd.Dispose()
  70:   
  71:          xmlDoc.LoadXml(UTF8ByteArrayToString(mStream.ToArray()))
  72:   
  73:          Dim ws As New SalesLeadListen.SalesLeadListen
  74:          Dim Result As New XmlDocument
  75:          Result.LoadXml(ws.LoadLeads(xmlDoc).OuterXml)
  76:   
  77:          Return Result
  78:      End Function
  79:   
  80:      Private Function UTF8ByteArrayToString(ByVal characters As Byte()) As String
  81:   
  82:          Dim encoding As New UTF8Encoding()
  83:          Dim constructedString As String = encoding.GetString(characters)
  84:          Return (constructedString)
  85:      End Function
  86:   
  87:      Private Function StringToUTF8ByteArray(ByVal pXmlString As String) As Byte()
  88:          Dim encoding As New UTF8Encoding()
  89:          Dim byteArray As Byte() = encoding.GetBytes(pXmlString)
  90:          Return byteArray
  91:      End Function
  92:   
  93:  End Class

 

Before we move on to the webservice we will add code to our default.aspx to call the SerializeXML function and display the returned result.

Now lets move over to our 2nd project and look at the webservice. We are going to start off by creating a small class object which we will use to build our result to return to the client.

Public Class Result
    Public Sucess As String
    Public exception As String
    Public Sub New(ByVal suc, ByVal ex)
        Sucess = suc
        exception = ex
    End Sub
    Public Sub New()

    End Sub
End Class
 
Looking at our DB Model we can see that our table schema is slightly different than that of the clients.
dbDiag2 

Stored Procedure

Depending on your business rules, you will either want to handle duplicate entries during this process, or afterwards.  I'm not taking duplicate entries into consideration in this example.

CREATE PROCEDURE dbo.LoadSiteLead 
    @leadXML As XML
AS

DECLARE @XMLDocPointer INT

EXEC sp_xml_preparedocument @XMLDocPointer OUTPUT, @leadXML
BEGIN TRANSACTION

INSERT INTO Lead(siteLeadID, siteContactID, siteVehicleID, siteID)
SELECT LeadID, ContactID, VehicleID, SiteID
FROM OpenXML(@XMLDocPointer, '/SalesLead/Leads/Lead', 1)
WITH (LeadID Int, ContactID Int, VehicleID Int, SiteID Int)

INSERT INTO Contact(
    siteContactID, 
    contactFullName, 
    contactFirstName, 
    contactLastName, 
    contactAddressLine1,
    contactCity, 
    contactState, 
    contactPostal, 
    contactEmail, 
    contactPhone, 
    contactBestContactTime, 
    contactBestContactMethod)
SELECT ContactID, 
    FullName, 
    FirstName, 
    LastName, 
    AddressLine1, 
    City, 
    State, 
    Postal, 
    Email, 
    Phone,
    BestContactTime, 
    ContactMethod
FROM OpenXML(@XMLDocPointer, '/SalesLead/Leads/Lead/Contact', 2)
WITH(ContactID Int '@ID',
    FullName varchar(250), 
    FirstName varchar(125), 
    LastName varchar(125),
    AddressLine1 varchar(200), 
    City varchar(50), 
    State char(2), 
    Postal VarChar(10), 
    Email VarChar(100), 
    Phone VarChar(100),
    BestContactTime varchar(50), 
    ContactMethod varchar(50) '@ContactMethod')

INSERT INTO Vehicle(
    siteVehicleID, 
    vehicleMake, 
    vehicleModel, 
    vehicleYear, 
    vehicleVIN, 
    vehicleColor, 
    vehicleBasePrice, 
    vehicleSRP)
SELECT VehicleID, 
    Make, 
    Model, 
    [Year], 
    VIN,
    Color, 
    BasePrice, 
    SRP
FROM OpenXML(@XMLDocPointer, '/SalesLead/Leads/Lead/Contact/Vehicle', 2)
WITH (VehicleID Int '@ID', 
    Make varchar(50), 
    Model varchar(50), 
    [Year] char(4), 
    VIN varchar(200), 
    Color varchar(50), 
    BasePrice Money, 
    SRP Money)


COMMIT
EXEC sp_xml_removedocument @XMLDocPointer

WebService : SalesLeadListen

You will notice in the Try Catch block that we are loading the Result Object with Success / Exception message.  This object is then returned to the client as serialized XML.

Public Class SalesLeadListen
    Inherits System.Web.Services.WebService

    <WebMethod()> _
    Public Function LoadLeads(ByVal LeadsXML As XmlDocument) As Result
        Dim sqlCon As New SqlConnection(ConfigurationManager.ConnectionStrings("salesPush").ConnectionString)
        Dim CmdTxt As String = "LoadSiteLead"
        Dim SqlCmd As New SqlCommand(CmdTxt, sqlCon)
        Dim rst As Result
        SqlCmd.CommandType = CommandType.StoredProcedure
        SqlCmd.Parameters.AddWithValue("@LeadXML", LeadsXML.OuterXml)

        Dim SB As New StringBuilder

        Try
            SqlCmd.Connection.Open()
            SqlCmd.ExecuteNonQuery()
            rst = New Result("Success", "0")
        Catch SQLex As SqlException
            rst = New Result("Failure", SQLex.Message)
        Catch ex As Exception
            rst = New Result("Failure", ex.Message)
        Finally
            SqlCmd.Connection.Close()
            SqlCmd.Dispose()
        End Try


        Return rst

    End Function

End Class

Wrapping this up, you can now see how using XML and Webservices we are able to push data from one server to another server regardless of Database Schema and Server Location. 

That's all for now, I'll see you on the flip side of things.

 

Enjoy,

Technorati Tags: , , ,

Dave

The XMLSerializer:

Recently a friend of mine and I were discussing the best way to serialize a Dataset to XML while being able to control the structure of the XML.  With a dataset we could easily use the dataset.WriteToXml method to write the dataset to XML.  However the only control we have in the structure of the XML with this method is through our dataset and how we load it.  Another option is to Loop through our Dataset and write it out to XML utilizing the XMLWriter giving us full control over the structure of the XML; however. Depending upon the size of our Dataset and intended XML this can end up being quite cumbersome.  We could load the Dataset directly into the XMLSerializer to write out our XML, but once again the only control we have with the schema of our XML will be in how we built our table.  Thus we are left with our topic for this entry.  How do we serialize XML from a dataset with custom attributes using the XMLSerializer? 

For the purpose of this project we are going to do this in a single web page.  Inside our web page we are going to create 2 classes and a method to do the actual serialization.  Our environment is Visual Studio 2005 and VB.Net.

Now let’s go ahead and open up VS2005 and create a new web application/project and name it XMLWrite.  Once we have our project open we can delete the default page, and add a new one named xmlserialize.aspx.  Go ahead and save this project as we will be revisiting the project in the other 2 parts to this series at a later date. 

Now let’s go ahead and get to some coding.  Start off by going to the code view of your page.  What we need to start off with is our import statements. 

Imports System
Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlClient
Imports System.Xml
Imports System.Xml.Serialization
Imports System.IO
Imports System.Collections.Generic    

I’ve included in this project an Address Book database with 2 tables (Person and Address) to use for our example.  Start off with making 3 Classes in your project. 

Public Class AddressBook
    <XmlElement("Entry")> _
    Public Person As New List(Of Person)
End Class 

Public Class Person
    Public FullName As String
    <XmlElement(""Address"")> _
    Public Address As New List(Of Address)
End Class 

Public Class Address
    <XmlAttribute("Type")> _
    Public AddressType As String
    Public AddressLine1 As String
    Public AddressLine2 As String
    Public City As String
    Public State As String
    Public Postal As Integer

End Class 

There are 2 areas’ I want to point out in our class’s here.  The first is that we have used generics to instantiate Person in our Class AddressBook and Address in our Class Person to take advantage of their built in “Add” method.  The 2nd area is the decoration in our Address Class.  <XmlAttribute(“Type”)> tells the XMLSerializer to set AddressType as an attribute of the node Address.  The XMLSerializer will set the name of the element, root, and attributes to the name of the object unless we specify it specifically as we did in the Attribute decoration. 

Private Sub SerializeXML()
        Dim sqlCon As New SqlConnection(ConfigurationManager.ConnectionStrings("addressbook").ConnectionString)
        Dim CmdTxt As String = "spi_GetAddress"
        Dim SqlCmd As New SqlCommand(CmdTxt, sqlCon)
        SqlCmd.CommandType = CommandType.StoredProcedure
        Dim sqlDS As New DataSet
        Dim sqlDA As New SqlDataAdapter(SqlCmd)
        Dim xns As XmlSerializerNamespaces = New XmlSerializerNamespaces
        Dim mySerializer As New XmlSerializer(GetType(AddressBook))
        Dim sWriter As New StreamWriter(Server.MapPath("address.xml"))
        Dim oBook As New AddressBook

        xns.Add(String.Empty, String.Empty)
        sqlDA.Fill(sqlDS)
        
       
For Each Row As DataRow In sqlDS.Tables(0).Rows
            Dim oPerson As New Person
            Dim oAddress As New Address

            oAddress.AddressLine1 = Row("AddressLine1").ToString
            oAddress.AddressLine2 = Row("AddressLine2").ToString
            oAddress.City = Row("city").ToString
            oAddress.State = Row("state").ToString
            oAddress.Postal = Row("postal").ToString
            oAddress.AddressType = Row("AddressType").ToString
            oPerson.FullName = Row("FullName").ToString
            oPerson.Address.Add(oAddress)
            oBook.Person.Add(oPerson)
        Next
        mySerializer.Serialize(sWriter, oBook, xns)

        SqlCmd.Connection.Close()
        SqlCmd.Dispose()
End Sub

Pay close attention to the line:

Dim xns As XmlSerializerNamespaces = New XmlSerializerNamespaces

xns.Add(
String.Empty, String.Empty)
 

Here we are resetting the name space that The XMLSerializer adds to the root by default and adding an empty string so that we have a root element of our xml with no namespaces.   The Final step to this look at customizing our XML from a XMLSerializer is to call our Method from the page load and execute it giving us a final XML. 

<?xml version="1.0" encoding="utf-8"?>
<AddressBook>
  <Entry>
    <FullName>John Doe</FullName>
      <Address Type="Home">
        <AddressLine1>100 One Street</AddressLine1>
        <AddressLine2 />
        <City>Dallas</City>
        <State>Tx</State>
        <Postal>75240</Postal>
      </Address>
  </Entry>
  <Entry>
    <FullName>Jane Doe</FullName>
      <Address Type="Business">
        <AddressLine1>1532 LBJ Freeway</AddressLine1>
        <AddressLine2>Suite 300</AddressLine2>
        <City> Dallas</City>
        <State>Tx</State>
        <Postal>75248</Postal>
      </Address>
  </Entry>
</AddressBook>  

Enjoy

 Dave Yancey

XMLWrite.zip (615.03 kb)

 

More Posts