Friday, July 29, 2005
Speaking and Drinking in Tampa last night
I had a great time speaking at the TampaBay .NET Users Group last night. We had a great turn out and it was a fun crowd. About 20 of us retired to a nearby bar afterwards for some suds and good conversation. Apparently they do that fairly regularly at their group. That is definitely the largest interactive mass of people I have encountered at a user group that goes out and really networks and has a good time together after the meeting on a regular basis.
Thanks for having me down guys and gals!!
The talk was on ASP.NET 2.0 Data Binding, and was a little rough since it was the first time I had given this talk. But hopefully people still got a lot out of it. I did all the demos on the fly, and as a result, a few of them didn't work out because I decided to take a few little side trips that I had not practiced, which is never a good idea on stage with new material.
If you are interested, here are the slides and outcome of the demos.
Wi Wi Wi Wi Wireless aplenty now
After my bitching about lack of wireless on travel here, I decided to shell out the bucks to make it a non-issue. I got a Verizon Wireless broadband PC card, so I now have 400-700 kbs speeds in about 25 major cities in the US, including here in the DC area, and 114 kbs just about everywhere else stateside. Sweet. I went with Verizon despite switching my cell away from Verizon to Cingular recently because Verizon's broadband access still kicks ass on speed compared to what Cingular and others have to offer.
Community | Travel  Friday, July 29, 2005 11:40:39 PM (GMT Daylight Time, UTC+01:00)  |
Thursday, July 14, 2005
Why Why Why Why Why no wireless???
I don’t get it. Why is it that every little Podunk airport I fly through (yesterday it was Ft. Wayne IN) has wireless internet access in the airport, but many major commuter hubs (Washington National, Dulles, Chicago, etc.) do not. I realize that for the bigger airport they would need to invest in a number of access points to give wide coverage. But it wouldn’t be that expensive. How many years is it going to be before you can reliably connect while killing time in an airport?
Friday, July 8, 2005
Wednesday, July 6, 2005
Tuesday, July 5, 2005
Using User Settings in Partial Trust
In my previous post on ClickOnce custom install steps, I talked about how to read and write custom settings from Isolated Storage. One thing I purposely avoided mentioning there (because I needed to get confirmation on some facts from the product team at Microsoft) was the use of the new User scoped settings in .NET 2.0 and Visual Studio 2005.
If you are not already aware, the approach to config files has changed quite a bit in .NET 2.0. When you create a project in Visual Studio 2005, you can add custom settings through the project property pages (Settings tab), and those will be persisted in your config file and available to you at runtime. Additionally, strongly typed properties are added to a Settings class that is created by the designer so that you can access those settings programmatically with code like this:
string myCustomString = Properties.Settings.Default.MyCustomString;
The Settings class is declared in a child namespace of your project namespace called Properties, and the Settings class has a singleton property called Default that you use to get a reference to the one and only one instance of the Settings class for your app. From there, you just access the settings in your config file through named strongly typed properties.
That on its own would be a big win from a design time and compile time perspective, but the full story is even better. For one thing, you can designate whether settings are scoped to the application or to the user. If scoped to the application, then the settings go in your application configuation file as usual (<appname>.exe.config or web.config as appropriate). If scoped to the user, the settings go into a slighly obfuscated directory under the user's profile, under <username>\Local Settings\Application Data\.
The other thing about user settings is that all the properties for them that get exposed through the Settings class are both read and write. So you can set the properties, call Save on the settings class, and your changes will be written out to the user config file:
Properties.Settings.Default.MyCustomString = "SomeNewValue";
Properties.Settings.Default.Save();
So for the scenario I was describing in the previous post about writing out a flag to indicate when it is no longer the first time you application has run, a boolean user setting is cerrtainly a prime candidate for a place to put that. And if you read the docs for user settings, you will see that it says that you can write to user settings in partial trust, so it should be a winner for ClickOnce deployed apps as well. The reason I held off on mentioning it in the other post is a typical story:
User Settings do not work in partial trust in Beta 2.
If you try to write out user settings (call Save) in a partial trust environment in Beta 2, you will get a FileIOPermission security exception thrown. The thing I was waiting for confirmation on was that this will be fixed for RTM, and the answer was yes. So for production apps releasing after RTM, you can do all your ClickOnce custom settings through user settings, and not have to write explicitly to IsolatedStorage. There may still be some scenarios where you want more explicit control over the I/O process, in which case Isolated Storage is still your best option for a ClickOnce app to minimize your permission requirements.
Monday, July 4, 2005
Amsterdam weather == Seattle weather?
Come on, this ain't fair for my first full week in Amsterdam in the middle of summer!! I might as well be in Seattle.

