다음을 통해 공유


C#: Local Files

Your first application goes live.  There you are awaiting the adulation of your new users.  
Over in the corner of your open office the first line support staff seems to be getting a lot of calls.

Maybe this is your admiring fans? ....or not!

Then all the support tickets come through and every user reports your application is crashing.

Oh no!


Background

Way back through the mists of time when all users had Windows XP a user could just write a file into the folder their exe was in. You could rely on that working and hence many experienced developers tend to recall this.

Then some Smart Alec came along and decided that windows should have extra security features.

Windows versions since Vista  - such as Windows 7 and 8 stop you creating files in a folder located under Program Files or Program Files (86).  You need to write files somewhere else.

The usual place to do this is somewhere in AppData.

That Can’t be Right. Can it?

This is somewhat counter intuitive, since you can install applications and they somehow write files into Program Files.  Applications involve files and they are written there using your credentials.  So what's going on there then?

Installers usually get round this limitation by not so much creating the files as copying them into your new folder.  The file already exists, it's just inside an msi or a zip or in your download folder.

You can copy files in, you can delete files.  What you can’t do is overwrite files in there or create new files directly in Program Files ( and child folders ).

There are tricks you can do to work round this problem but AppData is where you’re “supposed” to store application files. 

Those work rounds?

You can inherit rights on a file.  Copy an editable file in and if you have the right permissions then you can edit and save.  That's a bad idea though.

You are best advised to go with the flow and use AppData.  If you don’t then you could well find your users have less rights than you and those support calls start.

Proving the Point

Open up File Explorer and find an existing folder structure of something you already have installed in Program Files.

Open a folder up and right click there in some white space.  In your context menu, take a look at what you have there under New.  All you will probably see is “Folder”.

You can create folders, no problem.

Find a txt file somewhere in there.  As an example, 7Zip has several.   Open one of those in Notepad, edit it and try and save the file.

When you hit save you get a “save as” dialogue rather than the file just saving.  That's a clue something odd is about to happen.  Press on.

Try and save where it is and you will get an “Access Denied” error box pops up.

You don’t have the rights.

AppData Options

AppData is where you're supposed to keep all those settings and data files for an application.  It's a hidden folder.  In File Explorer you need to choose View and tick Hidden Items ( or equivalent if you’re not win 8.1 ).

AppData is located under the user’s folder.

For example.

C:\Users\Andrew\AppData

On the author’s machine – whose username is Andrew.

For desktop applications your choices lie in:

  • LocalLow
  • Local
  • Local\IsolatedStorage
  • Roaming

You could invent a new one if you really wanted to. It’s best to pick one of these options though.

If you go look in there on a machine you’ve been using for a while you will probably find that different applications take a different line on where they store their files.

Use Sub Folders

You should not just throw your files directly into one of these folders.  If you choose settings.xml and someone else chooses settings.xml your files will over write each other - that's why it's bad practice to store files directly in one of the above.

If your application is for external use then you will probably want to consider a company name folder within whichever you pick.
Within that you will want another folder with application or namespace name.
Namespace is often best since it's particularly unlikely to get changed without someone considering the consequences.

LocalLow

This folder is intended for “low integrity” applications.  Those requiring minimal authority.

Few applications use this but you will notice Internet Explorer and Silverlight folders under the Microsoft folder in there.  These illustrate Microsoft's conventions.

This is probably the least likely folder you should go with.

Local

This folder is intended to be used for files which the user will want just on this specific machine. You might think that you “may as well” have any files you want available on any machine.  There are perfectly sensible reasons for wanting machine specific files:

  • Big files
  • Machine orientated data
  • Time sensitive data
  • Single machine users
  • System Admins

OK, that last one is perhaps what one might call a pragmatic reason.

Big Files

When an application stores a particularly large file for some reason then you might not want to copy that around with a user because of the overhead involved.  Maybe this is a fairly static set of data or maybe it’s relatively unimportant data and the user does something which re-creates it anyhow.

Machine Orientated Data

Consider a user who has a 10 inch tablet and a desktop with 24 inch monitor.  On one machine they may want to set the font size large.  On the other they might want it smaller.  If that preference is saved in a settings file of some sort and copied between machines then they will end up having to change their settings every time they switch devices.  They might conceivably end up with text so small they can't see where to click to change options on one of those devices.  
On the other hand, screen definitions on tablets are such that you might find tablet and desktop are near enough that sharing these settings is an advantage.

Time Sensitive Data

Some data simply must be downloaded/updated every day.  When you cache that for use intra-day you don’t want to use that file tomorrow.  You could come up with some mechanism which checks to see when data was last downloaded.  It’s often simpler just to cache the data locally and get the data at session start or some other known point of user interaction.

Single Machine Users

In many offices a user has one desktop machine and for many people who work “on the road” they have their one laptop.  They might rarely work on some other machine.  Maybe the salesman is in the office for that monthly meeting.

Copying files off onto some server would be a pointless overhead.

