While Windows 8 apps work in sandbox with limited access to file system, WinRT provides two methods for accessing and manipulating files and folders on external storage devices, such us USB keys and external hard drives.

No matter which method is used, application’s manifest has to request Removable Storage capability and declare what file types will be used.

Capabilities

KnownFolders.RemovableDevices

By using Windows.Storage.KnownFolders.RemovableDevices application can get StorageFolder object that maps all removable devices as subfolders. For example, if there are three external hard drives plugged into computer, KnownFolders.RemovableDevices will contain three subfolders representing these drives.


var folders = await Windows.Storage.KnownFolders.RemovableDevices.GetFoldersAsync();
foreach (var storageFolder in folders)
{
   var files = await storageFolder.GetFilesAsync();

   // work with files
}

Notes:

KnownFolders.RemovableDevices allows accessing removable storages such us DVD disks, USB keys, memory cards, external hard drives, etc.

StorageDevice and DeviceInformation

Alternatively, external storage devices can be accessed using combination of StorageDevice and DeviceInformation classes.


// Get all removable devices
var selector = Windows.Devices.Portable.StorageDevice.GetDeviceSelector();
var devices = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(selector);

foreach (Windows.Devices.Enumeration.DeviceInformation device in devices)
{
   // Get device root folder
   StorageFolder rootFolder = Windows.Devices.Portable.StorageDevice.FromId(device.Id);
                
   // Get files
   var files = await rootFolder.GetFilesAsync();

   // work with files
}

StorageFolder object returned by StorageDevice.FromId() represents the selected device’s root folder. Similar to RemovableDevices folder, the returned files are filtered.

DeviceInformation class allows accessing portable storage devices such as USB keys, memory card readers, mobile phones, etc. Note that sometimes DeviceInformation.FindAllAsync() can return device that doesn’t support file operations (e.g. Windows Phone). Method StorageDevice.FromId() called for such device will throw exception.

DeviceWatcher

Method DeviceInformation.FindAllAsync() returns current list of the devices. If application is interested in continuous monitoring of the devices, it can use DeviceWatcher.


var watcher = DeviceInformation.CreateWatcher(DeviceClass.PortableStorageDevice);
watcher.Added += Watcher_Added;
watcher.Removed += Watcher_Removed;
watcher.EnumerationCompleted += Watcher_EnumerationCompleted;
watcher.Stopped += Watcher_Stopped;

watcher.Start();

DeviceWatcher supports four events:

Watcher can be stopped using Stop() method.

Interacting with Windows 8 Maps app using WindowsMapsHelper library

Windows 8 is shipped with preinstalled Maps application. This app provides all basic mapping features such us showing current location, searching for place or local business and calculating driving directions. Even better, Maps app implements bingmaps protocol to allow 3rd party applications to activate these features. There is just one disadvantage of the protocol: it’s basically a URL string and all numerous parameters need to be properly formatted.

WindowsMapsHelper library aims to simplify communications with Maps app. It encapsulates maps protocol into a few strongly typed classes.

How to install

PM> Install-Package WindowsMapsHelper

Open Maps app and show default map


await WindowsMapsHelper.MapsHelper.ShowMapAsync();

Select map area to display

The following example demonstrates map options. It opens map centered on downtown Toronto with traffic information.


var options = new MapOptions
{
    ZoomLevel = new MapZoomLevel(16),
    ShowTraffic = true,
    CenterPoint = new MapPosition(43.649126, -79.377885)
};

await WindowsMapsHelper.MapsHelper.ShowMapWithOptionsAsync(options);

Search points of interest

The following example searches for “tea” around London, UK. Note that method’s third parameter, options, is skipped to let Maps app to suggest the best view for the map.


await WindowsMapsHelper.MapsHelper.SearchAsync("tea", "London, United Kingdom", null);

Driving directions


var options = new MapOptions
{
    ShowTraffic = true,
    ViewStyle = MapViewStyle.Aerial
};

