Sep 23, 2019

Deploy corporate SPFx app pages

Hi SharePoint devs !

Today I am going to tackle a need many of us, SPFx developers, will face some day. Deploy a SPFx app, but not a WebPart, not an extension, a Single Page App. Okay.. What's the deal, is it not deployed the same way, you might say ? Well, it is, but that might actually be an issue, if you don't want it to be added or edited by end users. Let's get into it !

How to develop a Single WebPart app page in SPFx ?

It is actually really simple, there is 1 attribute in the manifest to modify, and that's it ! For this article I created a brand new SPFx WebPart solution (no-framework) in which I only changed some CSS for rendering purpose, slightly changed the manifest, but, really, there isn't any other changes. Let's check the WebPart manifest file

{
    "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
    "id": "85c66304-0802-417a-a753-de96fea19565",
    "alias": "MySinglePageAppWebPart",
    "componentType": "WebPart",
    // The "*" signifies that the version should be taken from the package.json   
    "version": "*",
    "manifestVersion": 2,
    // If true, the component can only be installed on sites where Custom Script is allowed.   
    // Components that allow authors to embed arbitrary script code should set this to true.   
    // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f   
    "requiresCustomScript": false,
    "hiddenFromToolbox": true,
    "supportedHosts": [
        "SharePointWebPart"
    ],
    "preconfiguredEntries": [
        {
            "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other     
            "group": {
                "default": "Other"
            },
            "title": {
                "default": "MySPA"
            },
            "description": {
                "default": "A Single Page Application developed with SPFx"
            },
            "officeFabricIconFontName": "Page",
            "properties": {
                "description": "Hello dear user"
            }
        }
    ]
}

If you take a look at line 16 supportedHosts, in VS Code, for instance, you will see that you can add other entries, such as SharePointFullPage , this setting will allow you to "embed" your WebPart in a dedicated page (with only this WebPart and nothing else). If we give it a try, let's add this entry, bundle and package solution, deploy it to app catalog. Now we see, If I add a new page from the command bar on the home page* I can see this

newpage apppagepicker.png  

  • on the home page only, it looks like it is the only location from where we can get to the page templates picker, either the new button from the site pages library or the link from the up right corner gear will directly create an article page If I click it, I have indeed a full page with only my WebPart ! Great !

However, the creator of the page has to provide:

  • The title of the page: pretty obvious
  • But also the configuration of the page !

In some scenarios, that totally makes sense... But in others, that is something we might want to get rid of ! Moreover, not only the creator but all the users with Edit permission (meaning the Members by default on Modern team sites) on the page will be able to update the configuration.

It is quite a consistent behavior in SharePoint, and basically, I would not want it to be different because all of the inconsistencies it would cause...

So it is as simple as changing 1 parameter in the manifest to convert a WebPart into an application page.

Deploy corporate applications on Intranet

All that said, a scenario that many organizations will probably require is to have corporate applications installed directly in their intranet, and it will be more than likely out of question that regular users are able to change anything in the application configuration. It will probably also be required that regular users are not able to add new instances of the application... From the page templates picker, Members will be able to do that as well. Regarding this very last point, I was actually expecting the hiddenFromToolbox flag in the manifest can make the app page hidden from the page templates picker but it doesn't...

After a bit of searching through the official docs and GitHub issues.

I realized the only way to achieve my need is actually to create a regular Modern page, add My WebPart to it, and then update the modern page layout.

Moreover, to make sure, no regular user can alter my application page, I will make sure that only owners (or any other wanted group) has full control on the page, all other will only have read permissions (It is classical SharePoint permissions management).

A PnP PowerShell script to deploy my Single Page application

First of all, to make sure my app page cannot be added by any editors of the sites, I will remove the supported host we added earlier, the WebPart manifest will be exactly like the one shown above.

You'll probably notice that in that manifest, the WebPart is then a regular WebPart (it is important for the PnP PowerShell script to work),  I also added the flag "hiddenFromToolbox" set to true to make sure we cannot add our application as a WebPart on any other page. From the SPFx solution side, it is the only thing to take care of, so basically have a regular WebPart solution, with the "hiddenFromToolbox" flag set.

Let's then write the script! Obviously PnP PowerShell will be our ally here ! The steps of the scripts will be :

  1. Connect to site with PnP PowerShell.
  2. Will Get or Create the page we want to be our application page.
  3. Get our component from the available components on the site ( the $appName parameter must be set from the value of preconfiguredEntries.title.default in the WebPart manifest.json file ).
  4. Add the WebPart to the page.
  5. Leave only the full control permissions for owners of the site.
  6. Change the layout of the modern page to "SingleWebPartAppPage" and publish the page.

