In one of the previous articles, I wrote about high-trust apps in SharePoint 2013. The high-trust apps or server-to-server apps (S2S) are intended to be installed on your SharePoint on-premises datacentre and don’t require connectivity to the Internet, unlike low-trust or cloud SharePoint apps such as those available in the SharePoint Store.
In this article I want to go beyond the “Hello, World” example and tackle some real-world issues with high-trust app development.
SharePoint Context Hurdles
The Visual Studio template for SharePoint high-trust app, provisions SharePointContext.cs and TokenHelper.cs files that hide all the complexity of making the authorized calls to SharePoint. This is achieved using a custom attribute in ASP.NET MVC or Pre_Init page event in Web Forms that inspects the current HTTP context, extracts the query string parameters and then makes the app access token with this information. This SharePoint context is stored in the user session, so that it doesn’t have to be made again for repeated requests to the app.
This article is published from the DNC Magazine for .NET Developers and Architects. Download this magazine from here [Zip PDF] or Subscribe to this magazine for FREE and download all previous and current editions
The standard app interaction flow implies that the user launches the app from SharePoint, clicking on the app icon. SharePoint then constructs the redirect URL with the app start URL and extra parameters such as the URL of the host web, the URL of the app web (if exists), SharePoint version and navigation bar colour. The browser is then redirected to this long URL and the app uses the extra parameters in the URL to figure out how to make the access token and to which SharePoint endpoint should it make the CSOM or REST call. Even if the SharePoint context instance is stored in the session, the URL parameters have to be present in every HTTP request as the SharePointContextProvider classes check in the CheckRedirectionStatus method for their presence to validate SharePoint contexts for every incoming request.
Figure 1: SharePoint Context provider needs to check URL parameters in every request to the app
Once our app is loaded in the browser, what happens when we click a link in the app that leads to another page or another action? These extra parameters that SharePoint puts in the URL are gone. The standard solution in the Visual Studio template is to use a JS script called spcontext.js that transparently appends these parameters in every application link, which is a very clumsy approach in my opinion.
A better way would be to retrieve the parameters in the app landing page, store them somewhere and then reuse them with no need to carry them around. This requires fiddling with the standard SharePointContext.cs and TokenHelper.cs.
We can even completely dispense with the parameters. How is this possible? In the normal flow, it’s SharePoint that provides these parameters. But, it is not the only way.
Imagine that instead of going to SharePoint for launching the app, we want to launch the app via its URL. The app would open but without the parameters it would be unable to connect to SharePoint. However, the parameters are nothing magic, just a couple of strings. We can store them in the web.config file, for example, and then use them as constants throughout the app code. As high-trust apps run in our own SharePoint farm, we can hardcode the URLs for both the host web (SharePoint site where the app is installed) and the app web (automatically provisioned hidden SharePoint side for the app to store its data). They are known as soon as the app is installed in SharePoint and we can copy them to our app configuration file.
In order to use this technique, we have to change the SharePointContext.cs file not to use any query strings but to use web.config provided parameters.
Instead of calls to SharePointContext.GetSPHostUrl we’ll call WebConfigurationManager.AppSettings["SpHostUrl"]. Also, in SharePointContextProvider.CreateSharePointContext method we’ll replace the query string parsing with the calls to AppSettings keys.
Another nuisance with the out-of-the-box TokenHelper.cs file is that it repeatedly queries SharePoint in the GetRealmFromTargetUrl method to obtain the SharePoint realm ID. This ID is a simple Guid and it is needed to make the app access token. In Office 365 world, each SharePoint tenant has a unique realm ID, but in our SharePoint farm the entire farm is a single realm. Thus, we can also put the realm ID in our app configuration file and skip the unnecessary realm checking altogether.
To get the realm ID, just open a SharePoint PowerShell console and type Get-SPAuthenticationRealm.
Figure 2: All the parameters are now placed inside web.config
Figure 3: The app still runs, even with no parameters in the query string
Tokens and Non-Windows Authentication
The high-trust app access token is created by the app itself. The app is responsible for inserting the user ID in the context so that the SharePoint can rehydrate the user in the CSOM call and check permissions.
In the default implementation of TokenHelper class, it is implied that the app runs with Windows authentication enabled, so the call to WindowsIdentity class won’t return null. However, with SharePoint, we aren’t limited to using only Windows authentication. We can use the broader claims identity instead. As a matter of fact, SharePoint 2013 uses claims authentication by default and TokenHelper.cs class converts the Windows identity of the app user into a set of claims that include the current Windows user’s security identifier (SID).
Figure 4: Hard-wiring the Windows identity in the token claims
While this approach is valid if we only use Windows authentication, when our SharePoint is configured to use alternative identity providers such as ADFS or Azure AD, the default token helper implementation will fail as user SID won’t be recognized as a valid user identifier for SharePoint. The exact mechanisms for user mapping between the high-trust app and SharePoint are very well described by Steve Peschka.
Let’s say that we want to use ADFS to federate SharePoint with another SAML identity provider. This could be a common scenario for extranet portals, where the external users come from another identity provider such as Microsoft Account, Google or Facebook. We will have to change SharePointContext.cs and TokenHelper.cs files to take federated identity into account and our app should also include federated identity authentication modules. The good news is that a fellow SharePoint MVP Wictor Wilén has already done the changes and documented the entire process in his blog.
Caching
The vast majority of the app samples show how to get the data from SharePoint, but very few of them take into account that SharePoint is just another data source for our app, akin to SQL Server. It means that we should call SharePoint sparingly, and keep a local copy of the data that our application needs. In other words, we should use some sort of cache mechanism in our app.
In .NET Framework 4, there is an in-memory object cache implementation, available in MemoryCache class in System.Runtime.Caching namespace. It allows for insertion and retrieval of objects by a key, together with cache lifetime and item expiration management. It is very easy to use and even easier to make a caching interface that allows for SharePoint data calls to be wrapped in a lambda expression. By having an interface, we can unit test the code with or without real implementations.
The code for a caching provider and interface is simple. The only detail is that the access to a static variable MemoryCache.Default is protected with a lock object to avoid concurrency issues.
public interface ICachingProvider
{
T GetFromCache<T>(string key, Func<T> cacheMissCallback);
}
public class CachingProvider : ICachingProvider
{
protected MemoryCache _cache = MemoryCache.Default;
protected static readonly object padLock = new object();
private const int DEFAULT_CACHE_LIFETIME_MINUTES = 60;
private void AddItem(string key, object value)
{
lock (padLock)
{
CacheItem item = new CacheItem(key, value);
CacheItemPolicy policy = new CacheItemPolicy();
policy.AbsoluteExpiration = TimeOffset.Now.AddMinutes(DEFAULT_CACHE_LIFETIME_MINUTES);
policy.RemovedCallback = new CacheEntryRemovedCallback((args) =>
{
if (args.CacheItem.Value is IDisposable)
{
((IDisposable)args.CacheItem.Value).Dispose();
}
});
_cache.Set(item, policy);
}
}
private object GetItem(string key)
{
lock (padLock)
{
var result = _cache[key];
return result;
}
}
public T GetFromCache<T>(string key, Func<T> cacheMissCallback)
{
var objectFromCache = GetItem(key);
T objectToReturn = default(T);
if (objectFromCache == null)
{
objectToReturn = cacheMissCallback();
if (objectToReturn != null)
{
AddItem(key, objectToReturn);
}
}
else
{
if (objectFromCache is T)
{
objectToReturn = (T)objectFromCache;
}
}
return objectToReturn;
}
}
Conclusion
The high-trust apps in SharePoint 2013 are one fine example of using the same model for apps in the cloud and on-premises. However, the implementation details of the code that manages the app communication with SharePoint steps in our way. In real-world scenarios, the default code that ships with Visual Studio project template for SharePoint apps should be extended and optimized.
Happy coding!
Download the entire source code of this article (Github)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Edin Kapić is a SharePoint Server MVP since 2013 and works as a SharePoint Architect in Barcelona, Spain. He speaks about SharePoint and Office 365 regularly and maintains a blog
www.edinkapic.com and Twitter @ekapic.