await MapsHelper.SearchDirectionsAsync(new MapAddress("Montreal"), new MapAddress("Ottawa"), options);

This code maps a route from Montreal to Ottawa. Map display mode is Aerial.

Next example demonstrates using coordinates for route searching.


await MapsHelper.SearchDirectionsAsync(null, new MapPosition(33.943538, -118.40812), null);

Addresses and coordinates can be mixed and start or stop location can be skipped to ask user to select this point manually.

Map options

MapOptions class helps to configure map view and provides the following fields:

Getting sources

WindowsMapsHelper source code is on GitHub.

Visual Studio 2012 days in Toronto

VS2012

The upcoming three weeks in Toronto will be action packed with several Visual Studio-related events.

File system change notifications in WinRT

.NET framework for Windows 8 Store apps provides a subset of features included in full .NET framework profile. Microsoft excluded many APIs considered obsolete or unsafe. One of the removed classes is FileSystemWatcher, a class that proves its usefulness in applications intensively working with Windows file system. Instead, WinRT provides an alternative mechanism for folders monitoring based on queries.

Any folder can be queried to filter and enumerate files in the folder. For example the following code will return all Word documents in app’s local storage.


async void SearchFolder()
{
   var options = new Windows.Storage.Search.QueryOptions { FileTypeFilter = { ".docx" } };
   var query = ApplicationData.Current.LocalFolder.CreateFileQueryWithOptions(options);
   var files = await query.GetFilesAsync();
   
   // …
}

This is one time search that returns current state of the target folder. If continuous folder monitoring is required, app needs to be subscribed on query’s ContentsChanged event.


async void SearchFolder()
{
   var options = new Windows.Storage.Search.QueryOptions { FileTypeFilter = { ".docx" } };
   var query = ApplicationData.Current.LocalFolder.CreateFileQueryWithOptions(options);
   
   query.ContentsChanged += QueryContentsChanged; //subscription

   var files = await query.GetFilesAsync();

   // …
}

void QueryContentsChanged(Windows.Storage.Search.IStorageQueryResultBase sender, object args)
{
   // handle changes
} 

QueryContentsChanged will be called for every file system event that happens in the target folder. File type filter is not taken into account – WinRT will notify about new files of any type, new or deleted folders, etc.

At the same time, another query option – FolderDepth – affects content change notifications. E.g. for the query defined above WinRT will not send notifications for subfolders of LocalFolder. Next sample creates query that will track changes in LocalFolder and all subfolders, at any depth.


async void SearchFolder()
{
   var options = new Windows.Storage.Search.QueryOptions 
         { 
            FileTypeFilter = { ".docx" } ,
            FolderDepth = Windows.Storage.Search.FolderDepth.Deep
         };

   var query = ApplicationData.Current.LocalFolder.CreateFileQueryWithOptions(options);
   query.ContentsChanged += QueryContentsChanged;
   var files = await query.GetFilesAsync();

   // …
}

Additional details

Limitations

App contests for Windows 8 developers

Windows 8 isn’t released officially yet, but developers already have all the tools required for Windows 8 Store app development. Writing applications for the new, almost empty marketplace is a unique opportunity by itself, but to make it even more interesting Microsoft and Intel initiated two competitions.

Apptivate.ms

This contest is a joint effort of Microsoft and StackOverflow. Submit your app before December 6th, 2012 to compete for cash prizes, one of the Windows RT tablets or a Grand Prize equals to $5000.

In addition to apps contest, Apptivate.ms includes a reviewer contest. By answering Windows 8-related questions and providing app reviews, you can win one of tablets or Windows 8 licenses.

The Windows 8 & Ultrabook App Innovation Contest

The contest is brought to you by Intel and CodeProject. Prize pool for this event includes 300 Ultrabooks, fifty $1,000 cash prizes, seven $10,000 cash prizes and a grand prize of $20,000.