How to handle prerequisites and custom install steps for a ClickOnce Deployed Application
UPDATE: There was one little hitch with the code I presented earlier in this posting... it didn't work in partial trust because the BinaryFormatter and serialization require security permissions that are not granted in partial trust. At the end of the posting is the new code that does work fine in partial trust, but only works for built-in basic types in .NET (int, string, bool, etc.) because a custom XmlSerializableHashtable was required to get things working. The full code can be downloaded here. It is also available on the IDesign web site, download tab at http://www.idesign.net/. The code below has been updated to show the full source of the helper class and the XmlSerializableHashtable, which was adapted from the one in the Configuration Management Block. One of the other improvements that were made at my colleague Juval Lowy's prompting was to make the Read and Write methods generic so that they could read and write in a type safe way instead of using object parameters and return types, which was an excellent suggestion.
-----------------
These are some common questions I get about ClickOnce:
"My app depends on XXX, how can I ensure it is on the user’s machine before trying to launch my ClickOnce app?"
"I need to do XXX the first time the app is run, how can I do this from ClickOnce?"
Prerequisite Detection
The answer to the first question is twofold. First, there is the issue of detection. There is no general way to do prerequisite detection across the board. It is going to depend a lot on what those prerequisites are. In some cases, you may be able to use WMI, in others, File I/O to look for some known signature in an expected folder. In almost all of these approaches, you are going to need highly elevated permissions on the box, both from the perspective of the user that executes the application, and second from a Code Access Security (CAS) perspective.
Probably a better way is to have some startup code in your application that only runs once for a given version that attempts to use the APIs (directly or indirectly) of the prerequisites that you depend on. If that attempt fails, catch the exception and notify the user that they are lacking the respective prerequisite. If it is MDAC or SMO or something like that, try to use the calls that your app uses that calls through those layers (to do something trivial and non-destructive obviously). I’ll talk about strategy for managing one-time steps like this in your ClickOnce apps towards the end of this post.
Prerequisite Installation
The second part of the answer to the question on dependencies is to use the bootstrapper. This is a new feature in .NET 2.0 that is not technically part of ClickOnce, but ClickOnce capitalizes on it for getting prerequisites deployed. Using the bootstrapper, you can wrap up all the MSI installs that need to be run for prerequisites for your app into one setup.exe that an admin needs to run on the client box just once. It will run each of the prereq setup packages as needed, and then you should be good to go to run your ClickOnce app.
If your deployment environment is a controlled intranet environment, then managing this through group policy/SMS/basic client machine configuration control should be sufficient. You have an admin for your domain somewhere that has configuration control over the user’s desktops, get them to roll out the prereqs necessary to support your application. If your target audience is open internet users, then you will have to provide them a link to the bootstrapper setup.exe that they run first (and they will have to be an admin on the box to do so), and then they can run your ClickOnce app.
One-Time Setup Steps
The answer to the second question – how to do one-time actions for your ClickOnce app before it runs – is actually fairly simple to solve, even though ClickOnce provides no direct mechanisms for doing so. By design, ClickOnce does not allow you to configure or run any custom install steps as part of the ClickOnce deployment. The idea is that there should be no way the act of deploying a ClickOnce app to a client machine will corrupt other applications or other user data on that machine. If they opened the Pandora box of allowing custom install actions, there would be no way to guarantee that. However, once your app is on the machine and executing, it can do whatever it was designed to do, provided it has sufficient CAS permissions and that the user is authorized to access whatever resources they are trying to access.
For example, say that the first time you run your application, you need to prompt the user for the name of the database server and database name (obviously a power user requirement – something more simple would be just prompting them for their name and initials like Office apps do the first time they are run). Once you prompt the user for that information, you need to store it somewhere. You also have to have logic in your app to know to only prompt the user for that information the first time the app is run, so you need a flag somewhere to detect when that is the case.
Well, one of the simplest ways to do this is to use the data you are collecting for these situations as the flag themselves. If it is some other kind of one time action you need to run that doesn’t generate stored data, then you will need a separate flag that is easy to read and check at app startup. So all you need to do is have some internal startup code in your app that checks for the first time run flag, or checks for the presence of the needed data, and does appropriate prompting if not.
Once you have collected the data, you need to persist it for the next run, whether it is a simple first-time-run flag, or whether it is the database server and database name example mentioned above. You could just write it out to some file in your application directory, but that is going to require File I/O permission. You app will not have that permission by default in a ClickOnce deployed app on the internet or local intranet, unless you deploy it requesting elevated permissions. You should avoid asking for more permissions than you really need, and if this is the only File I/O your app does, then a better choice is to use Isolated Storage.
Reading and writing to Isolated Storage is permitted in both the Internet and Local Intranet CAS zones for AppDomain isolation, so you don’t need any elevated permissions to be able to read and write your config settings there. There is a new level of isolation in .NET 2.0, scoped to the application level (as opposed to the assembly and AppDomain levels present in earlier versions of .NET). Application scoped isolated storage settings will be accessible regardless of the version of your application when ClickOnce deployed, so you won’t lose these settings when the application is updated. However, if you want your app to check for pre-reqs each time the app is updated, maybe writing that flag to an appdomain scoped isolated storage is exactly what you want, because then the setting will be refreshed for each update.
Writing to isolated storage is fairly straightforward. You create an instance of an IsolatedStorageFile scoped to the appropriate isolation level through the exposed factory methods. You then create an IsolatedStorageFileStream, and from there it is just straight I/O against a stream, the same as you would do for a normal file in .NET.
I wrote a little helper class to make this as easy as possible to read and write settings from isolated storage, both in ClickOnce and non-ClickOnce applications. The IsoStoreSettingsHelper is shown below. To use this as is, you will need to set a reference to the System.Deployment.dll assembly in the framework, and will need the using statements shown below. The Read and Write methods take the name of file you want to use or create for storing settings within isolated storage. The Read method attempts to read a named setting from the specified file name in isolated storage. If the file does not exist, does not contain a hashtable, or the setting key specified cannot be found, then null will be returned. Otherwise the value from the hashtable will be returned. When write is called, it will add the specified key/value pair to the settings file, and will create it if it does not already exist.
The code dynamically selects either isolation by domain or isolation by application based on whether the application has been ClickOnce deployed. The reason for this is that application isolation depends on an identity for an application that is only present in a ClickOnce deployed app. So you cannot use application isolation unless you are running through ClickOnce. It checks for this with the IsNetworkDeployed property on the ApplicationDeployment class.
UPDATED CODE:
IsoStoreSettingsHelper class:
using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Deployment.Application;// Need to add reference to System.Deployment.dll framework assembly
namespace IDesign.Utilities
{
public static class IsoStoreSettingsHelper
{
/// <summary>
/// Reads a setting from isolated storage.
/// Assumes isolation by domain for non-ClickOnce deployed apps.
/// Assumes isolation by application for ClickOnce deployed apps.
/// </summary>
/// <param name="fileName">The name of the settings file to use.</param>
/// <param name="key">The key name of the setting to look up.</param>
/// <returns>The value stored in the file if found, the default value for the type otherwise.</returns>
public static T Read<K,T>(string fileName,K key)
{
try
{
IsolatedStorageFile isoFile = OpenIsoStoreFile(fileName);
string[] files = isoFile.GetFileNames(fileName);
if(files.Length == 0 || files[0].Length == 0)
{
// File does not exist, return default
return default(T);
}
// File exists, deserialize the settings
using(Stream stream = new IsolatedStorageFileStream(fileName, FileMode.Open, isoFile))
{
XmlSerializableHashtable settings = XmlSerializableHashtable.Load(stream);
if(settings == null)
{
return default(T);
}
return (T)settings[key];
}
}
catch
{}
return default(T);
}
/// <summary>
/// Write a setting to an isolated storage setting file.
/// Assumes isolation by domain for non-ClickOnce deployed apps.
/// Assumes isolation by application for ClickOnce deployed apps.
/// </summary>
/// <param name="fileName">The name of the settings file to use.</param>
/// <param name="key">The key name of the setting to look up.</param>
/// <param name="value">The value of the object to store.</param>
public static void Write<K,T>(string fileName, K key, T value)
{
try
{
IsolatedStorageFile isoFile = OpenIsoStoreFile(fileName);
XmlSerializableHashtable settings = null;
// First try to read in existing settings is there are any
using(Stream stream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, isoFile))
{
try
{
settings = XmlSerializableHashtable.Load(stream);
}
catch
{
}
if(settings == null)
{
settings = new XmlSerializableHashtable(); // Create empty one for new settings
}
}
//Add the new setting to the collection
settings[key] = value;
//Now write the collection back out
using(Stream writeStream = new IsolatedStorageFileStream(fileName,FileMode.Create,isoFile))
{
settings.Save(writeStream);
}
}
catch
{ }
}
static IsolatedStorageFile OpenIsoStoreFile(string fileName)
{
IsolatedStorageFile isoFile = null;
if(ApplicationDeployment.IsNetworkDeployed)
{
isoFile = IsolatedStorageFile.GetUserStoreForApplication();
}
else
{
isoFile = IsolatedStorageFile.GetUserStoreForDomain();
}
return isoFile;
}
}
}
XmlSerializableHashtable class:
using System;
using System.Collections;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace IDesign.Utilities
{
// Support storage of .NET primitives
[XmlInclude( typeof(string) )]
[XmlInclude( typeof(bool) )]
[XmlInclude( typeof(short) )]
[XmlInclude( typeof(int) )]
[XmlInclude( typeof(long) )]
[XmlInclude( typeof(float) )]
[XmlInclude( typeof(double) )]
[XmlInclude( typeof(DateTime) )]
[XmlInclude( typeof(char) )]
[XmlInclude( typeof(decimal) )]
[XmlInclude( typeof(UInt16) )]
[XmlInclude( typeof(UInt32) )]
[XmlInclude( typeof(UInt64) )]
[XmlInclude( typeof(Int64) )]
public class XmlSerializableHashtable
{
#region Nested Class--Entry
/// <summary>
/// Represents an entry for the hashtable
/// </summary>
public class Entry
{
private object m_EntryKey;
private object m_EntryValue;
/// <summary>
/// Default constructor, needed by serialization support
/// </summary>
public Entry(){}
/// <summary>
/// Construct the Entity specifying the key and the entry
/// </summary>
/// <param name="entryKey"></param>
/// <param name="entryValue"></param>
public Entry( object entryKey , object entryValue )
{
m_EntryKey = entryKey;
m_EntryValue = entryValue;
}
/// <summary>
/// Return the key
/// </summary>
[XmlElement("key")]
public object EntryKey
{
get{ return m_EntryKey; }
set{ m_EntryKey = value; }
}
/// <summary>
/// Return the entry value
/// </summary>
[XmlElement("value")]
public object EntryValue
{
get{ return m_EntryValue; }
set{ m_EntryValue = value; }
}
}
#endregion
#region Declarations
private Hashtable m_HashTable;
#endregion
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public XmlSerializableHashtable()
{
m_HashTable = new Hashtable();
}
/// <summary>
/// Creates a serializable hashtable using a hashtable
/// </summary>
/// <param name="ht"></param>
public XmlSerializableHashtable( Hashtable existingHashTable )
{
m_HashTable = existingHashTable;
}
#endregion
#region Public Methods & Properties
/// <summary>
/// Indexer to access the underlying Hashtable
/// </summary>
/// <param name="key">The key for which you want to retrieve the value</param>
/// <returns></returns>
[XmlIgnore]
public object this[object key]
{
get
{
lock (m_HashTable.SyncRoot)
{
return m_HashTable[key];
}
}
set
{
lock (m_HashTable.SyncRoot)
{
m_HashTable[key] = value;
}
}
}
/// <summary>
/// Save the hashtable to an output stream
/// </summary>
/// <param name="outputStream">The stream to write to</param>
public void Save(Stream outputStream)
{
XmlSerializer serializer = new XmlSerializer(typeof(XmlSerializableHashtable));
serializer.Serialize(outputStream,this);
}
/// <summary>
/// Load a hashtable from an input stream
/// </summary>
/// <param name="inputStream">The stream from which to load</param>
/// <returns>A new instance of an XmlSerializableHashtable</returns>
public static XmlSerializableHashtable Load(Stream inputStream)
{
XmlSerializer serializer = new XmlSerializer(typeof(XmlSerializableHashtable));
return serializer.Deserialize(inputStream) as XmlSerializableHashtable;
}
/// <summary>
/// Returns the contained hashtable
/// </summary>
[XmlIgnore]
public Hashtable InnerHashtable
{
get{ return m_HashTable; }
}
/// <summary>
/// Used to serilalize the contents of the hashtable
/// </summary>
public Entry[] Entries
{
get
{
lock (m_HashTable.SyncRoot)
{
Entry[] entries = new Entry[m_HashTable.Count];
int index = 0;
foreach (DictionaryEntry entry in m_HashTable)
{
entries[index] = new Entry(entry.Key,entry.Value);
index++;
}
return entries;
}
}
set
{
lock( m_HashTable.SyncRoot )
{
m_HashTable.Clear();
foreach( Entry item in value )
{
m_HashTable.Add (item.EntryKey,item.EntryValue);
}
}
}
}
#endregion
}
}
Sunday, July 3, 2005
In Amsterdam for TechEd Europe
I arrived in Amsterdam yesterday to speak here at TechEd Europe. I am giving four sessions:
WCD221 - Deploying Applications with ClickOnce
WCD322 - Making the Most of Windows Forms 2.0 Data Binding
WCD324 - An In-Depth Look at Windows Forms in Visual Studio 2005
WCD440 - Smart Client Offline Data Caching and Synchronization
Should be a good time, this is my first time to TechEd Europe, and only my second time to Holland. The last time was in May for the SDC 2005 conference, but we were out in Arnhem and the schedule was packed, so I didn't get to see much. Hopefully I will have a chance to do a little more sightseeing in Amsterdam while I am here. People in old town Alexandria where I live like to brag about our 200 year old homes and buildings, but some of the structures here dating back to the 1600s or even 1400s kind of put old town to shame.
And yes, I did wander through the red light district last night to see what all the hubbub was about. Holy crap, talk about sensory overload. The laws are quite liberal here indeed. I didn't imbibe in any of the carnal pleasures available. It was enough of a shock to the system just wandering through. And Las Vegas calls itself sin city? Hah! Las Vegas is like a friggin bible camp compared to this place. :)
It does make an interesting contrast to the puritanical approach to everything that is the default in the US. Here, they just allow it, tax it, regulate it, and a lot less people get hurt in the process (both from the indulgences themselves, and especially from the lack of crime that surrounds the industries).
Speaking | Travel  Sunday, July 3, 2005 4:33:12 PM (GMT Daylight Time, UTC+01:00)  |
|








