Building a SharePoint App as a Timer Job

This post will show how to create an app as a timer job.

Background

One of the complicated parts of the app model today is trying to figure out how to do things that I used to do in full trust code using the app model.  Honestly, things look a little different, and this pattern will be useful to understand. 

As usual, if you aren’t interested in the narrative, skip down to the section, “Show Me the Code!”

I worked with a few customers who were concerned about some of their end users who kept using the new Change the Look feature of SharePoint 2013 to change the branding of their site.  They turned their site into something hideous like this.

image

This doesn’t conform to their corporate branding, so they wanted a way to go back and change this in an automated fashion.  Further, they want to do this on a daily basis to make sure the site is always changed back.  They didn’t want to remove permissions for the user to do this.  I’ll admit, there are other ways to do this, but it helps me to illustrate using a timer job to achieve the same thing.

Create the Console Application

In Visual Studio 2013, create a new Console Application.  Here’s one of my favorite parts… go to the NuGet Package Manager and search for “sharepoint app”.  You will see the App for SharePoint Web Toolkit. 

image

Add this NuGet package to your Console app and you will get the TokenHelper and SharePointContext code to work with SharePoint apps.

Creating the AppPrincipal

The first step to understand is how to create the app principal.  The app principal is an actual principal in SharePoint 2013 for the app that can be granted permissions.  When you create an app in Visual Studio 2013 and press F5, Visual Studio is nice enough to take care of registering the app principal for you behind the scenes.  It does this when using the Visual Studio template, but we’re not using their template here… we are using a Console Application.  We need to register the app principal first for our Console Application to be able to call SharePoint.

To register the app principal, we can use a page in SharePoint 2013 called “_layouts/AppRegNew.aspx”.

image

This page is used to create the client ID and client secret for the app.  Give it a name and click Generate, then click Create.

image

Note that I removed the actual string for the client secret for security purposes (hey, it’s a secret!)

The result is an app principal.

image

No, that is not the real client secret… I changed it for security purposes.   Just use the string that the page generates and don’t change it.

Giving the App Principal Permissions

Now we need to grant permissions to the app principal.  The easiest way to do this is to create a new provider-hosted app in SharePoint, give it the permissions that your app needs, then go to the appmanifest.xml and copy the AppPermissionRequests element. 

 <AppPermissionRequests AllowAppOnlyPolicy="true">
    <AppPermissionRequest Scope="https://sharepoint/content/tenant" Right="Manage" />
</AppPermissionRequests>

The permissions that we will grant will be Tenant/Manage permission because our Console Application will go to multiple webs that are located in multiple site collections and change the branding.  To have permission to access multiple site collections, I need to request Tenant permission.  To change the branding for a site, I need Manage… hence Tenant/Manage.

You then go to a second page in SharePoint called “_layouts/AppInv.aspx”. 

image

Look up the app based on the Client ID that you just generated and click Lookup, it will find the app principal.  Then paste the AppPermissionRequests XML into the Permissions text box and click Create.

image

Once you click Create, the result is the Trust It dialog.

image

Click Trust It (of course you trust it). 

App Only Permission

I previously wrote a blog post, SharePoint 2013 App Only Policy Made Easy, that talks about the app only policy. If you aren’t familiar with app only, you need to go read that post.  Our timer job will not have an interactive user, so we need to use the app only policy.  The relevant code for this is the TokenHelper.GetAppOnlyAccessToken.

 //Get the realm for the URL
string realm = TokenHelper.GetRealmFromTargetUrl(siteUri);

//Get the access token for the URL.  
//   Requires this app to be registered with the tenant
string accessToken = TokenHelper.GetAppOnlyAccessToken(
    TokenHelper.SharePointPrincipal, 
    siteUri.Authority, realm).AccessToken;

Once we have the access token, we can now create a ClientContext using that access token.

 using(var clientContext = 
    TokenHelper.GetClientContextWithAccessToken(
        siteUri.ToString(),accessToken))

We now have a client context to use with the rest of our CSOM operation calls.

Update the App.config

