Developing for Multiple Environments in SharePoint Online

Published 11/15/2018 04:09 PM   |    Updated 11/19/2018 08:21 AM
This article originally appeared on May 24, 2017.
 
The transition from developing for SharePoint on premises to SharePoint Online contains many hurdles that need to be overcome: switching to client-side object model, implementing custom branding without custom master pages and writing web parts in JavaScript with the removal of managed code, just to name a few. However, one of the major hurdles is writing code that can isolate a SharePoint Online site collection as its own environment due to architectural differences with the on-premise version.
 
This article will cover the following topics to help developers prepare for development in SharePoint Online:
 
  • On-premise vs. online architecture
  • Environment identification 
  • Using shared service applications for custom functionality
  • Querying lists and search service
 

On-premise vs. online architecture


The normal practice for a SharePoint On-Premises farm is to have different servers for the various environments: development, Quality Assurance (QA) and production. Each of these environments resides on a different server and is independent. This means each has its own set of service applications developers can leverage (search, user profiles, managed metadata, etc.).
 
Based on this architecture, developers can make more assumptions about where a site collection will reside, which normally is at the root of a web application. Each environment will have at least one web application with a unique host URL to distinguish it from another. With this isolation, the information on one environment will not appear in another environment. The image below illustrates the environment separation.
 
Figure B: SharePoint Online environments
 

Environment identification in SharePoint Online

 
Sometimes, a solution needs to know what environment it’s currently running in so it can adjust functionality, such as turning off caching in non-roduction environments. Since the host name is no longer unique between environments, there needs to be another way to determine where the code is running.
 
A way to achieve this is to dynamically change the contents of the code before it’s provisioned to a site. Using remote provisioning allows for this file manipulation. The example below shows how to use a .NET website with a web.config transforms on an AppSetting to perform this action.
 

Setting up web.config transforms

 
Visual Studio has a built-in web.config transformer that allows for manipulation of the web.config file when it’s published under the different configurations. The default configurations of Debug and Release will be used for this example. The current configuration can be changed via the drop-down in the Visual Studio toolbar.
 
Figure C: Visual Studio Configuration drop-down 
 
Below are the XML entries that will reside in their respective config file. In this case, the “value” attribute of the appSettings with the key of “Environment” will be adjusted based on the Visual Studio configuration selected.
 
Figure D: Visual Studio web config with transformations
 
Web.config
 
<configuration>
<appSettings>
    	<add key=“Environment” value=“DEV” />
  </appSettings>
</configuration>
 
Web.Debug.config
 
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="Environment" value="QA" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

Web.Release.Config
 
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="Environment" value="PROD" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>
 

Manipulation of JavaScript file

 
Once these web.config manipulations are in place, the code that provisions files to the SharePoint site can be used to modify the contents of a JavaScript file to create an environment JSON object (CSGEnvironment) that can be used by code written for the site.
 
JavaScript file before modification
 
var CSGEnvironment = {
    Environment: "{ENVIRONMENT}",
};
 
JavaScript file after modification for production deployment 
 
var CSGEnvironment = {
    Environment: "PROD",
};
 
C# code to modify file 
 