The goal of the contest is to create Ultrabook enabled Windows 8 applications. Windows 8 Store apps or Ultrabook-optimized desktop applications are equally acceptable.

During the first round, ends October 24, participants need to submit app write up as a CodeProject article. 300 first round winners will get Ultrabooks for app development and testing and will compete for the grand prize in round 2. Round 2 ends November 21.

What is Extensible Storage Engine and how it can be used by Windows 8 apps to persist data

If you start to explore Windows 8 system folders, you will quickly find a set of the same files presented in various locations:

ESE database files

What are these files? They are generated by Extensible Storage Engine (ESE), indexed sequential access data storage technology. ESE runtime has been a part of Windows since version 2000, empowering such products as Exchange, Active Directory and Desktop Search.

Windows 8 is not an exception. ESE is used by Zune Music, Zune Video, app repository and is available as a native API for all Windows Store app developers.

How to start

While ESE APIs can be used directly, C# developers may find using of a managed wrapper called ManagedEsent more convenient. All code samples in this post use ManagedEsent.

ManagedEsent supports Windows 8 starting from version 1.8. At this moment binaries for v1.8 are not available for download and you will need to compile sources by yourself or download assembly compiled by me to start using library .

The following code samples demonstrate how to create DatabaseRepository class that allows to instantiate ESE database, put, get and delete data. The data are represented by Event class:


public class Event
{
   public Guid Id { get; set; }
   public string Description { get; set; }
   public double Price { get; set; }
   public DateTime StartTime { get; set; }
}

Database

Creation of database includes two steps: creation of ESE instance and creation of database itself. Instances can include up to six databases and provide shared transaction log for all attached databases.

Create and configure instance


private Instance _instance;
private string _instancePath;
private string _databasePath;
private const string DatabaseName = "Database";

public void CreateInstance()
{
   _instancePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, DatabaseName);
   _databasePath = Path.Combine(_instancePath, "database.edb");
   _instance = new Instance(_databasePath);

   // configure instance
   _instance.Parameters.CreatePathIfNotExist = true;
   _instance.Parameters.TempDirectory = Path.Combine(_instancePath, "temp");
   _instance.Parameters.SystemDirectory = Path.Combine(_instancePath, "system");
   _instance.Parameters.LogFileDirectory = Path.Combine(_instancePath, "logs");
   _instance.Parameters.Recovery = true;
   _instance.Parameters.CircularLog = true;

   _instance.Init();
}

Create database

The following function creates database file and configures database schema to store Events.


public void CreateDatabase()
{
    using (var session = new Session(_instance))
    {
        // create database file
        JET_DBID database;
        Api.JetCreateDatabase(session, _databasePath, null, out database, CreateDatabaseGrbit.None);

        // create database schema
        using (var transaction = new Transaction(session))
        {
            JET_TABLEID tableid;
            Api.JetCreateTable(session, database, "Events", 1, 100, out tableid);

            // ID
            JET_COLUMNID columnid;
            Api.JetAddColumn(session, tableid, "Id",
                   new JET_COLUMNDEF
                   {
                      cbMax = 16,
                      coltyp = JET_coltyp.Binary,
                      grbit = ColumndefGrbit.ColumnFixed | ColumndefGrbit.ColumnNotNULL
                   }, null, 0, out columnid);
            // Description
            Api.JetAddColumn(session, tableid, "Description",
                   new JET_COLUMNDEF
                   {
                      coltyp = JET_coltyp.LongText,
                      cp = JET_CP.Unicode,
                      grbit = ColumndefGrbit.None
                   }, null, 0, out columnid);
            // Price
            Api.JetAddColumn(session, tableid, "Price",
                   new JET_COLUMNDEF
                   {
                      coltyp = JET_coltyp.IEEEDouble,
                      grbit = ColumndefGrbit.None
                   }, null, 0, out columnid);
            // StartTime
            Api.JetAddColumn(session, tableid, "StartTime",
                   new JET_COLUMNDEF
                   {
                      coltyp = JET_coltyp.Currency,
                      grbit = ColumndefGrbit.None
                   }, null, 0, out columnid);

            // Define table indices
            var indexDef = "+Id\0\0";
            Api.JetCreateIndex(session, tableid, "id_index",
                               CreateIndexGrbit.IndexPrimary, indexDef, indexDef.Length, 100);

            indexDef = "+Price\0\0";
            Api.JetCreateIndex(session, tableid, "price_index", 
                               CreateIndexGrbit.IndexDisallowNull, indexDef, indexDef.Length, 100);

            transaction.Commit(CommitTransactionGrbit.None);
        }

        Api.JetCloseDatabase(session, database, CloseDatabaseGrbit.None);
        Api.JetDetachDatabase(session, _databasePath);
    }
}

