Tag Archives: ASP.NET - Page 2

Representing a SQL Server Tuple with an Xml Field

In my last post, I gave an example of retrieving XML data from an SQL Server relation. So how can we now present that data? As the saying goes, there is more than one way to skin a cat. I’m going to go through two solutions that I came up with off the top of my head.

The primary key of each row correlates to a fragment of XML data. But such a fragment is not very human readable, unless you remove the XML tags. One very easy way of presenting this would be through using an embedded GridView:

note: I did not put any time into presentation. I just wanted to concentrate on functionality here.

To achieve this, I cheated a little bit by binding the XML field to a Label, which I included in the same template column as the embedded GridView (see line 7 for the embedded GridView and line 16 for the Label):

        
            
                
                
                
                    
                        
                                    
                                    
                                    
                                    
                                    
                                    
                            
                        
                        
                    
                                        
            
            
            
            
            
        

Then, in the code behind, I created a DataSet by reading the Text property of the Label into a StringReader. I was able to use that to read in the XML to the DataSet. There are a couple of other small nuances I included which you will see in the following code:

        protected void GridViewEmployees_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                Label LabelREP = e.Row.FindControl("LabelREP") as Label;
                DataSet dsXml = new DataSet("REP");

                String firstLine = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
                String frag = Server.HtmlDecode(firstLine + LabelREP.Text);
                StringReader readString = new StringReader(frag);

                dsXml.ReadXml(readString);

                GridView GridViewEmployeeDetails = e.Row.FindControl("GridViewEmployeeDetails") as GridView;
                GridViewEmployeeDetails.DataSource = dsXml;
                GridViewEmployeeDetails.DataBind();

                LabelREP.Text = string.Empty;
            }
        }

It is possible to achieve the same thing without using a Label, but I’ll leave that for another day as it is a bit more complicated.

The other approach that I thought of is more interesting than that first approach. With this approach, I am going to use the LINQ object XElement to work with the XML. And then I will dynamically (and manually) build the contents of the “Sales REP” column using a Placeholder, which will replace the embedded GridView that I used in the first example.

So, looking at the template field in the GridView. I now have a placeholder, along with the Label which will initially store the contents of the Xml field:

                <asp:TemplateField HeaderText="Sales Rep">
                    <ItemTemplate>
                        <asp:PlaceHolder ID="PlaceHolderXml" runat="server"></asp:PlaceHolder>
                        <asp:Label ID="LabelREP" runat="server" Text='<%# ((DataRowView)Container.DataItem)["SalesRep"] %>' />
                    </ItemTemplate>
                </asp:TemplateField>

I have included the whole code for the RowDataBound event handler. However, a lot of that code is guarding against situations like when the XML message is empty. The core part to see what I am doing is from line 43. That shows how I parsed the XML fragment into an XElement object and then traversed each of its child nodes to extract the relevant information for display:

        protected void GridViewEmployees_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {

                //  Populate PlaceHolder control dynamically with data from the relevent Xml msg.
                PlaceHolder xmlOutput = e.Row.FindControl("PlaceHolderXml") as PlaceHolder;
                Label LabelREP = e.Row.FindControl("LabelREP") as Label;
                string xmlMsg = LabelREP.Text;

                switch (xmlMsg == string.Empty)
                {
                    // if there is no Xml 'Details' field, display that to page;
                    case true:
                        {
                            Literal promptText = new Literal();
                            promptText.Mode = LiteralMode.PassThrough;
                            promptText.Text = "<div class=\"msgContent\" ><div class=\"msgContentHeader\">Sales Rep Details</div> " +
                                "<p> There was no XML field for this blocked item. </p></div><br />";
                            xmlOutput.Controls.Add(promptText);

                            break;
                        }
                    default:
                        {
                            //  Check for empty msg elements
                            switch (xmlMsg.CompareTo("<messages />") == 0)
                            {
                                case true:
                                    {
                                        //  If the msg is empty, write that out to the Page.
                                        Literal promptText = new Literal();
                                        promptText.Mode = LiteralMode.PassThrough;
                                        promptText.Text = "<div class=\"msgContent\" ><div class=\"msgContentHeader\">Sales Rep Details</div> " +
                                            "<p> The xml fragment was an empty element. </p></div><br />";
                                        xmlOutput.Controls.Add(promptText);

                                        break;
                                    }
                                default:
                                    {
                                        //  Get the xml fragment and parse it into an XElement variable 
                                        XElement dbXmlField = XElement.Parse(xmlMsg);

                                        //  Popluate the Placeholder control with content.
                                        Literal wrapperDiv = new Literal();
                                        wrapperDiv.Mode = LiteralMode.PassThrough;
                                        wrapperDiv.Text = "<div class=\"msgContent\" ><div class=\"msgContentHeader\">Sales Rep Details</div>";
                                        xmlOutput.Controls.Add(wrapperDiv);

                                        foreach (XElement el in dbXmlField.Descendants())
                                        {
                                            //  Traverse the descendant nodes, and add them to the 
                                            //  placeholder text where the node itself has only 1 child
                                            //  node i.e. the text node containing the data that we are 
                                            //  interested in.
                                            if (el.DescendantNodes().Count() > 1)
                                                continue;
                                            Literal promptText = new Literal();
                                            promptText.Mode = LiteralMode.PassThrough;
                                            promptText.Text = string.Concat(string.Concat("<div class=\"msgLabel\">", el.Name, ": "), "</div>");
                                            xmlOutput.Controls.Add(promptText);

                                            Literal valueText = new Literal();
                                            valueText.Mode = LiteralMode.PassThrough;
                                            valueText.Text = string.Concat("<div class=\"msgValue\">", el.Value, "</div>");
                                            xmlOutput.Controls.Add(valueText);
                                        }

                                        //  Close out the content being built in the Placeholder.
                                        Literal wrapperDivClose = new Literal();
                                        wrapperDivClose.Mode = LiteralMode.PassThrough;
                                        wrapperDivClose.Text = "</div><br />";
                                        xmlOutput.Controls.Add(wrapperDivClose);
                                        break;
                                    }
                            }
                            break;
                        }
                }
                LabelREP.Visible = false;
            }
        }

