Better cache management in ASP.Net Core (LazyCache/FusionCache)

LazyCache and the newer FusionCache are two great libraries that simplifies working with caching in ASP.Net Core. Something I’ve often been missing though is the ability to dispose of the entire cache, not just single items. (My understanding is that this limitation stems from them being built on top of the distributed cache in ASP.Net Core.) You could solve it by keeping track of all cached items, but that seems overkill. Let see how we can solve it in a simple way with some clever code!

Here is a typical way to use fetch an item from the database:

Notice  $"product-{id}"  which is the key by which the cache framework looks up data from the cache. Let’s add a helper class and move the key there instead:

Now we can use  CacheHelper.ProductKey  instead of the hard coded key:

Now if we want to clear the cache we can simply call CacheHelper.BustCache() ! We’re not actually clearing the cache, just changing the identifiers and thus forcing the cache to repopulate. The old cache items will go stale and eventually be flushed by the API.

Let’s improve the code a bit more. It’s usually a good idea to put logic and business rules in one place. Composing the cache key manually ( $"{CacheHelper.ProductKey}-{id}" ) and setting the duration every time we want to get a product item sounds like an accident waiting to happen, so we’ll move everything to the CacheHelper class:

Then to use it:

CacheHelper.GetProductKey(id)  makes it easy to get the correctly formatted key whenever we need it. For more advanced keys we can even add multiple parameters to the method. And when someone asks how long items are cached you can quickly look it up! This could also be a good place to add some comments on cache logic implemented elsewhere.

With just a few changes we’ve made cache management easier and more robust. Of course the code can be improved in many ways, but hopefully it gives some ideas on how to go  about!

Posted in Development, Tips | Tagged , , , , | Leave a comment

Modifying SharePoint list permissions with PnP Core

Working with SharePoint permissions is not always straight forward. This code snippet is an exercise using PnP Core to modify permissions on lists with unique (broken) permissions. All assigned permissions are changed to read only, except for owners group of the site. The code is hopefully a good starting point for similar tasks, and should be easy to adapt to list items if needed.


Posted in Development | Tagged , , , , | Leave a comment

Show React dialogs fluently with hooks and promises

Suppose you have a React component that displays a modal dialog and returns the result:

The typical way to use it would be to have a state variable controlling if it should be displayed or not, and a function for handling the return value. Something like this:

Nothing special, but I don’t like how the logic is located in different functions instead of a continuous flow. And what if we wanted to have multiple dialogs? We would typically repeat everything for each dialog…

What if we could open the dialog in a more fluent way, similar to JavaScript confirm:

Let’s implement a hook that wraps the dialog component in a promise!

Now we can open the dialog and handle the return value in a single flow, even with nested dialogs:

Pretty neat! :-)

Posted in Development, Tutorials | Tagged , , , , , , , , | Leave a comment

A faster way to get effective permissions with PnP Core

PnP Core SDK has built in functionality to get effective permissions for a user on a list item in SharePoint:

This is pretty slow; typically ~1000ms. So I tried calling the REST API directly instead:

Turns out this is almost twice as fast, ~600ms!

Posted in Development, Tips | Tagged , , , , , | Leave a comment

Searching with Microsoft Graph Client Library for .NET

Using the MS Graph SDK for .NET is convenient but I find the documentation and examples pretty lacking. So when wanted to use the Search endpoint it took me quite a while to figure out how to make it work. Here is an example showing how to search for SharePoint list items:

Notable gotchas:

  • You must specify all fields you want to return. For custom fields  you must use the Managed Property names (ending with OWS*) for custom fields. Tip: Use the SharePoint Search Query Tool to see all available fields you can use in a query
  • Search returns SearchHit  objects. You need to cast the SearchHit results to the correct type, in this case ListItems . Note though that the casted ListItem will only contain data in the dynamic AdditionalData/Fields structure, everything else will be empty.
  • PageIterator  does not work with Search, so you need to do pagination manually.
  • You can specify the number of items to return with the Size  property. Default is 25, max is 1000. 200 is “reasonable” according to docs. Docs also says that best practice is to start small and increase subsequent requests.
  • In the response, fields will retain their casing from the request – EXCEPT for the first letter which is always lower case…
