Display a Nested List Inside Another Repeater

A simple way to display a list of data is to use an asp:Repeater. If the data also needs to show a nested list of related data for each main item, one can place another Repeater inside the first. In the code behind, the queries used for each Repeater can be linked in a dataset as a parent-child relationship via a Data Relation.

In the example below we make the multiple queries in only one SQL command statement, then link the result tables together by making a new DataRelation. The link uses key and foreign key ID columns from each result set.

This C# example shows how you can have a list of phone numbers as an area inside a larger list of contacts.

             In the Page_Load function…
             …

        // Create and fill a DataSet
SqlDataAdapter myCommand = new SqlDataAdapter(

            “SELECT DISTINCT MemberID, Address, FirstName, LastName “ +
            ” FROM MemberNames M; “ +

            “SELECT MemID, phonetype, Number “ +
            ” FROM MemberNames M “ +
            ” INNER JOIN Phone P ON M.MemberID = P.MemID;” +
             , myConnection);

         DataSet ds = new DataSet();
        myCommand.Fill(ds);

         // Relate the two tables from the query together
        ds.Relations.Add(new DataRelation(“MemberID_Phone”, ds.Tables[0].Columns["MemberID"], ds.Tables[1].Columns["MemID"]));

        // Bind the outer Repeater to the DataSet.
        rptMember.DataSource = ds.Tables[0];
        rptMember.DataBind();

 When we bind the data for each item in the outer Repeater, we will create a child view using the relation we linked previously.

     protected void rptMember_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        DataRowView drv = e.Item.DataItem as DataRowView;
         Repeater rptPhone = e.Item.FindControl(“rptPhone”) as Repeater;

        rptPhone.DataSource = drv.CreateChildView(“MemberID_Phone”);
        rptPhone.DataBind();
    }

 This snippet from the .aspx files shows the nested repeaters.

<asp:Repeater id=”rptMember” OnItemDataBound=”rptMember_ItemDataBound” runat=”server”>

         <ItemTemplate>

          <!– Tags to display the bound data (name, address, etc.) –>  

          <!– Make a repeater for all phone numbers for each person –>

            <asp:Repeater id=”rptPhone” runat=”server”>
              <ItemTemplate>
                <%# DataBinder.Eval(Container.DataItem, “PhoneType”)%>:
                <%# DataBinder.Eval(Container.DataItem, “Number”)%>

              </ItemTemplate>
            </asp:Repeater>

            …

 To sum it all up, we implemented several technical elements here. 

  • Nested Repeaters
  • Using a multi-result query
  • Using DataSet Relations

 Our example displays a list of Contacts with a sub-list for multiple phone numbers.

Article Series and Planned Topics

by Allan Sieker