Data manipulation

Most of ESE data manipulations require opened session, started transaction and active table, so it makes sense to create a function to encapsulate all these activities:


private IList<Event> ExecuteInTransaction(Func<Session, Table, IList<Event>> dataFunc)
{
    IList<Event> results;
    using (var session = new Session(_instance))
    {
        JET_DBID dbid;
        Api.JetAttachDatabase(session, _databasePath, AttachDatabaseGrbit.None);
        Api.JetOpenDatabase(session, _databasePath, String.Empty, out dbid, OpenDatabaseGrbit.None);
        using (var transaction = new Transaction(session))
        {
            using (var table = new Table(session, dbid, "Events", OpenTableGrbit.None))
            {
                results = dataFunc(session, table);
            }

            transaction.Commit(CommitTransactionGrbit.None);
        }
    }

    return results;
}

Add event


public void AddEvent(Event ev)
{
    ExecuteInTransaction((session, table) =>
    {
        using (var updater = new Update(session, table, JET_prep.Insert))
        {
            var columnId = Api.GetTableColumnid(session, table, "Id");
            Api.SetColumn(session, table, columnId, ev.Id);

            var columnDesc = Api.GetTableColumnid(session, table, "Description");
            Api.SetColumn(session, table, columnDesc, ev.Description, Encoding.Unicode);

            var columnPrice = Api.GetTableColumnid(session, table, "Price");
            Api.SetColumn(session, table, columnPrice, ev.Price);

            var columnStartTime = Api.GetTableColumnid(session, table, "StartTime");
            Api.SetColumn(session, table, columnStartTime, DateTime.Now.Ticks);

            updater.Save();
        }
        return null;
    });
}

Delete event


public void Delete(Guid id)
{
    ExecuteInTransaction((session, table) =>
    {
        Api.JetSetCurrentIndex(session, table, null);
        Api.MakeKey(session, table, id, MakeKeyGrbit.NewKey);
        if (Api.TrySeek(session, table, SeekGrbit.SeekEQ))
        {
            Api.JetDelete(session, table);
        }
        return null;
    });
}

Get all events


public IList<Event> GetAllEvents()
{
    return ExecuteInTransaction((session, table) =>
    {
        var results = new List<Event>();
        if (Api.TryMoveFirst(session, table))
        {
            do
            {
                results.Add(GetEvent(session, table));
            }
            while (Api.TryMoveNext(session, table));
        }
        return results;
    });
}

private Event GetEvent(Session session, Table table)
{
    var ev = new Event();

    var columnId = Api.GetTableColumnid(session, table, "Id");
    ev.Id = Api.RetrieveColumnAsGuid(session, table, columnId) ?? Guid.Empty;

    var columnDesc = Api.GetTableColumnid(session, table, "Description");
    ev.Description = Api.RetrieveColumnAsString(session, table, columnDesc, Encoding.Unicode);

    var columnPrice = Api.GetTableColumnid(session, table, "Price");
    ev.Price = Api.RetrieveColumnAsDouble(session, table, columnPrice) ?? 0;

    var columnStartTime = Api.GetTableColumnid(session, table, "StartTime");
    var ticks = Api.RetrieveColumnAsInt64(session, table, columnStartTime);
    if (ticks.HasValue)
        ev.StartTime = new DateTime(ticks.Value);

    return ev;
}

