The Value of a TestApp

April 16th, 2007 by davidwise

 

The single greatest programming I've ever received is this: Use test apps religiously.  In this world of Nunit and the like, perhaps I should explain what I mean by test apps.

 

What is a test app?

A test app is simply the absolute smallest amount of code that you can use to create your new functionality in.  In my case, I usually create a new Windows Application in Visual Studio and put starter code in the Load event.  If I feel like being extravagant, I'll drop a button on the form and put the hook code in there instead.  That's it; the entire framework of a test app.

 

A test app is meant for creating a single piece of production code.  It lives for the code, it dies for the code and nothing beyond what is needed to support that code should *ever* be added.  Do not fall into the trap of creating a complex test application that can handle dozens of bits of code.  This is unmanageable and defeats its own purpose as the code in it gets further removed from the real code over time.  Disk space is cheap, don't be afraid to create a few folders.

 

Test apps are not to be confused with Test Harnesses and utilities like NUnit.  These tools are invaluable but are meant to be applied to the full body of code.  The test app is only for one little bit of functionality.  If you really feel that compelled to use NUnit everywhere, you can wire it into your test app as well :)

 

Why a test app?

  • Lightning fast revisions and compilation.  Since the test app is so small, you can test, fix, build and retest several times in the same amount of time that it would take to compile the main application once.
  • Fully Isolated debugging.  If it breaks in the test app, you are 99.999% sure that it is your code.  In the main app, things can break for an almost infinite number of reasons.   Perhaps someone checked in bad code, the database is down, someone playing with test data – all of that is completely unrelated to the code you are working on but slows you down nonetheless
  • You get full debugging abilities on the test app.  This may not always be the case in your test environment.  In many test environments, you are limited to writing to logs for everything.  This is nice, but far less useful than a fully interactive debugger.
  • Rich testing.  You can define any number of scenarios to run your new functionality through simply by copy-pasting a line a code in the Shell and changing a parameter or two.
  • Performance testing.  If you suspect that your code might be a bit slow, it is trivial to set up loops and timers in the test app.  I can run my code through millions of iterations with almost no effort in the test app.  Try setting up this type of test in the main application and you will immediately see the perks.
  • Easy experimentation with unproven algorithms.  There are times that you need to feel your way to a solution in code.  This provides a completely safe environment to do this in.  Trying to do this in the main app often requires a lot of instrumentation in order to call the new logic, all of which might just have to be undone if the algorithm doesn't work.
  • Known code.  Because of how small the test app is, odds are good that you will walk through the code in the debugger quite a few times while working on it.  This simple task might be all but impossible in a shared dev environment.
  • Generic.  In creating your new code so that it can be tested in the harness, you might discover that it works just fine if you define a parameter as IEnumerable instead of ArrayList or a custom collection, making it potentially even more useful to the main application.
  • There's also a free bonus benefit at the end of the article

 

The Code

Here is where a little design goes a long way.  Write your new logic so that everything can be passed into it as parameters (your eventual production code will thank you for this!).  If you are creating a new class, provide constructors that allow it to be set up with enough information to make the class think it is in the real app.  Ideally, your new code should know nothing about it's environment other than what is fed to it.

 

ABOVE ALL – TREAT THIS CODE LIKE YOU WOULD PRODUCTION CODE.  No hard-coded values, no assumed paths, etc.  If you need something like that, pass it in as a parameter.  This is so that you can literally copy-paste this logic into the larger application unchanged.

 

The Shell

Create the absolute least amount of code needed in order to support the functionality you are about to write.  By this, I mean hard-coded parameters and the like.   The shell is meant to be dirty, ugly code that you wouldn't show to anyone – even for money.  Its sole reason for existing is to be a fast way to call your new functionality, not as a show piece for CodeProject.

 

Connect it up

Go back to your shell code and connect it to your new code.  Does it work?

 

Test it and test it again

Once you have it working the way you want with the expected inputs, you can play all sorts of games with the code.  Hard code values in the parameters to test out error handling or to simulate conditions that may be very hard to reproduce in the main application.  Testing and fixing these sort of issues in a test app is easily 10 times faster than trying the same thing in the main application.  The bigger the main app, the greater the advantage of using a test app

 

Drop it in

Since you went through the effort of putting your new code into its own black box of sorts by parameterizing so much of it, dropping it into your real application is almost a no brainer – just feed it the parameters it needs and you are pretty sure that you have a reasonably well tested body of logic in place.

 

Revisit

Once you have the code in there, run it through it's paces in the main app.  If it breaks because you have, say, a null coming in where you hadn't planned for one, its nothing to fix.  Just create another test in your Test App to simulate the new situation and debug away happily.  When fixed, drop the updated code back in again.

 

