Create a List in the Host Web When Your SharePoint App is Installed...and Remove it from the Recent Stuff List

This is one of those questions that I've seen asked a lot but for some reason I've never really seen anyone post an answer to it. I did a quick Bing search before writing this post but didn't really see anything up there so I'm going to go out on a bit of a limb and guess that maybe there's still some use for this kind of information. In my particular scenario I am creating a SharePoint app and when it's installed I want it to create a list in the host web, because I'll be using it to store data for my application. I'm using a provider hosted app so I'm not going to have an app web, but even if I DID have an app web, I'd rather have this list in the host web in case my app is ever uninstalled and then reinstalled later, my application data will still be there and I can pick right up where I left off.

So to get started on this little adventure, I created a new SharePoint application in Visual Studio 2013. It began as just your basic out of the box application - I just wanted to get it up and working first to verify that all the plumbing is in place. With that ready to go, the next thing I wanted to do is have something similar to a feature receiver that you have for full trust code that would fire when my app is installed. Fortunately the cloud app model provides just such a thing. I just needed to select my SharePoint App project in Visual Studio and in the properties windows there is a property called HandleAppInstalled; just double-click that to change it from it's default value of false to true and when you do a new WCF endpoint is added to your provider hosted app. It's all fleshed out with a basic stub implementation, and then you can add your code in there. That's what we'll begin with.

The stub implementation looks something like this:

public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
{
SPRemoteEventResult result = new SPRemoteEventResult();

using (ClientContext ctx = TokenHelper.CreateAppEventClientContext(properties, false))
{
//blah blah blah
}

return result;
}

Now the challenge is that the CreateAppEventClientContext is geared towards creating a context for an app web, which we don't want. So instead we're going to create a client context the way that Visual Studio 2012 did when you create a provider hosted app, in the default.aspx code behind. That means we're going to need the Url to the host web, a context token, and the host name of our application. Here's how we're going to get all of that stuff.

To begin with, you can get the Url to the host web in the SPRemoteEventProperties parameter that is passed into our method. The properties parameter has an object property called AppEventProperties that contains a URI property called HostWebFullUrl. So we can get the first parameter we need - the host web Url - like this: Uri hostUrl = properties.AppEventProperties.HostWebFullUrl. The context token is actually very easy to get; it's a property of the properties parameter, so we can get it like this: properties.ContextToken. The last thing we need - the host name of our application - is more difficult to get in a WCF. That's because you don't have an HttpContext in a WCF, so you have to reach back into the ServiceContext to get the information you're looking for. Fortunately the ServiceContext contains a Host property that has a collection of URIs in the BaseAddresses property. Here's a chunk of code to pull out that information:

System.ServiceModel.OperationContext oc = System.ServiceModel.OperationContext.Current;

Uri localUrl = null;

//enumerate through the Host base addresses and look for the SSL connection
foreach (Uri u in oc.Host.BaseAddresses)
{
if (u.Scheme.ToLower() == "https")
{
localUrl = u;
break;
}
}

UPDATE: The code above looking through the BaseAddresses works great when you're using it with the IIS Express web site Visual Studio adds to your project when you create a new SharePoint App. HOWEVER...it blows up when you move your code to the full blown version of IIS. What I found in trying to get this working is that the you still get two BaseAddresses; the first one uses the HTTP scheme but IS the correct host name for your WCF service. The second one uses the HTTPS scheme, but is the fully qualified domain name of the server on which the code is running. As a result, the code above picks the second host name, but that is NOT the host name registered for the endpoint with SharePoint. As a result, the code below to get a client context fails and everything blows up at that point. NOTE: This is because my IIS web name is not the same as my machine name, which would generally be the case in a production environment. The code I'm using now that works with both IIS Express as well as IIS is just this:

if (oc.Host.BaseAddresses.Count > 0)
localUrl = oc.Host.BaseAddresses[0];