Get event by ID


public IList<Event> GetEventsById(Guid id)
{
    return ExecuteInTransaction((session, table) =>
    {
        var results = new List<Event>();
        Api.JetSetCurrentIndex(session, table, null);
        Api.MakeKey(session, table, id, MakeKeyGrbit.NewKey);
        if (Api.TrySeek(session, table, SeekGrbit.SeekEQ))
        {
            results.Add(GetEvent(session, table));
        }
        return results;
    });
}

Get events for a price range


public IList<Event> GetEventsForPriceRange(double minPrice, double maxPrice)
{
    return ExecuteInTransaction((session, table) =>
    {
        var results = new List<Event>();

        Api.JetSetCurrentIndex(session, table, "price_index");
        Api.MakeKey(session, table, minPrice, MakeKeyGrbit.NewKey);

        if (Api.TrySeek(session, table, SeekGrbit.SeekGE))
        {
            Api.MakeKey(session, table, maxPrice, MakeKeyGrbit.NewKey);
            Api.JetSetIndexRange(session, table, 
                  SetIndexRangeGrbit.RangeUpperLimit | SetIndexRangeGrbit.RangeInclusive);

            do
            {
                results.Add(GetEvent(session, table));
            }
            while (Api.TryMoveNext(session, table));
        }
        return results;
    });
}

Conclusion

Extensible Storage Engine is a fast, transactional database optimized for sequential data access. It provides simple, native data storage mechanism for Windows 8 Store apps. The main disadvantages of ESE are the verbose API and limited querying capabilities. Additional information about Extensible Storage Engine is available on Wikipedia.

Download complete implementation of DataRepository class.

Transhipment: A WinRT library for sharing Schema.org formatted data

Windows 8 provides a standard mechanism for sharing data between apps – Share Contract. It works like a charm for the well-known formats like HTML or Url, but there are some issues with custom structured data sharing. I have mentioned the formats compatibility issue before in “Do you need a reference implementation for schema.org formats?” post and now I’d like to introduce Transhipment library that aims to resolve this issue.

Transhipment provides implementation for the most popular schema.org formats that make sense in the context of data sharing. Supported schemas:

In addition, the library includes a few helpers to simplify use of these schemas with Share Contract:

Below you can see a complete example of using Transhipment for sharing geo coordinates from C# app:


void DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    var request = args.Request;

    var geo = SchemaFactory.Create(Schema.GeoCoordinates) as IGeoCoordinates;
    geo.Name = "Polar Bear Provincial Park";
    geo.Latitude = "54.596931";
    geo.Longitude = "-83.283978";

    request.Data.Properties.Title = "Sample data";
    request.Data.Properties.Description = "data for " + geo.Type;
    request.Data.SetStructuredData(geo);
}

Transhipment is a WinRT library and can be used from all supported languages, e.g. from JavaScript:


function dataRequested(e) {
    var request = e.request;

    var geo = Transhipment.SchemaFactory.create(Transhipment.Schema.geoCoordinates);
    geo.name = "Polar Bear Provincial Park";
    geo.latitude = "54.596931";
    geo.longitude = "-83.283978";

    request.data.properties.title = "Sample data";
    request.data.properties.description = "data for " + geo.type;

    Transhipment.SchemaFactory.setStructuredData(request.data, geo);
}

Retrieving of shared data in target app is also easy:


public async void Activate(ShareTargetActivatedEventArgs args)
{
    var shareOperation = args.ShareOperation;
    var geo = await shareOperation.Data.GetStructuredDataAsync(Schema.GeoCoordinates) as IGeoCoordinates;
    if (geo != null)
    {
        DefaultViewModel["ThingName"] = geo.Name;
        DefaultViewModel["Latitude"] = geo.Latitude;
        DefaultViewModel["Longitude"] = geo.Longitude;
    }

    // ...
}

