CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Grant Killian's Blog

No, this has nothing to do with beer -- but maybe it should?

September 2003 - Posts

  • Technical Documentation Nirvana

    One project I'm working on is approaching the exciting “ready for external beta testers” phase.  Like too many projects, the original design documents are scattered and haven't been updated.  I've been tasked with mapping a course to technical documentation nirvana, with scarce resources to utilize; my primary responsibilities are to final product polishing and working with the Sales group as to product direction, etc -- this course will be taken by a more junior person under my limited supervision, but they're familiar with the product so they'll do fine.

    Nirvana would consist of being able to bring other developers up to speed quickly with the help of this technical documentation.

    The old docs are so bad that I'm acting as if they don't exist.  I know C# comments will produce a form of technical documentation (HTML and all that), and combined with a database diagram (from SQL Server), this may be the path of least resistance.  I know there are reverse engineering tools out there and other modeling systems I could use, but I'm not convinced.

    Before starting down this road, I'm curious as to what others have used to achieve some measure of Tech Doc Nirvana?  Any bodhisattvas out there?

    Happy .Netting!

  • Server Script Tags for ASPX

    The Windows Apps for .Net class I'm teaching is wrapping up and I'm starting to look forward to the Web Apps for .Net sections. I like the web stuff better, probably because most of my programming jobs are more web-centric. For people transitioning from ASP "classic" to ASP.Net the behaviour of the server script tags can be confusing. If you know where to look on the webserver, however, one can gain some insight into the rules of server script tags and also how ASP.Net processes your pages. I'll walk through this here.

    If you create a new web app in VS.Net, WebForm1.aspx will be created by default. If you go into the HTML view of this page, you can add some code to end up with the following:
    <HTML><HEAD></HEAD>
    <body><form id="Form1" method="post" runat="server">
    <%
    Response.Write( System.DateTime.Now.ToShortDateString() );

    private void writeStuff() {
      Response.Write( System.DateTime.Now.ToShortDateString() );
    }
    %>
    </form></body></HTML>

    ASP "classic" would process a page like this without error (assuming you were using vbScript, Jscript, etc); you'd get the current date listed on the page twice. Much to the dismay of the new ASP.Net developer, however, .Net throws a compilation error with a page like this. The contents of the server script tags (between the angled brackets and percent signs) aren't permitted in the ASP.Net model.

    What's up?

    Start by deleting the writeStuff method, it's the "wrong stuff" in our example (sorry, couldn't help it). Let's replace it with some code to help us understand what .Net is doing:
    <script runat="server" language="C#">
    private void writeSetupInfo() {
      Response.Output.Write( "{0}<BR>", AppDomain.CurrentDomain.SetupInformation.CachePath );
      Response.Output.Write( "{0}<BR>", AppDomain.CurrentDomain.SetupInformation.ApplicationName );
    }
    </script>
    <%
      writeSetupInfo();
    %>

    Note how this new code shows the two forms of server script tags, the literal "<script . . ." and the more terse "<% . . . ", and the proper usage pattern for ASP.Net. As you'll see, methods can be defined inside the literal script tags while executable code (but not method definitions) can be in the terse "<%" tags. The writeSetupInfo method writes two strings of text to the browser, the first string is an absolute path to where ASP.Net places the cached output from your ASP.Net compilations. The second string is the name of the application according to ASP.Net's internal compilation engine . . . it is also the folder beneath the CachePath directory to find the specific .Net output of your program. If you browse the file system to the CachePath directory, and then open the AppName folder beneath the CachePath, you'll find what VS.Net generates as a result of compilation. Note that these CachePath and ApplicationName properties can be configured, I'm not so interested in that right now so I just go with the defaults.

    We're only interested in one file in this directory, and it's name will vary; it's a .cs file, and if you sort the files by Date Modified, it will be the most recent file. In my experience, the file name will be something like "puun_xsh.0.cs". Open this file with Notepad, VS.Net, or whatever you prefer. This cs file is a generated representation of the WebForm1.aspx file.

    In ASP "classic", I sometimes generated entire pages with Response.Write statements; in one project we even used COM dlls as our asp page factory -- writing text from the COM dll to the Response stream. I'd start with Response.Write( "<HTML><HEAD>" ) and so on.

    The ASP.Net framework builds on this concept of generating pages by writing HTML from code and creates a class file for every aspx page; the class file for our WebForm1.aspx is the *.cs file I'm having you explore. There's a lot to the generated file. Don't be intimidated by the #line statements you find or anything else, this is just an ordinary c# file like any other and conforms to c# grammar/syntax (the #line statements aid in debugging and exception messages -- msdn help can clarify if you need). You'll find a __BuildControlTree method in this file that contains the code to produce some of your literal HTML with statements like:

    __parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n<HTML>\r\n<HEAD>\r\n</HEAD>\r\n<body>\r\n"));

    You'll also see the writeSetupInfo method you defined in the literal server script tags ("<script . . ."); code inside the literal script tags gets compiled into the definition of the class -- this is why simple code execution statements like Response.Write( "Hello" ); will fail with ASPX. Try adding a statement like Response.Write( "Hello" ); to the literal server script block, recompile your program, and check out the resulting class file created in the CachePath. You'll see:
    public class WebForm1_aspx : aspxFoundations.WebForm1, System.Web.SessionState.IRequiresSessionState {
    Response.Write( "Hello" );
    /* . . . everything else omitted . . . */
    }

    Classes cannot have arbitrary code statements outside of method definitions; all code contained inside literal ASPX "<script" tags must abide by this same rule.

    Moving on to the terse "<%" tags, everything placed inside these gets included in a "__Render.." method of our generated class. __RenderForm1 contains our call to writeSetupInfo and other executable statements. Try placing the "writeStuff" method we had earlier into the terse "<%" tags and see where the code gets placed in our generated file. It's like we're trying to nest methods within methods -- and why our page throws an exception.

    Will you frequently browse the cached output of your ASPX applications? Doubtful; it's an academic exercise. But for those curious or struggling with the ASP to ASPX conversion, this can be a real eye opener. The code placed inside ASPX "<script..." tags gets combined with the type definition of the page class -- so no code statements outside of methods are allowed; the code place inside terse ASPX "<%" tags gets placed inside a method definition of the page class, so only executable code statements are permitted.

    Happy .Netting!

  • NGen NSight

    Courtesy of Brad Abrams, Jason Zander has an excellent post on NGen.  It's timely, since our .Net class just completed the Setup and Deployment portion of the curriculum (where we discuss NGen).

    In the past, setup and deployment was a dull topic to present to a class; I'd have to really get my carnival act together to engage the students with the material.  With VS.Net, however, Microsoft has mercifully discarded the Package and Deployment Wizard and made a much more professional system for creating deployment packages.  It may not quite put Installshield or Wise out of business (yet), but it's a vast improvement.  Presenting the subject to a class is easier because I certainly “buy in” to the technology, especially the extensibility available through custom actions.

    Happy .Netting!

  • VB.Next

    “Whidbey“ is the codename given to the new version of VB.Net, to be released in 2004.  Check it out here: http://www.ftponline.com/reports/vsliveny/2003/multimedia/demo_whidbey.htm (courtesy of Jan Tielen's blog)

    It's hard to make out the details with the small window, but you can get the idea.

    Happy .Netting

  • Async Exception Handling

    Before I went away on holiday, a few people asked me about exception handing for asynchronous methods -- possibly because they were experimenting with the various async examples I had blogged about.  While I don't have a lot of time (I'm supposed to be writing a lot of code documentation for a project I'm on --yuck), I need a break so let me share the error handling mechanism I usually employ for asynchrounous methods.  It's nothing more sophisticated than adding an "ErrorText" member variable to the custom eventargs class; I pass types of this eventargs class to my callback function and, therefore, the callback function can examine the value of the property to see if there is any error text to deal with.  This class serves as the messenger between the threads of execution.

    My custom eventargs class looks something like this (I've omitted the propery sets and gets and some other code for brevity):
     public class StatusEventArgs : EventArgs
     {
      public string ErrorText;
      public StatusEventArgs()
      {
       this.ErrorText = "";
      }
     }

    When an exception is raised, I simply assign a value to the ErrorText property in the catch portion of my try-catch block (e.ErrorText = ex.ToString() ) and then I use the callback method I discussed earlier to communicate the condition back to the client.

    Nothing too fancy, I treat Exceptions in the async component like state in any other business object.  There's many angles you could take with this.  You could expose a generic Exception object from the StatusEventArgs class instead of the string variable and use e.TheExceptionObject = ex; you'd have access to the full exception type generated by the asynchronous method this way.  I know some people who prefer using integer values to communicate Exception severity to the client, and this model would certainly accomodate that.  Others may want to use a simple boolean for success or failure, and again the approach I present will suffice.  The point is, extend the object you pass back and forth for asynchronous communications to include any error information you may be interested in.

    Happy .Netting!

  • Java as an SUV?

    I'm catching up on the blog-o-sphere that I missed while out of town.  I really like this post by Philip Greenspun (courtesy of Don Box); he likens Java to the SUV of software development. 

    It's been a while since I've touched anything Java (besides JavaScript -- which is very different despite the marketing folks at Netscape branding their scripting language after Java!), but I can relate to those students struggling to make heads or tails of the Java framework.  Once I got beyond the complexities and became comfortable with Java, it was indeed powerful.  I found switching to .Net (c# particularly!) pretty easy -- partly because of the hard work I invested in learning Java and the Java APIs.  Somebody new to .Net might feel like it's an SUV, too, but I think the tools and support from the .Net community have matured more quickly than Java ever did.  This is probably because Microsoft leverages the experience of others like no other business I know, and I come from a strong Visual Studio background.  The convergence of the familiar and slick development environment, Visual Studio, with the powerful .Net platform really struck a chord with me. 

    Going forward, Microsoft (a software company) and .Net are very closely tied; compare that to Sun Microsystems (when it comes down to it a high-end server company) and Java -- not the same sort of relationship; what does Sun get from Java anyway?

    Happy .Netting

  • I've been out . . .

    I'm sure my 3 loyal fans were concerned, but no need to worry!  I was on vacation to New England and then this hurricane thing hit . . . it really puts things into perspective.  Luckily, our neighborhood has power; my unscientific survey of students in the .Net Setup and Deployment class from last night showed only 10% had power at their homes.  It's astonishing!

    Anyway, I'm climbing back into the blog saddle so stay tuned.

    Finally, if anyone is looking for software developers in Maine, let me know -- I could be easily persuaded to spend some more time up there!  Maybe I should arrange a programmer-exchange program where a “country mouse” programmer switches with a “city mouse” programmer.  I'll have to look into it!  

  • Jupiter and Bitter Beta Kool-Aid

    I presented on BizTalk 2004, code-named 'Jupiter,' and .Net last night.  I think I may have scared people away from BizTalk 2004 with my warnings about the documentation and the tutorials.  While the support material certainly is lacking and there are errors/oversights in the tutorials, this is common to working with product betas in general; I call this the "bitter beta kool-aid."  In defense of the product, they note in their materials that this is a beta and it does look like there are some very cool features/enhancements to BZ2004; after all, they aren't claiming to serve the finest kool-aid in the land! 

    When BizTalk 2004 is officially released (early 2004 from my understanding), and the documentation/community support matures, this third iteration of the messaging/orchestration platform may be the one that pushes BizTalk to be adopted more widely; the flexibility in port configuration and .Net integration will make it very attractive.  It is pricey, however . . .

    I know, kool-aid snobs shouldn't go looking for the good stuff from a beta release, but I sure had my hopes up.  I didn't have the time to wade through all the obstacles the BizTalk 2004 beta stood in my way, so I brewed a home-grown integration engine instead, small and simple -- and more importantly on time.  

    I should add that my friend Jianming Li from UniSys rounded out the presentation very nicely.  He presented his success story with an earlier version of BizTalk and the creation of a local government product.  My favorite quote from his portion was about the complexity of some of the XML standards: "I don't know how anyone can take the time to understand all that stuff."  I know how he feels!

    So, for the record, BizTalk 2004 is a vast product that does a lot of very cool things, taking previous versions of BizTalk to new levels.  Most people wont have the time to persevere with the Beta, relying on a single BZ2004 newsgroup for support, unless they're doing R&D and have the luxury of time.  Keep it in mind, however, once the release hits the market as community support should not be underestimated!

    Happy .Netting!

  • Get To Know XPath

    Piggy backing on the XML discussion we had in class last night, let me for the record provide Don Box's post on XML and COM.  If you only track one blog (besides mine, of course!), I'd make a strong case for Don's based on the depth of information he presents; some of it verges towards the academic, but it is always detailed and interesting.

    Our class curriculum unfortunately skirts around XML, since the certification exam doesn't require much understanding of the topic; I hope most people don't care about only what's on the exam . . . and actually intend to truly learn .Net -- otherwise, you might get a job due to your certification, but you'll lose your job because you can't back it up.

    That being said, let me introduce XPath; XPath is a W3C recommendation (which means it's widely used at this point -- and not a Microsoft proprietary thing) for working with XML data.  We use it, for example, on WeProgram.Net to dynamically load the latest blog headlines from our members. 

    The following vb.Net code, while in C# on WeProgram.Net, is taken from my work there.  If you put a Button and a ListBox onto a windows form, and place this in the button click event, you can see how XPath works:

            dim xDoc as New Xml.XPath.XPathDocument( “http://dotnetjunkies.com/weblog/grant.killian/Rss.aspx” )
            dim xNav as Xml.XPath.XPathNavigator = xDoc.CreateNavigator()
            dim xIterator as Xml.XPath.XPathNodeIterator = xNav.Select( "/rss/channel/item/title" )
            while xIterator.MoveNext()
                listbox1.Items.Add( xIterator.Current.Value )
            End While

    You'll see a list of Titles from my recent posts in the listbox; in a few short lines of code, we connect to and queried an internet data repository (served up as RSS in this case).  When we construct our XPathDocument, we pass it an http address for the raw data.  The Select() statement on the XPathNavigator object, specifically, is where we query the contents of the RSS feed coming from this blog.  There's a lot of MSDN documentation on constructing XPath query statements; you'll find it no more difficult than SQL, but the syntax is very different.  The XPathNodeIterator lets us quickly and easily move across our data.  Microsoft's implementation of XPath has limitations, of course, usually when working with very large amounts of XML, but Xml.XPath is a great namespace to get to know. 

    This sample could easily be extended to use a textbox to control the string argument we pass into the XPathDocument constructor; we could type in “http://www.gotdotnet.com/team/dbox/rss.aspx” as another RSS feed.  Going further, we could pull back more than just the posting title; we could grab the date, the url address, even the entire content of it.  You could get really fancy and tie a database into the system, perhaps storing blogs for whatever purpose you dream up.  Before you know it, you'll be rolling your own blog aggregator like SharpReader by Luke Hutteman.  Not incidentally, I use SharpReader to track the several dozen blogs I follow; I highly recommend it if you follow blogs at all.

    Happy .Netting

  • .Net Breeds Workaholics?

    Dang!  I know I prefer posting code, but I feel moved to make this one less technical entry.  I just finished giving a class on XML and ADO.Net and jumped online to check email before heading home.  I responded to a few messages (some technical, some not) and -- this is the interesting part -- every person I responded to wrote me back immediately.  I, in turn, responded to them and around-and-around-we-go.  It's almost 9 PM here on the East Coast.  These same people, including bloggers like Darrel and Mark, are online early in the mornings too.  What is happening?  Do all the people I work with, including myself, have a case of workaholism?  

    I know the programming subculture thrives on an almost frantic work ethic, but when is it too much?  Besides class tonight and Weds, I'm giving a BizTalk and .Net talk tomorrow with a colleague.  This is an unusually busy week for me, but it's not uncommon for me to work into the wee hours of the morning.  Apparently, working beyond the 9-5 schedule is a standard in the software business.  Does your employer even know or appreciate it?  What about your family?

    Speaking of family . . . I'll wrap this up and go home to dinner . . .

  • Managing DataReaders vs DataSets

    A student prompted this post by asking about passing DataReaders from business objects, and what that means for managing connections, etc.  I have a lot to say on the topic as it leads towards so many other subjects; I'm struggling to keep my points organized and concise.  Let me take a stab at it . . .

    DataReaders are attractive for speed purposes, according to conventional wisdom, but I'm confident in sharing that DataSets and DataTables are my standard approach for windows apps because 1) handling data requests asynchronously is easy in .Net making the performance gains not so evident (sometimes tilting in favor of DataSets!) and 2) avoiding DataReaders allows me to use a cleaner data-access layer in my apps that only exposes DataSets/DataTables instead of the trickier DataReader, thus making for a more consistent design.

    Taking these points in reverse order, I say the DataReader is trickier because you must manage the connection carefully.  A good data-access layer might use the following to generate a DataReader:
        Public Function getDataReader( byval SQL as String ) as IDataReader
                dim cn as new SqlConnection( System.Configuration.ConfigurationSettings.AppSettings( "DSNSQLSERVER" ).ToString() )
                cn.Open()
                dim cmd as New SqlCommand( SQL, cn )
                dim dr as IDataReader = cmd.ExecuteReader( CommandBehavior.CloseConnection  )
                return dr
        End Function

    A few coments: I would avoid coupling my user-interface layer to any specific database implementation; this means working with the interface IDataReader instead of a specific SqlDataReader, OleDbDataReader, or any of the others.  Notice the CommandBehavior enum argument to ExecuteReader; this means our db connection will be closed when we close our DataReader.  Unless you plan to pass Connection instances as arguments (yuck), this is a necessary step if you're in the business of passing DataReaders around this way.  If I closed the connection within my getDataReader method, my DataReader would be empty; DataReaders require an open connection to the database, so all my client methods have to be sure to call dr.Close() in order for my connection to be released appropriately.  Conceptually, I don't like the idea of returning a data container with a direct link to the datasource; it doesn't seem like a clean design to me, especially when ADO.Net does such a good job with disconnecting data from the source. 

    Here is a simple, hypothetical data-access layer:
    Class dbLayer
        Public Function getDataSet( byval SQL as String ) as Object
                dim cn as new SqlConnection( System.Configuration.ConfigurationSettings.AppSettings( "DSNSQLSERVER" ).ToString() )
                cn.Open()
                dim da as New SqlDataAdapter( SQL, cn )
                dim ds as New DataSet()
                da.Fill( ds )
                cn.Close()
                da.Dispose()
                return ds
        End Function
        Public Function getDataReader( byval SQL as String ) as Object
                dim cn as new SqlConnection( System.Configuration.ConfigurationSettings.AppSettings( "DSNSQLSERVER" ).ToString() )
                cn.Open()
                dim cmd as New SqlCommand( SQL, cn )
                dim dr as IDataReader = cmd.ExecuteReader( CommandBehavior.CloseConnection  )
                return dr
        End Function
    End Class

    Both the "getData..." methods accept a SQL string and return a generic object; you'll see why in a few paragraphs.  In a real app, I would introduce an custom db interface layer to program my client against, but I want to wrap this up before work on Monday, so I cut that out.

    On paper, the DataReader is the fastest way to obtain "a forward only, read-only set of data," right?  Not always.

    Let's use the following client code in a windows form:
       
       Dim startTime as DateTime
       Dim dbQueryCompleteProcessor As New AsyncCallback( AddressOf Me.dbQueryComplete )
     
       Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim obj As new dbLayer
            Dim del As New DBWorkDelegate( AddressOf obj.getDataSet )
            starttime = system.DateTime.Now
            del.BeginInvoke( "SELECT TOP 100 LastName FROM Employees ORDER BY LastName", dbQueryCompleteProcessor, del )
        End Sub
        Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
            Dim obj As new dbLayer
            Dim del As New DBWorkDelegate( AddressOf obj.getDataReader )
            startTime = System.DateTime.Now
            del.BeginInvoke( "SELECT TOP 100 LastName FROM Employees ORDER BY LastName", dbQueryCompleteProcessor, del )
        End Sub

        Public Delegate Function DBWorkDelegate( byval strSQL as String ) as Object
        Sub dbQueryComplete( ByVal ar As IAsyncResult )
            if me.InvokeRequired then
               dim objArgs( 0 ) as Object
               objArgs( 0 ) = ar
               me.Invoke( new AsyncCallback( AddressOf dbQueryComplete ), objArgs  )
            Else
               Dim del As DBWorkDelegate
               del = CType(ar.AsyncState, DBWorkDelegate)
               Dim objResult as Object = del.EndInvoke( ar )
               if typeof objResult is DataSet then
                    listbox2.DisplayMember = "LastName"
                    listbox2.DataSource = ctype( objResult, DataSet ).Tables( 0 )              
                else
                   dim objDR as IDataReader = ctype( objResult, IDataReader )
                    while( objDr.Read() )
                       listbox2.Items.Add( objDr.GetString( 0 ) )
                    End While
                   objDR.Close()
               End If
               ListBox1.Items.Add( "Time: " & System.DateTime.Now.Subtract( startTime ).TotalMilliseconds  )
            End if
        End Sub

    If you're following along at home, you'll want to slap two button and two listbox controls onto the form (leave their default vb.net names).

    My two button click events create the worker delegate and invoke it; nothing I haven't already discussed in earlier posts.  The only interesting tidbit here is the dbQueryComplete method; it starts by synchronizing our asynchronous stuff (discussed more here), and then processes the results based on the type of object that's returned from our data-access layer.  This is one reason why our data-access methods all return object, we only need a single dbWorkDelegate to handle them in our client application.  We could break this up so we use a different delegate for each method, but I'm striving for consistency and ease of use.

    You'll also see some timer code in there; it's interesting to note that once we return more than 10 or 20 records, our DataReader starts to perform more slowly with this architecture.  Apparently, iterating through the IDataReader is slower than just assigning the Datasource property to a DataTable or DataGrid.

    What this is moving towards is a pattern for accessing data and asynchronous communications; wouldn't it be great if there was something that tackled this stuff for us?  Luckily for us, there's the open source Asynchronous Invocation Application Block and Data Access Application Block from Microsoft.  This is the MSFT attempt to abstract some of the details away for these frequently used programming tasks.  Instead of having to roll you own, you can leverage the codebase MSFT has made available; for the record, I like rolling my own for a number of reasons, including the understanding/appreciation it provides as to how the abstractions are working on our behalf!  Never underestimate getting behind the scenes! 

    Try this sample code out and appreciate that this example is contrived; you have to imagine a larger scale application making broader use of the dblayer   Building on this, I will post about the Application Blocks sometime soon, so stay tuned.

    Happy .Netting

  • In Lieu of a 5 day ADO.Net Bootcamp . . .

    Since our class is in the middle of the ADO.Net material, I feel it's my responsibility to share some lessons from the real (as opposed to the academic) ADO.Net world; the class material and the Microsoft Press book skirt around a lot of information that is important to any real world development effort.  Honestly, we could devote an entire 14 sessions just to ADO.Net (and WeProgram.Net has talked about offering something like this in a “ADO.Net for Enterprise Applications“ bootcamp format -- shoot me an email if you'd be interested).  Here are some good references I frequently use for ADO.Net . . .

    First off, Bill Vaughn does a fine job of pointing out the shortcomings of using the commandbuilder in this MSDN piece; he also discusses some better alternatives for working with databasesPaul Laudeman turned me on to this link several months ago, and I break it out each time ADO.Net comes up in the ITPro curriculum.

    Second, Microsoft has provided an open source layer between your ADO.Net objects and your business objects; it's called the Data Access Application Block and you can read all about it here

    Instead of writing something like this:
    dim cn as new SqlConnection( K_DSN )
    cn.Open()
    dim cmd as new SqlCommand( sql, cn )
    cmd.ExecuteNonQuery()

    You can write this:
    SqlHelper.ExecuteNonQuery( K_DSN, CommandType.Text, sql )

    And that's just the tip of the iceburg; it's a big productivity boost as the SqlHelper class wraps the lower level ADO.Net objects in a more developer-friendly fashion.  I reference it in most of my applications. Microsoft has a number of other Application Blocks for simplifying common development tasks, just search MSDN for "Application Blocks" and you'll turn them up.

    Third, this link offers architectural guidelines for data access and serves as a solid real-world reference

    Finally, no data acess desicisions should be made without also considering performance.  Here is a detailed treatment comparing various data access alternatives from Microsoft's performance testor/author Priya Dhawan.

    This is enough to get you to off to a good start, but I'd be curious to learn what other resources people find useful.

  • More on Asynchronous Stuff

    I’ve been posting a bit on delegates and asynchronisity (wasn’t that a song by the Police?), and I just finished a messaging implementation for a project that speaks to this topic.  I've simplified it some, hoping it's easier to follow this way.  I’m using C# in this example, but many of the concepts apply to the VB.Net folks too.

     

    The Problem: a long-running operation (minutes or hours in duration) needs to process without preventing the user from doing other things with the application; the user needs to have updates as to the progress of the operation.

     

    The Solution Level 1: I decided to implement a winform application (to avoid timeouts and other ugly async issues with a web solution), using a progress bar to show how the long-running operation was doing, and display a dialog box when the long running task was completely finished.  The long-running operation would be invoked asynchronously.

     

    The Solution Level 2:  The trick is communicating between the operation and the windows application.  I need a thread-safe means to invoke methods on the windows application from within the asynchronous, long-running operation.

     

    The Solution Level 3:  I need to launch the long-running operation asynchronously (delegate #1), and I need to communicate with the windows app from within the long-running operation (delegate #2). 

    I started by building my own EventArgs class:

                public class StatusEventArgs : EventArgs

                {

                            public bool isComplete;

                            public int TotalProgress;

                            public int ProgressSoFar;

                            public StatusEventArgs( int totalProgress, int progressSoFar )

                            {

                                        this.isComplete = false;

                                        this.TotalProgress = totalProgress;

                                        this.ProgressSoFar = progressSoFar;

                            }

                }

    This class extends the behaviour of the .Net EventArgs class and you can pass this object around in lieu of the generic EventArgs class; it contains the specific information I’m interested in communicating between my asynchronous operation and the windows client.  File this away as this is a handy tactic to bring into your general .Net programming.

     

    Next, I need a delegate (think typesafe function pointer) to invoke from my asynchronous operation:

    public delegate void showProgressDelegate( object sender, StatusEventArgs e );

     

    My long-running operation will accept a showProgressDelegate as a parameter to the method and make calls to the windows app via the delegate whenever it needs.  Here’s a condensed form of the long-running operation:

                            public void populateProducts( showProgressDelegate ShowProgress )

                            {

    StatusEventArgs e = new StatusEventArgs(  0, 10 );                                

    ShowProgress( sender, e ); //call to win app                      

                                        for( int x = 0; x < 10; x++ )

                                        {

                                                    System.Threading.Sleep( 5000 );

                                                    e.ProgressSoFar+=1;

                                                    ShowProgress( sender, e ); //call to win app

                                        }

                                        e.isComplete = true;

                                        ShowProgress(sender, e); //final call to win ap

                            }

    You can see how convenient it is to have your own custom EventArgs class; I’m incrementing the progress while we loop through our time consuming process.  Every call to ShowProgress communicates back to the windows client courtesy of .Net delegates. 

     

    Let me go ahead and show the windows client code (note that eManagerLib is the name of my class library containing the long-running operation and related code):

    public delegate void ShowProgressHandler(object sender, eManagerLib.StatusEventArgs e);

    private void btn_Click(object sender, System.EventArgs e)

    {

    eManagerLib.showProgressDelegate del =

    new eManagerLib.showProgressDelegate( ShowProgress );

                eManagerLib.ingramUtil obj = new eManagerLib.ingramUtil();

                ClientShowProgressDelegate  cliDel =

    new ClientShowProgressDelegate( obj.populateProducts );

                cliDel.BeginInvoke( del, null, null );

                btn.Enabled = false;

    }

    void ShowProgress( object sender, eManagerLib.StatusEventArgs e)

    {

    if( this.InvokeRequired == false )

                {

                            ShowProgressHandler showProgress =

    new ShowProgressHandler( ShowProgress );

                            Invoke( showProgress, new object[] { sender, e } );

                }

                else

                {

                            progressBar1.Maximum = e.TotalProgress;

                            progressBar1.Value = e.ProgressSoFar;

                            if( e.isComplete )

                            {

                                        MessageBox.Show( "Finished" );

                            }

                }

    }

    In a nutshell, in our button click event we construct a new showProgressDelegate (del) and a new ClientShowProgressDelegate (cliDel).  Our cliDel delegate points to the long-running operation (obj.populateProducts) and we invoke this delegate using BeginInvoke and pass the second delegate (del) for use in the populateProducts method.  When populateProducts chooses to notify the windows application, it will call the windows app’s ShowProgress method.  The two delegates provide our asynchronous messaging system: ClientShowProgressDelegate launches our asynchronous operation, the ShowProgressDelegate calls back to the windows app to update the client.  Our ShowProgress method receives the calls from the long-running operation, synchronizes them onto the proper UI thread (thanks again Peter), and updates the windows form appropriately.

     

    Note that I’ve omitted error handling and some other tidbits in the hopes of making this example clear and terse.  You can capitalize on this sort of framework to solve all sorts of messaging problems  -- no need to continuously poll a database, file, or static variable, let the components of your system send their own messages with a little .Net delegate elbow grease.

     

    Happy .Netting!

More Posts