Local - Code Sample

Local is where user settings for a project are stored.  This is also the reason why you can save  changes to user settings but not application settings.

Saving application settings is just one line of code:

Properties.Settings.Default.Save();

On a project called WPFClock the file ends up in:

C:\Users\Andrew\AppData\Local\WPFClock\WPFClock.exe_Url_d2cxf2d2ugdaaxm0vq4hxftvnrrkcabs\1.0.0.0\user.config

This is an xml file, here you can see an example of one with four properties serialised.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <userSettings>
        <WPFClock.Properties.Settings>
            <setting name="ApplicationTop" serializeAs="String">
                <value>0</value>
            </setting>
            <setting name="ApplicationLeft" serializeAs="String">
                <value>2926.4</value>
            </setting>
            <setting name="ApplicationWindowState" serializeAs="String">
                <value>Normal</value>
            </setting>
        </WPFClock.Properties.Settings>
    </userSettings>
</configuration>

Note - as what might be considered a plus or a minus, the folder structure includes the product version.

Local\IsolatedStorage

Isolated Storage is useful for desktop apps, Silverlight and windows store/phone apps.  Some of those options work slightly differently but this is broadly speaking the same mechanism.

Those of the above which are working in a security sandbox cannot readily save files anywhere on disk.  Silverlight, for example, can only serialise/save files into IsolatedStorage.

There are quite a range of options available which you can use to specify exactly how the file is stored.

Some interesting options include associating with a specific assembly.

You can use .net calls to find the store for an assembly without knowing the exact folder structure, which will be created if it doesn’t exist.

This allows you to automatically create a new structure as each version of your application is delivered.  Should you be serialising/deserialising classes which change in your application then this can be quite handy.  You know with your new exe you will have a new store.

Each store is associated with an assembly – a layered application which has several library project dll can have a store for each of these as well as another for the “parent” exe.

System admins can iterate and clear up Isolated storage.

It is also possible to allocate a limit to IsolatedStorage for an assembly, which can be useful if you want to stop users getting “carried away” with the amount of data they store for an application.

You can use GetUserStoreForAssembly to create ( if it doesn't exist ) the folder structure and return a path for it.

IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly();
string FullPath = store.GetType().GetField("m_RootDir", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(store).ToString();

Note that some variations of combinations of flags other than the above are not reliable.,

Roaming

This option is commonly used for small files since the user can log on to any machine and get the file stored from last use of an app.  You don’t get this for free and there is of course an overhead involved in copying these files around. 

Think twice about using Roaming for large files.

You can get where this folder is for the user Roaming using Environment.GetFolderPath on ApplicationData:

public static class AppData
{
    public static string Location = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 
        System.Reflection.Assembly.GetExecutingAssembly().EntryPoint.DeclaringType.Namespace);
}

The above static helper supplies the Local Folder with the NameSpace of the calling application appended.
Path.Combine just adds a slash between them.

You can then use that to create a folder if it doesn't exist and Path,Combine that with a file name you will work with.

Usage:

var fileName = Path.Combine(AppData.Location, "FontDetails.xml");
if (!Directory.Exists(AppData.Location))
{
    Directory.CreateDirectory(AppData.Location);
}
 
FontDetails fd = Application.Current.Resources["FontDetails"] as FontDetails;
DataContractSerializer ser = new DataContractSerializer(typeof(FontDetails));
var xmlSettings = new XmlWriterSettings { Indent = true, IndentChars = "\t" };
using (var writer = XmlWriter.Create(fileName, xmlSettings))
{
    ser.WriteObject(writer, fd);
}

The above snippet uses the helper to find whether the application's folder is there.
It creates it if it isn't.
An object is then serialised to disk in that folder as FontDetails.xml.
You can see this code in action in the WPF: Dynamic Fonts sample.

Windows Forms

In the System.WIndows.Forms namespace there is a way to generate the complete path.  This is arguably less useful than the environment based approach since it includes ProductVersion.  When you upgrade a product that can of course introduce a complication and you'd have to copy the old values.
Application.LocalUserAppDataPath returns a path of Roaming\CompanyName or Product Name\Product Name\Version
For example on a WIndows 8.1 machine:

C:\Users\Andrew\AppData\Roaming\MyCompany\Win1\1.0.0.0

Again, the user name is Andrew, the company is called MyCompany and the application is called Win1.  It is the default version of 1.0.0.0 

Other Options

If you’re going to share date between users then a database on some server is the obvious choice you should consider.
You can also allow users to store files on some file share.  Give them an area per user/team and the sys admin can allocate some limit to it.
You can also redirect that appdata roaming area to a server which can be backed up regularly. This is a good option in many large organisations, so long as people work at the people don't expect to work whilst roaming around outside the office.

See Also

C# Resources

System Admins

The lion might be king of the jungle but in the office, system admins are king of the computers.  You don’t want to cross them. If you don’t know who is responsible then go find them and agree a strategy.