navigation
 Sunday, May 06, 2007

I recently ran into an unexpected behavior of SSRS while writing a complex report. The report required various calculations that would refer to specific previous groups and details, and I had decided to solve the problem by writing a custom report function that would evaluate on each detail row, saving the information I would need later in a dictionary. My solution worked fine, as long as my custom calculation in a group footer didn't depend on the contents of that group's details. Upon further investigation, I learned something very interesting about the order in which SSRS evaluates group headers, footers, and details...

Here is how to observe the behavior for yourself: create a stored procedure like this one, that generates some simple test data:

CREATE PROCEDURE dbo.HundredRows
AS
SET NOCOUNT ON

create table #result (
id int,
grp int
)

declare @id int
set @id = 1

while @id <= 100
begin
insert #result
values ( @id, ( @id - 1 ) / 5 )

set @id = @id + 1
end

select *
from #result

This will return a result set whose id column increments in steps of one from 1 to 100, and whose grp column increments in steps of one for every five rows. Next, create a simple report layout with a table, showing the values of each column and grouping by the grp column. In the group header and footer, include the same column references. Next, add this custom code:

Dim eval_order As Integer = 0

Public Function show_eval_order() As String
  eval_order = eval_order + 1
  Return eval_order.ToString()
End Function

Display the result of this function in the group header, footer, and detail rows. Your layout should look like this:

Once you have the report set up, click on the preview tab to see the surprising results:

I've only shown the first two groups here, but notice what has happened! The order column clearly indicates that the group header and footer are both evaluated before the details in between! By looking at the value of the id column in the footer, we can see that the the group's header and footer rows are printed while the "current" row in the dataset is still positioned on the first row in the group.

While this is interesting, it isn't very important as long as you stick to using SSRS's built-in aggregation functions. It is only when you attempt to "roll your own" by saving the dataset in your own storage for later reference that you might run into trouble. In my case, I used the following workarounds:

  1. Move calculations that rely on the details having been traversed from the group footer to the following group's header. It's clunky and less intuitive, but that's what SSRS has forced upon us.
  2. For the final group, move the calculation out of the table entirely, and put it into textboxes just below the table. While there is no way to tell SSRS to evaluate the textboxes after it evaluates the table, it does so at least for the 2005 version.

In summary, it sure would have been nice if SSRS would have evaluated group headers and footers after it evaluated the details that each group contains, but since we have clearly demonstrated that it doesn't, we now know what we will have to do in order to make custom report code produce the output we want: by putting those custom calculations into the following group's header, and by putting the final footer calculation outside the table entirely.

posted on May 6, 2007  #    by Adam Anderson  Comments [0] Trackback
 Wednesday, May 02, 2007

We recently completed a self-paced tutorial for Telerik RadControls.

You can download the self paced tutorial from the Telerik site at:

http://www.telerik.com/support/self-paced-tutorial.aspx

The tutorial addresses the entire suite of RadControls, AJAX, client-side scripting, and custom data-binding techniques.

posted on May 2, 2007  #    by Noel Rice  Comments [0] Trackback

If you need to monitor your transactional replication with a custom monitoring service, Microsoft has provided some useful tools to help. Recently I was having trouble reliably monitoring my replication, and then I discovered this page.

 

http://msdn2.microsoft.com/en-us/library/ms146951.aspx

 

If you’re using transactional replication there is no better way to monitor its health than using your own tracer token. This is just like inserting a tracer token using the replication monitor utility in SQL Server Management Studio.

 

First you need to create a connection to the server.

 

   server = new ServerConnection(sci); 

 

then create a TransPublication object.

 

    TransPublication transPublication = new TransPublication(publicationName, publicationDBName, server);

 

Call LoadProperties of the new object to make sure that all publications and subscriptions are loaded.

 

   transPublication.LoadProperties(); 

 

Then post the tracer token and call refresh to send it on its way. You need to save the ID of the token so that you can clean it up later.

 

   id = transPublication.PostTracerToken();
   transPublication.Refresh();
 

 

Now that the tracer token is on its way we need to create a publication monitor to look for its return. You have to go through a couple of layers to get to it.

 

   Microsoft.SqlServer.Replication.ReplicationMonitor monitor =

      new Microsoft.SqlServer.Replication.ReplicationMonitor(server);
   PublisherMonitor pub = monitor.PublisherMonitors[publisherMonitor];
   PublicationMonitor publicationMonitor = pub.PublicationMonitors[publicationDBName, publicationName];