Posted in Development, Tips | Tagged , , , | Leave a comment

Finding out what service created a SharePoint site

SharePoint is the underpinning data storage for many Microsoft 365 services. Sites are are therefore created under the hood by many services.

Sometimes it it useful to know from where a site was created. We can actually find this in the SharePoint Online Admin Center. The column is however hidden by default, so you need to click Customize Columns and select to show “Created From”:

Note: This is a fairly new feature. It appears to have been added late 2020. Sites created earlier will have and empty field.

What if we wanted to get this information programmatically for all sites? Here is a solution written in PowerShell with the help of PnP PowerShell. It uses a hidden list on the Admin Center that aggregates all site collections in your tenant. Here we can find a field called SiteCreationSource  which is what we are looking for:

Not too difficult. Note that “SiteCreationSource” returns a GUID. We need to map this to a service. In the code I have simply hardcoded a few services. To get a list with all available services, this list can be queried:

Finally I want to point out that the site aggregation list contains a lot of useful information (besides being the easiest way to get a list of all sites). Some other fields that might be of interest: AllowGuestUserSignIn, ChannelType, ChannelSitesCount, ConditionalAccessPolicy, CreatedBy, CreatedByEmail, DeletedBy, ExternalSharing, FileViewedOrEdited, GroupId, HubSiteId, Initiator, IsGroupConnected, LastActivityOn, NumOfFiles, PageViews, PagesVisited, SensitivityLabel, SiteCreationSource, SiteId, SiteUrl, SiteOwnerEmail, SiteOwnerName, State, StorageQuota, StorageUsed, StorageUsedPercentage, TemplateName, TimeCreated, TimeDeleted, & RelatedGroupId.

Posted in Development | Tagged , , , , | Leave a comment

Starter project for AzureAD authenticated communication with ASP.Net Core 6 API, React SPA and console app

Setting up authentication for a new project is important but can be a pain since all the pieces must fit together perfectly. I recently struggled with one of my projects, and for future reference I decided to create a starter project dealing with Azure AD authentication and authorization for a set of typical projects:

  1. ASP.Net Core 6 Web API (the server)
  2. React/Typescript SPA (single page application) (the client running in the browser)
  3. Standalone .Net 6 console application (e.g. a daemon or web job)

Download the projects from my GitHub repo

They all use Microsoft Authentication Library (MSAL) to acquire access tokens (bearer) from Azure AD to then connect to secured services. (It is important is to remember that different services require different tokens!)

  • The SPA logs the user in and then connects to the API, Microsoft Graph and SharePoint Online
  • The console app connects to the API and Graph using an app only context
  • The API accepts authenticated connections from these two. It also calls Microsoft Graph with an app only context as well as a delegated context in the the user’s name

One of the trickiest part is to correctly setup your apps in Azure AD. Each project gets its own app registration. Besides only allowing authenticated users and apps, I use two security features of app registration to secure the API. First of all the API exposes different scopes that the other apps can consume. Then there are also app roles that further restricts access (RBAC) to different endpoints.