The app.config will be used to store the URLs for various webs that need to have their branding updated via a timer job.  The app.config also stores the Client ID and Client Secret for our app.

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="Sites"
             type="System.Configuration.NameValueSectionHandler"/>
  </configSections>
  <appSettings>
    <add key="ClientId" value="0c5579cd-c3c7-458c-91e4-8a557c33fc50"/>
    <add key="ClientSecret" value="925gRemovedForSecurityReasons="/>
  </appSettings>
  <startup>
    <supportedRuntime version="v4.0"
                      sku=".NETFramework,Version=v4.5" />
  </startup>
  <Sites>
    
    <add key="site2"
         value="https://kirke.sharepoint.com/sites/dev"/>
    
    <add key="site1"
         value="https://kirke.sharepoint.com/sites/developer"/>
    <add key="site3"
         value="https://kirke.sharepoint.com/sites/dev2"/>
         
  </Sites>
</configuration>

Our Console Application will read the Sites section, pull the URL for each site, and call CSOM on it to update the branding.

Show Me the Code!

 using Microsoft.SharePoint.Client;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TimerJobAsAnApp
{
    class Program
    {
        /// <summary>
        /// To register the app:
        /// 1) Go to appregnew.aspx to create the client ID and client secret
        /// 2) Copy the client ID and client secret to app.config
        /// 3) Go to appinv.aspx to lookup by client ID and add permission XML below
        /// </summary>
        /// <param name="args"></param>         

        /*          
         <AppPermissionRequests AllowAppOnlyPolicy="true">
            <AppPermissionRequest Scope="https://sharepoint/content/tenant" Right="Manage" />
         </AppPermissionRequests>
        */
        static void Main(string[] args)
        {
            
            var config = (NameValueCollection)ConfigurationManager.GetSection("Sites");
            foreach (var key in config.Keys)
            {
                Uri siteUri = new Uri(config.GetValues(key as string)[0]);
                    
                //Get the realm for the URL
                string realm = TokenHelper.GetRealmFromTargetUrl(siteUri);

                //Get the access token for the URL.  
                //   Requires this app to be registered with the tenant
                string accessToken = TokenHelper.GetAppOnlyAccessToken(
                    TokenHelper.SharePointPrincipal, 
                    siteUri.Authority, realm).AccessToken;

                //Get client context with access token
                using(var clientContext = 
                    TokenHelper.GetClientContextWithAccessToken(
                        siteUri.ToString(),accessToken))
                {
                    //Poor man's timer
                    do
                    {
                        ApplyTheme(clientContext);
                        System.Threading.Thread.Sleep(10000);
                    }
                    while (true);
                }
            }            
        }

        /// <summary>
        /// Applies a red and black theme with a Georgia font to the Web
        /// </summary>
        /// <param name="clientContext"></param>
        private static void ApplyTheme(ClientContext clientContext)
        {
            Web currentWeb = clientContext.Web;
            clientContext.Load(currentWeb);
            clientContext.ExecuteQuery();

            //Apply RED theme with Georgia font
            currentWeb.ApplyTheme(
                URLCombine(
                    currentWeb.ServerRelativeUrl, 
                    "/_catalogs/theme/15/palette022.spcolor"),
                URLCombine(
                    currentWeb.ServerRelativeUrl, 
                    "/_catalogs/theme/15/fontscheme002.spfont"),
                null, false);
            clientContext.ExecuteQuery();
        }
        private static string URLCombine(string baseUrl, string relativeUrl)
        {
            if (baseUrl.Length == 0)
                return relativeUrl;
            if (relativeUrl.Length == 0)
                return baseUrl;
            return string.Format("{0}/{1}", 
                baseUrl.TrimEnd(new char[] { '/', '\\' }), 
                relativeUrl.TrimStart(new char[] { '/', '\\' }));
        }
    }
}

The Result

The sites in the app.config used to have that hideous theme.  Once the code runs, the sites in the app.config will have the colors of my favorite college football team (Go Georgia Bulldogs!), even using the “Georgia” font Smile

image

Of course, you can use whatever logic you want, the logic we use here is setting branding based on pre-configured URLs for the sites.  You can use whatever you want to schedule the timer job.  I used a poor man’s timer job, using a While loop with Thread.Sleep, but you could use the Windows Scheduler, a Cron job, or event Azure Web Jobs.

For More Information

SharePoint 2013 App Only Policy Made Easy