Now we need to enumerate all of the tokens in the publication. You must call LoadProperties to refresh this list.

 

   publicationMonitor.LoadProperties();
   ArrayList tokens = publicationMonitor.EnumTracerTokens();
 

 

You can cast the items in the array list to a TracerToken type to find the token we sent with the ID we saved earlier.

 

   TracerToken token = null;
   foreach (TracerToken t in tokens)
   {
      if (t.TracerTokenId == tokenID)
      {
         token = t;
         break;
      }
   }
 

 

Now that we have our token we need to enumerate the tracer token history with this call.

 

   DataSet tth = publicationMonitor.EnumTracerTokenHistory(token.TracerTokenId); 

 

This returns a dataset that you need to parse through to get the data we are interested in. These include distributor latency, subscriber latency, and overall latency. If these values are blank, then the token has not returned yet. You need to then enumerate the tokens and check again. When the token has returned you then need to cleanup. A simple call will do this for us.

 

   publicationMonitor.CleanUpTracerTokenHistory(tokenID);

 

If any part of the replication fails for any reason, the token will fail to return and you know you have a problem.

posted on May 2, 2007  #    by Bary Nusz  Comments [0] Trackback
 Tuesday, May 01, 2007

You can support docking in your web applications using Telerik's new control suite "Prometheus".  Prometheus is completely redesigned to use Microsoft's ASP.NET Ajax.  The Prometheus docking controls make it easy to define objects that may be dragged and areas where objects may be dragged to.  With docking support you can create web portal sites, "PageFlake" style web pages (where the user can dynamically add controls and drag them around on the page), or even "post-it notes" can be added to the page.  The current state-of-play is that the controls are in beta and have a few quirks, but the performance is very responsive and the look and feel is also quite good.

To use Prometheus "RadDock" controls first download and install the Microsoft ASP.NET Ajax extensions at http://ajax.asp.net/, then get the free Prometheus beta download at: http://www.telerik.com/products/aspnet-prometheus/download.aspx.  Once installed, create a new project type "ASP.NET AJAX-Enabled Web Application".  BTW, if you try to use the standard ASP.NET web application you will get really interesting results.

Your toolbox will have the new AJAX extension controls, including "ScriptManager".  ScriptManager is the workhorse of Microsoft's ASP.NET Ajax that registers client side scripts that enable AJAX functionality. The ScriptManager is automatically placed on the default web page so you don't need to do anything further there.

Also in the toolbox are the new Prometheus controls.  The three you need for drag and drop support are:

  • RadDock is the container for text or other controls that need to be dragged on the web page.
  • RadDockZone defines an area on the screen where a RadDock can dropped onto.
  • RadDockLayout can contain a number of RadDockZone controls so you can set the skin for everything at once.  RadDockLayout also has a property StoreLayoutInViewState that can be used to persist the RadDock locations across multiple postbacks.

To test these controls drop a RadDockLayout, two RadDockZones within the RadDockLayout and RadDock controls in each of the RadDockZones. 

How about adding content to the RadDock? 

  • Use the Radock Text property for simple text only that doesn't involve any other controls.
  • Create your own ITemplate implementation class and set the RadDock ContentTemplate property to that class in the code behind.
  • Add to the ContentTemplate property markup.  At this stage of development I don't see a smart tag or other UI assistance so instead add a ContentTemplate tag and add controls within the tag.

For example we could add another new Prometheus control "RadColorPicker":

<ContentTemplate>
  <telerik:RadColorPicker ID="RadColorPicker1" runat="server" Preset="Standard">
  </telerik:RadColorPicker>
</ContentTemplate>

To finish up we set the Skin property of RadDockLayout to "Longhorn".  If you've used the current version of RadDock you're used to adding various skin files to the project.  Not with the Prometheus version where the skins are built-in and you can choose the Skin property value from a drop down list.  Finally you can set the title bar text for each RadDock using the Title property.

When you run the Prometheus version of the docking controls you should experience snappy responsiveness and a very respectiable UI.  Try downloading Prometheus and retrofit some part of your current web application to support docking.  Enjoy!

Note: if you want to learn Telerik RadControls from the ground up, check out the Falafel-authored, self-paced tutorial at http://www.telerik.com/support/self-paced-tutorial.aspx and download the sample projects from our community download site at http://www.falafel.com/community/files/Default.aspx.

posted on May 1, 2007  #    by Noel Rice  Comments [0] Trackback
 Saturday, April 28, 2007