Here is how I registered the apps:

  • In Azure Active Directory, go to “App registrations
    • Register a new app called “AuthStarter API App”:
      • Select “Accounts in this organizational directory only (Single tenant)”
      • When registered, find the “Application (client) ID” and copy it.
      • Also find the “Directory (tenant) ID” and copy it
    • Repeat this to register two more apps:
      • “AuthStarter Client App”
      • “AuthStarter Console App”
  • Edit the “AuthStarter API App“:
    • Go to “Certificates & secrets”
      • Add a new Client Secret.
      • When done, copy the “Value”.
    • Go to “API permissions”
      • Add a permission: Microsoft Graph -> Delegated -> “Directory.Read.All Application”
      • When added, click “Grant admin consent for [tenant]”
    • Go to “Expose an API”
      • Set the Application ID URI. Accept the default value “api://[clientid]”
      • Click “Add a scope”
        • We will add two scopes
        • Name the scopes “AuthStarter.Daemon” and “AuthStarter.Client”
        • Select “Admin only” under who can consent
      • Click “Add a client application”
        • We will add two applications. Recall the Client IDs from the other two apps we registered above
        • First enter the client id for you “AuthStarter Client App” app and select the scope “api://[clientid]/AuthStarter.Client”
        • Secondly, enter the client id for you “AuthStarter Console App” app and select the scope “api://[clientid]/AuthStarter.Daemon”
    • Go to “App roles”
      • Create three app roles:
        • “AuthStarter.User” and “AuthStarter.Admin” with allowed member type set to “Users/Groups”
        • “AuthStarter.Application” with allowed member type set to “Applications”
  • Edit the “AuthStarter Client App“:
    • Go to “Authentication”
      • Click “Add a platform
        • Select “Single-page application”.
        • Set the redirect URI to “http://localhost:3000”
      • Further down select both “Access tokens (used for implicit flows)” and “ID tokens (used for implicit and hybrid flows)” and save.
    • Go to “API permissions”
      • Add a permission: Find “My APIs” and select “AuthStarter API”. Select “Delegated” and then “AuthStarter.Client”
      • Add a permission: Find SharePoint (delegated) and select Sites.Search.All
      • Click “Grant admin consent for [tenant]”
  • Edit the “AuthStarter Console App“:
    • Go to “Certificates & secrets”
      • Add a new Client Secret.
      • When done, copy the “Value”.
    • Go to “API permissions”
      • Remove “User.Read (delegated)”
      • Add Microsoft Graph -> Application permissions -> “User.Read.All”
      • Click “Grant admin consent for [tenant]”
  • Go back to Azure AD. Find “Enterprise Applications
    • (An “Enterprise Applications” is an instance of an app in the tenant. Apps that are multi tenant are registered in a single tenant but can have instances in many.)
    • Open your “AuthStarter API App”
    • Go to “Users and groups”
      • Add a user of your choice. Select Admin as role
      • Optionally add another user with User as role


With the apps registered you can configure the projects using the ID’s you copied in above steps. These are the relevant files:

  • API: appsettings.json
  • Client: authConfig.ts
  • Console: appsettings.json

Now, fingers crossed, you should now be able to run the samples! Good luck :-)

Screenshot of the client running in a browser

PS: This is just a scaled down starter sample and does not always show best practice. Credentials are for example stored in the source code. Do not use in production as is or without understanding what you are doing.

Posted in Development, Tutorials | Tagged , , , , , , , , , , , | 2 Comments

Read & Write MS Office custom properties with PowerShell

While working on automating Microsoft Word I needed to read and write custom properties in documents. I found several resources out there. Reading worked fine, but when writing custom properties they all crashed. I finally managed to fix it though, and though I’d share my solution.

I compiled cmdlets for reading and writing both built-in and custom properties. The cmdlets are generic and work with Word, Excel and PowerPoint. Here is an example how to use with Word :


Here is the full script (also available on my GitHub):


Posted in Development, Tutorials | Tagged , , , , , , | 1 Comment

If Word randomly crashes when opening SharePoint documents

I’ve been working on a PowerShell script that automates Microsoft Word to open all files in a SharePoint library, modify the header and other properties and then save it again. I ran the script against 4000 documents and everything went fine. But then I tried running it again (against the same files). Now Word began crashing randomly when opening files, with an error message The remote procedure call failed. (Exception from HRESULT: 0x800706BE) . The same could happen if I manually opened documents from SharePoint. Word just died silently.

Turns out it was a problem with the cache. After clearing and disabling the cache in Word, my script magically started working again! Edit you saving options:You may also want to clear you %TEMP% directory, just to be sure.

Posted in Development, Tips | Tagged , , , , , , , , | Leave a comment

Excluding page templates in CAML queries

Typically, when requesting SharePoint pages we do not want to include templates, just normal pages. When saving a SharePoint page as a template, this is noted in a property called OData__SPSitePageFlags . With this information we can easily exclude templates by adding the following to the CAML query:


Posted in Development, Tips | Tagged , , , | Leave a comment