I'm looking for the host that is using HTTPS, since that's what our apps should always communicate over. It is arguably unlikely that you would have two different host names for HTTP and HTTPS, but you never know so why tempt fate? Now that I have the three parameters I need I can create my client context for the host web; note that I always FIRST check to make sure that localUrl is not null, which would happen if I had no SSL endpoints on my provider hosted app. Getting my client context now looks like this:

//this is what was originally here
//using (ClientContext ctx = TokenHelper.CreateAppEventClientContext(properties, false))
using (ClientContext ctx = TokenHelper.GetClientContextWithContextToken(hostUrl.ToString(), properties.ContextToken, localUrl.Authority))

Awesome! Now that I have a client context for my host web, as long as my user / application has sufficient rights I can create a new list in my host web. I'm not really going to cover that in great detail because I think creating lists via CSOM is all over the interwebs. But for completeness here's a shortened version of my code to create a new list:

Web web = ctx.Web;

ListCreationInformation ci = new ListCreationInformation();
ci.Title = LIST_NAME;
ci.TemplateType = (int)ListTemplateType.GenericList;
ci.QuickLaunchOption = QuickLaunchOptions.Off;

l = web.Lists.Add(ci);

//add a description and some fields in here
//blah

l.Hidden = true;
l.Update();

//this creates the list
ctx.ExecuteQuery();

Okay, all of that is great, I now have my list in my host web. There is one important aspect to this scenario though, and that is that my list should be hidden from users. Now you might think that I have that covered with my ListCreationInformation (ci.QuickLaunchOption) and the Hidden property of the list...but that is not enough. Unfortunately SharePoint still throws it in the Recent bucket that shows up on the Quick Launch navigation. In fact the Recent list is NOT a list, it's a collection of NavigationNode items. So if you REALLY want your list to be invisible, you need to remove it from over there as well. Doing that is three step process (NOTE: I'm leaving out all the error handling and try...catch blocks for read-ability; you would of course want that in any code you write...but you knew that, I know... :-) ):

Step 1: Get the quick launch navigation node collection

In this step you're going to make a call back to get the root web of the site to get the quick launch navigation nodes. This code is just continued from the create list code above, so it's still in the "using" statement for my ClientContext (ctx) shown above:

//get the site and root web, where the navigation lives
Site s = ctx.Site;
Web rw = s.RootWeb;

//get the QuickLaunch navigation, which is where the Recent nav lives
ctx.Load(rw, x => x.Navigation, x => x.Navigation.QuickLaunch);
ctx.ExecuteQuery();

//now extract the Recent navigation node from the collection
var vNode = from NavigationNode nn in rw.Navigation.QuickLaunch
where nn.Title == "Recent"
select nn;

NavigationNode nNode = vNode.First<NavigationNode>();

Step 2: Get the Child Nodes of the "Recent" Navigation Node

Okay, now that I have the "Recent" navigation node, I need to populate it's child property, which is where I should find a node for the list I just created. This gets simpler now, here is the code to retrieve that:

//now we need to get the child nodes of Recent, that's where our list should be found
ctx.Load(nNode.Children);
ctx.ExecuteQuery();

Step 3: Find The Node for the New List and Delete It

Now that I have the collection of items in the Recent navigation, I can find my item and delete it like you would any other item via CSOM. Here's the code:

var vcNode = from NavigationNode cn in nNode.Children
where cn.Title == LIST_NAME
select cn;

//now that we have the node representing our list, delete it
NavigationNode cNode = vcNode.First<NavigationNode>();
cNode.DeleteObject();

ctx.ExecuteQuery();

And there you have it. You've now created a new list in the host web, made it hidden and not shown on the quick launch bar, and removed it from the Recent navigation list so it is REALLY out of sight.

