Scott Hanselman

Being a good .NET citizen means certain things...start with your debugging skills

December 03, 2004 Comment on this post [5] Posted in ASP.NET | Web Services | Bugs
Sponsored By

I've not been one to work the newsgroups, answering questions. I probably should. I'm more of a one on one person, and I tend to go the extra mile when folks (largely strangers) ask me technical questions. I've had email threads 10-deep with total strangers on technical questions, and only at the end do I say, "Um, do I know you?"

I haven't done what Scott Mitchell wisely did and setup a "Getting Help" policy, but I'm quickly getting there. I'll happily answer your question for $75 too, satisfaction guaranteed, and I'll blog the answer. I've done this hundreds of times for free. :)

Anyway, the point of this post was this: People, for crying out loud, debug a few things before you ask for help. If you don't know how to debug, learn or ask someone to teach you.

So, I present:

The Hanselman List of .NET Debugging Dos and Don'ts

Don't - Say "Hey, I got a NullReferenceException," what's the problem?
Do - Provide a Stack Trace/Dump with the line number it likely happened on.

Don't - Get deep into your complicated program, find a bug and insist it's BillGs fault.
Do - Reproduce the bug in some simple test program and tell the world. Remember, 9/10 times it's you.

Don't - Decide there's a problem if you don't know the preferred behavior.
Do - Always Assert your assumptions. If null can happen, check for it. BUT, if null must never happen it's time for a Debug.Assert

Don't - Move code around blindly, somehow fix your bug, ignore it and keep coding. Programming by Coincidence!
Do - Understand your program fully. Remember what Andy and Dave say about lucky folks who step into minefields and don't die. Just because you didn't die, doesn't mean there aren't mines!

Don't - Reformat or "pave" something because you don't know what's wrong. If you get a spot on your carpet, fix the spot. Don't lay new carpet.
Do - Know enough about your environment to know what your program's dependencies are. If your registry settings can get boogered, Debug.Assert that you are getting good values from the registry.

Don't - Get overly frustrated with Assembly loading/versioning/policy. At least the Assembly Loader follows clear, set, rules.
Do - Make a folder called C:\FusionLogs, then go to the registry in HKLM:\Software\Microsoft\Fusion and make a DWORD value LogFailures=1 and string value LogPath=C:\FusionLogs. Every AppDomain that has a binding failure or weird redirect will get logged. Know: What assembly you want, what they looked for, what you got. Know where Assemblies are searched for.

Don't - Avoid debugging. Debugging in .NET is easier than ever before. Remote debugging and AttachToProcess are gifts. Don't stop at a point in the call stack if you can keep going by finding PDBs.
Do - Keep your Source and PDBs in the same location. We keep ZIPs of every build's PDBs. Just today we dug up 9-month old PDBs and source (from CVS) to debug into some confusion. Not saving those PDBs would have screwed us. Create a Symbol Server.

Don't - Limit yourself to the QuickWatch. Learn what VS.NET has to offer.
Do - Use the Immediate Window to test theories. Remember that you can perform Casts in the Watch Window. Remember that you can drag and drop variables into the Watch Window. Remember you have 4 Watch Windows, Autos, Locals, not to mention. Learn how to use Conditional Breakpoints!

Don't - not debug something just because you can't figure out how to launch the process from the VS.NET Project Properties.
Do - Debug|Processes|Attach to attach to processes that have your DLL loaded. Use ProcExp from SysInternals as a better Task Manager to see .NET processes, as well as a system-wide DLL search. Who's got you loaded?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

FireBlogging - The View From My House

December 02, 2004 Comment on this post [0] Posted in Musings
Sponsored By

It's 1:09am on Thursday, December 2nd 2004, and here's the view from my bedroom window. The house next door is burning and we share a wooden fence. Pretty exciting stuff! Fortunately, I'm not too worried, I'm in a family of fire-fighters.

CIMG2539 (Small) CIMG2546 (Small)

CIMG2547 (Small) CIMG2540 (Small)

P.S. For those of you not in the U.S., most, if not all, residential housing (especially in the Suburban Western U.S.) is made of wood and quite flammable. My wife's still not used to this fact, and her family isn't impressed that our family fights fires. :)

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Adobe PDF Reader slower than Molasses? Speed up Acrobat Reader 10x+

December 02, 2004 Comment on this post [10] Posted in Musings
Sponsored By

Here's a great little free util that Omar has found.  I used to move the plugin's manually to speed things up, but PDF Speedup makes me NOT DREAD opening a PDF anymore. BTW, does Acrobat 6 suck LOTS more than Acrobat 5? I HATE the new Find Dialog.

I have previously written about how darn slow Adobe Acrobat 6 is when launching. I don't understand why Acrobat is so darn annoying. Here are some things I don't care for:

* Don't create a “My eBooks“ folder in My Documents when I have nothing to put there.
* Don't load 500 plugins when none of them are necessary to view a PDF
* Don't place shortcuts for some lame Internet Printing thingy in my Start Menu (I loathe Start Menu Advertising)
* Do install a PDF IFilter so that indexing products like Lookout can index PDFs w/o installing it seperatley
* Don't load PDFs in IE because it is god awful slow
* Don't make copy and paste so freaking hard
* Don't ask me to install other Adobe software when I boot Acrobat
* Don't create an updater (6.0.2) that creates an additional entry in my add/remove programs

