Red Gate Reflector


Well, it looks like one of the best .NET utilities out there has changed hands. Lutz Roeder, the original creator and developer of Reflector has agreed with Red Gate software (they of SQL Compare and SQL Toolkit fame) to let them continue development of it from now one. An interview with both Lutz and James Shore of Red Gate outlining the deal and what Red Gate hopes to do with Reflector can be found here on simple talk.

Red gate are quite clear that they “will continue to offer the tool for free to the community.” but don’t categorically state that they may not offer a corporate, paid-for version later down the line. As long as there’s a free version I’m not too fussed. Reflector’s new home is http://reflector.red-gate.com/

Technorati Tags: ,

author: Dan Maharry | posted @ Thursday, August 21, 2008 9:20 AM | Feedback (0)

RadioButton Groups, Table Rows And NamingContainers


(In which AJAX-enhanced CheckBoxes become more useful than RadioButtons but inheritance saves the day, and a simple RadioButton-derived control establishes the purpose of a control's naming container)

The RadioButtonList. Very handy for inviting users to select just one item from a list although because of the screen real estate it takes up, used less and less in favour of the DropDownList. In plain HTML, the browser knows to enforce the unique selected value amongst a list of radio buttons because they all have the same value for their name attributes.

<input type="radio" name="RadioButtonList1" value="1" />
   <label for="RadioButtonList1_0">Orlando Gee</label><br />
<input type="radio" name="RadioButtonList1" value="2" />
   <label for="RadioButtonList1_1">Keith Harris</label><br />
<input type="radio" name="RadioButtonList1" value="3" />
   <label for="RadioButtonList1_2">Donna Carreras</label> 

And in ASP.NET, the RadioButtonList control lets you set the name attribute for each radio button in the list using the control's ID property. 

<asp:RadioButtonList ID="RadioButtonList1" runat="server" /> 

Alternately, you can group individual RadioButton controls together using their GroupName property.

<asp:RadioButton runat="server" ID="rbSelectUnique" GroupName="selectList" /> 

And the effect is still the same; each HTML radio button (with the same GroupName) has its name attribute set to the same value.

The Problem

The main problem with RadioButtons though is that by default you cannot span a group of them across table rows. Let's take an example, I have a sortable, pageable GridView of customers which I like, but I want to make sure the user can select only one of those customers as we go down the list. The obvious (in my mind anyway) solution is to add a template field to the GridView containing a RadioButton for each row and make the table generated by a GridView into a big radio button list by setting their GroupName attribute the same.

<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" DataKeyNames="customerid"
    DataSourceID="SqlDataSource1">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:RadioButton runat="server" ID="rbSelectUnique" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="name" HeaderText="name" ReadOnly="True" SortExpression="name" />
        <asp:BoundField DataField="customerid" HeaderText="customerid" InsertVisible="False"
            ReadOnly="True" SortExpression="customerid" />
    </Columns>
</asp:GridView> 

Perfectly sensible except that because each RadioButton is dynamically added to a table row by the GridView, ASP.NET generates the name attribute of each radio button by appending the value you give it in GroupName to the internal name it generates for it when rendering the GridView. Hence we get this code for the <asp:GridView> above.

<table cellspacing="0" rules="all" border="1" id="GridView2" style="border-collapse:collapse;">
   <tr>
      <th scope="col">&nbsp;</th><th scope="col">name</th><th scope="col">customerid</th>
   </tr>
   <tr>
      <td>
         <input id="GridView2_ctl02_rbSelectUnique" type="radio" 
            name="GridView2$ctl02$rbSelectUnique" value="rbSelectUnique" />
      </td>
      <td>Orlando Gee</td><td>1</td>
   </tr>
   <tr>
      <td>
         <input id="GridView2_ctl03_rbSelectUnique" type="radio" 
            name="GridView2$ctl03$rbSelectUnique" value="rbSelectUnique" />
      </td><td>Keith Harris</td><td>2</td>
   </tr>
   <tr>
      <td>
         <input id="GridView2_ctl04_rbSelectUnique" type="radio" 
            name="GridView2$ctl04$rbSelectUnique" value="rbSelectUnique" />
      </td><td>Donna Carreras</td><td>3</td>
   </tr>
</table> 

As you can see, the radio button's name value takes the form GridView2$ctlXX$rbSelectUnique, where XX changes for each row and renders the list of the radio buttons useless. You can select them all at the same time if you wish. And it turns out that this issue is by design, according to the bug report on MS Connect.

Each row in a GridView is its own naming container so the controls' names don't collide. However, RadioButtons do not support spanning multiple naming containers and having their groupname attribute still work correctly. We will be looking at solving the RadioButton GroupName/multiple naming container issue in future versions of the product.

Or, in English, asp.net generates a unique NamingContainer for each cell in a table generated by a GridView and then uses that as a basis for all control IDs within the cell. It seems to be the only way to keep track of which events occurred where when the page posts back.  That was written in April 2005 and it hasn't been fixed in VS2008, so it's time to look at workarounds.

The Solution

If you're using AJAX already on the page, or are prepared to use it on the page, one way to get a radio button list of sorts into your GridView is to use checkboxes and a MutuallyExclusiveCheckBoxExtender in each TemplateField.

<ItemTemplate>
    <asp:CheckBox runat="server" ID="chkSelect" />
    <cc1:MutuallyExclusiveCheckBoxExtender ID="mecbe1" runat="server" 
        Key="chkSelectGroup" TargetControlID="chkSelect" />
</ItemTemplate> 

Now this works fine, but if you're trying to keep your pages lean, the additional script for each MECBE added to the page might not be so good. From a UI point of view, you might also object to having checkboxes work like radio buttons and so might your users.

Fortunately, a quick check in Visual Studio’s Object Browser reveals that the RadioButton derives from a CheckBox, so you can easily swap out one for the other. And behold your RadioButtonList is back, spanning table rows.

<ItemTemplate>
    <asp:RadioButton runat="server" ID="rbSelect" />
    <cc1:MutuallyExclusiveCheckBoxExtender ID="mecbe1" runat="server" 
        Key="rbSelectGroup" TargetControlID="rbSelect" />
</ItemTemplate> 

Not The Solution, But Good To Know

If you’re not willing to use AJAX, the issue is always with the naming container clashing with the radio button list’s name property. Perhaps an obvious solution is to use your own RadioButton Control which overrides the name attribute for the button when it is rendered. Something like this.

using System;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace TryOuts
{
    public class MyRadioButton : RadioButton
    {
        public string GroupName2
        {
            get  {
                String s = (String)ViewState["GroupName2"];
                return ((s == null) ? "" : s); }
            set { ViewState["GroupName2"] = value; }
        }

        public bool Checked
        {
            get { return base.Checked; }
            set { base.Checked = value; }
        }

        protected override void Render(HtmlTextWriter writer)
        {
            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            HtmlTextWriter hw = new HtmlTextWriter(sw);

            base.Render(hw);

            if (String.IsNullOrEmpty(GroupName2))
            {
                writer.Write(sb.ToString());
            }
            else
            {
                string html = Regex.Replace(
                   sb.ToString(), @"name=""[\w\$]+""", 
                   String.Format("name=\"{0}\"", GroupName2));
                writer.Write(html);
            }
        }
    }
}

Now this approach works to a degree. Replacing a standard RadioButton with this control does indeed override the control’s Name property and the RadioButtons all work within the table as required.

<cc2:MyRadioButton runat="server" ID="mrbTest" GroupName2="test" />

However, do anything simple such as select a row and post back the page (by clicking a button) and you’ll see that you can’t determine which radio button has been clicked.

foreach (GridViewRow row in GridView.Rows)
{
   RadioButton rb = (RadioButton)row.FindControl("mrbTest");
   if (rb.Checked)
   {
      lblSelected.Text = row.Cells[1].Text;
   }
}

And that’s because the NamingContainers for the cells containing the RadioButtons in the table generated in the GridView can no longer identify the RadioButtons in the cells in the table. Because our custom control has overridden the name attribute for each RadioButton to “test”, ASP.NET can’t find any controls called GridView2$ctlXX$mrbTest which is what it expects them to be called and why the naming container exists in the first place. Without the naming container producing these unique names in the way it does, we can’t do useful things like iterate over the rows in a GridView and why AJAX is the way forward when it comes to this particular problem.

Now strictly speaking, it should be possible to write a HttpModule that rewrites a RadioButton’s name attribute as it is sent to the browser so it works as part of a RadioButtonList and then rewrites the name attribute back as the page is posted back so the naming container appears to still be intact. But think about it, that’s a lot of effort for a small thing. Is the AJAX-free convenience worth the hassle?

author: Dan Maharry | posted @ Thursday, August 14, 2008 5:42 PM | Feedback (2)

Three Different Ways To Find The Currently Edited Row In A GridView


The GridView, and indeed all the other templated data bound controls, are lifeblood to an ASP.NET developer when it comes to creating websites. A little DataSource control here, a GridView there and even if it isn't exactly what you want, you've got a prototype page up and running in no time to start the real development with. Today's chores involved tweaking the contents of a GridView's EditItemTemplate so that the contents of one DropDownList (Clients) would change based on the contents of another (Roles) and should the Update button be clicked the values of both DropDownLists saved out to the database.

EditItemTemplate

The roles DropDownList always contained the same items, so it was bound to a DataSource control separate to the GridView itself and then its selected value bound to the current value for the row in the GridView. The RoleId is two-way bound to the SelectedValue of the DropDownList so clicking Update works correctly.

<EditItemTemplate>
   <asp:DropDownList runat="server" ID="ddlRolesForEditUserDialog" AutoPostBack="true"
      DataTextField="Name" DataValueField="RoleId" 
      DataSourceID="RolesDataSource" SelectedValue='<%# Bind("RoleId") %>'
      OnSelectedIndexChanged="ddlRolesForEditUserDialog_SelectedIndexChanged" />
</EditItemTemplate>

The clients DropDownList on the other hand, needed

  1. to be populated dynamically when the the GridView's row was switched to Edit mode
  2. to be populated dynamically when the choice in the roles List changed. 
  3. to be accessed when Update is clicked and the DataSource is making changes to the database.

As it turns out, each occasion used a slightly different way to find the row being edited and through that, using FindControl to access the ClientList.

In case 1, by the time a GridView's OnRowEditing event (raised when 'Edit' is clicked on the GridView) is handled, the EditItemTemplate for the row hasn't become accessible yet, so the client list can't be found. The option instead then is to handle the GridView's OnRowDataBound event at which point the EditItemTemplate has been created and the client list is accessible. The trick is to identify the row being edited is to use its RowState property. Every row on a GridView is rebound when Edit is clicked, so we can use the following.

protected void grdUsers_RowDataBound(object sender, GridViewRowEventArgs e)
{
   if ((e.Row.RowState == DataControlRowState.Edit) || 
       (e.Row.RowState == (DataControlRowState.Edit|DataControlRowState.Alternate)))
   {
      //Get the client dropdownlist
      DropDownList clientList = (DropDownList)e.Row.FindControl("ddlClients");

      //more stuff here
   }
}

In case 2, the EditItemTemplate already exists. Indeed, we're capturing an event on the roles List within a cell in the EditItemTemplate. One option to then find the client List in the cell next to it is to start with the object that raised the event, find the control that contains both it and the client List and then call FindControl.

protected void RulesList_SelectedIndexChanged(object sender, EventArgs e)
{
   //Get the roles dropdownlist
   DropDownList ddlRoles = (DropDownList)sender;

   //Get the client dropdownlist
   DropDownList clientList = 
      (DropDownList)ddlRoles.Parent.Parent.FindControl("ddlClients");

   // more stuff here
}

Or, as in Case 3, because we know the EditItemTemplate is visible, we can locate the row being edited through the GridView's EditIndex template. This returns -1 if no row is being edited or the index of a row in the GridView.

protected void usersDataSource_Updating(object sender, EventArgs e)
{
   GridViewRow row = grdUsers.Rows[grdUsers.EditIndex];
   DropDownList clientList = (DropDownList)row.FindControl("ddlClients");

   // more stuff here

}

Case 3 is the easiest way to find the currently edited row, though the other two are no less valid.

author: Dan Maharry | posted @ Tuesday, August 05, 2008 5:06 PM | Feedback (1)

Is this the truth of it?


Achieving perpetual mass for any community only takes place if the underlying purpose of the community is appealing to enough users that they decide to contribute some of their cognitive surplus to its furtherance.

More of that discussion here. Discuss

author: Dan Maharry | posted @ Monday, August 04, 2008 9:49 PM | Feedback (0)

Enabling SSL Certificates on any Given HTTP Port


Following on from my travails with HttpListeners not working as a non-admin user, it turned out that the Cardspace samples (download them here if you're interested) had one more sticking point up their sleeve before everything worked. The main example demonstrates how a simple Security Token Service is used to verify the managed card a user wants to send to a site. However, the service is accessible only through the HTTPS protocol on port 7001 and Cardspace was unable to access it. A little digging revealed that the setup scripts for the sample tried and failed to build a copy of httpcfg, a utility found on Windows Server 2003. I didn't have the necessary files to build httpcfg successfully, bit it turns out that the netsh utility that comes with Vista and helped me out previously could also help me out here to.

The command required to add the certificate to a port with netsh is

netsh http add sslcert ipport=0.0.0.0:7001 certhash=thumbprint appid=arbitrary_guid

And the corresponding one to remove it is

netsh http delete sslcert ipport=0.0.0.0:7001

The add command needs two pieces of information besides the port number.

  • appid is an arbitrary guid to represent the application accessing the port.
  • certhash requires the certificate's thumbprint to identify it to netsh

If you've never needed to find a certificate's thumbprint before, hit Win+R and run certmgr.msc from the prompt to open the Vista Certificates MMC Viewer. (If the certificate is stored in the Local Machine certificate store rather than your own accounts store, you'll need to run certmgr.msc as an admin). The certificate should be stored in Personal\Certificates. When you find it, double-click it and select the Details tab. If you scroll the view down, you'll see Thumbprint towards the bottom of the dialog.

CertThumbprint

You'll need to copy all 20 pairs of hex digits and remove all the spaces. Given the example above then, you can add the certificate shown to port 7001 using this command.

netsh http add sslcert ipport=0.0.0.0:7001 certhash=d47de657fa4902555902cb7f0edd2ba9b05debb8 appid={C61EC2E2-BC18-4522-903B-F44A56299787}

And then you can check that all's well with this command

netsh http show sslcert

This will show you all the certificates bound to a port on your machine. netsh - the network admin's swiss army knife

author: Dan Maharry | posted @ Friday, August 01, 2008 11:45 AM | Feedback (0)

Enabling HttpListeners for Non-Admins


I've just started working through the Cardspace samples to learn some more about online identity layers (download them here if you're interested). The first example demonstrates a web service running on http://localhost:4123 requesting a certificate from a client. However, as a non-admin, I get the following error from Visual Studio when running the sample.

AddressAccessDeniedException was unhandled
HTTP could not register URL http://+:4123/HelloService/. Your process does not have access rights to this namespace

Coincidentally, the error is totally analogous to a similar problem I've had while trying out the CR_Documentor plug-in that Travis Illig has created and it's Travis and co that figured out both problems in hindsight. To paraphrase,

By default, only local admins have permission to listen to http prefixes. Other accounts require explicit grant using either httpcfg.exe for WinXp/2003 users or netsh for Vista/2008 users. This isn't a .net permission, it goes right to the windows urlacl level.

Just as this cardspace demo relies on port 4123 being accessible, so too does CR_Documentor rely on port 11235. The full commands to call either can be found here, explaining the various options.

Thus, to solve my cardspace problem, I needed to open a command prompt as an admin and run the following command to match the URL given in the error dialog.

netsh http add urlacl url=http://+:4123/ user=cweb\dan

And hey presto, I can continue debugging my web services as a standard user.

Thanks again to Travis and the CR_Documentor guys for figuring this one out and explaining it here. I'm just passing on the info

author: Dan Maharry | posted @ Monday, July 28, 2008 1:50 PM | Feedback (0)

Tutorials


Note to self, there are some good tutorials and 101s out there which should help a lot in a few areas. Make time to get through

I'm looking for a good one on Ruby and on Javascript as well. Anyone got anything good? The new Wrox First wiki on Javascript frameworks looks good, but it does cost.

author: Dan Maharry | posted @ Friday, July 25, 2008 9:17 AM | Feedback (2)

Assert.AreValueEqual


One of the interesting aspects of backfilling documentation is uncovering methods few seem to have discovered. Case in point, the Assert.AreValueEqual method in MbUnit v2. Rather than the straightforward AreEqual methods to compare the values of a property in an object or the AreSame methods to compare whether two objects are actually the identical object or of the same type, AreValueEqual verifies that two objects, expected and actual,

  • both have a property described a PropertyInfo object,
  • that the property is not null,
  • and that the value of the property in both objects is equal.

It's a bit of a black box tester then and works across class hierarchies.

AreValueEqual takes three parameters and an optional fourth.

  • The PropertyInfo object indicating the property to be tested
  • The object containing the expected value of the property
  • The object containing the actual value of the property
  • The index of the value to compare in the property if it is an indexed property

Let's take a few examples to demonstrate its various pass and fail scenarios. First, some boilerplate. You'll need to include System.Reflection for the PropertyInfo class and MbUnit.Framework for the Assert class.

using System;
using System.Reflection;
using MbUnit.Framework;

namespace MbUnitAssertDocs
{
   [TestFixture]
   public class AreValueEqualTests
   {

 

The first test passes as it compares the Length property of two string arrays both containing four strings. Only the Lenght property is being tested here, so whether the strings are equal is irrelevant

      //This test passes
      [Test]
      public void AreValueEqual_SameValues()
      {
         // Create two arrays
         String[] a = new String[4] { "this", "is", "a", "test" };
         String[] b = new String[4] { "this", "is", "a", "camel" };
      
         // Generate the PropertyInfo object for an array's length
         PropertyInfo pi = typeof(Array).GetProperty("Length");
         Assert.AreValueEqual(pi, a, b);
      }

 

The next test also passes and demonstrates using the optional fourth parameter to test the value of an indexed property. In this case, we're treating a String object as an array of Char and testing the fourth character.

      //This test passes
      [Test]
      public void AreValueEqual_SameValuesUsingIndices()
      {
         // Create two strings
         String a = "this is a test";
         String b = "this is a camel";

         // Generate the PropertyInfo object for the string as a Char array 
         PropertyInfo pi = typeof(String).GetProperty("Chars");

         // Test the fourth letter
         Assert.AreValueEqual(pi, a, b, new Object[] {4});
      }

 

If one of the objects being tested is null, the test fails with an AssertionException.

      // This test fails with an AssertionException
      [Test]
      public void AreValueEqual_OneValueIsNull()
      {
         // Create two arrays
         String[] a = new String[4] { "this", "is", "a", "test" };
      
         // Generate the PropertyInfo object for an array's length
         PropertyInfo pi = typeof(Array).GetProperty("Length");
      
         Assert.AreValueEqual(pi, a, null);
      }

 

If one of the objects does not have the property specified by the PropertyInfo object, the test also fails with an AssertionException. This next test fails because the String reference class has a Chars property while the string value type does not. (One capitalised letter makes all the difference)

      //This test fails with an AssertionException
      [Test]
      public void AreValueEqual_PropertyNotPresentInOneObject()
      {
         // Create two arrays
         String[] a = new String[4] { "this", "is", "a", "test" };
         string[] b = new string[4] { "this", "is", "a", "camel" };
      
         // Generate the PropertyInfo object for an array's length
         PropertyInfo pi = typeof(Array).GetProperty("Chars");
      
         Assert.AreValueEqual(pi, a, b, new Object[] {4});
      }

 

Finally, if the value of the property shared by the two objects does not have the same value, the test fails with a NotEqualAssertionException.

      //This test fails with a NotEqualAssertionException
      [Test]
      public void AreValueEqual_DifferentValues()
      {
         // Create two strings
         String a = "this is a test";
         String b = "this is a camel";
      
         // Generate the PropertyInfo object for the string as a Char array 
         PropertyInfo pi = typeof(String).GetProperty("Chars");
      
         // Test the tenth letter
         Assert.AreValueEqual(pi, a, b, new Object[] { 10 });
      }
   }
}

 

Hopefully, this gives you a few ideas on how you might use Assert.AreValueEqual. MbUnit itself uses it in our DataAssert class, the source for which you can see here. There's also a discussion on the MbUnit-User forum discussing this method and whether or not it should make it into MbUnit v3. If you'd like to see it stay, or would like to propose an alternative, reply to the thread.

Technorati Tags: ,

author: Dan Maharry | posted @ Monday, July 21, 2008 10:13 PM | Feedback (1)

Learning that WAPs on IIS are for Admins Only The Hard Way


(Or rather "How a fixed bug and a rubbish COMException error dialog ended up making me take four months to learn that Web Application Projects running on IIS rather than Cassini can only be edited in VS when running with Administrative privileges" but that seems slightly long for a blog title post)

I've been happily running VS2008 as a standard user since it came out. And with TestDriven.NET and VisualSVN, I'm motoring along quite well. Yesterday, I decided to upgrade VisualSVN to the latest version v1.5.1. And then bang!! I loaded up my current work and got this dialog.

CropperCapture[1]

And my Web Application Projects have failed to load in VS2008. After mild panic has finished setting in, I try regressing to my previous version v1.3.2 and the project loads fine. Which is mildly inconvenient as v1.3.2 doesn't work with TortoiseSVN 1.5 which is something I also updated on my machine.

Looking on the intarwub, it's mildly amusing to see that Google's best match to this situation as I search for it is a forum thread that I started on VisualSVN's forums when I got the same error trying out v1.4. However, one response in there since I last read it suggests removing the <FlavorProperties> element from the wap's .csproj file. Which I do and it works. But having loaded the project back in again and reset it back up to run on IIS, the dialog appears again.

A bit more googling for Web Application Projects and COMExceptions yields this page from Martin Kulov bemoaning the same useless dialog but with the following golden nugget of information.

In order to load the Web Application Project ... if you are running Vista, you should run in elevated privileges as well.

Which is a bit of a shock as I've been happily running Vista and developing this project as a standard user for four months. Furthermore, it turns out that that useless dialog is a VS2008 bug that will be fixed in Service Pack 1.

So apologies to VisualSVN for doubting you. I'm never a fan of running Visual Studio as an admin, but I guess I have no choice. But whatever bug was present in v1.3.2 so I could develop WAPs on IIS as a standard user, maybe you could put it back?  :)

author: Dan Maharry | posted @ Thursday, July 10, 2008 9:50 AM | Feedback (0)

The use of LINQ - in summary


As with all programming tasks, there are many different ways to create a program that solves the problem. The trick is, as ever, to write the program, get it working, and then optimize it. It may be that after you have your program up and running (and profiled), you'll discover that there are some places that you've used LINQ which would be better off using stored procedures running within your database or vice versa or using LINQ to access stored procedures. The trouble is that whether LINQ is the best approach for your apps is something that you'll find out only after you've worked with it for a while. What is sure though is that the advantages of using LINQ are so tremendous that it cries out for a code now, optimize later approach.

author: Dan Maharry | posted @ Sunday, June 15, 2008 7:59 PM | Feedback (0)