And the fruit of my efforts looks something like the following (showing two rows from the result set):

Using LINQ with Dynamic Placeholder

Machine Key Generator

I have been playing around with the security classes lately, and decided to make a useful little utility.

ASP.NET programmers will be familiar with Machine Keys and the machineKey element in the Web.config file. This is used for things like hashing passwords. The validationKey attribute can be from 40 – 128 characters long. The decryptionKey can be either 16 or 48 characters long. In Matthew MacDonald’s excellent book “Pro ASP.NET 2.0 in C# 2005”, he included a nice snippet of code that creates random validation and decryption keys for the machineKey element. I put a GUI front-end on that code, and voila, a nice handy utility that creates validation and decryption keys for the machineKey element:

Screen Shot of app

Machine Key Generator - Winforms App

If you would like to download it, click here. Just chuck the exe assembly in any directory you want for a nice, easy, no-touch installation. Once you generate the keys, just copy and paste them as required for your needs. Then click clear to clear the fields.

Accessing Membership Details in web.config Programmatically

The default membership provider from the machine.config file looks like this (except I substituted myApp in for / as the applicationName):

<add name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="LocalSqlServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="true"
applicationName="myApp"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="7"
minRequiredNonalphanumericCharacters="1"
passwordAttemptWindow="10"
passwordStrengthRegularExpression="">

I wanted to read the applicationName attribute into a variable in one of my code-behind files. My first attempt at this worked, but was not so good. The code:

 // using the System.Configuration and System.Xml namespaces

Configuration config = WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
MembershipSection mem = config.GetSection("system.web/membership") as MembershipSection;
SectionInformation sectionInfo = mem.SectionInformation;
XmlDocument memdoc = new XmlDocument();
memdoc.LoadXml(sectionInfo.GetRawXml());
XmlElement el = memdoc.GetElementsByTagName("add")[0] as XmlElement;
XmlAttribute appNameAttrib = el.Attributes["applicationName"];
appName.Text = appNameAttrib.Value;

The MSDN documentation on the GetRawXml method of the SectionInformation class states that: “This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.”
I immediately concluded that I had not gone about this the correct way; the .NET way. So, I revisited the MembershipSection class to see if I’d missed something. And sure enough, it leapt out at me…the Providers property! The revised code is:


Configuration config = WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
MembershipSection memSection = config.GetSection("system.web/membership") as MembershipSection;
ProviderSettingsCollection membershipProviders = memSection.Providers;
ProviderSettings memSettings = membershipProviders["AspNetSqlMembershipProvider"];
appName.Text = memSettings.Parameters["applicationName"];

The Parameters collection has the NameValueCollection type, the values of which are accessible by keys. So, the key “applicationName” returns the value ascribed to that key i.e. “myApp”.

The other thing I noted was the performance increase. It’s much faster doing things the way they were intended!

Find the CheckBox that Fired the AutoPostBack in a CheckBoxList

Scenario: you have a CheckBoxList and you have set AutoPostBack to true. You go to handle the SelectIndexChanged event for that CheckBoxList and realise that there is no “built-in” way to figure out which CheckBox was selected/deselected by the user. That is, the good old EventArgs has not been overridden such that an EventArgs object has a property which identifies the relevant CheckBox.

There is actually a very easy way to address this. As you can see from the following screen capture, on a PostBack, the UniqueID of the CheckBoxList is suffixed with a dollar sign and then a number:


The number is the index of the relevant CheckBox in the Items collection of the CheckBoxList. And so, as an example, here is some code which handles an AutoPostBack event by a CheckboxList:

protected void CheckBoxListRoles_SelectedIndexChanged(object sender, EventArgs e)
{
	int itemIdx = GetPostBackItemIndex(this);
	ListItem itemPosted = CheckBoxListRoles.Items[itemIdx];

	switch(itemPosted.Selected)
	{
		case true: Roles.AddUserToRole(userName, itemPosted.Value); break;
		default: Roles.RemoveUserFromRole(userName, itemPosted.Value); break;
	}

}

public int GetPostBackItemIndex(Page page)
{
	string ctrlname = page.Request.Params.Get("__EVENTTARGET");
	return int.Parse(ctrlname.Substring(ctrlname.Length - 1, 1));
}

A Common ObjectDataSource Error – Update

In my previous post, I outlined a scenario whereby I could not figure out how an ObjectDataSource could be passing a parameter to a method in the business layer, where there was no such parameter in the declarative  markup for the ObjectDataSource. I have since found the answer in this article.

In a nutshell, if a GridView contains a non-read-only field in its edit template, then even if there is no corresponding parameter for that field  in the ObjectDataSource, the GridView will add one for it to the parameters collection of the ObjectDataSource. This diagram tells the story.

Mystery solved!