I Object!

This is great and all, but isn't this actually extra coding?  Yes, slightly.  The shell might take you 30 seconds to create if you type really slow, and then the main hooks another 2-3 minutes or so.  The rest of the code you have to write anyway so you might as well do it in a place where you can beat the snot out of it easily and quickly.

No way, my test environment is SO complex.  That might be true, but that doesn't mean the functionality you are writing needs to know about it.  Odds are good that it can be abstracted pretty easily.  Not always, but more often than you might think.

 

Enjoy!

I have written several thousand test apps in the 12 or so years that I have been using them and have never thought even one to be a waste of time.

 

Freebie benefit

Remember I said that you should never show your test app to anyone?  That's not entirely true.  There is one person who will be *very* grateful for it – the guy who comes to you and asks: "How do I use your object?"  You just toss him your test app and he can immediately see not only how to call it, but what you had in mind for it when you created it.  For most of us, a quick example with actual code is worth a 1024 words. 

 

Speaking of which, here's a somewhat contrived example of a test app:

Sample Code

Worthless Backups

April 11th, 2007 by davidwise

 

What is with corporate backups?  I've been in this field over a decade and have yet to actually see a Restore of anything actually work.  Seriously!  Files, databases, anything.  Anytime that I've been in a situation that needed something restored from backup, the backup was either corrupted or the systems people lacked the ability to selectively restore only the item needed.

 

I actually had a system admin tell me that in order to restore a file, he would have to restore the whole volume. This was all the worse because this was a Fortune 500 company and I know that their backup software and systems were state of the art.  They just put a moron in front of it and assumed it would still do miracles.

 

