If you have followed this blog at all over the years, you know I started the bandwagon really early on with automating site and site collection provisioning with SharePoint. In fact this has been my topic at all of the SharePoint conferences except the last one. I was a little tired of the topic and thought everyone had heard it, but I was wrong and there still was a session on the topic anyway. I have simply seen too many customers that turn the knob either too far or too little in terms of enforcing their governance policies into this process. If an organization puts too few controls in place then you end up with the wild, wild west and uncontrolled sprawl of sites with no reason for naming conventions, urls, selected templates, etc. Go too far and make it so that a human has to intervene for any type of site request and well you have an awesome SharePoint deployment that no one will want to use because sending the file via email will get the job done quickly.
Finally with the release of SharePoint 2013, there are hooks in the product for you to jump in and take control of the process. My favorite technique with this release is to simply provide a URL to a custom application that is going to provide the form and take over the entire process. I am not the only one to advocate this technique. In fact, Richard diZerega (my fellow MTC colleague) has a great post on how to do exactly that as a SharePoint app. He has his code and a video to go along with his solution.
The goal of this blog is first to draw attention to Richard's great example and I am not going to re-walk you through his code and solution. Second, I want to point out a common customer ask that I have extended into Richard's work. After I show the custom form much like the one in Richard's demo, the customer often asks - "How do I incorporate a workflow into that so an approval has to happen?". Great question!!! I thought I would outline here what I added to Richard's solution to incorporate this requirement.
First the design of the solution: Since we want a workflow to take place before the site or site collection is created, we need to revisit how to gather the user input, what should trigger the workflow, and how to launch the custom code in the application that automates the site creation. In my demo, I decided to use a simple custom list called Site Requests to be the user input. If you wanted a nicer user interface, you could use the UI in the custom app that Richard has and then have it write an entry to the list, but having the list item gives us something to hook our workflow to. The custom list can really be placed in any site where you want to collect this user requests. Here is a screen shot of my list with some sample test data in it:
Looking that the details of this shape, the URL to the REST web service includes several key elements:
https://012bfa74-f840-4fa3-93ad-610003cc4200.o365apps.net/api/Provision/[%Current Item:SiteType%]?siteTitle=[%Current Item:Title%]&siteUrl=[%Current Item:Url%]&siteDescription=[%Current Item:Description%]&appWebUrl=https%3A%2F%2Fedhild613%2D7eb7f3f84addc7%2Esharepoint%2Ecom%2Fsites%2Fapps%2FSelfServiceProvisioning
The first part of the URL is the actual URL to the remote web where the custom application form in Richard's solution is hosted. Instead of using an ASPX form however, the endpoint is a REST service at /api/Provision. The very next part of the URL is the SiteType, so our request to the REST web service looks like /api/Provision/Blog or whatever SiteType is requested. The remaining part of the URL is a few querystring parameters that the REST service will need both to complete the request as well as information for it to establish the remote context to call back to SharePoint. These include the requested title, url, and description for the site as well as the URL of the appWeb where the custom app is deployed.
The last part of the solution is to just take the logic that is in Richard's ASPX form and wrap it as a REST service. You can still include the ASPX form in the same remote web, so these changes I highlight here are additions to his solution. To begin with, you will need a Global.asax file to register the REST endpoint. Once you add it to the project, place the following code in the Application_Start event:
protected void Application_Start(object sender, EventArgs e)
Then, add a new Web API Controller class named ProvisionController. The name of this file is important as the part before the word Controller matches the /api/Provision name in our Url. This Visual Studio item type should stub out a place for you to write code for the GET requests that we are sending. Here is my implementation which also collects the various querystring parameters.
// GET api/<controller>/5
public string Get(string siteType)
string siteTitle = String.Empty;
string siteUrl = String.Empty;
string siteDescription = String.Empty;
string appWebUrl = String.Empty;
string siteOwner = "I hard coded the email address of the owner";
foreach (var param in this.Request.GetQueryNameValuePairs())
siteTitle = param.Value;
siteUrl = param.Value;
siteDescription = param.Value;
appWebUrl = param.Value;
string returnVal = ProvisionSite(siteType, siteTitle, siteUrl, siteDescription, siteOwner, appWebUrl);
The ProvisionSite method is code that wraps the same code in Richard's solution that is in the code-behind of the ASPX page. I only made a small change from the code you can download from his blog. Basically, I made it so the REALM could be dynamically determined from the AppWeb Url. The beginning of my method looks like this:
private string ProvisionSite(string siteType, string siteTitle, string siteUrl, string siteDescription, string siteOwner, string appWebUrl)
const string targetPrincipal = "00000003-0000-0ff1-ce00-000000000000";
//get configuration information
Uri appWeb = newUri(appWebUrl);
string realm = TokenHelper.GetRealmFromTargetUrl(appWeb);
string appOnlyAccessToken = TokenHelper.GetAppOnlyAccessToken(targetPrincipal, appWeb.Authority, realm).AccessToken;
using (ClientContext clientContext = TokenHelper.GetClientContextWithAccessToken(appWeb.ToString(), appOnlyAccessToken))
And Richard's code will take you from there. The end result is now a list item that has a SharePoint 2013 Workflow attached to it that makes a REST call to a SharePoint App's Remote Web where the actual provisioning takes place. On a side note, I am finding that this pattern of having a workflow call a REST endpoint in a SharePoint App remote web seems to be quite a common way to extend the platform. Good luck -Ed