Archive for August, 2009

Where do I put my data to keep it safe from ClickOnce updates?

August 19, 2009

Sometimes you want to include a file with your ClickOnce application and then update it using input from your customer. Or about your customer. Or about what your customer is doing with your application. (“You say you didn’t hit the delete button, but according to the log file, you hit it exactly 47 minutes ago.”)

To include the file in your deployment, add it to your project, and set the properties correctly. “Build Action” should be set to “Content”, and “Copy to Output Directory” to “Copy Always”.

When you deploy a file with a ClickOnce application, it is kept with the installed files. When you deploy a new version of the same application, it creates a new folder for the new version, and installs the files there, and you lose access to the original file, which defeats the whole purpose of saving the user’s changes to it. So if you don’t want to lose the file when there is an update, you need to move it out of the deployment folder.

If you are running Windows Vista or Windows 7, there are very few places to which you can write data on the user’s computer (Cancel or Allow?). You could put it in MyDocuments, but the user is likely to stumble over it and hurt his toe and sue you, or he may delete it because he doesn’t know what it is. So to store data used by your application, the recommended location is Local Application Data.

Create a folder in LocalApplicationData using your company name or product name. You could use another company’s name (like, say, ‘Microsoft’), but this might lead to unpleasant surprises down the road. You could also call it George, but you’ll forget that and then spend an hour looking through all the folders trying to find your file.

The first time the user runs the application, you want to create your folder and copy the file over there. When the user runs it the next time, you don’t want to copy the file over there. If you did that, it would be exactly what ClickOnce does, and would defeat the purpose of this entire blog post.

So the most effective thing to do is check for the directory. If it’s not there, create it. Then check for the file in the directory, and if the file’s not there, copy it over. You could assume if the directory is there, the file will be, but if your application mysteriously crashes (like the user’s laptop gets run over by a truck at that exact moment of execution), then the file could be missing. It only costs you one line of code to protect yourself from an embarrassing execution error.

I’ve tried to make this narrow enough to actually fit in my blog. Ordinarily, I wouldn’t use all the extra variables. I’m posting this in both C# and VB because I’m a C# developer trapped in a VB developer’s body.

Here’s the code in C#:

string localAppData =
  Environment.GetFolderPath(
  Environment.SpecialFolder.LocalApplicationData);
string userFilePath
  = Path.Combine(localAppData, "MyCompany");

if (!Directory.Exists(userFilePath))
    Directory.CreateDirectory(userFilePath);

//if it's not already there, 
//copy the file from the deployment location to the folder
string sourceFilePath = Path.Combine(
  System.Windows.Forms.Application.StartupPath, "MyFile.txt");
string destFilePath = Path.Combine(userFilePath, "MyFile.txt");
if (!File.Exists(destFilePath))
    File.Copy(sourceFilePath, destFilePath);

Here’s the code in VB:

Dim localAppData as String = _
  Environment.GetFolderPath( _
  Environment.SpecialFolder.LocalApplicationData)
Dim userFilePath as String = _
  Path.Combine(localAppData, "MyCompany")

If (Not Directory.Exists(userFilePath)) Then
    Directory.CreateDirectory(userFilePath)
End If

'if it's not already there, 
'copy the file from the deployment location to the folder
Dim sourceFilePath as String = Path.Combine( _
  System.Windows.Forms.Application.StartupPath, "MyFile.txt")
Dim destFilePath as String = _
  Path.Combine(userFilePath, "MyFile.txt")
If (Not File.Exists(destFilePath)) Then
    File.Copy(sourceFilePath, destFilePath)
End If

One thing to note is that when the user uninstalls the application, the data will not be removed, it will remain there forever, or until the user’s laptop gets run over by a truck, and then technically it’s still there even though he may no longer be able to access it. If the user donates his laptop to one of those electronics recycling centers, and instead of wiping the hard drive like they claim, they actually look at the files on it, they will see your file. (You might not want to store the user’s social security number in the file w/o encrypting it.)

On the other hand, if the user ends up having to uninstall and reinstall the application because you accidentally changed the target CPU setting in the deployment properties and didn’t realize it would change the deployment identity and everybody in the company plus a bunch of customers would call you and ask why their upgrade didn’t work (don’t ask), his cached data is still available (small comfort, but better than nothing).

You could actually use this method to handle deploying a SQLServer Compact Edition database or an Access database. Or you could copy Hamlet over to the folder and have the computer read it aloud to your customer. (Everything I learned about Hamlet, I learned from that episode of Gilligan’s Island where they did Hamlet to the music from Bizet’s Carmen. “Neither a borrower not a lender be…” “I ask to be, or not to be, and that is the question that I ask of thee…”)

Chocolate Oatmeal No-Bake Cookies

August 2, 2009