Companies spend a fortune on backup software, disaster recovery planning and the like, yet, when it is really needed, the data is nowhere to be found – as the recent incident in Alaska is yet another indication.  (http://www.cnn.com/2007/US/03/20/lost.data.ap/index.html)

Microsoft "Tool" Book Covers

April 11th, 2007 by davidwise

This has been bugging me for some time and I have to vent about it. For some years now Microsoft has been putting tools on the covers of its technical books, which is fine and even makes sense. But, shouldn't they put NEW tools on them, or at least the coolest tools to be found? Instead Microsoft has used old, dirty, rusty, bizarre and/or antiquated tools! Just what message are they trying to convey?
 
Some examples
http://www.microsoft.com/mspress/books/6715.aspx
http://www.microsoft.com/mspress/books/5200.aspx
http://www.microsoft.com/mspress/books/5621.aspx
http://www.microsoft.com/MSPress/books/9692.aspx
http://www.microsoft.com/MSPress/books/10512.aspx

Looking at the upcoming titles, it does look like they have removed the rusty ones, but some of tools chosen are simply bizarre. A horseshoe for Sharepoint? Pliers for Communication? An old drill for driver programming? Are they saying that their software tools are merely old, or old AND obscure? Perhaps they are trying to convey that the information within is outdated?
 
Don't get me wrong.  I'm a huge fan of these books as the content is usually top notch.  It's just that the first questions that come to my mind when I see these covers is about the tool. 1) What the heck is it? And 2) What in the world would a person use that for? Neither of those are questions that are particularly favorable to an entire line of books dedicated to software tools.
 
My non-tech friends see them and ask: "Why do you have so many books about old tools?"

Approval Workflow is Broken on Publishing Sites

April 11th, 2007 by davidwise

I ran into an odd issue the other day where the Approval workflow for a Publishing site simply would not work.  .Net 3.0 was installed properly and the site was otherwise functioning perfectly so it didn't appear to be a problem with the install. 

 What was happening was that when I pulled up the first page in a workflow, I received one of these two messages:

"The form has been closed" – which is the lovely generic InfoPath version of the Sharepoint "An error occurred" message.  Not terribly useful

- or –

"The server is currently being taken down for maintenance. Check with your system administrator to see when this server is scheduled to come back online.

Click Try Again to attempt to load the form again. If this error persists, contact the support team for the Web site.

Click Close to exit this message."

 

… yet, the site was running fine aside from the workflow.

 

The Solution

Somehow the site had gotten itself into a partially quiesced state where some of the site was convinced that the whole farm was quiesced while other parts thought everything was running fine.  The fix was to go into the Quiesce Farm option in 

Central Administration > Operations > Quiesce Farm and then restart the farm.  After that, everything worked fine.

Quiesce farm

An interesting thing that pointed even more to this was the error in 12/LOGS that read: "Loading event log failed, because the form was quiesced". 

A duplicate site ID … error in Event Log after moving Content Databases.

March 1st, 2007 by davidwise

After moving the content database to another SQL Server, I started getting the following message in the event log every hour : "A duplicate site ID … was found. This might be caused by restoring a content database from one server farm into a different server farm without first removing the original database and then running stsadm -o preparetomove. If this is the cause, the stsadm -o preparetomove command can be used with the -OldContentDB command line option to resolve this issue."

Nice and simple and even a recommended solution in the error message – except that it wouldn't work. After a lot of additional searching I turned up this command, which did work:

stsadm -o sync -DeleteOldDatabases 0

I am reposting it here because of how hard it was to find in the first place.  For what its worth, the full syntax of this is:

stsadm -o sync
           {-ExcludeWebApps <web applications> |
            -SyncTiming <schedule(M/H/D:value)> |
            -SweepTiming <schedule(M/H/D:value)> |
            -ListOldDatabases <days> |
            -DeleteOldDatabases <days>}

The original answer was posted by Vikram Garlapati.

How to determine expiration date of your eval copy of sharepoint

March 1st, 2007 by davidwise

The other day, I was asked by my management to provide the exact day that our Eval copy of SharePoint 2007 would expire.  Well, I knew it was before Christmas and I could get a rough idea by looking at the timestamps in the 12 hive, but still didn't have the exact date and time. 

After some digging, it turns out that Microsoft was nice enough to include the exact date and time right in the Central Administration console, albeit tucked away in a less than intuitive place.

When you go to Central Administration -> Application Management -> Office SharePoint Server shared Services -> Check Services enabled in this farm, you will see a message like the one below that lists the exact date and time that your SharePoint eval will go belly up . or whatever it is that SharePoint does when you hit the expiration. 

I asked MS about the expiration behavior and they said that it was hard to say exactly what it would do, but it would be something between "stop working entirely" and "disable all admin and publishing functions".  Really!

Mystery Master Page or CMS Gotcha

January 9th, 2007 by davidwise

 

Testing Master page changes in Sharepoint can be a little tricky.  I had a situation recently where I would view the master page and see all of my changes, a person sitting down the hall from me would see some of the changes, and a person across from her saw none of the changes – and we were all looking at the same page!

I checked browser cache, disabled output cache on the site, restarted IIS, restarted the computers, and restarted the servers (desperate), with no luck.

The Gotcha here is that it turns out that the CMS abilities of Sharepoint extend even to Master pages.   In order for everyone to see the same page, you must check in your new Master file, then Approve it by clicking 'Approve/Reject" in the file dropdown in the Master Page Gallery.

What happened in my case was that I had created the initial new .Master file from a base .Master file and installed it as a feature (1.0 and Approved),  I then checked that out in Designer, made most of the modifications and checked it in (v1.1, Unapproved).  I later checked it out and made other changes and saved them but did not check them in because I wasn't done (1.2 draft).

At this point, all three of us could view the exact same page and see three different layouts.  I have no hard proof on exactly why this happened, but I do have a theory.  Here goes:  I was seeing v1.2 because I was the one making the changes and had the files checked out.  The person down the hall saw v1.1 because she was an approver and was viewing the most recently checked in copy, presumably to approve them.  The person across from her saw v1.0 because that was the most recently Approved version of the page.  Once I checked in the files and approved them, everyone saw the same thing again.

This all makes sense but really catches you off-guard if you aren't used to the new CMS mindset.  Remember, if someone comes to you saying that two (or more) people are seeing different versions of the exact same item, the approval process for that item might be a place to start looking.

One .Master to Rule Them All (Two, actually)

January 8th, 2007 by davidwise

One of the biggest annoyances with Sharepoint 2007 is the quirky things you have to do in order to customize a site.  This is especially true when it comes to custom master pages.  You create a stunning master page in Designer, configure the site to use it, then load the page and wait to bask in the glory. Lo and Behold!  It worked!  Job done, go grab a beer … but you better drink it fast because Sharepoint has a nasty surprise in store for you.  That master page only works on the content pages in your site.  System pages  (i.e. viewlists.aspx) will refuse to use your amazing Master page.  All that work is wasted on a half complete user experience.  Or is it?

Why is it not doing what I tell it to do?
This is because those system pages are hard-wired to use a different master page (application.master) .  To make matters worse, you only get one application.master for everywhere.  You could go modify this file, but be careful: changes to this will affect ALL pages based on that master, everywhere.   It's not something that can be customized on a site-by-site basis.  To make matters still worse, Microsoft *will* update this file in upcoming patches, so odds are good that it will break on you sometime in the future, and likely with little warning.

Ok, so what's the skinny?
Create a custom httpModule and install it on your Sharepoint site that remaps which .Master pages your pages use.  If you aren't familiar with httpModules, fear not, they are extremely simple.

The httpModule sits in the middle of the http request processing stream and can intercept page events when they initialize.  The pages load up and say "I'm going to use application.master", to which your module replies "not on my watch, buddy" and not so gently forces the page to use the Master page of your choice.

The Gory Details
(this assumes that you already have the aforementioned Nifty Master Page created.  If not, please search Google for any of the hundreds of tutorials on how to do this)

Prepare your Master Pages
You will need two .Master pages.  One to replace default.master and the other to replace application.master.  It is very important that when you are creating these pages that you include all of the ContentPlaceHolders that exist in the .Master page you are replacing.    Throw any ContentPlaceHolders that you are not using in a hidden DIV at the bottom of the page – but before the closing FORM tag (the only exception to this seems to be "PlaceHolderUtilityContent" which goes in after the closing form tag).  Once in place, you can use the normal Master Page options in the UI to select the default.master replacement.

Second, be sure to remove the Search Control from your Application.Master replacement.  The reason for this is that the search box does not normally appear on system pages and will cause an error during rendering.  

You can probably simplify this a bit by using nested master pages, but I haven't had a chance to look into that yet.

Step 1 – Create the httpModule
Create a new Class Library project in Visual Studio and start with the code below.  So simple, even a manager could do it (maybe).  Obviously, you will have to change the path to match your environment.  Oh, and sign the assembly as well.

using System;
using System.Web;
using System.Web.UI;
using System.IO;

public class MasterPageModule : IHttpModule
{
   public void Init(HttpApplication context)
   {
      context.PreRequestHandlerExecute +=
new EventHandler(context_PreRequestHandlerExecute);
   }
   void context_PreRequestHandlerExecute(object sender, EventArgs e)
   {
      Page page = HttpContext.Current.CurrentHandler as Page;
      if (page != null)
      {
         page.PreInit +=
new EventHandler(page_PreInit);
      }
    }
    void page_PreInit(object sender, EventArgs e)
   {
      Page page = sender as Page;
      if (page != null)
      {
         // Is there a master page defined?
         if (page.MasterPageFile != null)
         {
            // only change the application.master files as those are the offenders
            if (page.MasterPageFile.Contains("application.master"))
            {
               page.MasterPageFile =
"/_catalogs/masterpage/MyCustom.master";
            }
         }
      }
   }

   public void Dispose()
   {
   }
}

Note the path above: that is required so that all pages can find the Master page as not all pages are running from the site context

This is a simplified example but you can see the potential here.  With this in place, YOU control the horizontal and YOU control the vertical.  Or, for a more modern reference, YOU decide who gets the red pill and who gets the blue pill.

Build it and then throw the DLL in the /bin folder in your Sharepoint site root (usually something like InetpubwwwrootwssVirtualDirectories80in).  You may have to create the in folder if one is not there.  Once you have it working the way you want, you will need to sign it and move it to the GAC, but this works for getting started.

Step 2 – Register the httpModule
Another easy step – throw in the bolded line below in your web.config file at the bottom of the httpModules section.  This section should already be there.

<httpModules>
… stuff you dont care about …

<add name="MyHandlerTest" type="MasterPageModule, MasterPageHandlerModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6bdb1331dfc11306"/>
</
httpModules>

Step 3 – Load the Page
Navigate to the home page of your site.  That should work normal as it is using the normal Sharepoint Master page logic.  Now go to a System page, like 'Documents' , 'Lists' or 'Pictures'.  These should now be using your Master page

If you've followed the this tip you will actually be able to see the real error, if any, when you load the page.

Step 4 – Go get that beer
Do you really need instructions for this?

Special  thanks to K. Scott Allen for his post showing how to change the .Master page using an httpModule.  You will notice that the code above bears an amazing resemblance to his.

 

3/1/2007 – UPDATE!
In response to comments, I have updated these instructions, in particular, I have added the "Prepare your Master Pages" section that addresses most of the issues encountered in the comments.

Also, do not use this method if your sharepoint install has the shared Service Provider (SSP) installed on the same web application as your main sharepoint environment.  The system pages used by the SSP do not work properly when their master page is replaced like this.  I'm sure there is a logical reason why, I simply haven't had the time to dive into it.

HowTo: Filter a View based on Workflow Status

December 11th, 2006 by davidwise

One of the known issues with SharePoint 2007 is the inability/quirkiness of filtering a view based on the status of a workflow field.  While this is a pain, there is an easy workaround.  Simply create the filter and tell it to look for the integer representation of the status value you are looking for, like this:

This will create a filter that returns all "Approved" items.  The codes are:

  Status Value  
  Not Started 0  
  Failed on Start 1  
  In Progress 2  
  Error Occurred 3  
  Canceled 4  
  Completed 5  
  Failed on Start (retrying) 6  
  Error Occurred (retrying) 7  
  Canceled 15 * This is defined but I don't think this value is used
  Approved 16  
  Rejected 17  

 

  12/19/2007 Update:  Filled in list of values using the actual values found in the field definition itself.

5/28/2008 Update:  Hit by a flash of the blindingly obvious.  If you put the status fields on a view, then view that in the Datasheet mode, it shows you the integer values.  I don't know why I hadn't seen this before ….

How to update a single status column when using multiple workflows

December 8th, 2006 by davidwise

The canned workflows are powerful enough as is, but what happens if you really need to use them, but are in a situation that is just a little beyond their ability?  There are ways to augment them slightly without having to re-create the whole workflow from scratch.  Below is just one example.

This overall situation is pretty unique, but parts of it should be familiar to anyone who has done very much with the canned workflows.  Hopefully this may provide another option for those of you beating your heads against similar issues.

Here's my scenario:

The main problem:  A single list item can be subject to dozens of different workflows, which are all variations on the canned Approval flow but with different lists of approvers.  Reports need to be generated so that a single report will show all items that are in a given state regardless of which flow it is in.   Views like this are almost impossible out of the box with this many possible workflows.

The second problem: The current status needs to be visible in lists.  However, the text of the status needed by the client is different than that defined in the workflow.

The third problem:  The same workflow needs to run multiple times on the same item, but will result in a different status upon completion.  The reason is that there is often a gap of weeks or months between the completion o the first workflow (feasibility) and that of the second (manufacture).  During this time, things may have changed that make the product no longer feasible, so it has to go through the same people again to make sure that it can still be done.  If so, the completed status becomes "Authorized".

The solutions
At first glance, it looks like you could take the hardline approach and create dozens, if not hundreds, of custom views, each tailored to the workflow needed.  While this is doable, it doesn't address the second and third problems.  It also sets up a maintenance nightmare.

The first problem was solved by the fact that all workflows in my situation are started by my WebPart rather than using the Automatic or Manual options.  When the WebPart starts a workflow, the code determines the prope workflow to launch based on form data.  It then copies that workflow's EventData and starts a single workflow using that data.  (See my other post about the specifics of this)

The second problem proved much more difficult to conquer, but this solution manages to not only take case of the second one, but the third problem as well

EventHandlers to the rescue!
The solution was to create an EventHandler and attach it to the ItemAdded event of the list that the workflows put their history into.  In my case, all history goes into a single list so that part was simplified.

Create the handler
A basic EventHandler is one of the simplest things to create.  I've attached the code for my handler at the bottom of this post that illustrates getting the workflow status and updating the proper Item
http://msdn2.microsoft.com/en-us/library/ms437502.aspx
http://msdn2.microsoft.com/en-us/library/ms453149.aspx

Binding the Handler
In order for the handler to be called, it has to be registered in Sharepoint in some way.  There are actually two ways to do this, but I found this to be easiest:
 
/ssa/archive/2006/11/09/15899.aspx
Please note that if you do use this method to bind your handler, it only needs to be run once.  There is no need to rerun it after you rebuild the EventHandler.

The other way is listed in the second link above and involves creating it as a feature.  This just seemed like overkill for such a simple thing.

Wave Magic Wand
Determining the state of the workflow actually requires checking two places.  The first place is the InternalState property of the workflow to determine if it running or errored out but it will not tell you if the item was Approved or Rejected, only that the workflow completed.  That's a good start, but how do I find out Approved/Rejected?

Fear not!  With a little searching, you can get hold of the Workflow which has a handy Xml property that holds all sorts of juicy nuggets of information.  The one of interest here is the Status1 attribute as that is the keeper of the desired status.   A Status1 of 17 is Rejected, 16 is Approved.   (I'm sure that there are constants or an enum somewhere that maps to this, but I was unable to find it)

With this tidbit of Information, I can now immediately update my single Status field with the text that the customer needs to see.  With this field now populated according to the proper workflow, I can create one set of views that all key off of the one single field.  Nice and easy.

What's that you say?  Why not just look for the status in the field associated with the workflow in the ListItem?  This is a fine question indeed and one to which the only proper answer is 8.  The only value that is ever in that field is 8, regardless of Approve/Rejected status.  I'm assuming that 8 means Workflow Completed which, while informative and accurate, is of almost no use whatsoever. 

The code that is attached demonstrates how to get to the actual status.