This script has been tested on a fresh Modern Team Site, the permissions to update might be different if you already customized them.

param(     
  [Parameter(Mandatory = $true)]     
  [string]$web,    
  [Parameter(Mandatory = $true)]     
  [string]$pageName,     
  [Parameter(Mandatory = $true)]     
  [string]$appName,     
  $appConfig,     
  $pnpCredentials 
)  
Write-Host "Ensure connection to SharePoint site..." -ForegroundColor Yellow 
try {     
  $ctx = Get-PnPContext -ErrorAction Ignore     
  If ($null -eq $ctx) {         
    If ($null -eq $pnpCredentials) {             
      Connect-PnPOnline $web         
    } Else {             
      Connect-PnPOnline $web -Credentials $pnpCredentials         
    }     
  }     
  Write-Host "Connected to SharePoint site." -ForegroundColor Green 
} catch {     
  throw "Cannot connect to SharePoint site $web..." 
}   
# Ensure page name ends with .aspx 
if (!$pageName.EndsWith(".aspx")) {     
  $pageName = $("$pageName.aspx") 
}  

try {     
  # Check if the page already exists     
  Write-Host "Checking if $pageName already exists..." -ForegroundColor Yellow     
  $page = Get-PnPClientSidePage -Identity $pageName -ErrorAction Ignore     
  If ($null -eq $page) {         
    # If the page does not exist, create it         
    Write-Host "Creating page $pageName..." -ForegroundColor Cyan         
    $page = Add-PnPClientSidePage -Name $pageName         
    Write-Host "Page $pageName has been successfully created." -ForegroundColor Green     
  } Else {         
    Write-Host "Page $pageName does exist." -ForegroundColor Green     
  } 
} catch {     
  throw "Page cannot be found and could not be created." 
}   

# Try to get the component from available components 
$appComponent = Get-PnPAvailableClientSideComponents -Page $page -Component $appName 
If ($null -eq $appComponent) {     
  Throw "The component $appName could not be found in the tenant. Please install it first" 
}  

# Ensure the app configuration if not specified 
if ($null -eq $appConfig) {     
  $appConfig = @{ }; 
}  

try {     
  Write-Host "Adding client side component $appName to page..." -ForegroundColor Yellow     
  # Add the component (WebPart) to the page     
  Add-PnPClientSideWebPart -Page $page -Component $appComponent -WebPartProperties $appConfig -OutVariable $addWpOut     
  Write-Host "Client side component successfully added." -ForegroundColor Green 
} catch {     
  throw "Client side component could not be added to page..." 
}   

try {     
  Write-Host "Removing Edit permissions from application page for members..." -ForegroundColor Yellow     
  # Set the edit permissions for the app page only for owners (Set read only for members)     
  $members = Get-PnPGroup -AssociatedMemberGroup     
  $appPageListItem = Get-PnPListItem -List "Site Pages"  -Query "<View><Query><Where><Eq><FieldRef Name='FileLeafRef'/><Value Type='Text'>$pageName</Value></Eq></Where></Query></View>"     
  Set-PnPListItemPermission -List "Site Pages" -Identity $appPageListItem.Id -Group $members -RemoveRole Edit -AddRole Read -OutVariable $setPageOut     
  Write-Host "Permissions successfully applied to application page" -ForegroundColor Green 
} catch {     
  throw "Cannot change permissions for application page..." 
}  

try {     
  Write-Host "Changing page layout type to single WebPart app page and publishing..." -ForegroundColor Yellow     
  # Publish the page and change the layout to Single WebPart App page     
  Set-PnPClientSidePage -Identity $page -LayoutType SingleWebPartAppPage -Publish -OutVariable $publishedOut     
  Write-Host "Layout successfully changed and page successfully published" -ForegroundColor Green 
} catch {     
  throw "The application page could not be published..." }  
  Write-Host "Corporate single page application has been deployed to $web" -ForegroundColor Green
}

scriptexec.png

The owners of the site will see

spa_edit.png

While any other users will see

spa_read.png

Conclusion

As usual, PnP PowerShell helps us here and we have a quite simple solution. However, I would have preferred a more straightforward procedure for achieving this, The flag in the supportedHosts is great, but, IMHO, it is still not sufficient for us, developers and solutions providers to seamlessly install corporate applications, if, by default, end-users can interfere with it. I hope you found this post useful, and please, let me know any feedback you might have on this !

Cheers!

Yannick

Other posts