I offered to share my recipe for Chocolate Oatmeal No_Bake Cookies with someone I met on Twitter (Jennifer Fong, a great resource for Direct Sales & Social Media), who also has a WordPress blog with some of her own recipes on it. I thought I would just post it here on my blog, because others may enjoy it too.

For you tech people reading this, these cookies work no matter what browser you use, whether it’s IE, Firefox, Safari, or Opera, which I’m sure will be a relief. Also, there’s none of that pesky caching problem.

Granted, this has nothing to do with ClickOnce deployment, so in order to justify putting it here on my ClickOnce blog, I will say that this is something you can do while installing the .Net 3.5 Framework (with or without SP-1) … and still have time left over to do unit testing on the resulting product.

Of course, it takes longer to install the .Net 3.5 Framework (with or without SP-1) than it takes to build all of the backend products for GoldMail, listen to half of a DotNetRocks! podcast, or read 1/4 of my favorite Data Binding book. But none of those activities yield a plate of cookies.

Now, for all you tech people checking this out, I will point out that even YOU can make this recipe. It’s mostly just adding a bunch of stuff to a pot, stirring it and letting it boil while you count slowly to 60 (or the amount of time it takes to write a complicated linq statement), then glopping it out on wax paper (which you can buy at the grocery store, where the Saran Wrap is. You remember Saran Wrap — it’s what you wrap your extra bare hard drives in, to keep the connectors clean).

The hard part is waiting a few minutes for the cookies to set before you start eating. That’s easier to do if you spend that time cleaning out the pan with your finger, if you know what I mean. You should do that anyway, because it makes it easier to actually wash the pan.

Here’s the recipe in C#. If you need it in VB, post a comment and I will translate. Just instantiate the class, and you’ll have cookies…

/// This creates cookies.
/// It is assumed that there is another process
/// throwing events from the saucepan
/// which result in the state changing.
class ChocolateOatmealNoBakeCookies
{
  internal class Ingredients
  {
    internal int Two_Cups_Sugar {get; set;}
    internal int Half_Cup_Cocoa { get; set; }
    internal int Half_Cup_Milk { get; set; }
    internal int Half_Cup_Butter { get; set; }
    internal int Half_Cup_Peanut_Butter { get; set; }
    internal int Teaspoon_Vanilla { get; set; }
    internal int Three_Cups_Oatmeal { get; set; }
  };

  Ingredients input;

  public ChocolateOatmealNoBakeCookies()
  {
    input = new Ingredients();
    MakeCookies();
  }

  private void MakeCookies()
  {
    int three_qt_saucepan = input.Two_Cups_Sugar
      + input.Half_Cup_Cocoa
      + input.Half_Cup_Milk + input.Half_Cup_Butter;
    string heat_level = "medium";
    string state = "cold";
    while (heat_level == "medium")
    {
      do
          stir();
      while (state != "boiling");
      heat_level = "off";
    }
    three_qt_saucepan += input.Half_Cup_Peanut_Butter;
    while (state != "melted")
        stir();
    three_qt_saucepan += input.Teaspoon_Vanilla;
    stir();
    three_qt_saucepan += input.Three_Cups_Oatmeal;
    while (state != "mixed")
      stir();
    int spoonful_of_stuff = 1;
    int waxpaper = 0;
    while (state != "empty")
    {
      three_qt_saucepan -= spoonful_of_stuff;
      waxpaper += spoonful_of_stuff;
      if (three_qt_saucepan == 0)
        state = "empty";
    }
  }
  private void stir()
  {
    //stir the stuff in the pot
  } 
}

Here’s the recipe for those who don’t speak C# or .Net. Not that there’s anything wrong with that.

Chocolate Oatmeal No-Bake Cookies
2 cups sugar
1/2 cup cocoa
1/2 cup milk
1/2 cup butter
1/2 cup peanut butter (chunky or smooth, your choice)
1 teaspoon vanilla
3 cups oatmeal (old fashioned oats — the kind that takes 5 minutes to cook, not the instant kind)

  • Combine the sugar, cocoa, milk, and butter in a 3-quart saucepan.
  • Cook on medium while stirring, until it starts to seriously boil. Boil for one to one-and-a-half minutes. (Use a timer, seriously. Use the clock on your computer if you have to, or the stopwatch on your iPhone in the World Clock app).
  • Remove from heat. At this point, it’s going to start to set, so act fairly quickly or you will end up with 1 chocolate oatmeal no-bake cookie in a pot.
  • Add the peanut butter; stir until melted.
  • Add the vanilla, stir it in, then add the oatmeal.
  • Stir until mixed, then drop it on waxed paper with a spoon in whatever size you want. (I drop about 1/2 tablespoon at a time).
  • Let them set.
  • Eat them.

Servings: 1. Total number of calories: 4,250.

You don’t have to store them in the fridge, although I do, because I like them cold. And I’ve never had a problem with them going bad. That doesn’t mean they will last forever, it just means I’ve never had any last long enough to find out how long they last. I hope you enjoy them!