Behind this dropdownlist is some of the coolest code I have written for ages!

 

 

How can that be? Well, I learnt a lot about generics and anonymous delegates in the process… if you want to see some totally geeky code, read on,

Of course, there is a chance that for most of you this is all old hat, in which case you can go back to sleep…it was new to me for sure.

 

Still awake?

 

So, first of all, I had a lot of fun retrieving the data that populates this list.

The data that drives this DropDownList is a list of what I call FeatureGroups (the list has 2 elements, Defects and  Manage Projects), which has a Master Detail relationship to Features (a FeatureGroup contains 0 or more Features). It is in fact a List<AFFeatureGroup>, where each FeatureGroup contains a List<Feature>. Feature references back to its containing FeatureGroup through a FeatureGroupID member.

 

When I go to populate this list of FeatureGroups, I want to do it in one database call, so what I fetch is a flattened out Result Set that contains { FeatureGroup.ID, FeatureGroup.Name, Feature.ID, Feature.Name }.

I then need to iterate through that result set and for each FeatureGroup.ID I find, see if I already have created a FeatureGroup for that ID, if not, create it, then add the Feature to that new or existing feature group.

 

This is a standard pattern, you would have a list of your FeatureGroups in one hand, and be adding to it and searching in it as you go while looping through the flattened result set. Well here is how to spice it up!

 

It turns out that the generic List<T> type has a Find<T>( Predicate<T> match ). The predicate method is a method that takes a Predicate<T> and returns a bool. This method passed in as this parameter is called once on each item in the list, and the first time it returns true, that will be the item returned by Find. And, when invoking Find, you can pass in an anonymous delegate. In other words, you don’t have to pass in a new Predicate<T>( myCompareMethod ), you can just pass in the implementation of myCompareMethod…

This is how it looks:

 

private static List<AFFeatureGroup> GetFeatureGroupsFromFeatures( List<AFFeature> features )
{
  List<AFFeatureGroup> result = new List<AFFeatureGroup>();
  foreach (AFFeature f in features)
  {
    AFFeatureGroup fg = result.Find(
      delegate(AFFeatureGroup featureGroup) { return featureGroup.ID == f.FeatureGroupID; });
    if (fg == null)
    {
      fg = new AFFeatureGroup(f.FeatureGroupID, f.FeatureGroupName);
      result.Add(fg);
    }
    fg.Features.Add(f);
  }
  return result;
}

 

Let me break this down:

 

-          The method receives a List of AFFeatures, this is the flattened out result set containing one Feature in each row with its FeatureGroup as well.

-          The first line, result = new List<…> just creates an empty list of AFFeatureGroups that I will be returning once populated

-          Then,  I loop through all the features that I have fetched

-          In the loop, I call the Find method of List<AFFeatureGroup>, passing  my find implementation. Note two things:

o   a) I declare that the anonymous delegate accepts a AFFeatureGroup, which is mandated by the signature of Predicate<T>, as this code will be called once for each AFFeatureGroup in the list until a match is found

o   b) Inside the implementation curly braces, I can compare the featureGroup.ID of this passed in AFFeatureGroup to the value of f.FeatureGroupID. Think about that some! This is the powerful part! It’s kind of like in Delphi when you could pass in the address of a local nested method and that method would have access to any local variables or parameters in the enclosing method scope. The variable f is in the loop that encloses the call to Find, so in effect, what the compiler does is it captures the current value of f when creating the anonymous method, and makes it available inside the method body! Basically you have a dynamically created nested local method inside of the for loop. This is awesome compiler magic!

-          OK, so if a match is not found, I create a new AFFeatureGroup, and I then add it to the result list

-          If a match was found, or if I just created one, I can now add the feature itself to the feature group

 

And that is it: with this simple set of code I am doing a pretty complex construction of objects! This is what the language features of C# 3.0 will be taking one step further, where I will be able to use type inference to get rid of even more code above, for instance the declaration delegate(AFFeatureGroup featureGroup) can go away and I will just have a lambda expression there instead.

 

So, now for geek part 2…. I now have a method that will take a flattened list and build my two dimensional structure, but now I want to cache that list. Well, first I started with the classic approach, I look in the Cache object for a certain key, and if I don’t find it, I go fetch the data from the database (using the method in part 1), and store it in the cache. But this gets so repetitive, and I also wanted to centralize some of the aspects of the caching, like for how long the cached data is kept.

 

