CSOM is FINALLY available for .NET Core!

I don’t think anyone excpected that the 23rd of June 2020 would turn out to be such a crazy day. For years fresh developers of SharePoint have been surprised and in disbelief that the Client Side Object Model was not available for .NET Core. For some developers, who never even install Visual Studio, the premise of going “back to the old ways” could seem daunting and perplexing.

Today however, Microsoft saves the day by letting christmas, new years and thanksgiving come early, and provide us – finally – with support for .NET Standard (and thus .NET Core) with CSOM. Check out the announcement here: https://developer.microsoft.com/en-us/microsoft-365/blogs/net-standard-version-of-sharepoint-online-csom-apis/

Let’s dive in and check out what this means for us moving forward.

First off, there are some differences to the API’s. The .NET Standard version is missing some of the functionality of the .NET Framework version. Most notably, there is no support for on-premises SharePoint in the .NET Standard version. Further, there are some functions “missing” although it’s mostly legacy functionality that you shouldn’t be using anyways. What this means however, is that you can’t just update your .NET version and it will automatically work, you might have to do some modifications to make it play nicely.

I decided to try using just the command line and Visual Studio Code for creating this test, apparently that’s what all the cool kids are doing right now.

So first thing’s first: create a folder for the solution , naturally we’re going to run all the commands in the new Powershell Core 7!

Alright, we have our folder, time to fire up VSCode!

Visual Studio’s GUI has now been replaced with the “dotnet” command line interface, so to start, we’re going to just run:

dotnet new console

This creates a new project, a Program.cs-file and an obj-folder. Great! Now let’s just build and ensure it executes by running

dotnet run

The output from the sample is as expected

Excellent! Now we can start trying out the CSOM-code. First off, we need to load in the repository from Nuget. The syntax for that is dotnet add package.

Easy enough! Time to run some code!

So the first, and arguably most important thing Microsofts example shows us, is creating the ClientContext object. Funny enough, they underline the importance of setting FormDigestHandling to false. A property that’s been present in CSOM for a long time. In my project however, I get an error message saying it’s not part of the class. Bad luck i guess, we’ll just remove it.

The biggest difference between vanilla CSOM and .NET Standard CSOM is that you need to provide an access token no matter what. You can’t just apply a username and password to the ClientContext.Credentials like before. So luckily Microsoft has provided us with a nice AuthenticationManager class that helps get that access token. I decided to simplify it a bit though.

Their example, just calls the authorize endpoint to get an access token to an AAD-app.

So let’s head to the Azure portal and register a new app and give it some permissions to SharePoint.

We’re going to be signing in with a username and password, so i’m selecting delegated permissions for this app.

We’ll also need to register it as a “public client”, since we want to use the username and password flow.

We’re now all set, let’s simplify the example a looooooot and run this code in our new project (PS. Yes, there is no real “security” in this example by using SecureString, but it’s a good pattern to adhere to)

Running this in the terminal and we should yield the name of the web….

Hurrah! It’s running!

Let’s modify it and see if we can do some simple list operations:

Yup – works like a charm! This is really nice!

Final thoughts

  • Debugging and running C#-code in .NET Core is a BIG step forward for SP-developers. The debugging process is painless, modern and quite snappy.
  • There doesn’t seem to be any reason to delay moving to .NET Core, although migrating should be planned with care of course.
  • Probably the #1 reason developers will wait with moving over relates to authentication and waiting for PnP to update their codebase, especially concerning AuthenticationManager. Abstracting the work to obtain the access token will be a massive help. Also of course, ExecuteQueryRetry is a must for modern day CSOM-development.

All in all, this is a major big change for SharePoint developers, and one more nail in the coffin for .NET Framework.

Automatically provisioning a Teams hosted SPFx webpart

Strangely, this was a lot harder than i thought. This post describes the challenges i faced, any my solution to this issue.

>> DISCLAIMER: The methods written here are in no way shape or form supported by nor endorsed by Microsoft, they are absolutely not to be used for production applications and should be considered educative <<

[Jump to the technical section]

The above was perhaps one of my most frustrated Google-queries the past week. Seriously, nobody has done this?

So Teams provisioning is really hot these days, everyone wants a solution to have some governance on their teams, and pre-provision some content to make teams more usable and relevant for the department they’re working in. Some even want to use Teams as a tool, to churn data.

The challenge

So if you’ve made an SPFx webpart, and you want to add it to your Team, you deploy it via the App Catalog, then click the “Sync with Teams”-button, and there it is, ready to be added whenever the user should so desire.

So say you want to deploy this finely crafted SPFx webpart to your newly provisioned Team, but you don’t want the end-user to have to do it. Surely there must be tons of examples online, right? Nope. There are actually none. Why? Because it’s not yet “supported”, there “isn’t functionality for that”. So most solutions you’ll find, involve using one of the standard apps, or the non-user-friendly “Website” Teams app. The issue with that one however, is that you get the super ugly bar saying “If the site isn’t loading correctly, click here”.

As we say in Norway; Off on me. Now you can actually set this to a SharePoint page, but the background will be white, and it stands out as something separate. Somethings’ just not right.

In contrast, adding a TeamsTab SPFx webpart, will be shown with the team’s theme, gray background integrated into the app and even an integrated settings pane for configuring the webpart!

Let’s get technical

