Joe Wrobel

  • Web Profile Builder 2.0.0.0

    It's been over five years since I've made any updates to this project.  I had basically

    left it for dead because I personally no longer have a need for it.  I know a lot of

    people do still rely on it though.  I had some free time recently so I decided to give

    the project a little bump to make it easier to use and more accessible to those who do

    still use it.

     

     

    What's changed?

    • Most importantly, this is no longer required to be installed in the GAC.
      • There is no installer at all anymore!
      • Now it can be included in the project source control and referenced
        locally.
    • Added support to install using NuGet.
      • PM> Install-Package WebProfileBuilder
    • Simplified the configuration.
      • Support for configuration via Web.config has been removed. This was
        more of a "nice to have" feature and added unneeded complexity to
        the code base.
      • All configurable options are still supported, but now it has to be
        configured in the web project file.  See below for a complete
        example of the configuration.
    • Moved project home to CodePlex.
    • Added build automation to the source code using NAnt.

     

     

    IMPORTANT NOTES:

    • The core code base has not been changed.  I didn't want to introduce any bugs, so
      I only changed the code necessary to achieve my goal.  All code changes were
      related to configuration.
    • If you are new to WebProfileBuilder, know the following:
      • The generated profile class does not get automatically included into the
        project.  You must use the Solution Explorer to show all files, then
        manually include the generated class into your project.  You only
        need to do this once.
      • You also must create the "Profile" property in your Page class. See
        below for an example.

     

     

    Example web project file:

    <?xml version="1.0" encoding="utf-8"?>

    <Project ToolsVersion="4.0"

             DefaultTargets="Build"

             xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

     

        <!-- ... other project content ... -->

       

        <!--WebProfileBuilder setup.-->

        <UsingTask TaskName="BuildWebProfile"

                   AssemblyFile="..\packages\WebProfileBuilder.2.0.0.0\tools\
    WebProfileBuilder.dll"
    />

       

        <Target Name="BeforeBuild">

            <!--WebSiteRoot, RootNamespace, and Language are required.-->

            <!--ClassName, Directory, and FileName are optional for additional customization.-->

            <BuildWebProfile WebSiteRoot="$(ProjectDir)"

                             RootNamespace="$(RootNamespace)"

                             Language="$(Language)"

                             ClassName="MyWebProfile"

                             Directory="CodeFiles"

                             FileName="MyWebProfile" />

        </Target>

       

        <!-- ... other project content ... -->

       

    </Project>

     

     

    Example page class:

    using System;

    using System.Collections.Generic;

    using System.Web.UI;

     

    namespace CsExample {

        public partial class _Default : Page {

            //... other class content ...

     

            public static MyWebProfile Profile {

                get { return MyWebProfile.Current; }

            }

           

            //... other class content ...

        }

    }

    Read more...

  • Conditional Project Reference

    I develop a lot of different applications. They range from large inventory tracking websites, to Windows services, to user interfaces encapsulating scripts written by someone else. I get asked to make many different things. It can sometimes be difficult to keep from reinventing the wheel in different projects. So it's very useful to maintain a common library to reference from the different projects. Regardless of what you keep in the library, utilities, base classes, or the dreaded "helper methods", it does become challenging to work with.

    Read more...

  • Automating ClickOnce Deployment

    Building a ClickOnce deployment outside of Visual Studio can be a difficult task. One point I want to make clear is that there is no magic going on to make your application deployable using ClickOnce. Well, unless you are using the tooling inside of Visual Studio, in which case there is a lot of magic happening. Much to its credit, Visual Studio does make it very easy to setup and publish a ClickOnce deployment for your application. That said, my suggestion would be to just use Visual Studio if it fits your workflow. However, if you need a fully automated solution to create a ClickOnce deployment outside of Visual Studio, then continue reading. In my environment, my builds are automated using NAnt, which are then built on a build server using CruseControl.Net.

    My end goal with automating the ClickOnce deployment was to mimic the output as created by Visual Studio.  I didn't have to do this, but I wanted to just incase I might ever need to resort back to using Visual Studio.  I didn't want to get caught in a situation where my automated ClickOnce deployment files conflict with the files generated by Visual Studio.

    In general, a ClickOnce deployment requires two files. An application manifest and a deployment manifest. The application manifest contains details of the application. Some of these details include dependencies, security privileges, and a complete listing of every file required by the application.

    The deployment manifest contains details of, you guessed it, the deployment. For this file, my focus is primarily on the deployment strategy. It is also worth noting that this file will contain a dependency which is basically a pointer to the application manifest.

    I know I'm only scratching the surface of what these two files actually contain. I'm calling out the details which are directly relevant here. I'm trying hard to avoid using the phrase, "beyond the scope of this article", but there it is. As much as I dislike that phrase, I'm using it anyway. Really, if you want to know more about these two files, look at the Microsoft documentation.

    As I went through this process, I did find a walkthrough in the Microsoft documentation that you may find helpful. I followed the steps myself, but it didn't take me where I wanted to go and was hard to follow due to lack of detail. When I finished the walkthrough, I had more questions than when I started. But it did help to guide me in the right direction, so I want to point it out.

    Steps to automate a ClickOnce application

    1. Obtain or create a ".pfx" key file for signing the manifest files. The key file can also be used to sign the assembly/executable of the application if wanted, but not required. You can create a key file using the "Signing" tab of the project properties window. I think there are other kinds of keys that can be used for signing, but I am not an expert in this area so I'm saying as little about it as possible.

    2. Add an "app.manifest" file to the project. This will give you a physical file that you can make custom edits to if needed. I personally didn't need to make any custom edits, but at least I have that option if I ever need to. This file will get updated post build using the Mage.exe utility.

    3. Build the project/application to get all the files required for the application to run. Also, copy any extra files needed to deploy with the app such as the main app icon. The goal here is to create a folder containing your entire application. All your resource files, data files, help files, referenced dlls, everything.  I use NAnt to automate this process.

    4. Make any last changes to configuration files or whatever content you need to change for the deployment target.  This is important. Do not change any application content after the application manifest has been updated because it will make the application manifest invalid. The application manifest contains hash codes for every file. This is a security measure to prevent any tampering with the files.

    5. Use Mage.exe to update the application manifest. Note, if you are using the ".deploy" extension for your files, you'll want to do this step before appending the ".deploy" extension to the files. If this doesn't make sense right now, don't worry about it yet. I'll explain more about this with web hosted deployment.  Below is an example of this command.

    mage.exe -Update build\ClickOnceExample-Release\ClickOnceExample.exe.manifest
        -ToFile "build\ClickOnceExample-Release\Application Files\1.1.0.6125\ClickOnceExample.exe.manifest"
        -FromDirectory "build\ClickOnceExample-Release\Application Files\1.1.0.6125"
        -Version 1.1.0.6125

    6. Use Mage.exe to sign the application manifest using your personal ".pfx" key file. Once the file is signed, your done with it. Leave it alone. Below is an example of this command.

    mage.exe -Sign "build\ClickOnceExample-Release\Application Files\1.1.0.6125\ClickOnceExample.exe.manifest"
        -CertFile src\Robolize.ClickOnceExample\ClickOnceExample.pfx
        -Password ClickOnceExample

    7. Create a deployment manifest file. This is the most troublesome part and I'll do my best to explain. I didn't find any single good solution to generate this file. One thing I can't explain is that once I added the app.manifest to the project, Visual Studio started generating a deployment manifest as well. I guess it's one of those magic tricks that Visual Studio performs. I didn't find a way to make this file not generate. I wouldn't mind, but I couldn't use the file as it was and I don't know of a way to set any properties for the generation of the file to make it useable. But in the end it doesn't really matter because it will get overwritten anyway.
    This became a two-step process because not all the desired properties of the deployment manifest can be set using one method or the other. When using Mage.exe, you cannot set the update strategy to "beforeApplicationStartup", which is what I wanted. Someone else did some in-depth research on this issue and discovered a brick wall. I took his word for it, but you can see for yourself here.  When using the "GenerateDeploymentManifest" task, you cannot properly set the "EntryPoint" because the final output of the application files will have a different directory structure than the default flat output from building inside of Visual Studio. So here's what to do.

    7a. First, setup the "GenerateDeploymentManifest" task in the "AfterBuild" target of the main project file. This will generate a usable deployment manifest file.

       1:  <?xml version="1.0" encoding="utf-8"?>
       2:  <Project ToolsVersion="4.0" DefaultTargets="Build" 
       3:      xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
       4:   
       5:    <!-- Other project content... -->
       6:    
       7:    <Target Name="AfterBuild">
       8:      <GenerateDeploymentManifest 
       9:          AssemblyName="ClickOnceExample.application" 
      10:          Product="ClickOnceExample" 
      11:          Install="true" 
      12:          UpdateEnabled="true" 
      13:          UpdateMode="Foreground" 
      14:          CreateDesktopShortcut="true" 
      15:          OutputManifest="$(OutputPath)\ClickOnceExample.application" 
      16:          EntryPoint="$(OutputPath)ClickOnceExample.exe.manifest" 
      17:          TargetFrameworkVersion="4.5" 
      18:          TargetFrameworkMoniker=".NETFramework,Version=v4.5" />
      19:    </Target>
      20:  </Project>
     
    The most important properties to set in the "GenerateDeploymentManifest" task are the following:
    • Install="true"
    • UpdateEnabled="true"
    • UpdateMode="Foreground"

    7b. Next, use Mage.exe to update the deployment manifest file. This step assumes that the application directory structure has been setup either manually or as part of the build script. One important note to make for updating the deployment manifest with Mage.exe, Do not set the "Install" flag here because it will wipe out the settings set by the "GenerateDeploymentManifest" task and defeat the purpose of having used it in the first place.  Below is an example of this command.

    mage.exe -Update build\ClickOnceExample-Release\ClickOnceExample.application
        -AppManifest "build\ClickOnceExample-Release\Application Files\1.1.0.6125\ClickOnceExample.exe.manifest"
        -AppCodeBase "Application Files\1.1.0.6125\ClickOnceExample.exe.manifest"
        -Publisher "Robolize Division"
        -Version 1.1.0.6125
        -ProviderUrl file://YourServerName/YourShareName/ClickOnceExample/ClickOnceExample.application

    The most important properties to set here are the following:

    • AppManifest
    • AppCodeBase
    • Publisher (defaults to "Microsoft" if not set.)
    • Version
    • ProviderUrl

    8. Use Mage.exe to sign the deployment manifest using your personal ".pfx" key file. As with the application manifest, once you sign it, you're done with it. Leave it alone.  Below is an example of this command.

    mage.exe -Sign build\ClickOnceExample-Release\ClickOnceExample.application
        -CertFile src\Robolize.ClickOnceExample\ClickOnceExample.pfx
        -Password ClickOnceExample

    9. Optionally copy the deployment manifest file to the location of the application manifest file for safe keeping.  This is not required, but it may come in useful if you ever need to revert to an earlier version. You could just replace the current deployment manifest with an old version pointing to the old version of the application.

    Web Hosted Deployment

    I mentioned earlier that I would explain the ".deploy" extension. The ".deploy" extension is primarily used when the ClickOnce deployment is being hosted within a website. By default, the web server will not allow downloading files with specific extensions like ".exe", ".config", and others. The web server can be configured to allow these file extensions to be downloaded, but for security reasons, you really don't want to do that. It is better to just give every file a ".deploy" extension so there is only that one extension to be concerned with. If you need to use the ".deploy" extension, the setting MapFileExtensions="true" in the "GenerateDeploymentManifest" task should do the trick. I personally am using the file share deployment so I don't have the need to do this. I have not gone through the steps to verify that what I'm saying actually works, but I don’t see any reason why it wouldn't work.

    Other Thoughts

    Apparently there is a way to publish a ClickOnce deployment by executing MSBuild on a project file and specifying the "publish" target.  This didn't work for me and I assume it's because I execute MSBuild on a solution file, not a project file.  I always use solution files because everything I work on is made up of multiple projects.  I didn't go far down this path, but you can have a look here and here.

    The Mage.exe tool, on my system, is located here, C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\mage.exe
    It could be located somewhere else on your system.

    Looking at the entire process I describe here, it might be confusing to follow because the interweaving of steps between NAnt and MSBuild.  To help with understanding, I created a simple demo solution utilizing the entire process.  You can download and inspect my demo example here.  This demo I created is actually a good example of using NAnt for build automation.  It contains the core aspects which are present in all applications I manage.

    Read more...

  • Conditional Formatting in the Silverlight DataGrid

    I’ve been an asp.net developer for some time now and I was excited to jump on to Silverlight when it 2.0
    was released a few months ago.  One thing I really struggled with was applying conditional formatting to
    the individual cells in the DataGrid control.  Coming from an asp.net background, I carried a lot of
    assumptions with me (big mistake).  I thought I could get a hold of the rows or cells collection and have
    my way with it, but no such luck.  I stumbled down several paths which all ultimately lead to dead ends. 
    After killing hours (maybe days) on trying to figure this out, I had to let it go and move on.  Now a month
    later I decided to give it another shot and I finally got it.  The answer was right in front of me all along. 
    I knew about the IValueConverter interface, but I didn’t fully understand its capabilities.  I thought it was
    only used for converting an object into a text representation or vice versa.  Actually, you can return
    anything you want from it.  So you can return a Button, Grid, or whatever.

    Another aspect I couldn’t figure out was how to get access to page members from within the Convert
    method.  For example, I wanted to render a button in the cell and wire up the button’s click event to a
    method in the page.  Sure, you could do this to a certain extent using templates, but then I couldn’t
    find a way to change the template conditionally based on a value in the bound data item.

    The solution I came up with was to create a delegate along with a class which implemented the
    IValueConverter interface and exposed two events.  One for converting and the other for converting
    back.  I can then declare this converter in the resources collection and setup a handler in the page
    as shown below.

    <UserControl.Resources>
    <local:UniversalConverter x:Key="ageConverter" Converting="ConvertAge" />
    <local:UniversalConverter x:Key="nameConverter" Converting="ConvertName" />
    </UserControl.Resources>

     

    Here is the markup i used in the “First Name” column of the DataGrid.

    <data:DataGridTemplateColumn Header="First Name">
    <data:DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
    <ContentControl Content="{Binding Converter={StaticResource nameConverter}}"
    HorizontalContentAlignment="Stretch"
    VerticalContentAlignment="Stretch" />
    </DataTemplate>
    </data:DataGridTemplateColumn.CellTemplate>
    </data:DataGridTemplateColumn>

     

    Here is the “ConvertName” method I wired up from the converter defined in the resources collection.

       1:  private object ConvertName(object value, Type targetType, 
    object parameter, CultureInfo culture) {
       2:      Employee employee = value as Employee;
       3:      if (employee == null) {
       4:          return value;
       5:      }
       6:   
       7:      if (employee.FirstName.Contains('a')) {
       8:          Button btn = new Button();
       9:          btn.Content = employee.FirstName;
      10:          btn.Click += ((sender, e) => {
      11:            HtmlPage.Window.Alert(
      12:              string.Format(
    "There is a button here because \"{0}\" contains an \"a\".",
      13:              employee.FirstName));
      14:          });
      15:   
      16:          return btn;
      17:      }
      18:      return new TextBlock {Text = employee.FirstName};
      19:  }

     

    This is the IConverter class and delegate I created to handle the conversions.

       1:  public class UniversalConverter : IValueConverter {
       2:   
       3:      public object Convert(object value, Type targetType, 
    object parameter, CultureInfo culture) {
       4:          return this.OnConverting(value, targetType, 
    parameter, culture);
       5:      }
       6:   
       7:      public object ConvertBack(object value, Type targetType, 
    object parameter, CultureInfo culture) {
       8:          return this.OnConvertingBack(value, targetType, 
    parameter, culture);
       9:      }
      10:   
      11:      protected object OnConverting(object value, Type targetType, 
    object parameter, CultureInfo culture) {
      12:          UniversalConverterHandler handler = this.Converting;
      13:          if (handler != null) {
      14:              return handler(value, targetType, parameter, culture);
      15:          }
      16:          return value;
      17:      }
      18:   
      19:      protected object OnConvertingBack(object value, Type targetType, 
    object parameter, CultureInfo culture) {
      20:          UniversalConverterHandler handler = this.ConvertingBack;
      21:          if (handler != null) {
      22:              return handler(value, targetType, parameter, culture);
      23:          }
      24:          return value;
      25:      }
      26:   
      27:      public event UniversalConverterHandler Converting;
      28:   
      29:      public event UniversalConverterHandler ConvertingBack;
      30:   
      31:  }
      32:   
      33:  public delegate object UniversalConverterHandler(object value, Type targetType, 
    object parameter, CultureInfo culture);

     

    And finally, here is a screenshot of the DataGrid. (Ugly, I know, but it proves my point.)
    image

     

    Here is a link to the complete solution.

    I hope you find this useful and I look forward to hearing feedback and suggestions. 
    Most importantly, let me know if you know of a better way to do this.

    Thanks
    -Joe

    Read more...

  • Web Profile Builder 1.3

    I released a new build of Web Profile Builder.
    Not much has changed for the 1.3 release.  The focus of this release was to revert an undesirable change I made for version 1.2.  I did however add one new feature so I changed the version to 1.3.  What was the new feature added?  I changed the generated profile class to be a partial class.  Now you can easily add any custom code to the profile class (in a separate code file) without losing your changes when the profile class gets regenerated.

    Files can be downloaded from the MSDN Code Gallery here.

    Thanks
    -Joe

    Read more...

  • Web Profile Builder 1.1.0.0 Released

    Files can be downloaded from the Web Profile Builder project page.

    If you are unfamiliar with Web Profile Builder, you can read my initial blog post about it here.

    Changes made for release 1.1.0.0:

    • Added the ability to detect changes made to the profile section of the web.config file
      and only rebuild the Profile class if changes have been made.

    Notes:

    • If you used the previous release, remember to uninstall it first.
    • Also, if you used the previous release and added the customize section in the
      web.config file, remember to update the assembly reference to
      “WebProfileBuilder.WebProfileConfigurationSection, WebProfileBuilder, Version=1.1.0.0,
      Culture=neutral, PublicKeyToken=01d50f1f82943b0c”.

    Thank you to everybody who provided me with valuable feedback.  This release should address
    all of your concerns.

    Thanks
    -Joe

    Read more...

  • ClientID Problem In External JavaScript Files Solved

    Well, at least for me it is.  I say that because this solution might
    not appeal to the masses, but it works great for me.

    The binary and source files can be downloaded from the MSDN Code Gallery.
    Here is the direct link.
    http://code.msdn.microsoft.com/RegClientControls

    Up until lately, I have been writing my JavaScript in the .aspx file.
    That way I could use the ClientID trick.  <%=TextBox1.ClientID%>
    I just began working with a team who prefers to write all JavaScript in
    external .js files.  What they had been doing is hard coding the
    ClientID prefixes caused by the container controls.  I guess this works
    fine.  It has been working for them so far.  I personally can't do this.
    It just feels wrong.

    I took some time to figure out a better way to deal with the ClientID in
    an external JavaScript file.  I found an interesting article
    here about
    Creating JavaScript objects from ASP.NET objects.  I liked David's technique,
    but it still required manually writing JavaScript on the .aspx page. I wrote
    a control called "RegClient" that encapsulates and automates this technique.
    If you place this control on your page anywhere below the Script Manager,
    then all you have to do to access the controls from JavaScript is something
    like "var controlObj = PageControls.TextBox1;".

    The RegClient control is dependant on Microsoft ASP.NET AJAX 1.0.  The
    source could easily be targeted to .NET 3.5 if you wanted.  I'm sure
    that with a little work it could even be library independent, but I
    didn't have that requirement.

    The RegClient control has two modes, "Marked" and "All".

    Marked:
    With the RegClient control set to Marked, only the controls marked with
    RegClient="true" or ClientControlID="{yourClientName}" will be registered.

    <Robo:RegClient ID="RegClient1" runat="server" ClientControls="Marked" />

    All:
    With the RegClient control set to All, every control in master
    and content pages will be registered unless RegClient="false".

    <Robo:RegClient ID="RegClient1" runat="server" ClientControls="All" />

    To access the controls in the external JavaScript file, handle the
    "ready" event of
    the "PageControls" object like shown here.
    PageControls.add_ready(function(){
       
    //Write your code here.
    });

    All of the controls that have been registered will be available from
    the "PageControls" object in JavaScript.  Here is an example of accessing
    the controls.

    PageControls.add_ready(function(){ 
        $addHandler($get('Button2'), 'click'
            function() { 
               
    PageControls.Label1.innerHTML = PageControls.tbText.value; 
           

       
    ); 
    });

    One caution I would make for using this control is to be careful when you
    have the ClientControls setting to "All".  This control will find and
    register every control that derives from WebControl.  If you're using
    a GridView or something similar on the page, then there could be many
    extra controls that you don't want to get registered.

    Here is a screen shot of the RegClient control.  There really is only one
    setting for this control, but I added a big blob of text as a reminder of
    how to use it with other controls.

    image

    When the page renders, this is what the generated output looks like at
    at the bottom of the page.

    image

    So, that's it.  I hope you find this useful and if you know of any ways
    this control could be improved, please let me know.

    Thanks
    -Joe

    Read more...

  • Web Profile Builder for Web Application Projects

    Files can be downloaded from the Web Profile Builder project page.

    If you use Web Application Projects, you have probably run into the issue of not being
    able to access the Profile at design time.

    Thankfully, some nice people created an Add-In for Visual Studio 2005 that will generate
    a wrapper class as a workaround.  That project can be found here.  I wanted to contribute
    to the Web Profile Generator project, but my emails went unanswered.  I decided to start
    a new project.

    This project picks up where that one left off and is based on their source code.  I started
    out rebuilding that project as it stood to add support for Visual Studio 2008.  I got it to
    work, but I wanted to do more. 

    I decided to go ahead and address the issues listed on the original project site.

    Here is a summary of the changes made.

    • First of all, this is no longer an Add-In.  Instead, it is a Build Task.
    • Works for Visual Studio 2005 and 2008.
    • Added ability to set the file name.
    • Added ability to set the directory the file gets created in.
    • Added ability to set the class name.
    • Added ability to set the name space.
    • Added an extra method as requested here.

    Usage:

    1. Run the installer.
    2. Add this Import statement to your project file.  (see special notes below)
      <Import Project="$(MSBuildExtensionsPath)\WebProfileBuilder\WebProfileBuilder.targets" />
    3. Done.

    The profile will get generated every time you build the project.

    Special Notes:

    After you modify the project file by adding the import statement, you will get this security
    warning when the project loads.  Choose the "Load project normally" option and press OK.
    image 

    Extended Usage:

    If you want to customize the web profile, you can add the following sections to your web.config.

    This section needs to be at the top of the file just under the opening configuration tag.
    <configSections>
       
    <sectionGroup name="robo.webProfile">
           
    <section name="webProfileSettings"
             
    type="WebProfileBuilder.WebProfileConfigurationSection, WebProfileBuilder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=01d50f1f82943b0c"
             
    allowLocation="true"
             
    allowDefinition="Everywhere"/>
       
    </sectionGroup>
    </configSections>

    This section can be anywhere under the configuration section.
    <robo.webProfile>
       
    <webProfileSettings className="MyWebProfile"
                           
    nameSpace="CustomNameSpace" 
                           
    directory="CodeFiles"
                           
    fileName="MyWebProfile" />
    </robo.webProfile>

    The directory name is not a virtual directory, but is in reference to the root of the Web Application.
    The fileName is just the name of the file and should not include an extension.
    The className and nameSpace are as you would expect.
    None of these settings are required.  You don't even need to define this section at all.

    If you would like to use Web Profile Builder, it can be downloaded here.
    Also available is the complete source code and examples in VB and C#.

    Thanks
    -Joe

    Read more...

  • A more elegant solution to display GridView header and footer when the data source is empty.

    I think the need to always show the header and footer of a GridView is pretty common.
    When I first ran into this problem, I went to Google and found lots of content about this.
    Some suggest tampering with the data source to add an extra row if it’s empty. 
    Others show overriding the CreateChildControls method.

    I was not satisfied with either of these solutions. I didn’t like that dirty feeling I had by tampering
    with the data source.  And I didn’t like overriding the CreateChildControls method because it
    simply didn’t work of me anyway.  This solution only gave the appearance of the header and
    footer existing.  I ran into issues because I was programmatically adding controls to the header. 
    Upon postback, if the data source was empty, the control hierarchy would not be the same as
    before thus causing an error.

    So here is my solution.  I'll cover the main points of interest and if you want to see more, I have
    uploaded all the source and example to the new MSDN Code Gallery.
    http://code.msdn.microsoft.com/AlwaysShowHeaderFoot

    Start out by extending the GridView control and add the following structure. 
    The most important part here is to override the PerformDataBinding method.
     ClassStructure

    As you can see here.  I am intercepting the data and making sure there is at lease one row. 
    If there is, the control behaves normally.
    On the line with the red arrow, I am checking if the binding source is a DataView. 
    If it is, I can just add a row here and be done with it.
    If the binding source is not a DataView (or DataSet), then I fire an event that will need to be handled.
     image

    The event "MustAddARow", as seen here, will provide access to the binding data and allow you to add a row.
     image

    Here is a snippet from a page where I handle the MustAddARow event.
    In this case, I am binding a List of Products to the GridView.
    As you can see, I'm just adding a new Product to the list.  It doesn't matter what data you add here
    because it will get hidden in back in the GridView.
    image

    And finally back in the GridView, I override the OnDataBound method so I can hide that extra row.
    image 

    So there you have it.  This is my first blog post ever. 
    Hopefully someone will get some use out of this.

    Thanks
    -Joe

    Read more...