If memory serves me correctly...
Memory is king and he who remembers the most rules will rule the most (not a real quote but it sounds interesting). Anyway, it is very easy to create "widget" apps with VSTO that "do" something, but often where the really interesting projects come into play is when the app can "do" something and then save state. Sometimes state can be minor feature like keeping the previous values used in a control or something large like storing associations and actions to allow data manipulation.
In this post I'm only going to cover an example of lightweight state caching and I will leave it up to you to imagine some of the more interesting scenarios this can be applied to. So in this little project I'm working on I have a drop down control on Ribbon Control of a Customized Document. This control contains a list of "Emotive States" that I might want to associate with blocks of text. In typical lazy fashion I could just set the initial on startup and not worry about it from moment to moment but lets say I know that likely the user of the document is most likely going to use one state more often than the others. As a nice "micro-feature" I want to save the state between instances of opening the document. Because this state is more of a "program state" rather then "document state" ideally I will want to store the state with customization and not with the document itself.
So lets consider some of the Data features in VSTO:
Manually created Data Files out to disk on the user's machine but there is a lot of work around things like "where" and potentially permissions issues that might come up. Ideally I don't want to have to think about these things myself.
Binding to a Database seems like overkill. Really all I want is to know what the last stored value for the current user is, I don't want to have to have them deal with databases, etc.
Custom XML in the document is a possible option but this is better for saving state specific to the document rather than things like control settings. Also this feature takes a lot of work to really understand how to hook into the Office Files nicely so I'm not really going to use this approach this time.
VSTO Data Store is also associated with the Document and really is a much nice way of achieving the same results of Custom XML without the pain of the Office object model so while a possibility I still am going to bypass this feature also.
ClickOnce Data Directory is the feature that I really want to use. I can associate Data with the customization code rather than a specific instance of the document. This gives me a nicely protected User space for allowing user specific settings and data.
Okay so that seemed a little forced even to me...basically if you want to store things like application settings and users settings, I think this is the route to take since it provides a "safe" method for producing files and it eliminates a lot of the details of file management that might have to otherwise be create by the developer (or myself in this case).
So lets talk about the ClickOnce Data Directory. Basically it is a folder "somewhere" that code executing under the user's credentials can write to. Additionally since the data folder is managed by ClickOnce, the headache of worrying about things like "un-installation" are simply not something you have to worry about. The data directory is obfuscated just like any other part of the cache. If you add a reference to System.Deployment you can use the ClickOnce API to access the Data Directory.
VSTO supports using the data directory but currently in VS 2008 there isn't any UI around it specifically. If you have much experience with Window Forms you may know that you can set many files as data files. You may can do the same with VSTO but you would have to do so manually in the manifest files (and then you would have to re-sign them). By default though if you include an xml file (must be of the .xml extension) we assume it is a data file and should be copied into the Data Directory on installation. In VS you must set the xml file as a content file that is always copied. Once you have done this step you're already well on your way to having application data settings.
So let us assume you've created an XML settings files, something that might just contain a root node like: <data></data>
The first thing you will want to do is hook it up using the deployment API's and open the file to show you can in fact access the data file. Unfortunately if you do this and hook up to the API then hit F5, you will get an exception. The reason is fairly straight forward, until you install the solution into the Cache it isn't a "real" ClickOnce Solution and there isn't a data directory to get to. So here's a code example on how to hook up to the API such that the data directory is used when the solution is "installed" into the cache and simply expects the file to be side-by-side with the customization dll when it is run in the "local" (|vstolocal) context. If you wrap any calls to the data files in this way you should be able to F5 your solution, create an MSI installer or use ClickOnce without changing the code.
So here's the code i used in my project:
[code]
private EmotiveState LoadEmotiveState()
{
string xmlfile = "";
XDocument CachedDoc;
if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
xmlfile = System.Deployment.Application.ApplicationDeployment.CurrentDeployment.DataDirectory + "\\EmoTesterSettings.xml";
else
xmlfile = ".\\EmoTesterSettings.xml";
CachedDoc = XDocument.Load(xmlfile);
XAttribute Oldstate = CachedDoc.Root.Attribute("EmotiveState");
if (Oldstate == null)
return EmotiveState.Neutral;
else
return EmotiveStateHelper.EmotiveStateFromString(Oldstate.Value);
}
private void SaveEmotiveState(EmotiveState state)
{
string xmlfile = "";
XDocument CachedDoc;
XAttribute Oldstate;
XAttribute NewState = new XAttribute("EmotiveState", state.ToString());
if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
xmlfile = System.Deployment.Application.ApplicationDeployment.CurrentDeployment.DataDirectory + "\\EmoTesterSettings.xml";
else
xmlfile = ".\\EmoTesterSettings.xml";
CachedDoc = XDocument.Load(xmlfile);
Oldstate = CachedDoc.Root.Attribute(NewState.Name);
if (Oldstate == null)
CachedDoc.Root.Add(NewState);
else
Oldstate.Value = NewState.Value;
CachedDoc.Save(xmlfile);
}
[code]
As you can see, by keying off the "isNetworkDeployed" property I can choose to use the Data directory or (in the case of F5) the bin folder the dll's are in.
In my example above you can see that I expect there to be a root node, in this case the file contains <data></data>. After running the solution the xml looks something like: <data EmotiveState="Happy"></data>. There is no reason why the files have to be published and installed before the first run, it is possible to add files to the directory, but if you do want to have default settings, using a pre-built data file instead of hard-coded values is a much better way to implement it.
So with that said there are some caveats about the Data Directory. If the solution is rolled back the data may or may not be preserved, generally if you store stuff in the data directory it is important to be aware that you shouldn't be putting mission critical information in it. If the user takes an update (usually) the data they have stored into the data directory is kept which means you also have to consider backwards compatibility. Unfortunately I don't know for certain if there is a reliable way to flush the data directory outside of un-installing the solution, I haven't had time to investigate it yet. Certainly there is a lot more documentation on this feature on MSDN under the general ClickOnce stuff and I fully recommend doing a little more investigation before diving any deeper than I have.
Thank You for reading.
Kris