When it comes time to write an interesting article about a technical topic my mind goes into a deadlock in trying to find the balance between space/time constraints and keeping the content interesting.  Too short of an article and it seems trivial because the technical stuff doesn’t get covered enough.  Too technical, and the article becomes long and boring.  All the while, wanting to keep things original, informative, and light.  So did I end up with?  How about a series of related articles that covers something that is near and dear to all of us?   

 I bought my first computer in 1978 (yes, I still have it) and as my home network expanded to what it is now – several servers, workstations, and laptops, the need for keeping a file inventory goes with the territory.  Sure, over the years I created my own file databases written in several languages (BASIC, Pascal, dBASE, Clipper, VB, C#) and they all served their purpose, but technology keeps improving and I always want more.   

 What if data warehousing concepts were applied to capture the “slowly changing dimension” of file updates?  What if file collections were recognized and managed as applications and other entities?  What if all of the computer file inventories were gathered locally then stored centrally for searches via a web interface?   What if backup history were also available?

 This article series will cover a broad range of technical topics with the end goal being a respectable system for home or business usage.  Concepts will be discussed and code will be available for download.  References to other articles and postings will also be made.

 Here is a brief list of planned topics: 

  • Using recursive methods to collect file information from all of the folders on a drive.
  • Creating console and Windows “file agent” applications to collect file data and write to an XML file.
  • Creating a SQL Server file inventory database.
  • Balancing cost and architecture to avoid over-costing and over-engineering.
  • Importing “file agent” XML into the database.
  • Creating an ASP.NET web application for searching and retrieving application & file information.
  • Making “file agent” applications downloadable from the web site using ClickOnce.
  • Using the Visual Studio Report Designer and the ReportViewer control to create web reports.
  • Creating a “wrapper” application for Microsoft’s Backup to manage and track backups.
  • Detecting media and image file duplicates based on file content instead of file name.

 Feedback is always welcome.

Importing Mixed ZIP Codes into SQL Server from Excel

I had a spreadsheet with tens of thousands of mailing addresses. One of the columns was  the ZIP code for each address row.  Some of those ZIP codes were in the ZIP + 4 format (12345-6789) and others were not.  Those others were 3, 4 or 5 digit numbers.  When the data was put into the spreadsheet, Excel removed the leading zeroes from some ZIP codes (like “00345” truncated to “345”).

I used the Import functionality in SQL Server Management Studio 2005 to put the data from the Excel spreadsheet into a table.  Without modifying the spreadsheet data the import would place a NULL into each row that had a 3,4 or 5 digit ZIP code, while keeping the ZIP + 4 codes correctly.

To get both “5-Digit” and “Zip Plus Four” ZIP codes into the same database column I needed to make Excel see them as text. To do this I made a new column next to my ZIP Codes with this style of formula:                =TEXT(B2,”00000″)

zipcode-excel-as-text

This causes the data to have at least 5 digits and leading zeroes if less than five  digits. Next I copied this new column and then used “Paste Special” with the “Values” option to replace my original ZIP code column. Saving the spreadsheet and executing a SQL Server import of the XLS file, all ZIP codes were then correct.

zipcode-excel-paste-special

I also found that using the “Format Cells” functionality with the Special “Zip Code” setting only changes how the data appears. It does not change the underlying data. For example “345” shows as “00345” but if you look at the actual data, it remains unchanged. So just doing Excel formating did not work when importing in to a SQL Server database.

zipcode-excel-formatted

Using A Data Container Column as an Inline IF Condition

I find myself using .net data containers in many web applications.  Binding controls such as asp:Repeater and asp:DataList to data sources can make displaying your data more manageable.  In a few of those instances I need to have some of the data display in a little different manner depending on that particular info.

For example, a “sold” image could be shown if the status of a data item warrants it.  If the status is anything else no extra HTML is rendered.

<%# (DataBinder.Eval(Container.DataItem, "Status").ToString() == "Sold") ?
<img src='/images/sold.gif' />" : "" %>

Instead of making a server-side control in my .aspx page then having my code behind find it in some sort of databound function, I find it cleaner to use an inline C# IF statement for simple display changes such as these.

In the sample code below, we are making an anchor link. The data in the “Type” column from the database describes if the data is a URL or an email address.  Our inline IF statement has this structure:

If the Type equals “Website” then add nothing to the data.  Otherwise, the data is an email address, so make a “mailto:” link out if it.

<asp:Repeater id="rptAddress" runat="server">
<ItemTemplate>

<a href="<%# (DataBinder.Eval(Container.DataItem, "Type").ToString() == "Website") ?
 "" : "mailto:" %>   <%# DataBinder.Eval(Container.DataItem, "Address")%>" >
<%# DataBinder.Eval(Container.DataItem, "Address")%>
</a>
</ItemTemplate>
</asp:Repeater>
Just remember, the C# syntax for an inline If uses the question mark and a colon:
<% (condition) ? "true" : "false" %>

Before You Start Increasing the CommandTimeout Value…

by Allan Sieker

Just recently I had the opportunity to investigate a timeout error happening on an ASP.NET web application of a new client.   This web application is a very intense and complex business solution involving tiered hierarchies of entities for companies, resources, processes, and projects.  Definitely not your typical web site.

The source of the timeout was an embedded SQL query consisting of a SQL view joined to another table.  A SqlDataAdapter was being used to fill a DataSet from a SQL Server database.   Very straight forward on the surface, but then I noticed that the a CommandTimeout setting of 60 was being used.  Why would such a simple query be taking more than 60 seconds to execute?

My first was to set a breakpoint just before the execution of the query so I could see what the actual parameter values were.   My second step was to copy/paste the query into an open query tab on SQL Server Management Studio and alter the query to use literal values from the first step.   To my surprise, the query did take a bit of time to execute.  Time to drill down.

I scripted out the SQL view and found that it consisted of a 13 table join where 3 of the joins were to views with joins of their own.  And one of the returning columns was a function having several querys of its own – and a 3 table join.  Time to get another Diet Mt. Dew…

I now understood why the previous developer slapped in a 60 second timeout.  Sure, I could have increased the timeout to 120 seconds and it would probably have solved the problem.  But would it be the professional way of solving the problem?  I thought not.  Besides, I didn’t want to put the client through a compile/rebuild/release cycle for just changing one line of code.  Another important thing to consider is that the users will have to endure this long query several times a day.  I couldn’t take that road.

This query and all of its branches was way too complicated to digest quickly, so I started breaking each branch down and checking to see if the queries were taking advantage of indexes. 

As it turned out, in about an hour I found four places where an index could be added and the yield of that effort took the 60+ second query down to 3 seconds.  The users were  ecstatic.

So the moral of my story is this:  Please do not blindly increase the timeout values without an honest attempt to tweak the query.    The time you take this one time will benefit your users every day.

Could not load file or assembly ‘apache fop.net’ or one of its dependencies

By George Zheng

When we deploy one of our web sites to a 64 bit web server, we get following error: “Could not load file or assembly ‘apachefop.net’ or one of its dependencies. An attempt was made to load a program with an incorrect format.

This web site is using fop.net to generate the PDF for printing. After investigation, we realize the fop.net can’t run on IIS with 64-bit version of ASP.NET.

IIS 6.0 on a 64-bit hardware supports both the 32-bit version of ASP.NET and the 64-bit version of ASP.NET. However IIS 6.0 does not support running both modes at the same time on a 64-bit version of Windows.  Here are the steps to run the 32-bit version of ASP.NET 2.0 on IIS:

  1. Click Start, click Run, type cmd, and then click OK.
  2. Type the following command to enable the 32-bit mode:
    cscript %SYSTEMDRIVE%\inetpub\adminscripts\adsutil.vbs SET W3SVC/AppPools/Enable32bitAppOnWin64 1
  3. Type the following command to install the version of ASP.NET 2.0 (32-bit) and to install the script maps at the IIS root and under:
    %SYSTEMROOT%\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe -i
  4. Make sure that the status of ASP.NET version 2.0.50727 (32-bit) is set to Allowed in the Web service extension list in Internet Information Services Manager.

Useful Links

How to switch between the 32-bit versions of ASP.NET 1.1 and the 64-bit version of ASP.NET 2.0 on a 64-bit version of Windows

Process XML in C#.net

By George Zheng

XML has been used in a lot of places for transferring data because of its platform independence and its simple, text-based, self-describing format. It is a common requirement for preparing and consuming XML data. Microsoft provides several convenience tools to help developers deal with XML under .NET platform.

  1. System.Xml.XmlTextReader/System.Xml.XmlTextWriter
  2. System.Xml.Serialization.XMLSerializer
  3. System.Xml.Linq

XmlTextReader/XmlTextWriter
XmlDocument objects are easy to use for navigating the DOM copying, modifying, or inserting nodes. However, they can also use a large amount of memory to store the entire DOM of a large XML string in memory. Because of this, the XmlTextReader and XmlTextWriter classes can be used for stream based manipulation of an XML string.
Let’s say you have an XML as:
<?xml version=1.0 encoding=utf-8 ?>
<user id=1>
<firstname>George</firstname>
<lastname>Zheng</lastname>
</user>

Following code will read it into a user object.

User user = new User();
XmlTextReader reader = new XmlTextReader(“user.xml”);

while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name == “user”)
{
user.Id = Convert.ToInt32(reader.GetAttribute(“id”));
}
if (reader.Name == “firstname”)
{
user.Firstname = reader.ReadElementContentAsString();
}
if (reader.Name == “lastname”)
{
user.Lastname = reader.ReadElementContentAsString();
}
}
}
With XmlTextWriter, following code will generate XML string:

StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
XmlTextWriter writer = new XmlTextWriter(sw);

writer.WriteStartElement(“user”);
writer.WriteAttributeString(“id”, user.Id.ToString());
writer.WriteElementString(“firstname”, user.Firstname);
writer.WriteElementString(“lastname”, user.Lastname);
writer.WriteEndElement();

return sb.ToString();

XMLSerializer

If you don’t want to deal with the XML element by element, .NET provides an alternative approach to convert object instance to XML and read the data back – XML Serialization. This is more convenience for a complex object.

TextReader tr = new StringReader(xml);
XmlSerializer s = new XmlSerializer(typeof(User));
User user = (User)s.Deserialize(tr);

Following code can be used to serialize the object:

XmlSerializer s = new XmlSerializer(typeof(User));
TextWriter writer = new StringWriter();
s.Serialize(writer, user);
writer.Flush();
string xml = writer.ToString();
writer.Close();

The object can be a complex object like:

public class User
{
[XmlAttribute("id")]
public int Id { get; set; }

[XmlElement("firstname")]
public string Firstname { get; set; }
[XmlElement("lastname")]
public string Lastname { get; set; }

[XmlArray("projects")]
[XmlArrayItem("project", typeof(Project))]
public List<Project> Projects { get; set; }
}

public class Project
{
[XmlAttribute("id")]
public int Id { get; set; }
[XmlElement("name")]
public string Name { get; set; }
}