Ok, so what’s the big deal? Surely you can just add a webpart to some random Team, fetch the tab configuration using Graph and then just provision it? Surely it must be that simple.. right?

Not really, but let’s go through it to see. We’ll fire up our trusted Graph explorer for this.

First off, create a standard SPFx webpart and enable it for Teams by following the docs. open Teams and add your SPFx webpart to a team.

Next, head over to Graph explorer. Let’s do some queries:

  • https://graph.microsoft.com/v1.0/me/joinedTeams?$filter=displayName eq 'teamname'
    • Get the id from the result, we’ll need it later. For me, the ID for my team is
  • https://graph.microsoft.com/v1.0/teams/6b38dba8-9cbf-4e5f-82e4-b12de6b06ced/primarychannel
    • Tabs live in the channels, so we need to get the ID of our General channel. Easiest way to do this, is to use the primarychannel endpoint of the team. Note down the id. Mine is:
  • https://graph.microsoft.com/v1.0/teams/6b38dba8-9cbf-4e5f-82e4-b12de6b06ced/channels/19:43ddeed0a99e484b833bff8fdd30e491@thread.tacv2/tabs
    • Now we’re at the end. You’ll see a list of the tabs added to the team. Here’s mine:

The webUrl is the link to the tab. The interesting part here, is the contentUrl. The contentUrl specifies where the tab will navigate when the user opens it. Let’s examine the url more closely:
"contentUrl": "https://smebydev.sharepoint.com/sites/DMRAP-fcew324/_layouts/15/TeamsLogon.aspx?SPFX=true&dest=/sites/DMRAP-fcew324/_layouts/15/teamshostedapp.aspx%3Flist=fd4bd3ba-e285-4c3b-9ceb-2469715c0392%26id=1%26webPartInstanceId=aecd337d-4472-4e2f-8cda-dfa257c7f112",

Ok, so what’s happening here is that it first calls the TeamsLogon.aspx-file, which obviously does some kind of login action to Teams. It then calls teamshostedapp.aspx with some parameters. But wait, what is this? list? id? webPartInstanceId? Just when we thought it was going to be easy, it turns out it isn’t!

Luckily, Teams is web-based so we can still dissect what’s going on. This time i’ll save you the walkthrough and just reveal the secret immediately: The list-id and id are references to a hidden list on the site called “Hosted App Configs”. It’s a very special list, which you’re not supposed to play with. However, when you add a new app to a team, Teams does a request to an internal SharePoint api endpoint:

It sends in a hostType, and some webpart info.

This got me interested. It really looks as if it just adding an element to that list, right? So why don’t we just add an element to that list when we automatically provision the webpart, and dust off our hands and call it a days work?

Spoiler alert: The list does not exist until the user has interfaced with apps in Teams. Yup, that’s right, just as the General-folder doesn’t exist until the user has clicked in to “Files” (when you provision with Graph, not with their internal api)…

So this leaves us with a small issue. We really shouldn’t create the list, obviously we then risk actually breaking something, since creation is handled elsewhere. Also I’ve tried it, it didn’t work (although I honestly didn’t put in much effort).

So how can we trigger that list? Well – remember when you configured your manifest for your SPFx webpart? It looks like this:
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=34e2d1bf-92ea-4c72-a4f8-af1e094a863d",

This is actually the page showing the Teams logo before you add your app.

If you right click in the white space, you’ll see it’s an iframe that’s loaded the URL above. It’s the Content URL from our manifest that’s loaded – and it does a very important thing – it initializes the list (and probably a lot of other important things too). Notice also, how the Save-button is deactivated until the page has finished loading.

The solution

Starting to guess what my solution was?

It’s not optimal, i’ll tell you that, but i feel it’s as close to a stable solution as possible which should work

1. User clicks “New team” – start provisioning, install the app to the site.
2. Once the team is created, show an iframe like Microsoft does, and track if it’s showing the Teams logo or not. If it is, the site is instantiated, and we can start finalizing he provisioning.

That’s right, the CanvasContent1 field is the webpart markup of the Teams hosted app page, so we can just make our own InstanceId, and update the field with our own webpart markup. We can then reference that instance Id in our tab markup on post-creation

Inspecting the ContentUrl, you see that we’re referencing the ListId, ItemId and our newly created webpart instance Id.

Happily, this works, and the webpart is beautifully rendered and hosted internally. Love it!

What are the drawbacks however?
Well, first off, obviously this is plain and simple “hacking” (in the coding sense). It uses a routine-based approach and doesn’t use an API. It’s obviously something that can change at any moment, and i hope it does. We need an API endpoint to initialize these Team sites.

Another drawback, is that the iframe solution sometimes fails if it’s shown too soon after site creation. I’ve found however that if we just refresh it automatically a couple of times, it pops up as expected.

Another drawback is that we have 0 guarantee that this doesn’t have an effect on other tabs.

But the biggest drawback, is that we still need the user to refresh that iframe…

So in conclusion, yes it’s possible to automatically provision Teams-hosted apps, but right now, not completely without user intervention.

What’s this?

Hi, my name is Helge and i work as a Microsoft 365 product manager! I’m one of those guys who loves a challenge and often goes to great lengths to make things work the way i feel they should, even if we’re not provided the tools. It’s about being creative, and seeing potential and possibilities where others don’t.

Here i intend to share some of my work, and everything else I feel would be nice to share with the world. Who knows, maybe someone will know better and let me know in the comments?