Creating HoloLens applications is just cool! But connecting them to services in the Cloud makes it even cooler. Think of provisioning information on real-life objects or straw through Cloud data like persons, related contacts, documents and other stuff. This allows you to build rich applications containing information you normally process in a 2D world  like your browsers. By extending it to a 3D world, you are able to process the data in a completely different way. Think of creating teams of people within your organization and group them based on specialties, getting a more clear inside view of your site structure in SharePoint or have a 3D model of the Microsoft Graph entities related objects.

This article is split into several posts which together describes how to connect your HoloLens App with the Microsoft Graph.

  1. Introduction 
  2. Register, Connect and get data (this post)
  3. Reference your class library in Unity
  4. Create a scene in Unity and connect your library

Connecting to Microsoft Graph

The following implementations are part of the OfficeGraphLibrary Class Library based on Universal Windows which we use for this application. As earlier explained we move that code to a separate class library.

Register the app based on Azure AD v2.0 endpoint

In my case i want to use the Azure AD v2.0 endpoint which allows us to use AD user accounts and Microsoft accounts to login. For that we need to go to https://apps.dev.microsoft.com to register an app.

Capture

This registration describes what the app is allowed to do and which delegated permissions we give it. Delegated permissions describe what the app can do when accessing the resources through the API.

Capture

You still need to give consent once as user when running the app. This happens normally only the first time when the application starts. It is also possible to give admin consent by the Administrator so your users can directly use the application on the device without the need for giving consent.

NuGet packages

To access the Microsoft Graph we are going to use the Microsoft.Graph and Microsoft.Graph.Core packages. They implement the core functionality for retrieving Microsoft Graph data as objects.

Capture

We are also going to use the Microsoft.Identity.Client package which helps us authenticating against Azure AD. This package does support the .NET Core libraries which are used by the UWP app.  This package is still in preview. So keep in mind that things will change in time till there is a release version available.

Code for authentication

The following code is used for connecting to Azure AD. It is exactly the same which you can find in several GitHub projects using authenication and is also found when you request assistance when registering a new app on devs.app.microsoft.com.

Capture

When you add a new app you have the option in the first screen to have a guided setup. This guided setup will also provide you the code you need to authenticate against an Azure AD v2.0 endpoint. The code is as follow:

public class AuthenticationHelper
{
        static string clientId = "a270fe15-cc81-xxxx-xxxx-xxxxxxxxxxxx";
        public static string[] Scopes = { "User.Read", "Mail.Send", "People.Read", "People.Read.All", "User.Read.All", "Sites.Read.All", "Files.ReadWrite", "Group.Read.All", "Tasks.Read", "Calendars.Read", "Calendars.Read.Shared", "Directory.Read.All" };

        public static PublicClientApplication IdentityClientApp = new PublicClientApplication(clientId);

        public static string TokenForUser = null;
        public static DateTimeOffset Expiration;

        private static GraphServiceClient graphClient = null;

        public static GraphServiceClient GetAuthenticatedClient()
        {
            if (graphClient == null)
            {
                // Create Microsoft Graph client.
                try
                {
                    graphClient = new GraphServiceClient(
                        "https://graph.microsoft.com/v1.0",
                        new DelegateAuthenticationProvider(
                            async (requestMessage) =>
                            {
                                var token = await GetTokenForUserAsync();
                                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);

                            }));
                    return graphClient;
                }

                catch (Exception ex)
                {
                    Debug.WriteLine("Could not create a graph client: " + ex.Message);
                }
            }

            return graphClient;
        }

        public static async Task GetTokenForUserAsync()
        {
            AuthenticationResult authResult;
            try
            {
                authResult = await IdentityClientApp.AcquireTokenSilentAsync(Scopes, IdentityClientApp.Users.First());
                TokenForUser = authResult.AccessToken;
            }

            catch (Exception)
            {
                if (TokenForUser == null || Expiration <= DateTimeOffset.UtcNow.AddMinutes(5))
                {
                    authResult = await IdentityClientApp.AcquireTokenAsync(Scopes);

                    TokenForUser = authResult.AccessToken;
                    Expiration = authResult.ExpiresOn;
                }
            }

            return TokenForUser;
        }

}

You need to specify the same scopes you want to have as delegated permissions. The key is automatically generated for you when you registered the app. The code uses the GraphServiceClient class based on the URL of the Graph API to request access. It needs the bearer token based on the logged on user to authorize.

Code for requesting the users

