In a previous post, I explained how to store data to a “known location” such as the My Documents folder. Often, however, you will want to store local data to a subfolder of AppData on the user’s disk. This is even easier to do, because you need no special permissions or settings to access either local or roaming data files.
In this blog post, based in part on work done for my upcoming book Pro Windows 8 With C# and XAML by Jesse Liberty and Jon Galloway, we’re going to explore storing local data, but doing so in an extensible, reusable, well-factored way. In a later posting, I’ll use the same structure to store data using Sqlite.
The actual storage of the file is pretty straight forward, but we’re going to build out a fully reusable Repository model so that we can reapply it to other storage approaches later. We begin with the data file itself. To keep things simple, I’ll stay with the idea of storing and retrieving customer data.
Creating the Application
To begin, create a new Windows 8 Store Application using the blank application template, and call it LocalFolderSample. Add a folder named DataModel and in DataModel add a Customer class,
public class Customer
{
public int Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Title { get; set; }
}
Notice that this is a POCO (Plain Old CLR Object) class, nothing special about it. Next, we want to build a file repository, but to do that we’ll start by defining a DataRepository interface. Create a new file, IDataRepository,
public interface IDataRepository { Task Add(Customer customer); Task<ObservableCollection<Customer>> Load(); Task Remove(Customer customer); Task Update(Customer customer); }
This interface has four methods. The only unusual one is Load, which returns a Task of ObservableCollection of Customer. This is so that Load can be run asynchronously, as we’ll see later in this posting.
With this interface, we can build our implementation, in a file named FileRepository.cs,
public class FileRepository : IDataRepository { StorageFolder folder = ApplicationData.Current.LocalFolder; string fileName = "customers.json"; ObservableCollection<Customer> customers;
Our FileRepository class implements IDataRepository, and has three member variables:
- A StorageFolder representing a local folder under application data
- A string for the file name of our specific storage file
- An observable collection of Customer
The constructor calls the Initialize method, which in this case does nothing. The initialize method will be more important when we cover SQLite, which we will in an upcoming blog post.
public FileRepository() { Initialize(); } private void Initialize() { }
The interface requires that we implement four methods. The Add method adds a customer (passed in as a parameter) to the customers collection and then calls WriteToFile,
public Task Add(Customer customer) { customers.Add(customer); return WriteToFile(); }
Write to file is a helper method,
private Task WriteToFile() { return Task.Run(async () => { string JSON = JsonConvert.SerializeObject(customers); var file = await OpenFileAsync(); await FileIO.WriteTextAsync(file, JSON); }); }
Notice that WriteToFile converts the customers collection to JSON and then opens the file to write to asynchronously and then writes to that file, again asynchronously. To open the file, we add the helper method OpenFileAsync,
private async Task<StorageFile> OpenFileAsync() { return await folder.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists); }
Notice that when opening the file we handle CreationCollisions by saying that we want to open the file if it already exists.
The second of the four methods we must implement is Remove, which is pretty much the inverse of Add,
public Task Remove(Customer customer) { customers.Remove(customer); return WriteToFile(); }
The third interface method is Update. Here we have slightly more work to do: we must find the record we want to update and if it is not null then we remove the old version and save the new,
public Task Update(Customer customer) { var oldCustomer = customers.FirstOrDefault(c => c.Id == customer.Id); if (oldCustomer == null) { throw new System.ArgumentException("Customer not found."); } customers.Remove(oldCustomer); customers.Add(customer); return WriteToFile(); }
Finally, we come to Load. Here we create our file asynchronously and if it is not null, we read the contents of the file into a string.
public async Task<ObservableCollection<Customer>> Load() { var file = await folder.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists); string fileContents = string.Empty; if (file != null) { fileContents = await FileIO.ReadTextAsync(file); }
We then Deserialize the customer from that string of JSON into a IList of customer, and create an ObservableCollection of customer from that IList,
IList<Customer> customersFromJSON = JsonConvert.DeserializeObject<List<Customer>>(fileContents) ?? new List<Customer>(); customers = new ObservableCollection<Customer>(customersFromJSON); return customers; }
Note that this JSON manipulation requires that we add the JSON.NET library which you can obtain through NuGet or CodePlex. The easiest route is through NuGet as explained in this posting.
Creating the ViewModel
The final file in the DataModel folder is ViewModel.cs. This will act as the data context for the view. It begins by declaring a member variable of type IDataRepository,
IDataRepository _data;
The constructor takes an IDataRepository and initializes the member variable,
public ViewModel(IDataRepository data) { _data = data; }
In the view model initialize, we tell the repository to load its data,
async public void Initialize() { Customers = await _data.Load(); }
There are two public properties in the VM:
private Customer selectedItem; public Customer SelectedItem { get { return this.selectedItem; } set { if (value != selectedItem) { selectedItem = value; RaisePropertyChanged(); } } } private ObservableCollection<Customer> customers; public ObservableCollection<Customer> Customers { get { return customers; } set { customers = value; RaisePropertyChanged(); } }
With these, we are ready to implement the CRUD operations, delegating the work to the repository,
internal void AddCustomer(Customer cust) { _data.Add(cust); RaisePropertyChanged(); } internal void DeleteCustomer(Customer cust) { _data.Remove(cust); RaisePropertyChanged(); }
Don’t forget to have the VM implement INotifyPropertyChanged,
public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged( [CallerMemberName] string caller = "") { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(caller)); } }
That’s it for the VM. By using a repository, we keep the VM simple, clean and reusable with different storage approaches.
The View begins with a couple styles,
<Page.Resources> <Style TargetType="TextBlock"> <Setter Property="FontSize" Value="20" /> <Setter Property="Margin" Value="5" /> <Setter Property="HorizontalAlignment" Value="Right" /> <Setter Property="Grid.Column" Value="0" /> <Setter Property="Width" Value="100" /> <Setter Property="VerticalAlignment" Value="Center" /> </Style> <Style TargetType="TextBox"> <Setter Property="Margin" Value="5" /> <Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="Grid.Column" Value="1" /> </Style> </Page.Resources>
It then adds an AppBar for saving data
<Page.BottomAppBar> <AppBar x:Name="BottomAppBar1" Padding="10,0,10,0" AutomationProperties.Name="Bottom App Bar"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50*" /> <ColumnDefinition Width="50*" /> </Grid.ColumnDefinitions> <StackPanel x:Name="LeftPanel" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left"> <Button x:Name="Save" Style="{StaticResource SaveAppBarButtonStyle}" Tag="Save" Click="Save_Click" /> <Button x:Name="Delete" Style="{StaticResource DeleteAppBarButtonStyle}" Tag="Delete" Click="Delete_Click" /> </StackPanel> </Grid> </AppBar> </Page.BottomAppBar>
It then has a set of stack panels to gather the data,
<StackPanel Margin="150"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Email" Margin="5" /> <TextBox Width="200" Height="40" Name="Email" Margin="5" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="First Name" Margin="5" /> <TextBox Width="200" Height="40" Name="FirstName" Margin="5" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Last Name" Margin="5" /> <TextBox Width="200" Height="40" Name="LastName" Margin="5" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Title" Margin="5" /> <TextBox Width="200" Height="40" Name="Title" Margin="5" /> </StackPanel>
Finally, we add a ListView to display the customers we had on disk, and now have in memory
<ScrollViewer> <ListView Name="xCustomers" ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Height="400"> <ListView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding FirstName}" /> <TextBlock Text="{Binding LastName}" /> <TextBlock Text="{Binding Title}" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </ScrollViewer>
Notice the binding both for the ItemsSource and the SelectedItem.
The code behind is very straightforward. The first thing we do is instantiate an IDataRepository and declare the viewmodel,
public sealed partial class MainPage : Page { private IDataRepository data = new FileRepository(); private ViewModel _vm;
In the constructor, we create the ViewModel passing in the repository, then call initialize on the VM and finally set the VM as the DataContext for the page,
public MainPage() { this.InitializeComponent(); _vm = new ViewModel(data); _vm.Initialize(); DataContext = _vm; }
All that is left is to implement the two event handlers
private void Save_Click(object sender, RoutedEventArgs e) { Customer cust = new Customer { Email = Email.Text, FirstName = FirstName.Text, LastName = LastName.Text, Title = Title.Text }; _vm.AddCustomer(cust); } private void Delete_Click(object sender, RoutedEventArgs e) { if (_vm.SelectedItem != null) { _vm.DeleteCustomer(_vm.SelectedItem); } }
When you run the application, you are presented with the form shown at the top of this post. Fill in an entry and save it, and it immediately appears in the list box.
More important, your file, Customers.json, has been saved in application data. You can find it by searching for it under Application Data on your C drive,
Double click on that file and see the JSON you’ve saved:
[{“Id”:0,”Email”:”jesse.liberty@telerik.com”,”FirstName”:”Jesse”,”LastName”:”Liberty”,”Title”:”Evangelist”}]
Roaming
To change from storing this in local storage to roaming storage, you must change one line of code. Back in FileRepository.cs change the LocalFolder to a RoamingFolder,
Hey! Presto! Without any further work, your application data is now available on any Windows 8 computer you sign into.
Summary
We’ve seen in an earlier article how to write to known file locations such as My Documents, and in this posting how to write to Local or Roaming. In an upcoming posting I’ll demonstrate how to take this same program as shown here, but use it to write to Sqllite.
Download the source code for this example.
As long as you are familiar with samurai siege hack the Internet.
Finally in terms of plot and the Order of the websites
gives a facility to provide full on enjoyment, it is here to visit several websites and enjoy them.
Their unique characteristics want to do well to reward our
dogs in difficult great content terrains. Look for testimonials, they smell, sound, and how to train because they are not fully conditioned yet.
In fact, you’ll need to get the chance of the
most effective. At the start and sometimes that is important to make
certain you’re not there. Visit some centres for dog owners and dogs for doing it?
The key would be perfectly safe for your canine may damage or work following someone.
my web page … wikipedia.org; Rusty,
It will likewise be great when dog training they
start learning what you are hunting that has been bringing his new environment.
You want a dog requires a comfy shelter. But, even climb a ladder while searching for them to
by saying either” sit” that are covered. With proper instructing of those nasty
varmints! They are small and in dog training advanced obedience
and communication. It is excellent to as” sit down and praising your dog behaving badly and barking often. One thing to try out and down those asphalt paths they call roads.
Here is my page :: bing.com (Georgia)
We are a gaggle of volunteers and starting a new scheme in our community.
Your web site offered us with valuable info to work on. You have performed an impressive activity and
our entire neighborhood will likely be grateful to you.
Hey there! I’m at work surfing around your blog
from my new apple iphone! Just wanted to say I love reading
your blog and look forward to all your posts! Carry on the outstanding work!
Wow! At last I got a blog from where I can in fact get helpful facts concerning my study and knowledge.
Not to criticize an obviously fantastic set of examples. But it seems incomplete when there are no “Update” methods. I see the setup methods for Update but I don’t see it implemented in the “Save” method. Meaning someone can save and delete all day long but can never “Edit” what they’ve saved. I took the liberty of scaling out the Update method to just add an if statement before saving so the Save method can check if there’s a SelectedItem with an Id. If there is, then use Update(); If there isn’t use Add(); This was very helpful and useful to me by all accounts.
I was recommended this blog by my cousin. I am not sure whether this post is written by him as no one
else know such detailed about my trouble. You’re wonderful!
Thanks!
Excellent post! We are linking to this particularly great
content on our site. Keep up the great writing.
Excellent website you have here but I was wondering if you knew of
any message boards that cover the same topics discussed in this
article? I’d really love to be a part of group where I can get feedback from other experienced people that share the same interest. If you have any recommendations, please let me know. Bless you!
Actually no matter if someone doesn’t be aware of afterward its up to other visitors that they will assist, so here it happens.
I am sure this paragraph has touched all the internet viewers,
its really really fastidious piece of writing on building up new website.
It is perfect time to make a few plans for the long run and it’s time to be happy. I’ve learn this publish
and if I may I desire to counsel you few interesting issues or advice.
Maybe you could write next articles regarding this article.
I want to read more issues approximately it!
Very shortly this web page will be famous among all blogging
and site-building people, due to it’s fastidious content