private static System.IO.Stream ReplaceFileTokens(string filePath) 
{
            	string fileText = System.IO.File.ReadAllText(filePath);
            	
string currentEnvironment = WebConfigurationManager.AppSettings["Environment"];
fileText = fileText.Replace("{ENVIRONMENT}",  "PROD");  

              	byte[] bytes = System.Text.Encoding.ASCII.GetBytes(fileText);
            	System.IO.Stream ms = new System.IO.MemoryStream(bytes);
            	return ms;
 
 

Using shared service applications for custom functionality

 
As previously called out, all environments will share the SharePoint services on a SharePoint Online tenant. This section will discuss approaches that can be taken to keep your environments separated inside those shared services.
 

Term Store/User Profile services

 
Often, a developer will use the Term Store and User Profile services as part of custom functionality. Common use cases would be cross-site collection navigation using a term set in the Term Store or user personalization on the site using a custom user profile property to store configurations.
 
When the development is done on premises, the same name can be used across environments for these items due to the isolation of the service applications between environments (see Figure A). In SharePoint Online, this is not the case since these service applications are shared (see Figure A), which means term sets and user profile properties should be created for each environment with different names.
 
To make sure the code is accessing the proper items in the services, the same environment identifier approach with web.config transforms, as outlined above, can be used to specify the correct name at deployment time.
 
JavaScript file Before Modification
 
var CSGEnvironment = {
    Environment: "{ENVIRONMENT}",
    TermSetGlobalNav: "{TERM_SET_GLOBAL_NAV}",
    UserProfilePersonalize : "{USER_PROFILE_PERSONAL}"
}; 
 
JavaScript file after modification for production deployment 
 
var CSGEnvironment = {
    Environment: "PROD",
    TermSetGlobalNav: "CSGGlobalNav-Prod",
    UserProfilePersonalize : "CSGPersonalize-Prod"
};
 

Querying lists and search service

 
When retrieving data from lists and search, developers need to actively specify where they would like the data to be pulled from. This section will provide some recommendations on how this can be accomplished.
 

Querying lists

 
Due to each environment being a separate site collection, it’s important to make sure list querying being done is at the correct scope. When using the REST API to get list data, the request URL can dynamically be adjusted prior to the REST call being made. The JavaScript code snippets below show how this can be done to generate a call at the host, site collection and web levels.
 
var restURLHost = getHostUrl() + "/_api/web/lists/getByTitle('ListTitle')/items?$select=ID,Title”
//restURLHost: https://company.sharepoint.com

var restURLSiteCollection = getSiteCollectionUrl() + "/_api/web/lists/getByTitle('ListTitle')/items?$select=ID,Title"
//restURLSiteCollection: https://company.sharepoint.com/sites/SiteCollectionName

var restURLWeb = getWebUrl() + "/_api/web/lists/getByTitle('ListTitle')/items?$select=ID,Title"
//restURLSiteCollection: https://company.sharepoint.com/sites/SiteCollection/Subsite



function getHostUrl() {
    return window.location.protocol + "//" + window.location.host;
};

function getSiteCollectionUrl() {
    return getHostUrl() + _spPageContextInfo.siteServerRelativeUrl;

};
function getWebUrl() {
    return getHostUrl() + _spPageContextInfo.webServerRelativeUrl;
}; 
 

Search service

 
Since SharePoint Online only contains one search service, the data for all environments will reside in a single search index. Search is a powerful tool inside SharePoint to get data across site collections and sites. To make sure the proper data is being retrieved, the “Path” managed property can be used in the query to guarantee search returns the data from the desired sites. The syntax being used for the example is Keyword Query Language (KQL). The following query condition will only return data from the development site collection (/sites/dev).
 
Path:https://company.sharepoint.com/sites/dev
 
For custom code using the search API to get the current environment’s URL, the same functions in the query lists section can be used to dynamically change the query.
 
When using out-of-the-box search web parts, such as Search Results or Content Search, SharePoint provides a “Site” token that can be placed into the query to get the URL of the current site. The web part will replace that token with the correct information prior to running the query.
 
Path:{Site.URL}
 
The screenshot below is the edit query window for the out-of-the-box Content Search web part. The query has been modified to return any document or list item on the current site that contains the word “Benefits” in the title.
 
 
 
Figure E: Content query web part screenshot
 
There are many other tokens that can be used as well. See the links in the References section at the end of this article.
 

Summary

 
Understanding the architecture of your SharePoint environment at the start of development will eliminate some of the headaches encountered the first time code gets promoted to a QA or production environment. Here are the key items to remember:
 
  • Modify code files at deployment time to specify the environment information.
  • SharePoint servers that were previously isolated between environments are now shared in SharePoint Online, requiring more forethought and work for the developer to make sure to display the correct data.
  • When querying lists, make sure the correct site collection or web’s URL is being used since SharePoint Online has a single host name and environments need to be site collections rather than web applications.
 

References

 

Is this answer helpful?