| May, 2013 (1) |
| April, 2013 (2) |
| March, 2013 (2) |
| February, 2013 (2) |
| January, 2013 (2) |
| December, 2012 (3) |
| November, 2012 (1) |
| October, 2012 (1) |
| August, 2012 (2) |
| June, 2012 (2) |
| May, 2012 (3) |
| April, 2012 (1) |
| March, 2012 (2) |
| February, 2012 (2) |
| January, 2012 (1) |
| November, 2011 (4) |
| October, 2011 (1) |
| September, 2011 (2) |
| August, 2011 (1) |
| July, 2011 (1) |
| May, 2011 (5) |
| March, 2011 (4) |
| February, 2011 (2) |
| January, 2011 (3) |
| November, 2010 (4) |
| October, 2010 (1) |
| September, 2010 (5) |
| August, 2010 (5) |
| July, 2010 (6) |
| June, 2010 (8) |
| May, 2010 (2) |
| April, 2010 (2) |
| January, 2010 (1) |
| December, 2009 (3) |
| November, 2009 (2) |
| October, 2009 (3) |
| September, 2009 (3) |
| August, 2009 (2) |
| July, 2009 (3) |
| May, 2009 (3) |
| April, 2009 (2) |
| March, 2009 (1) |
| February, 2009 (2) |
| January, 2009 (2) |
| December, 2008 (1) |
| November, 2008 (2) |
| October, 2008 (5) |
| September, 2008 (4) |
| August, 2008 (2) |
| July, 2008 (1) |
| June, 2008 (2) |
| May, 2008 (2) |
| April, 2008 (3) |
| February, 2008 (6) |
| January, 2008 (3) |
| December, 2007 (1) |
| November, 2007 (1) |
| October, 2007 (5) |
| September, 2007 (1) |
| July, 2007 (3) |
| June, 2007 (8) |
| April, 2007 (2) |
| March, 2007 (4) |
| February, 2007 (1) |
| December, 2006 (2) |
| November, 2006 (9) |
| October, 2006 (5) |
| September, 2006 (3) |
| August, 2006 (2) |
| July, 2006 (4) |
| June, 2006 (5) |
| May, 2006 (10) |
| April, 2006 (4) |
| March, 2006 (2) |
| February, 2006 (12) |
| January, 2006 (7) |
| December, 2005 (2) |
| November, 2005 (15) |
| October, 2005 (6) |
| September, 2005 (7) |
| August, 2005 (3) |
| July, 2005 (10) |
| June, 2005 (11) |
| May, 2005 (7) |
| April, 2005 (8) |
| March, 2005 (6) |
| February, 2005 (2) |
| January, 2005 (6) |
| December, 2004 (3) |
| November, 2004 (5) |
| October, 2004 (2) |
| September, 2004 (5) |
| August, 2004 (13) |
| July, 2004 (6) |
| June, 2004 (14) |
| May, 2004 (17) |
| April, 2004 (12) |
| March, 2004 (8) |
| February, 2004 (10) |
| January, 2004 (14) |
| December, 2003 (9) |
| November, 2003 (13) |
| October, 2003 (3) |
Sign In
|