Extensibility

All Transhipment schemas support ExtendedProperties dictionary to extend formats and share application-specific data.

How to start

Transhipment is available as a NuGet package.

PM> Install-Package Transhipment

Transhipment source code is on GitHub and if you miss any of schema.org formats – feel free to send me your pull requests.

Inspecting local and roaming settings for Windows 8 Store app

ApplicationDataContainer class supports saving of complex hierarchies of local and roaming settings. Sometimes there is a need to verify saved values but unfortunately Windows 8 development toolset doesn’t include any settings inspector. This post describes a method for inspecting settings manually.

Application settings are stored in C:\Users\<user_name>\AppData\Local\Packages\<package>\Settings\settings.dat. settings.dat is a Windows NT registry file (REGF) and includes local and roaming settings. This file format, REGF, also known as Registry Hive File, is supported by Registry Editor.

To open settings file, close your app and open Registry Editor. Than select HKEY_LOCAL_MACHINE or HKEY_USER and open Load Hive dialog from File menu.

Load Hive

Browse for the settings file, open it and enter key name

Set Hive Key

Registry Editor will open selected settings and attach them as a new key

Hive

All values are stored as binary data with last 8 bytes containing timestamp or version information – these bytes are modified with every value change.

Values

You can modify values in Registry Editor and after unloading hive they will become available for your application.

My next talk about VisualStudio 2012 at Markham .NET UG

I will speak about Visual Studio 2012 on Monday, September 10 at Markham .NET User Group and Thursday, October 18 at North Toronto .NET UG.

Visual Studio 2012 has reached RTM and will be officially launched on September 12. Join us to see what is new in VS 2012 and which opportunities are unlocked by this release. This presentation will cover a variety of topics around Visual Studio 2012, including Web and Cloud, Windows 8, Team Development and much more.

Details at MarkhamDotNet.com and NorthTorontoUG.com

Preventing unauthorized modifications of XAML files in Windows 8 apps with XamlIntegRT

1. Your Metro-style app needs protection and here is why
2. Simplest approach to protecting advertisements in your Windows 8 app
3. Preventing unauthorized modifications of XAML files in Windows 8 apps with XamlIntegRT

I finished my last post with the example of instantiating ad controls in code behind to prevent having them turned off. While this method is simple to implement, it forces developer to use code behind and doesn’t protect other parts of XAML from unauthorised modifications.

An alternative approach for protection is implemented in XAMLIntegRT library. It is based on comparison of hash codes generated design-time for XAML pages with hashes calculated for pages installed on the target system. XAMLIntegRT includes two components – command line hash code generator and C# library for run-time validation.

XAMLIntegRT

Quick start

1) Add XAMLIntegRT library to your project by getting sources from GitHub repository or by using NuGet:

PM> Install-Package XamlIntegRT

2) Compile command line hash code generator from retrieved source code or download compiled XamlIntegRTGen.exe from GitHub.

3) Generate hash data for your XAML files. XamlIntegRTGen.exe accepts three mandatory and one optional parameter:

XamlIntegRTGen.exe <project_folder> <file_path> <namespace> [/ads]

XamlIntegRTGen.exe generates .cs file that contains hashes for XAML pages. The generated file needs to be added to the target project. You need to perform this step every time XAML page is modified and it makes sense to automatically call XamlIntegRTGen.exe from your build script as a pre-build event.

4) Now you are ready to implement run-time validation:


async void MainPageLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
    if (!await XamlValidator.Validate(new XamlFilesData()))
    {
        var dialog = new MessageDialog("XAML modifications detected");
        await dialog.ShowAsync();
    }
}

XamlValidator.Validate method returns false if any of the pages are modified and it’s up to you how to proceed from this point. Library source code is available and you can easily add validation for any additional types of files, for example images or text resources.

Next posts Previous posts