I started off along the lines that my colleague Adam Markowitz pioneered in the Velocity project, where you basically can create a CacheManager class that uses generics to do a type safe cache search. But then I figured if List<T> can accept a Predicate<T> in its Find method, why can’t I write a Cache.Get<T> that also accepts a T Fetcher<T> that will be invoked if there is no cache hit? Of course, Fetcher<T> might need some parameters, so I decided I would need two types of delegate, one that takes no parameters, and one that takes an instance of some arbitrary type D as a parameter (I could have been lazy and used an object instead of D, but what I love about Generics is the type safety it gives you without the performance drawbacks) :

 

public delegate T Fetcher<T, D> ( D data );
public delegate T ParameterlessFetcher<T>();
 

 

Now, my CacheManager can accept one of these 2 delegate types as a parameter in its GetFromCache method, and invoke it if the Cache doesn’t have the sought after data. For instance using the parameterless delegate:

 

public static T GetFromCache<T>(string key, ParameterlessFetcher<T> fetcher)
{
  T res = GetFromCache<T>(key);
  if (res == null)
  {
    res = fetcher();
    AddToCache(key, res);
  }
  return res;
}

 

The GetFromCache and AddToCache are the actual Cachce accessors:

 

public static void AddToCache(string key, object dataToCache)
{
  HttpContext.Current.Cache.Insert(key, dataToCache, null, DateTime.MaxValue, m_defaultTimeSpan);
}

