The easiest way to store user settings of your Microsoft 365 app

The easiest way to store user settings of your Microsoft 365 app

When building a Microsoft 365 app, you might want to store user settings. Here's the easiest way to do it.

Microsoft 365 applications

With more and more organizations being in the cloud, building Microsoft 365 apps is a perfect opportunity to extend the standard capabilities to organization's needs and make better use of the available technologies. Microsoft 365 is a rich platform that offers you a lot of flexibility for the apps you want to build. You can make fully-fledged line of business applications, hosted on Microsoft Azure and using some of the AI services available on the Microsoft cloud. There are also scenarios, for which a single-page application without a backend can offer added value as well. By tying the different resources available on Microsoft 365 and putting them in a business context, you can help your organization be more productive.

Configurable application

We all work differently. Applications that are most widely adopted take that into account and let us store our preferences. If the application that you're building, has some kind of backend, you will likely store the settings there. But what if Microsoft 365 is your backend? Where would you store user settings then?

Storing user settings the SharePoint way

If you're used to working with SharePoint, your first choice to store user settings could be using the user profile service. Each SharePoint user has an individual profile with a number of properties, such as name, email, office, etc. Often, developers would create a custom user profile property and store the serialized configuration data in there.

While the approach is working, it has one limitation. It's not possible to script creating new user profile properties, so as a part of your application deployment, you need to include a manual step of adding the new profile property.

Another challenge with using the user profile service is that it's not exposed via a REST API. So if you use anything else than than SharePoint Client-Side Object Model (CSOM) you would need to manually construct SOAP calls to store and retrieve the data. PnPjs addresses this limitation by offering you a fluent API but still you will need to create the custom property in the first place.

Configuration lists

An alternative approach to store user settings is by using a SharePoint list. On its first use, your application would create a hidden list, where the settings for the different users will be stored.

While creating the list can be fully scripted, each time your application wants to read or write settings, it first needs to check if the list exists and create it if it doesn't. This adds extra complexity and can introduce a noticeable delay. Also, if you choose to store the different settings in columns, you might need to transform the previously stored settings should your configuration model change.

There is however another way to store user settings, which is way easier than using the user profile or SharePoint lists.

Store user configuration in the application's personal folder

One of the hidden gems of Microsoft Graph is the application's personal folder. It's a special folder in user's OneDrive for Business that's automatically created whenever application reads or writes data to it. Each application gets its own folder in which it can store any number of files and folders.

Store data in the application's personal folder

To store data in the application's personal folder, execute the following request:

PUT https://graph.microsoft.com/v1.0/me/drive/special/approot:/settings.json:/content
content-type: text/plain
authorization: Bearer abc

{"key": "value"}

If you're using the Microsoft Graph SDK, the request looks like follows:

const settings = { key: "value" };
this.graphClient
  .api('/me/drive/special/approot:/settings.json:/content')
  .header('content-type', 'text/plain')
  .put(JSON.stringify(settings));

In the API URL, approot points to your application's personal folder and will be automatically resolved by Microsoft Graph to https://contoso-my.sharepoint.com/personal/user@contoso.com/Documents/Apps/<your AAD app name>. settings.json is an arbitrary name of your configuration file. The body of the request, is the serialized representation of your configuration object. In this example it's JSON but it could be anything that you can write to a file.

Retrieve data from the application's personal folder

How you retrieve the data from a configuration file stored in the application's folder, depends on the type of your application.

If your application calls Microsoft Graph from server-side code, you can get the contents of your configuration file by executing the following request:

GET https://graph.microsoft.com/v1.0/me/drive/special/approot:/settings.json:/content
authorization: Bearer abc

If your application communicates with Microsoft Graph client-side, as it's the case for SharePoint Framework solutions or single-page applications, you need to use the following two requests instead:

GET https://graph.microsoft.com/v1.0/me/drive/special/approot:/settings.json?select=@microsoft.graph.downloadUrl
authorization: Bearer abc

followed by:

GET <url from the response['@microsoft.graph.downloadUrl'] property>

The reason you need two different approaches is, that calling https://graph.microsoft.com/v1.0/me/drive/special/approot:/settings.json:/content returns a 302 redirect to a pre-authenticated URL on the contoso-my.sharepoint.com domain. Because this URL is on a different domain, your browser will try to comply with CORS and call the URL with the OPTIONS method, which will fail. So to avoid it, you need to avoid the automatic redirect by retrieving the target URL and calling it yourself.

If you use the Microsoft Graph SDK, you would use the following setup:

const defaultSettings = { key: "value" };
this.graphClient
  .api('https://graph.microsoft.com/v1.0/me/drive/special/approot:/settings.json?select=@microsoft.graph.downloadUrl')
  .get()
  .then((response: { '@microsoft.graph.downloadUrl': string }): Promise<HttpClientResponse> => {
    return this.httpClient
      .get(response['@microsoft.graph.downloadUrl'], HttpClient.configurations.v1);
  })
  .then((response: HttpClientResponse): Promise<string> => {
    if (response.ok) {
      return response.text();
    }

    return Promise.reject(response.statusText);
  })
  .then((settingsString: string): Promise<ISettings> => {
    try {
      const settings: ISettings = JSON.parse(settingsString);
      return Promise.resolve(settings);
    }
    catch (e) {
      return Promise.resolve(defaultSettings);
    }
  }, _ => Promise.resolve(defaultSettings));

In this example, httpClient is an instance of the regular HttpClient exposed by the SharePoint Framework. If you build a single-page application, you can replace it by fetch.get or its equivalent capable of issuing an anonymous web request.

Considerations

Application's personal folder is created per Azure AD application. So if you want to use it in context of a SharePoint Framework solution, your application will store its data in the SharePoint Online Client Extensibility Web Application Principal folder. To avoid colliding with other applications' settings, you should store your files in a sub-folder.

To be able to read and write files in the application's personal folder, your application needs to have the Microsoft Graph Files.ReadWrite permission granted.

For more information about working with the application's personal folder see the Microsoft Graph docs.

Photo by James Coleman on Unsplash

Comments

comments powered by Disqus