As you can see, serializer attributes can be used to specify how the object’s properties map to XML entities. I’m not going to discuss these attributes here. Details can be found from Useful Links below.

LINQ

In some case, convert the whole XML may not be a good idea. Linq provides a convenience approach to query a complex XML.

Here is the example account feed response from Google Analytics API

<?xml version=1.0 encoding=UTF-8?>
<feed xmlns=http://www.w3.org/2005/Atom xmlns:openSearch=http://a9.com/-/spec/opensearchrss/1.0/ xmlns:dxp=http://schemas.google.com/analytics/2009>
<id>http://www.google.com/analytics/feeds/accounts/abc@test.com</id>
<updated>2009-06-25T03:55:22.000-07:00</updated>
<title type=text>Profile list for abc@test.com</title>
<link rel=self type=application/atom+xml href=http://www.google.com/analytics/feeds/accounts/default/>
<author>
<name>Google Analytics</name>
</author>
<generator version=1.0>Google Analytics</generator>
<openSearch:totalResults>12</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>12</openSearch:itemsPerPage>
<entry>
<id>http://www.google.com/analytics/feeds/accounts/ga:1174</id>
<updated>2009-06-25T03:55:22.000-07:00</updated>
<title type=text>www.googlestore.com</title>
<link rel=alternate type=text/html href=http://www.google.com/analytics/>
<dxp:tableId>ga:1174</dxp:tableId>
<dxp:property name=ga:accountId value=30481/>
<dxp:property name=ga:accountName value=Google Store/>
<dxp:property name=ga:profileId value=1174/>
<dxp:property name=ga:webPropertyId value=UA-30481-1/>
<dxp:property name=ga:currency value=USD/>
<dxp:property name=ga:timezone value=America/Los_Angeles/>
</entry>
<entry>
<id>http://www.google.com/analytics/feeds/accounts/ga:6284812</id>
<updated>2009-01-06T17:39:33.000-08:00</updated>
<title type=text>www.googlestore.com (Test Team)</title>
<link rel=alternate type=text/html href=http://www.google.com/analytics/>
<dxp:tableId>ga:6284812</dxp:tableId>
<dxp:property name=ga:accountId value=30481/>
<dxp:property name=ga:accountName value=Google Store/>
<dxp:property name=ga:profileId value=6284812/>
<dxp:property name=ga:webPropertyId value=UA-30481-1/>
<dxp:property name=ga:currency value=USD/>
<dxp:property name=ga:timezone value=America/Los_Angeles/>
</entry>
</feed>