public static T GetFromCache<T>(string key)
{
  return (T) HttpContext.Current.Cache[key];
 

 

Note that I centralize the timespan handling so that this is consistent:

 

private static TimeSpan m_defaultTimeSpan = new TimeSpan(0, 20, 0);

 

Here is how the parameter version looks:

 

public static T GetFromCache<T,D>(string key, Fetcher<T,D> fetcher, D data )
{
  T res = GetFromCache<T>(key);
  if (res == null)
  {
    res = fetcher(data);
    AddToCache(key, res);
  }
  return res;
}

 

And, putting it all together:

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Caching;

namespace Falafel.ActiveFocus.Common
{
 public class AFCacheManager
 {
  private static TimeSpan m_defaultTimeSpan = new TimeSpan(0, 20, 0);

  public delegate T Fetcher<T, D> ( D data );
  public delegate T ParameterlessFetcher<T>();

  public static T GetFromCache<T,D>(string key, Fetcher<T,D> fetcher, D data )
  {
    T res = GetFromCache<T>(key);
    if (res == null)
    {
      res = fetcher(data);
      AddToCache(key, res);
    }
    return res;
  }

  public static T GetFromCache<T>(string key, ParameterlessFetcher<T> fetcher)
  {
    T res = GetFromCache<T>(key);
    if (res == null)
    {
      res = fetcher();
      AddToCache(key, res);
    }
    return res;
  }

  public static void AddToCache(string key, object dataToCache)
  {
    HttpContext.Current.Cache.Insert(key, dataToCache, null, DateTime.MaxValue, m_defaultTimeSpan);
  }

  public static T GetFromCache<T>(string key)
  {
    return (T) HttpContext.Current.Cache[key];
  }
 }
}
 

 

Now, what is exciting here is that I have a completely generic cache manager that can store and retrieve anything in a typesafe manner, using a delegate to do any fetching needed! Here is how I now use it to fetch that cached list of FeatureGroups:

 

public static List<AFFeatureGroup> CacheAllFeatureGroupsAndFeatures()
{
  return AFCacheManager.GetFromCache<List<AFFeatureGroup>>(
    CACHEKEY_FEATUREGROUPS,
    delegate() { return GetFeatureGroupsFromFeatures(new AFFeatureData().GetFeatures()); });
}

 

This is just amazingly concise to me! All I am supplying is :

 

-          the type of the cached object (a List<AFFeatureGroup>)

-          the Cache key to use (the constant CACHEKEY_FEATUREGROUPS)

-          the code to call when missing the data in the cache

 

Note that the code is now another anonymous delegate, it actually calls the method I explained in step 1 above that parses out the flat data. Here you also see how that method in turn gets the data by calling another method, GetFeatures. That in turn actually uses a whole similar approach of generics, that I wrote, which gives as a typesafe  way to convert result sets to lists of objects, but I won’t go there now!

 

And finally, to get down to that dropdownlist:

 

private void PopulateFeatureGroups()
{
  List<AFFeatureGroup> fglist = AFFeatureGroupData.CacheAllFeatureGroupsAndFeatures();
  ddlFeatureGroups.Items.Add(new ListItem("(All)", AFConsts.EMPTY_ITEM_VALUE.ToString()));
  foreach (AFFeatureGroup fg in fglist)
    ddlFeatureGroups.Items.Add(new ListItem(fg.Name, fg.ID.ToString()));
}

And it’s a wrap!

 

And what did I gain from all this convoluted programming asides from elegance and brevity?

I gained a reusable cache that can be tested and trusted and will save me a bunch of time caching other objects in a consistent and type safe manner. Now wasn’t that a geeky way to fill a drop down list?

posted on April 28, 2007  #    by John Waters  Comments [0] Trackback
 Tuesday, April 24, 2007
Yesterday I ran into an exception trying to read a non null DateTime value into a nullable type (DateTime?), using a helper method that was calling Convert.ChangeType. It turns out that this method, although very useful, doesn't support nullable types. However, I soon came across a useful blog that has a wrapper class that takes care of that particular problem, courtesy of Peter Johnsson.
posted on April 24, 2007  #    by John Waters  Comments [0] Trackback
 Tuesday, April 17, 2007

You might overlook the JavaScript debugging utility that's already built in to Visual Studio 2005: the Script Explorer window.  The Script Explorer can take care of the usual debugging tasks like stepping through code, adding watches and evaluating variables. 

To use the debugger in Internet Explorer navigate to the browser Tools | Internet Options | Advanced tab and make sure that "Disable script debugging" is turned off. 

Run your web application in Visual Studio 2005.  Then select the menu option for Debug | Windows | Script Explorer.  Notice in the background the tags for telerik RadEditor controls...

The first thing you notice in the Script Explorer window is a series of JavaScript and resource files that are currently loaded.  Double click on the aspx file you're currently working with and you will see the evaluated HTML returned from the server.  The RadEditor control now shows as its computed HTML, CSS and JavaScript that will actually be functioning in the browser. 

You can also navigate up to the script for the page and set breakpoints and watches.  When the JavaScript executes and hits your breakpoint you get all the usual Visual Studio debugging capabilities for free.

Next blog I'll show the excellent "Firebug" debugging utility for Firefox.  Firebug doesn't stop at just JavaScript but works with the entire stack of AJAX related technologies (and has a high cool-factor).

posted on April 17, 2007  #    by Noel Rice  Comments [0] Trackback

The Firebug debugger add-in for Firefox handles the entire stack of AJAX related technologies.  Ever wanted to tweak the margins in your style sheet while you watch the changes?  Profile a web page and see a visual representation of when scripts are loading and how big they are?  Watch the XmlHttpRequest (i.e. AJAX) requests move over the wire in real time?  You can do all this in Firebug, and of course you can step through your JavaScript code.  At Falafel we use this tool in our consulting work and telerik recommends Firebug for use in web applications using their RadControl suite.

Firebug is an innovative tool that handles usual tasks you would expect from a combined DOM explorer, AJAX/JavaScript profiler, and JavaScript debugger.  But it combines technologies in a new way that is definitely cool and a lot of fun to use.  This will take a few blogs to talk about in depth, but this should get you started.

Firebug only installs and runs in Firefox.  Get Firefox at http://www.mozilla.com/en-US/firefox if you don't already have it installed.  In Firefox download and install Firebug from http://www.getfirebug.com/Now run any page in Firefox and notice the green checkbox in the lower right hand corner.  Click it to start up Firebug for the page you're on. 

The Console tab is used for logging output.  The logging statements can be embedded in your script or run interactively on the Firebug interactive JavaScript command line.  The image below shows the special "dir" command line API dumping the contents of the "<body>" tag to the console.  A series of "console" commands output with visually helpful icons.  There's more on this tab that will wait for another blog (or jump ahead by checking out the API documentation at http://www.getfirebug.com/docs.html).

If you couldn't wait and are running Firebug right now, try clicking the Inspect button, then move your mouse on a web page.  The HTML and Style tabs will display the corresponding markup in real time as you move.  Click once on the page to stop inspecting.  Notice the crossed out items in the Style window?  The window is showing how styles are cascading and what styles are not in effect.  Try double clicking a style value -- you can edit the value and see the results immediately!  Also notice when your mouse cursor passes over a color or image tag that a thumbnail pops up.  Very smooth...

Try clicking on the Edit button (next to the Inspect button).  You can edit the HTML and see the results.  Feel free to add a completely different tag like the image below reading "Modify HTML on-the-fly!!".

By the way, when you click the refresh button, all changes go away.  The author of this tool, Joe Hewitt, mentions in his talk about the advanced features of Firebug (http://yuiblog.com/blog/2007/01/26/video-hewitt-firebug/) that this version of Firebug is not intended to be an editor, but more of an exploring and auditioning tool. 

Speaking of auditioning new settings, what about style layout settings?  The Layout tab shows the offset, margin, border and padding for each element you select.  In the browser you will see rules and other visual metric devices overlaying the web page.  Now try clicking one of the settings, say top padding for an image as shown below.  You don't have to enter a number off the keyboard.  Instead try the arrow keys to raise and lower values.  That way you can keep your eye on the layout until it's just right.

Firebugs profiling features show you the JavaScript and XHR requests going over the wire.  The example below is a demo using a set of telerik date controls using a RadAjaxManager to AJAX enable the whole process (thanks to John Waters for letting me steal the example).  In the Net tab we can select to see all or only certain traffic.  The image below shows all the JavaScript traffic; when it loads and how big each piece is.  For the web resources that contain images you can pass the mouse over to see thumbnails.  Click the plus sign to get the details like HTTP headers, requests and responses.

If we click the XHR tab we see only traffic initiated by the XMLHttpRequest object.  XMLHttpRequest is a major component of AJAX, so this feature is very important for evaluating web site performance with and without AJAX, tweaking AJAX performance, and even checking XHR traffic for security vulnerabilities. 

Firebug is after all a debugger.  All the capabilities you expect like step over, step into, run to line, step out, conditional breakpoints, watches, and automatic local variable display are there.  The conditional breakpoint window is a nice piece of UI programming in itself (see below).

Note: Thanks to Ramesh Theivendran for letting me steal the code for this XMLHttpRequest demo (I see a pattern forming here). 

This has been the briefest look at a tool that is sure to set the bar for all web debuggers.  In coming blogs I'll show Firebug in more depth, but until then I hope you try it yourself.

 |  |  | 
posted on April 17, 2007  #    by Noel Rice  Comments [0] Trackback
 Thursday, April 12, 2007

Ever need to show something in a databound gridview that isn't in your datasource? Don't worry, relax, forget about that datasource. Just create that extra row dynamically.

Lets assume I have a GridView with 2 columns. The first column shows the items on an order,  the second column shows the price of that item and the footer shows the order total.  How would I inject a row that shows the tax?

Something like this in the RowDataBound EventHandler would do the trick:

decimal orderTotal = 0.0m;
decimal tax = 0.0m;
protected void gvOrderDetail_RowDataBound(object sender, GridViewRowEventArgs e)
{
    //TODO: Calculate the orderTotal and the tax when RowType = DataRow
    if (e.Row.RowType == DataControlRowType.Footer)
    {
        //Create the Row
        GridViewRow row = new GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal);
        //Add the two Columns
        row.Cells.AddRange(CreateCells());
        //get a reference to the table that holds this row
        Table tbl = (e.Row.Parent as Table);
        //Add the row at the end of the list, but before the footer.
        tbl.Rows.AddAt(gvOrderDetail.Rows.Count + 1, row);

        //Don't forget to account for any changes in the footer. Since we added a row to show the tax,
        //that tax must also be accounted for in our footer. Calculating the orderTotal and the tax
        //is an exercise for the reader.
        Label lbl;
        lbl = (Label)e.Row.FindControl("lblTotal");
        lbl.Text = String.Format("{0:C}", (orderTotal + tax));
    }
}

private TableCell[] CreateCells()
{

    TableCell[] cells = new TableCell[2];
    TableCell cell;
    Label lbl;

    //The order item column
    cell = new TableCell();
    lbl = new Label();
    lbl.Text = "Sales Tax";
    cell.Controls.Add(lbl);
    cells[0] = cell;

    //The price column
    cell = new TableCell();
    lbl = new Label();
    lbl.Font.Bold = true;
    lbl.Text = tax.ToString("C");
    cell.HorizontalAlign = HorizontalAlign.Right;
    cell.Controls.Add(lbl);
    cells[1] = cell;

    return cells;
}

posted on April 12, 2007  #    by Mike Dugan  Comments [0] Trackback
 Wednesday, April 11, 2007
A nice blog post highlighting some aspects of Haskell and the new functional elements of C# 3.0 http://themechanicalbride.blogspot.com/2007/04/haskell-for-c-3-programmers.html
posted on April 11, 2007  #    by Adam Anderson  Comments [0] Trackback