[MPC Series] Inking P2 – Writing to OneNote

Okay, that was a lot longer than my ETA of 2 weeks. I just haven’t had the time to write this up – however the actual implementation of adding OneNote didn’t take as long as I expected!

The code is already there and ready for you to use @ the OneNoteDev GitHub. They have samples read for iOS, Android, Windows. Ruby, PHP etc. etc. I pretty much did a lot of copy pasting.

OneNote API Docs
OneNote API Sample Code

I was a little reluctant to write this with OneNote becoming part of the Microsoft Graph APIs (currently in beta), because it would mean having to change my code, and rewrite this post! I think it will be used the same, it’s till a RESTful API so that’s all good. But would we still be able to use the OneDrive SDK for auth? Maybe not if there will be a Graph SDK coming out for this (coming soon apparently)

Current API Route: https://www.onenote.com/api/v1.0/pages
Graph API Route: https://graph.microsoft.com///notes/
Example: https://graph.microsoft.com/beta/me/notes/pages

 

Screenshot 2016-03-23 17.05.23

Auth

I used the Microsoft Account (previously known as Live ID) authentication method as this is a commercial application. Now I haven’t tried this on Android and iOS (maybe I should?), but if you’re also developing a windows consumer application you will need to copy over the LiveIdAuth.cs file and the ApiBaseResponse.cs file. These are all you need to get started.

From there you can take a look at the OneNoteApi folder for examples on how to get and create pages, sections, notebooks and so on.

Also worth noting, the OneNote API uses the OneDrive SDK for auth, so you can easily get the OneDrive SDK as a nuget package and it will save you a ton of time. I think they wrote it better:

OneNote apps can use the OneDrive API SDK to get the access tokens that are required for all requests to the OneNote API. The SDK makes authentication easier for you. You just provide your identity information and integrate a few calls, and the SDK handles everything from sign in and consent to getting, storing, and refreshing tokens. Then, you can make REST calls to the OneNote API.

There are other methods available to use from the LiveIdAuth class, but the main one is GetAuthToken():

private static CredentialPromptType credPrompt = CredentialPromptType.PromptIfNeeded;
internal static async Task<string> GetAuthToken()
{
	if (String.IsNullOrWhiteSpace(_accessToken))
	{
		try
		{
			var serviceTicketRequest = new OnlineIdServiceTicketRequest(Scopes, "DELEGATION");
			var result = await Authenticator.AuthenticateUserAsync(new[] { serviceTicketRequest }, credPrompt);
			if (result.Tickets[0] != null)
			{
				_accessToken = result.Tickets[0].Value;
				_accessTokenExpiration = DateTimeOffset.UtcNow.AddMinutes(AccessTokenApproxExpiresInMinutes);
			}
		}
		catch (Exception ex)
		{
			// Authentication failed
			if (Debugger.IsAttached)
				Debugger.Break();
		}
	}
	await RefreshAuthTokenIfNeeded();

    credPrompt = CredentialPromptType.PromptIfNeeded;

    return _accessToken;
}

 

Adding Images and Text to a OneNote Page

I used the CreatePageWithImage method and changed the html to also add my own paragraphs and headings to the page. I also changed the image input and other minor things so that it worked how I wanted it to:

public static async Task<String> CreatePageWithImage(string token, string apiRoute, string content, StorageFile file)
{
    // This is the file that was saved by the user as a gif (see previous blog post)
    Stream fileStream = await file.OpenStreamForReadAsync();

    var client = new HttpClient();

    // Note: API only supports JSON return type.
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    // I'm passsing in the token here because I get it when the user logs in but you can easily get it using the LiveIdAuth class:
    // await LiveIdAuth.GetAuthToken()
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

    string imageName = file.DisplayName;
    string date = DateTime.Now.ToString();

    string simpleHtml = "<html>" +
                        "<head>" +
                        "<title>" + date + ": " + imageName + "</title>" +
                        "<meta name=\"created\" content=\"" + date + "\" />" +
                        "</head>" +
                        "<body>" +
                        "<p>" + content + "<p>" +
                        "<img src=\"name:" + imageName + "\" alt=\"" + imageName + "\" width=\"700\" height=\"700\" />" +
                        "</body>" +
                        "</html>";


    HttpResponseMessage response;

    // Create the image part - make sure it is disposed after we've sent the message in order to close the stream.
    using (var imageContent = new StreamContent(fileStream))
    {
        imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/gif");

        // Post request to create a page in the Section "Jots" https://www.onenote.com/api/v1.0/pages?sectionName=Jots
        var createMessage = new HttpRequestMessage(HttpMethod.Post, apiRoute + "pages?sectionName=" + WebUtility.UrlEncode("Jots"))
        {
            Content = new MultipartFormDataContent
            {
                {new StringContent(simpleHtml, Encoding.UTF8, "text/html"), "Presentation"},
                {imageContent, imageName}
            }
        };

        // Must send the request within the using block, or the image stream will have been disposed.
        response = await client.SendAsync(createMessage);

        imageContent.Dispose();
    }

    return response.StatusCode.ToString();
}

I’ve also updated the save method from my previous post so that it saves to OneNote by calling the above method:

private async void OnSaveAsync_Click(object sender, RoutedEventArgs e)
{
    string dialogMsg = "Didn't save, canvas is empty";

    //Using if statement so we don't save an empty canvas
    if (inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Count > 0)
    {
        dialogMsg = "Your jots have been saved locally";

        var savePicker = new Windows.Storage.Pickers.FileSavePicker();
        savePicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary;
        savePicker.FileTypeChoices.Add("Gif with embedded ISF", new List<string> { ".gif" });

        file = await savePicker.PickSaveFileAsync();

        if (file != null)
        {
            using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
            {
                await inkCanvas.InkPresenter.StrokeContainer.SaveAsync(stream);
            }
        }

        // Recognising the text on the canvas and saving it to the var savedWords
        GetSaveText();

        /*  I'm using localSettings from the Settings page I created to get the access token when the user
            chooses to login to OneNote. Like I said previously, you don't need this - you can do:
            string token = await LiveIdAuth.GetAuthToken();
        */
        if (localSettings.Values.Keys.Contains("AccessToken"))
        {
            var token = localSettings.Values["AccessToken"];

            //Calling the OneNote create page method with the auth token, api route, recognised words on the canvas and the gif file
            string result = await OneNote.CreatePageWithImage(token.ToString(), _apiRoute, savedWords, file);

            if (result == "Created")
                dialogMsg = dialogMsg + " and have been added to OneNote";
        }
    }

    var dialog = new MessageDialog(dialogMsg);
    dialog.Commands.Add(new UICommand("OK"));
    await dialog.ShowAsync();
}

Other

That’s pretty much it. Really easy to do, just copy the code from the GitHub examples and altered it to what you need.

I’ve made a ton of other changes to this application including adding a “Hamburger” menu and a settings page (with a terrible UI). It looks a little better but still needs a responsive UI 🙂

Code is here as always! https://github.com/liliankasem/Jots/

jots