With following code, you will get ID, Title and AccountName for each entry.

XDocument doc = XDocument.Parse(xml);
XNamespace dxpSpace = doc.Root.GetNamespaceOfPrefix(“dxp”);
XNamespace defaultSpace = doc.Root.GetDefaultNamespace();

IEnumerable<Account> entries =
from en in doc.Root.Descendants(defaultSpace + “entry”)
select new Account
{
ID = en.Element(defaultSpace + “id”).Value,
Title = en.Element(defaultSpace + “title”).Value,
AccountName =
en.Elements(dxpSpace + “property”).Where(
xe => xe.Attribute(“name”).Value == “ga:accountName”).First().
Attribute(“value”).Value
};

Useful Links

Insert XML Nodes Using XmlTextReader and XmlTextWriter
Using the XmlSerializer Attributes
Added Google Analytics Reader for .NET


download Download source code

Overhauling the Security Framework of our Content Management System

The past few weeks have been challenging in an interesting and fun way. We are on track to release version 2.6 of our Content Management System and finally decided it was time to overhaul our security framework. We are moving from a simple role based architecture to a framework that supports the following:

  • Roles
  • Permissions
  • Ability to assign Permissions to Roles
  • Ability to assign Supplemental Permissions to Users external of Roles
  • Ability to assign Roles to Users
  • Ability to grant/deny access down to the control level
  • Reverse capability of locking individual elements down by assigning Roles/Permissions to user created elements

In working on this there have been LOTS of questions as you can imagine. Here are a few resources I found useful and/or interesting while working on this implementation.

  • I am a regular reader of the .NET Security Blog and found the CAS and CLR discussions pertaining to .NET 4 interesting.
  • I found a nice way to get a flattened hierarchy of controls on a page over at the Vault of Thoughts. We implemented something similar but with a few additions.
  • Here is a good read on general role based security.

Derek Bemis

Coding Standards and SQL Mistakes

 By Allan Sieker

If you want to learn something from one of the best, please check out Clint Edmonson’s web site where you can download free coding standards for both VB and C#.  I worked with Clint at AB and I must say that he is one sharp guy.  I am sure he raised Microsoft’s collective talent a few notches when he joined them.  Be sure to look him up at the St. Louis “Day of .NET” Conference in August.

Shifting gears from .NET to SQL….

I ran across “Ten Common Database Design Mistakes” and found it to be most interesting.  I’m sure you will too.

Saving Web Contact Info to a Downloadable Spreadsheet

Usually a website owner has the data from their contact forms emailed to them.  But sometimes it can be useful for the site to automatically add that info to an ongoing list of contacts.  One way that most people like to be able to see and sort such data is in a spreadsheet.  It is quite easy to make a “fake” spreadsheet using an html table that Excel will open like any other .xls file.

 

Here are the basic steps:

  • Create and save a “spreadsheet” which defines the header row
  • Upon each contact form submittal, write an html row to that file 

Read the rest of this entry »