March, 2009

Posts
  • Eric Gunnerson's Compendium

    Introduction to HealthVault Development #10: Adding additional data to healthvault

    • 3 Comments

    We have a new user request. One of our users would like to store how hungry he whenever he enters a weight value.

    We head off to the HealthVault MSDN page, go to the learn page and then the reference page, and then choose HealthRecordItem Type Schema Browser. This page talks to the developer platform to find out what types are currently supported by the platform.

    Looking through all the data types available on the page, we don’t find one related to hunger. So, we can just head home for the day and spend some quality time with our XBox.

    Or, we could explore other ways to add the hunger data to HealthVault.

    The first way is to ask the HealthVault team to add a new data type. We regularly add new data types and revise existing ones to meet the needs of our partners. If you think that the data you want to store is of general interest, please start a conversation with us.

    The downside of this is that it’s going to take us a while to figure out what the data type should be, design it, and then add it to a release. At best, it’s about 6 weeks from when we start working on it to when it shows up on your doorstep, gift-wrapped in a small box with a big red bow (gift wrap, box, and bow not included). If the type is complex, requires a lot of research, or if we’re especially busy, it could take longer.

    While you wait for us – or if the information is not of general interest – there are three ways to add information:

    • All data types provide the CommonData.Note and CommonData.Tags properties for your use.
    • All data types support adding extension data in XML format.
    • A custom application-specific data type can be created.

    Application-specific data types are only accessible by the application that created them, and there is therefore no data liquidity using such types. While there are scenarios where that makes sense, our guideline is that applications should make as much data as possible accessible to other applications, and therefore limit the use of application-specific types.

    Extensions

    Extensions are created and accessed using the HealthRecordItemExtension class, and there is a collection of these classes on the CommonData property of the data type instance.

    The XML for the extension is in the following format:

    <extension>
        <extension-name>
            xml data goes here
        </extension-name>
    </extension>

    The meaning of the XML data inside is purely up to the application.

    For hunger, we’ll use an entry like this:

    <extension><hunger>Moderate</hunger></extension>

    Adding the extension to our project

    In default.aspx, make the c_lableHunger and c_dropDownListHunger controls visible. Here’s the code we need to parse the values, create the extension, and add it to the weight object:

    void AddHungerToWeight(Weight weight)
    {
        HealthRecordItemExtension extension = new HealthRecordItemExtension(ExtensionWeightTrackerHunger);
        XPathNavigator navigator = extension.ExtensionData.CreateNavigator();
        string innerXml = @"<extension><hunger>" + c_dropDownListHunger.SelectedItem.Value +
            @"</hunger></extension>";
        navigator.InnerXml = innerXml;

        weight.CommonData.Extensions.Add(extension);
    }

    That helper is already in the project, so you can just add the call right before the weight value is saved:

    AddHungerToWeight(weight);
    PersonInfo.SelectedRecord.NewItem(weight);

    The ExtensionWeightTrackHunger constant is used to uniquely identify the extension. Because there is a possibility for collision here, it would be a good idea to use a “<application>.<extension-name>” format. The constant that is currently in the project is incorrect – change it to “WeightTracker.Hunger”.

    In addition to the actual data stored in the extension (the ExtensionData property), there are a few other items that you can store:

    • Logo is the url of a logo to display with the extension.
    • Source is the source identifier.
    • Transform is the URL of a transform that converts the extension data into an HTML form.
    • Version is the version number of the extension

    We’ll follow our usual pattern in modifying our fetching code:

    First, add a “Hunger” header as the second-to-last entry.

    Then, we need some code to iterate over all extensions, similar to how we iterated over the related items. There is a helper in the project, but I don’t like the way it works any more, so we’ll use this code instead:

    string hunger = String.Empty;
    foreach (HealthRecordItemExtension extension in weight.CommonData.Extensions)
    {
        if (extension.Source == ExtensionWeightTrackerHunger)
        {
            XPathNavigator navigator = extension.ExtensionData.CreateNavigator();
            XPathNavigator value = navigator.SelectSingleNode("extension/hunger");
            if (value != null)
            {
                hunger = value.Value;
                break;
            }
        }
    }

    Finally, add hunger to the call to AddCellsToTable(). Run the project and verify that it works.

    A Nicer Approach

    We can make things better by using a derived class for the extension.

    Add a new class to the project, and name it “Hunger Extension”. Visual studio will ask you if you want to put it in App_Code – tell it “yes, that would be lovely”.

    Replace the class with the following:

    using System;
    using System.Xml;
    using System.Xml.XPath;
    using Microsoft.Health;
    using Microsoft.Health.ItemTypes;

    public class HungerExtension : HealthRecordItemExtension
    {
        static readonly string ExtensionSource = "WeightTracker.Hunger" ;

        string m_hunger;

        public HungerExtension()
        {
        }

        public HungerExtension(string hunger)
                       : base(ExtensionSource)
        {
            m_hunger = hunger;
            Source = ExtensionSource;
        }

        public string Hunger
        {
            get { return m_hunger; }
            set { m_hunger = value; }
        }

        protected override void ParseXml(IXPathNavigable extensionData)
        {
            XPathNavigator navigator = extensionData.CreateNavigator();
            XPathNavigator hungerNode = navigator.SelectSingleNode("extension/Hunger" );

            if (hungerNode != null)
            {
                m_hunger = hungerNode.Value;
            }
        }

        protected override void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteStartElement("Hunger");
            writer.WriteString(m_hunger);
            writer.WriteEndElement();
        }

        public static void RegisterExtensionHandler()
        {
            ItemTypeManager.RegisterExtensionHandler(ExtensionSource, typeof(HungerExtension), true);
        }
    }

    The class is pretty simple. The ParseXml() method pulls the data out of XML and puts it in the class, and the WriteXml() method does the opposite. The RegisterExtensionHandler() method registers this extension with the SDK, so that when it sees that extension, it creates an instance of our derived type instead of a generic HealthRecordItemExtension instance.

    Now that we have the class, it’s much easier to use our extension.

    First, add the call to register the extension before the weight values are loaded:

    HungerExtension.RegisterExtensionHandler();

    Modify the code to fetch the hunger value to the following:

    string hunger = String.Empty;
    foreach (HealthRecordItemExtension extension in weight.CommonData.Extensions)
    {
        HungerExtension hungerExtension = extension as HungerExtension;
        if (hungerExtension != null)
        {
            hunger = hungerExtension.Hunger;
        }
    }

    Change the code to save the hunger to the following:

    weight.CommonData.Extensions.Add(
            new HungerExtension(c_dropDownListHunger.SelectedItem.Value));
    PersonInfo.SelectedRecord.NewItem(weight);

    Run the application, and verify that it works. Then delete the unnecessary helper methods and string constant from default.aspx.cs

    Other resources:

    There is another set of pages where you can look at the schemas for the item types, and schemas for all the platform methods. We will be unifying these two sources in the future.

    Next Time

    Next time, we’ll talk about data filtering.

  • Eric Gunnerson's Compendium

    After SchooL chocLate cookies

    • 4 Comments
    • 2 cups chocLate Syrip
    • 2 cups flour
    • 1/4 cup maLLases
    • 5 cups oatmeaL
    • 5 TabLespoons SaLt
    • 5 teaspoons BakingSoda

    Put OatmeaL, SaLt, fLour and Soda in bowl

    Stir in syrip and MaLLases

    Bake ten minits

    Temp 345

    Eric, age 7

    (I was cleaning out some old stuff this weekend, and thought I would share it with you)

  • Eric Gunnerson's Compendium

    Introduction to HealthVault Development #9: Optional Authorization

    • 2 Comments

    This week, we have a new customer request. One of our customers wants to track her mood on a daily basis. We can do this by using the Emotion data type, but after talking with our broader customer base, we find that some of them don’t want this information displayed by the application.

    What to do? What to do?

    Enter optional auth

    Optional auth allows different users to grant our application different amounts of data access. All users are requested to grant us the base auth for the application, but our application decides when to ask users for additional data access.

    We will use the specific access granted by a user to determine what information to query for and what to display in our user interface.

    Setting up our optional authorization rule

    Go to the application configuration center, pick your application, and add a new rule, named “Opt_Emotion”, make it optional, ask for Create/Read permissions, and choose the “Emotional State” data type. Save your rule.

    Determining type permissions

    We can ask the platform for information about our access to the Emotion data type with the following code:

    List<Guid> items = new List<Guid>();
    items.Add(Emotion.TypeId);
    Collection<HealthRecordItemTypePermission> permissions =
            PersonInfo.SelectedRecord.QueryPermissions(items);

    This code asks the selected record whether it has access to Emotion. If there is no access, the permissions collection will be empty.

    Since we’re only concerned with one rather than several types, I wrote a little wrapper to make our code a bit simpler. Put this at the beginning of Page_Prerender:

    HealthRecordItemTypePermission emotionPermission =
                              GetItemPermission(Emotion.TypeId);

    If you look at the HealthRecordItemTypePermission class, you’ll see that it stores the permissions for both online and offline access. In our case, we want to look for online access.

    if (emotionPermission.OnlineAccessPermissions == HealthRecordItemPermissions.None)
    {
        c_linkEnableMoodTracking.Visible = true;
    }
    else
    {
        c_labelMood.Visible = true;
        c_dropDownListMood.Visible = true;
        PopulateDropDownFromEnum(c_dropDownListMood, typeof(Mood));
    }

    Because I’m going to be defining the rule set, I know ahead of time that the user is either going to have enough access to Emotion for me to do what I want, or no access at all. I can therefore simply test whether there is any access to control my UI.

    It’s possible, however, for me to ask for read and write access separately, and if I had done that, I would need to check for specific permissions (Create, Read, Update, or Delete).

    It would probably be a good time to run the project to see what happens. There should be a link below the Save button that says “Enable Mood Tracking”.

    Asking the user for access

    You might remember when you first ran the application, you were redirected to HealthVault so that you could authorize access for the application. For optional authorization, we’re going to send the user back to that same page.

    But first, we’d like to explain to the user the advantages of providing us access. That is done with a landing page named AuthorizeEmotion.aspx.

    Here’s the code that will do the redirect:

    string TargetQuery =
        "appid=f36debe2-c2a3-434a-b822-f8c294fdecf9&onopt1=Opt_Emotion";
    RedirectToShellUrl("APPAUTH", TargetQuery);

    The appid parameter must be set your application’s application id, and the onopt1 parameter is set to the name of the rule we want to ask for. It is possible to ask for multiple rules and to ask for offline access as well – the details are on the HealthVault Shell Redirect Interfaces page.

    Run your application, and choose the “Enable Mood Tracking” button, and click “Authorize”. When you get back to the authorization page, you will now find that it now asks for access to the Emotional State data type, with a checkbox next to it. Click the checkbox, approve access, and you’ll now find that the UI shifts into the “mood-enabled” mode.

    Storing and displaying Mood values…

    Storing and display Mood values is similar to the code we wrote to store the exercise values. We add the following code after we save the Exercise instance:

    Emotion emotion = null;
    if (c_labelMood.Visible)
    {
        emotion = new Emotion(new HealthServiceDateTime(DateTime.Now));
        emotion.Mood = (Mood) Int32.Parse(c_dropDownListMood.SelectedItem.Value);
        PersonInfo.SelectedRecord.NewItem(emotion);
    }

    and then modify the code that sets up the relationship:

    weight.CommonData.RelatedItems.Add(
                 new HealthRecordItemRelationship(exercise.Key));
    if (emotion != null)
    {
        weight.CommonData.RelatedItems.Add(
                new HealthRecordItemRelationship(emotion.Key));
    }

    PersonInfo.SelectedRecord.NewItem(weight);

    Displaying mood values in the table

    The header on the table needs to change based on whether we are tracking mood or not:

    if (c_labelMood.Visible)
    {
        AddHeaderCells(c_tableWeight, "Date", "Weight", "BMI", "App Name",
                               "Minutes", "Mood", "&nbsp;");
    }
    else
    {
        AddHeaderCells(c_tableWeight, "Date", "Weight", "BMI", "App Name",
                               "Minutes", "&nbsp;");
    }

    We add the following to our relationship-fetching code:

    Emotion emotion = relatedItem as Emotion;
    if (emotion != null)
    {
        mood = emotion.Mood.ToString();
    }

    after declaring “string mood” right below “string minutes”.

    And finally, we modify our code that adds the data to the table:

    if (c_labelMood.Visible)
    {
        AddCellsToTable(c_tableWeight, weight.When.ToString(),
                   weight.Value.DisplayValue.ToString(), bmiString, appName, minutes, mood);
    }
    else
    {
        AddCellsToTable(c_tableWeight, weight.When.ToString(),
                   weight.Value.DisplayValue.ToString(), bmiString, appName, minutes);
    }

    With that code, the application should now support entering and displaying mood information.

    De-authorization

    If we want to give the user the opportunity to turn off mood tracking, we can do it by using the same call as we used to authorize the access.

    Add a page named DeauthorizeEmotion with appropriate text and code, and then modify the main page code so that it shows the authorize or deauthorize link as applicable.

    Current Code

    To help keep your code in sync with mine, I’ve uploaded my current code to MSDN Code Gallery. You will need to change my source so that it uses your application ID rather than mine.

    Suggestions

    I have a list of topics that I’m planning on covering, but if you have a suggestion for what you’d like to see, let me know.

    Next Time

    Next time, we’ll be looking at how to store hunger information in our application.

Page 1 of 1 (3 items)