One of the cool features of Windows Phone from the start has been its tight and seamless integration of various sources into common and central places on the phone, like the people hub.
To this end, multiple contact entries can be and often even are automatically linked together so information from different origin like Facebook, Twitter or Outlook is aggregated and merged into a single profile. However, this mechanism of deep integration was something that was managed by the operating system and first party apps. As developer, you had no chance of using similar features from within your own apps. With Windows Phone 8, Microsoft has improved this and added a way for us developers to integrate with a user's contacts list.
Contact Stores
In Windows Phone 7, what you could do was use the "SaveContactTask" class [1] located in the namespace Microsoft.Phone.Tasks to initiate the creation of a new contact. The entry could be pre-populated with data from your app, however just like with any other task or chooser the user is redirected to the corresponding default system dialog for the intended action, and only if they chose to save the contact was the data actually persisted:
1: var task = new SaveContactTask();
2: task.FirstName = "Ewald";
3: task.LastName = "Kratzkopp";
4: task.Show();
With Windows Phone 8, Microsoft introduces a new concept of "custom contact stores" [2]. In addition to the read-only access to the user's contact list and the above demonstrated way to use a separate task for creating new entries (both of which available in 7.x) we now are able to write our own data to the people hub silently and without user consent. However apps still cannot manipulate existing contacts that originate from somewhere else. In this sense, the data that belongs to an app is somewhat isolated from the rest. This concept is what is described as contact store – each app can have its own, writable part of the global contacts, but can only read from the rest. Which details of its own contacts can be consumed by other apps is decided by the app itself. However, the minimum setting is to allow reading the description and retrieving the associated display image, so you cannot completely hide your entries from the rest of the world.
Getting access to a contact store is easy. First you need to specify the ID_CAP_CONTACTS capability in your app's manifest. If you fail to do so, the actions that follow will result in an InvalidAccessException.
Now you can use the corresponding class in the Windows.Phone.PersonalInformation namespace [3], in particular its static method to create a new contact store or open it if it already exists:
1: var contactStore = await ContactStore.CreateOrOpenAsync(
2: ContactStoreSystemAccessMode.ReadWrite,
3: ContactStoreApplicationAccessMode.LimitedReadOnly);
The two options that you can optionally pass in control how others can interact with your contacts: whether the system can only read or also write to your contacts and second, whether other apps will be able to read the full data from your contact entries or only the minimum set of information. Please note that the "system" rights directly affect whether the user is able to manipulate a contact through the phone's common UI in the people hub. If you specify "ReadOnly" here, the user won't be able to delete contacts you added, and they will not be able to edit existing data, only add additional information (which is achieved through another linked contact):
Specifying "ReadWrite" on the other hand enables users not only to edit, but even to create completely new contacts in your contact store. In that case you should be careful not to assume any data always to be present and make sure everything is consistent with what you expect.
Another important detail is that you cannot change the access rights of a contact store later on, or mix and match them as you wish, depending on the situation. When you have created a store once and e.g. set other applications' rights to "ReadOnly", then trying to downgrade the rights later by specifying "LimitedReadOnly" on successive opening will result in an ArgumentException. You can however use the "DeleteAsync" method on an existing store to remove it, and then start over with different access rights.
With the contact store object, you can now create new and retrieve existing contacts easily.
Create New Contacts
Contact store entries are named (who would've thought that) stored contacts. The required class is located in the same namespace as the "ContactStore" class. To create a new contact, you have to pass in the store object the contact will be located in to the constructor of the "StoredContact" class. Optionally you can also use an existing "ContactInformation" instance to pre-populate the new entry, which also allows to e.g. import data from vCards [4]. However, it's possible to set all properties manually after creation, as shown in the following snippet:
1: // create the contact store
2: var contactStore = await ContactStore.CreateOrOpenAsync(
3: ContactStoreSystemAccessMode.ReadWrite,
4: ContactStoreApplicationAccessMode.LimitedReadOnly);
5:
6: // create a new contact
7: var storedContact = new StoredContact(contactStore);
8: storedContact.GivenName = "Ewald";
9: storedContact.FamilyName = "Kratzkopp";
10:
11: // set additional properties
12: var properties = await storedContact.GetPropertiesAsync();
13: properties[KnownContactProperties.Email] = "kratzewald@example.com";
14:
15: // save the new contact
16: await storedContact.SaveAsync();
The code is pretty straight-forward. One noteworthy detail is that the properties on the contact object itself only provide direct access to a very limited set of data, like the name. To access and manipulate other common properties, you have to use the "GetPropertiesAsync" method to retrieve a dictionary. The "KnownContactProperties" class helps you using common keys in a type-safe manner, like "Email" above. There's also an "GetExtendedProperties" method that allows you to provide your own properties that are unrelated to the common data typically found on the phone.
The nice thing about contacts that are added to your apps contact store is that the usual mechanism of the platform still apply. This means that e.g. entries you create are auto-linked to existing entries if applicable, or can be linked by the user manually if they decide to. From the above example, the email address I specified is merged with an existing contact I had created on the phone manually before:
This allows you to e.g. use the new and the existing read-only APIs in combination to create the illusion that you actually extend existing contacts, if you want.
Please note that if your app creates and saves an entry repeatedly it will not magically be merged with existing ones (by name, for example), and this will also not result in errors. These multiple entries hence will appear as separate entities in the contacts list. Because the entries typically will be auto-linked this behavior might not be obvious at a quick glance, but becomes apparent on the detail page:
To solve this, you should search for existing contacts first and edit them, instead of blindly creating new entries.
Working with Existing Contacts
When you save a new contact, it will be assigned an Id that you can use to easily retrieve the contact later on:
1: var existingContact = await contactStore.FindContactByIdAsync("{8.30001.3}");
This requires you to retrieve and store the Id that the phone assigns though. You can also turn this mechanism upside down and provide your own Id when you create the contact, for example taken from your database or some other internal data structure. This Id needs to be stored in the "RemoteId" property, because the phone still generates its own Id. It's your responsibility to ensure that your Ids are unique; creating multiple entries with the same Id results in an exception. To lookup existing entries, you then use the corresponding "FindContactByRemoteIdAsync" method of the contact store.
1: // create a new contact
2: var storedContact = new StoredContact(contactStore);
3: storedContact.GivenName = "Ewald";
4: storedContact.FamilyName = "Kratzkopp";
5: storedContact.RemoteId = "MyId";
6: await storedContact.SaveAsync();
7:
8: // the Id property of storedContact now contains the auto-generated Id
9:
10: // later: re-load the contact by external Id
11: var existingContact = await contactStore.FindContactByRemoteIdAsync("MyId");
Another way of accessing existing contacts is to use a contact query. This allows you to access all of your app's contacts and offers additional options like specifying the sort order and performing paging. Here is an example:
1: // set some query options
2: var queryOptions = new ContactQueryOptions();
3: queryOptions.DesiredFields.Add(KnownContactProperties.Email);
4: queryOptions.OrderBy = ContactQueryResultOrdering.FamilyNameGivenName;
5:
6: // create and execute the query
7: var contactQuery = contactStore.CreateContactQuery(queryOptions);
8: var contactCount = await contactQuery.GetContactCountAsync();
9: var contacts = await contactQuery.GetContactsAsync(0, contactCount);
10:
11: foreach (var contact in contacts)
12: {
13: // do something
14: }
The snippet also makes use of the "DesiredFields" property. This allows you to specify what properties of the contacts you are going to use after retrieving them. You still have to use the mechanism I've demonstrated above to access the values (using "GetPropertiesAsync"), however specifying the DesiredFields up front can result in a performance gain as no additional round-trips to the internal database might be needed later on.
Once retrieved, you can manipulate its properties and save back the contact, or export them as vCard [5], for example.
Security Concerns
Let's talk a moment about security concerns that may arise from the new features. To a lot of people phrases like "write silently and without user consent", and "the user cannot delete contacts you create as read-only" sound alarmingly when we talk about a central store like the contacts, which are shared across the whole phone. What if an app decides to rampage and spam your phone with undesired entries? Well, of course the first frontier against things like these is Microsoft's validation process for apps in the Phone Store. But let's assume that a malicious app has made it through certification, and managed to flood your contact list with bogus entries.
The good news is that unlike with the SaveContactTask that requires explicit user consent, entries that are generated using custom contact stores are automatically removed when the originating app is uninstalled. This means that as user you can easily get rid of everything an app has added to your global contacts list by removing the app in question, which I think is an acceptable and sufficient safety net to protect users.
This behavior should also be a hint for you as a developer not to use contact stores as primary storage for important data. The data your app's contact entries are based on should always be stored e.g. in a back end like the cloud, to enable easy recovery and of course other scenarios like users migrating to other devices. To help you with this, custom contact stores support revisions and the retrieval of e.g. manual changes made by the user, so you can sync the local version with the one in your back end [6].
Conclusion
With the new concept of custom contact stores Microsoft managed to both open up new possibilities for developers as well as keep security and isolation of data on a high level. For the user, due to features like linked profiles, these security-related restrictions are almost invisible and do not come at a cost of comfort. Some apps (like Skype, for example) already actively make use of the new features, and I'm quite interested in what we'll see in this area in the coming months. Surprise us ;).