Comments

  • Anonymous
    January 01, 2003
    Hi @Robert, a few comments:

    1. Regarding getting the client context, in my testing it was not getting set correctly for the host web; it only worked for the app web. It seems as though your mileage may vary with it. In terms of using it for either ACS or S2S, I personally don't find that super compelling because I have yet to run across a case where I started building an app with one security model and then switched it to the other. The S2S case is usually different (at least in my experience) than the simple "use Windows auth everywhere" approach that the OOB classes assume. For example, expecting Windows auth won't work at all if you're deploying to an Azure web site, which is exactly what I was doing in my case. The net of course is that you should just design your app based on your requirements.
    2. Regarding the approach for getting the host URI for creating context, the method I described above works fine when the local server name does not match the host name. Again, this is deployed in an Azure web site and worked great.
    3. I'll go back to my last point in #1 about design based on your requirements. However given that you need to be a site owner to install an app, and this event receiver only fires on app installed, I was not worried about inadequate permissions. But you're right, if you're concerned about that based on your application requirements then looking at using an app only token makes sense.

    Steve
  • Anonymous
    January 01, 2003
    The comment has been removed
  • Anonymous
    January 01, 2003
    The comment has been removed
  • Anonymous
    May 11, 2014
    I have been most of the week on vacation, spent 3 days a t re:publica #rp14 Conference in Berlin. So
  • Anonymous
    May 11, 2014
    Pingback from SharePoint, Office365 & Yammer Nuggets of week 19
  • Anonymous
    May 19, 2014
    Hi Steve,

    to me it looks like that the whole part about getting the ClientContext is not really necessary. The second parameter in the TokenHelper.CreateAppEventClientContext(properties, false) determines whether to create the ClientContext for the AppWeb or the HostWeb - when it is false (as in the VS2013 Update 2 project template), then it will get you the context for the host web. Even better, this method first checks if you're running as S2S or against the ACS and creates the client context the right way (your code will actually only work with SP set up to use ACS, in S2S we don't have the Context Token). Am I missing something obvious?

    In addition, I did something similar already to provision custom event receivers to the host web on app install and I was using the OperationContext.Current.IncomingMessageHeaders.To.ToString() to get the address of my WCF service at runtime - so far it looks like it is working properly, but haven't tested all possible combinations of the IIS and the app server names. I would be interesting to see what Uri you get back from OperationContext.Current.IncomingMessageHeaders.To in your case where the IIS name is different from the machine name.

    And lastly - the more I work with app lifecycle event receivers, the more I think they should always run in the app only security context - it is way to risky to rely on the user's permissions to be in place when messing up with the host web (or worse, with the site collection) at install time. There's nothing worse than an app installed half-way because of some silly permission not being there when you need it...

    Regards, Robert
  • Anonymous
    August 11, 2014
    This is going to be a multi-part series to dissect and tear apart an application I tweeted about using
  • Anonymous
    August 11, 2014
    In Part 4 of this series we looked at the integration with various Azure services in the CloudTopia app
  • Anonymous
    September 18, 2014
    The comment has been removed
  • Anonymous
    November 10, 2014
    The comment has been removed
  • Anonymous
    January 08, 2015
    m88 : http://m88en.com
    M88.com offer online sports games Asia, Sports Betting Asia, Sports Betting Sites Asia.
    m88asia : http://m88en.net
    Link to M88BET phone: m88en.com. – Register and Open Betting Account and Membership M88BET.
    m88bet : http://www.linkm88vip.com
    MANSION88 the house is one of the largest and most prestigious. Appeared quite early in the Asian market, the so-MANSION88 currently attracts more players.
    link m88 : http://m88wiki.com
    Home the M88 is the official sponsor of the football club in the Premier League
    Wish you happy with the new M88
    m88 casino online : http://m88free.com

    Modern Thai restaurant combines outstanding traditional cuisine and a subtle modern decor with a warm welcoming ambience. Thai Restaurants in Brisbane :http://www.watersidethainoodles.com.au , traveller reviews of Brisbane Thai restaurants and search by price, location, and more..