The next step is writing code to get data from the Microsoft Graph. In this example we keep it simple by getting the users in the Office 365 tenant. First we need to get a valid GraphServiceClient. Then we need to retrieve the users by calling Users.Request:().GetAsync(). This will return an interface to an object containing the result of the call. By calling AddRange(…) with the result we have the result as a List based object.

    public class GraphManager
    {
        public async Task<List<User>><GetAllUsers()
        {
            List persons = new List();

            var graphClient = AuthenticationHelper.GetAuthenticatedClient();

            if (graphClient != null)
            {        
                var users = await graphClient.Users.Request().GetAsync();

                persons.AddRange(users);
            }

            return persons;
        }

}

As you can see the call is asynchronous. The call to get the users is executed with a wait statement. If for some reason there is some latency when getting the data, everything will wait for that call. Later on in the code of the HoloLens app we will be using a callback method to handle the returned data at a later point of time.

Data model for our HoloLens App

If we want to use this method from outside the class library we will run into a small problem. Because the objects returned are part of the Microsoft.Graph NuGet packages, it will only work when we include the same packages inside our generated solutions from Unity. And that is something we do not want to. So we need to specify some own classes for returning the data back the HoloLens app.

    public class ItemData
    {
    }

    public struct PhotoDetail
    {
        public int? Width ;
        public int? Height;
        public byte[] Photo;
    }

    public class PersonData : ItemData 
    {
        public string FullName = "";

        public PhotoDetail PhotoDetail;
    }

Purely for demonstration purposes we store the full name of the user and the photo details. The photo details contain the width, height and the photo stored in bytes.

Code for getting the users with our own data objects

We use the following method to return the same list of users back as a list of PersonData objects.

  
    public class DataManager
    {

        public void GetAllPersons(OnGetDataCompleted dataCompleted)
        {
            IAsyncAction asyncAction = Windows.System.Threading.ThreadPool.RunAsync(
            async (workItem) =>
            {
                List persons = await GraphManager.Instance.GetAllUsers();

                List items = new List();

                foreach (User person in persons)
                {
                    PhotoDetail detail = await GraphManager.Instance.GetPhoto(person.Id);

                    items.Add(new PersonData { FullName = person.DisplayName, PhotoDetail = detail });
                }

                dataCompleted(items);               
            });
        }

}

Within this method we use RunAsync to call the GetAllUsers method asynchronously. Each of the User objects returned are migrated to our PersonData object. Getting the photo details is somewhat special and is explained later.

Delegate for handling results

Because this method is called asynchronously, the method directly returns after it is called. Therefor we give a method based on a delegate as a property. That method is called at the end of the call. The delegate is specified below.

 
    public delegate void OnGetDataCompleted(List list);

This allows us to handle the returned data at a later time instead of waiting on the method to finish. Especially since holograms and other assets in your scene would hang during that time.

Getting the picture

Getting the picture of the user is handled by a separate method in the GraphManager. the method request the photo details based on the id of the user. Those details describe the width and the height of the photo. A second call is necessary to retrieve the photo content stream.  That content is streamed into a MemoryStream and converted to a byte[] array.

 
    public class GraphManager
    {

        public async Task GetPhoto(string userId)
        {
            PhotoDetail detail;

            var graphClient = AuthenticationHelper.GetAuthenticatedClient();

            try
            {
                ProfilePhoto photo = await graphClient.Users[userId].Photo.Request().GetAsync();

                Stream photoStream = await graphClient.Users[userId].Photo.Content.Request().GetAsync();

                using (MemoryStream ms = new MemoryStream())
                {
                    photoStream.CopyTo(ms);
                    detail.Photo = ms.ToArray();
                }

                detail.Width = photo.Width;
                detail.Height = photo.Height;
            }
            catch(Exception)
            {
                int i = 0;

                detail.Width = 0;
                detail.Height = 0;
                detail.Photo = null;
            }

            return detail;
        } 

}

Both photo details and byte[] are stored as a PhotoDetail struct containing the image.

Previous articleConnecting your HoloLens app with Microsoft Graph – Introduction – 1 of 4
Next articleConnecting your HoloLens app with Microsoft Graph – Reference your class library in Unity – 3 of 4
A professional which inspires, motivates and educates businesses on how to leverage emerging technologies using Virtual, Augmented and Mixed Reality in their journey to digital innovation. He has a strong background on SharePoint, Office 365 and Microsoft Azure and is working with machine learning, artificial intelligence and cognitive services. Primarily focused on manufacturing, construction, industry, logistics and maritime/offshore, his goal is helping businesses to achieve more on creating, improving, smartening and shortening of business processes by using services, apps and devices. Furthermore, he engages in speaking, blogging and is organizer of events like the Mixed Reality User Group, Mixed Reality Talk @ VR, SP&C and the Global AI/MR Bootcamp. With more than 20 years of IT Business experience, acting in different roles like solution architect, he believes the future is just starting! Highly experienced in all realities, HoloLens and other devices, User experiences on devices and connecting the world to the cloud will help him to shape that future!

5 COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here