PortiBlog

Microsoft Graph API: How to change images

26 november 2018

Mavention Workspace has the ability to easily change the image of a group (or as we like to call them: Workspace). This will set the new image visible on all platforms including Microsoft Teams (if it is a Teams workspace), Outlook and SharePoint. This sounds like an easy to write code-module. And when I began writing the module, I thought just the same. However, there are some tricky parts in communicating with the Graph API if you want to send files. In this blog I will elaborate on how to upload images using the Microsoft Graph API.

Graph API endpoint

In order to upload or replace an image and make it available on all platforms we have to update the values that already exist . The endpoint we are looking for is described here.

As you can read in the documentation there are some permissions that have to be set according to your needs:

  •  User.ReadWrite -> For the profile picture of the signed in user
  •  User.ReadWrite.All -> For the profile picture of any user in the organization, on behalf of the app's identity
  •  Group.ReadWrite.All -> For the profile picture of a group
  •  Contacts.ReadWrite -> For the photo of a contact

For uploading a group image this endpoint will do:

PUT /groups/{id}/photo/$value

Call the Graph API

This is where the tricky parts starts. We have to give some value to the endpoint, and as you can read in the Microsoft Docs it cannot be a Base64 string, which would be a lot easier. Instead, the endpoint expects binary data of the image. But how to convert a Base64 string to binary data? It's easy to say a Blob will do some magic. But then again; how to put this huge string into a Blob?

Firstly the base64 string has to be gathered from the uploaded file. Once a file is selected using a simple <input type="file"/> we can access the data. This can be done with the following code:

var file = document.querySelector('#fileInput').files[0];

public uploadImage() {
getBase64String(file).then(base64Image => {
const groupId = 'The group ID here';
const request = {
method: 'PUT',
url: 'https://graph.microsoft.com/v1.0//groups/' + groupID + '/photo/$value',
responseType: 'application/json',
data: base64Image
};

this.$http(req).then(result => {
// Image has been set
}, (err) => {
// Image has not been set
});
});
}

public getBase64String(file: any) {
return new Promise((resolve, reject) =&gt; {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () =&gt;
resolve(reader.result);
reader.onerror = error =&gt; reject(error);
});
}

 

On this page I have found a way to convert Base64 strings to binary data and return a blob which we can send to the Graph API endpoint. The answer of Jeremy also contains information that helps you understand what is actually going on in the function. However, this function will not work if you are calling it with a base64 string that is gathered by something like this:

var file = document.querySelector('#fileInput').files[0];

This is because the file is rendered with 'data:image/{imageType};base64/' infront of it. So in order to directly gather the base64 string from an HTML element we have to remove this part of the string. This can be done by replacing:

var byteCharacters = atob(base64Image);

With:

var byteCharacters = atob(base64Image.replace(/^data:image\/(png|jpeg|jpg);base64,/, ''));

Performance

As you can read in Jeremy's answer he has roughly tested the performance of the b64toBlob function.
I have tried to test it abit further. As you can see on the same page of Jeremy's answer another developer used 1024 as sliceSize, where as Jeremy used 512. So, what is the exact difference? And why split the length anyways?  Obviously the performance speed differs per machine, but just for the sake of reference I performed some tests.
I have used four slicesizes to see how it affects performance speed. For each sliceSize I uploaded three different images. One of a few kB, one of 1.94Mb and one of 3.91Mb. 4Mb is the maximum size the endpoint accepts, so that makes +/- 2Mb a nice average.

No slices

When using no sliceSize, or set sliceSize to base64Image.length we get the following results:
We can see some exponential time difference between the three images, which is expected.

File size: 10.66Kb, String length: 14570, Execution time: 0.8139msFilesize: 1.94Mb, string length: 2710978, execution time: 53.123779ms

File size: 3.91Mb, String length: 5468183, Execution time: 144.135ms

Slicesize 512

Let's see how Jeremy's recommendation works out.
We see that files around 2Mb are slower than when using a sliceSize of base64Image.length. However, large files seems to be quicker!

File size: 10.66Kb, String length: 14570, Execution time: 1.090087ms File size: 1.94Mb, String length: 2710978, Execution time: 79.64013ms

File size: 3.91Mb, String length: 5468183, Execution time: 119.24121ms

Slicesize 1024

Let's see how that other suggestion turns out.
Clearly this slicesize is faster than the previous two sizes.

File size: 10.66Kb, String length: 14570, Execution time: 0.883056msFile size: 1.94Mb, String length: 2710978, Execution time: 58.1508789ms

File size: 3.91Mb, String length: 5468183, Execution time: 98.963134ms

Slicesize 2048

We see that doubling the slicesize has a positive effect between 512 and 1024. Can we speed it up even more when doubling 1024?
I guess this is where we have to draw the line. 1024 Is faster than 2048, so we can assume that
4096 won't be faster than 1024.

File size: 10.66Kb, String length: 14570, Execution time: 0.80712ms File size: 1.94Mb, String length: 2710978, Execution time: 67.643066ms

File size: 3.91Mb, String length: 5468183, Execution time: 108.047851ms

Conclusion

Setting an image for users, groups or contacts is doable via the Graph API. It's the conversion from base64 to binary data that can be a bit difficult. The documentation is clear on what the endpoint expects, but does not state a way how to convert a file to binary. As for performance; if you think your users will use large files between 2.5 and 4Mb consider using the sliceSize of 512. For small to average files using the total length of the base64 string will do no harm and can even speed things up. Here's a quick tip for you: How about first checking the filesize and set the slicesize accordingly?

I hope this blog has helped you. If you have questions or comments feel free to mail me!

See you next blog ;-)

Submit a comment