If you want to fix most of these things, PDF SpeedUp is a free application that should come bundled with Acrobat. It's a must have piece of software to make Acrobat behave (as much as you can anyway).
[shahine.com/omar/]

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

NUnit Unit Testing of ASP.NET Pages, Base Classes, Controls and other widgetry using Cassini (ASP.NET Web Matrix/Visual Studio Web Developer)

December 01, 2004 Comment on this post [6] Posted in ASP.NET | NUnit | Bugs
Sponsored By

There's a lot of info out there on how to cobble together NUnit Unit Testing of ASP.NET Pages and assorted goo. NUnitASP is a nice class library to facilitate this kind of testing, but it doesn't solve a few problems:

  • Do you have/want a Web Server on your Test/Build machine?
  • How do you get your Test Pages and such over to the Web Server? Just automatically copy them?
  • Are your Test cases self-contained? That is, do they require external files and other stuff to be carried along with them?

I have a need to test a number of utility classes, base classes for System.Web.UI.Page and other miscellany and I'd like the tests to be entirely self contained and runnable only with NUnit as a requirement.

So, here's a small solution we use at Corillian. I use Cassini, the tiny ASP.NET Web Server that brokers HTTP Requests to System.Web.Hosting and the ASP.NET Page Renderer. You may know Cassini as the precursor to the Visual Developer Web Server from Visual Studio "Whidbey" 2005. Cassini usually comes with two parts, CassiniWebServer.exe and Cassini.dll.  However, I don't want to launch a executables, so I'll just refer to Cassini.dll as that is the main engine.

using Cassini;

 

    [TestFixture]

    public class WebTests

    {

        private Server webServer;

        private readonly int webServerPort = 8085;

        private readonly string webServerVDir = "/";

        private string tempPath = AppDomain.CurrentDomain.BaseDirectory;

        private string tempBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"bin");

        private string webServerUrl; //built in Setup

Cassini is the 'private Server webServer' in the code above. I'm using a fairly random port, but you could certainly scan for an open port if you like. Note that I'm building a /bin folder, as Cassini's own ASP.NET Hostingn AppDomain will look for DLLs to load in /bin.

Cassini starts up another AppDomain, and that AppDomain then loads Cassini.dll AGAIN, except the new AppDomain has a different search path that includes /bin, so it won't find Cassini.dll in the current directory. Usually this problem is solved by putting Cassini.dll in the GAC, but I want this test to be self-contained, and since I'll need my other DLLs in /bin anyway...

[TestFixtureSetUp]

public void Setup()

{

    //Extract the web.config and test cases (case sensitive!)

    ExtractResource("web.config",tempPath);

    ExtractResource("test1.aspx",tempPath);

    ExtractResource("test2.aspx",tempPath);

 

    //NOTE: Cassini is going to load itself AGAIN into another AppDomain,

    // and will be getting it's Assembliesfrom the BIN, including another copy of itself!

    // Therefore we need to do this step FIRST because I've removed Cassini from the GAC

 

    //Copy our assemblies down into the web server's BIN folder

    Directory.CreateDirectory(tempBinPath);

    foreach(string file in Directory.GetFiles(tempPath,"*.dll"))

    {

        string newFile = Path.Combine(tempBinPath,Path.GetFileName(file));

        if (File.Exists(newFile)){File.Delete(newFile);}

        File.Copy(file,newFile);

    }

 

    //Start the internal Web Server

    webServer = new Server(webServerPort,webServerVDir,tempPath);

    webServerUrl = String.Format("http://localhost:{0}{1}",webServerPort,webServerVDir);

    webServer.Start();

 

    //Let everyone know

    Debug.WriteLine(String.Format("Web Server started on port {0} with VDir {1} in physical directory {2}",webServerPort,webServerVDir,tempPath));

}

A couple of important things to note here. 

  • This method is marked [TestFixtureSetup] rather than [Setup] as we want it to run only once for this whole Assembly, not once per test.
  • We're copying all the DLLs in the current directory down to /bin for Cassini's use during the test, but we'll delete them in [TestFixtureTearDown] after Cassini's AppDomain.Unload.
  • We build webServerUrl and start Cassini (remember, it's the "webServer" variable).
  • At this point we are listing on http://localhost:8085/

Additionally, I've snuck a new method in, ExtractResource(). This takes the name of an Embedded Resource (one that is INSIDE our test assembly) and puts it into a directory. In this case, I'm using the current AppDomain's directory, but you could certainly use Path.GetTempPath() if you like.

private StringCollection extractedFilesToCleanup = new StringCollection();

private string ExtractResource(string filename, string directory)

{

    Assembly a = Assembly.GetExecutingAssembly();

    string filePath = null;

    string path = null;

    using(Stream stream = a.GetManifestResourceStream("Corillian.Voyager.Web.Test." + filename))

    {

        filePath = Path.Combine(directory,filename);

        path = filePath;

        using(StreamWriter outfile = File.CreateText(filePath))

        {

            using (StreamReader infile = new StreamReader(stream))

            {

                outfile.Write(infile.ReadToEnd());   

            }

        }

    }

    extractedFilesToCleanup.Add(filePath);

    return filePath;

}

The ExtractResource method takes a filename and directory (and could, if you like, take a namespace, although I've hardcoded mine) and pulls a file as a Stream of bytes that was embedded as a resource in our Assembly and puts it into a directory. Hence the name, ExtractResource(). There is a StringCollection called extractedFilesToCleanup that keeps track of all the files we'll want to delete at the end of these tests, in [TestFixtureTearDown].

At this point, I've got a web.config (which was important to my tests, and will be looked at by Cassini/System.Web.Hosting) and a test1.aspx and test2.aspx in my current directory. The Cassini Web Server is started up and listing on port 8085 for HttpRequests. I also turned Page Tracing on in the web.config which will allow me to make certain Assertions about what kinds of code were called by the Page and helper classes within the Cassini ASP.NET context.

I'll add a helper method to create HttpRequests and return the response as a string:

private string GetPage(string page)

{

    WebClient client = new WebClient();

    string url = new Uri(new Uri(webServerUrl),page).ToString();

    using (StreamReader reader = new StreamReader(client.OpenRead(url)))

    {

        string result = reader.ReadToEnd();

        return result;

    }

}

Now I can write some tests! My tests need to test things like the overridden behavior of custom BasePages (derived from System.Web.UI.Page) as well as some helper functions that require (to be TRULY tested) an HttpContext. However, I don't want the hassle of a CodeBehind or a lot of other files, and certainly not the effort of a whole separate test project, so I'll make my ASPX pages self contained using <% @Assembly %> directives. So, for example: 

<%@ Assembly Name="Corillian.Voyager.Web.Common" %>
<%@ Page Language="C#" Inherits="Corillian.Voyager.Web.Common.SharedBasePage" %>
<script runat="server">
    public void Page_Load(object sender, EventArgs e )
    {
        Label1.Text = "Hello";
    }
</script>
<html>
  <body>
    <form runat="server">
        <p>
            <asp:Label id="Label1" runat="server">Label
            </asp:Label>
        </p>
    </form>
  </body>
</html>

A test to determine if this page executed successfully might look like:

[Test]

public void BasicSmokeTestOfWebServer()

{

    string result = GetPage("test1.aspx");

    Assert.IsTrue(result.IndexOf("Hello") != -1,"Basic smoke test of test1.aspx didn't find 'Hello' in response!");

}

You could easily expand your tests to include NUnitASP, or continue to use WebClient with specific HttpHeaders like accept-language or user-agent. One of our QA guys uses NUnit to automate the IE WebServer Control, the manipulates the DHTML DOM to make Assertions.

Finally, my cleanup code is simple, deleting all the files we extracted as well as toasting the /bin folder.

[TestFixtureTearDown]

public void TearDown()

{

    try

    {

        if (webServer != null)

        {

            webServer.Stop();

            webServer = null;

        }

        CleanupResources();

        Directory.Delete(tempBinPath,true);

    }

    catch{}

}

 

private void CleanupResources()

{

    try

    {

        foreach(string file in extractedFilesToCleanup)

        {

            File.Delete(file);

        }

    }

    catch (Exception ex)

    {

        Debug.WriteLine(ex.ToString());

    }

}

Now the WebServer, Test Cases and Test are nicely self-contained and can be moved to the CruiseControl.NET Continuous Integration Build Server. 

Enjoy. I did.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Opportunity: Windows is completely missing the TextMode boat...

December 01, 2004 Comment on this post [12] Posted in Musings
Sponsored By

With all this talk of shiny Avalon, I'm surprised that more people aren't mentioning "text-mode" applications.  I assume we all realize that there are literally millions of Windows machines from 95 to XP that exist only to allow more than one Telnet/ProcommPlus/Terminal window at a time, so end-users can interact with remote systems.

Point of Sale is a huge example:

  • Blockbuster Video – I'd hate to have the video store guy have to reach for a mouse and click on a Gray Screen button OR a shiny Avalon Form.
  • Toyota Service – Searching for Parts, making service appoinments, it's considerably faster in text mode than any *.*Forms technology, and I've seen them open as many as 8 windows at a time.
  • Teller Banking Systems – Many banks are changing their TextMode systems over to intranets, and I personally waited 90 mins at a large bank last week to open a checking account, while I watch the teller move between three intranet ASP applications and two Word Macros, then attaching the Word files to an Outlook Email.  This same process, in text mode, at First Technology Credit Union took 10 mins. 

I'd like to see how far someone could take the new Colored Console support in Whidbey and make me a forms renderer. 

I’m just saying that my Tab,Tab,Tab,Enter will beat your Click,Tab,Alt-F,O,Click,Double-Click, more often than not and I will take the Pepsi Challenge otherwise. :) 

Am I nuts to think that Windows is missing the text-mode boat?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.