iPhone Detected, site running in minimal mode.
Home     Tags/Archives     Tweets     About Kevin

Thanks to everyone that came out for my session at SharePoint Saturday "The Conference" ​that took place this past week in Washington, DC.

I only did one session this year, entitled, Creating a Custom Gatherer for the SharePoint 2010 Activity Feed.  As promised you can download a hi-res image version of the 3D-animated mindmap that was used in the presentation by clicking the thumbnail below:



In addition here is the Visual Studio project for the Twitter Gatherer Timer Job.  And lastly, here are links to some of the resources we talked about during the session:

 

The conference itself was pretty epic.  200+ sessions over 3 days time with experts from all around the world.  As always my favorite part of attending one of these conferences is interacting with everyone.  It was great catching up with old friends and meeting loads of new ones!  The SharePoint community is so special and it's amazing to see people sharing their "art" with each other.  It doesn't matter if you are hanging out with an SP rockstar or just someone that is passionate about their work-- it such a cool opportunity to learn, grow, and of course SHARE.

In the geek-irony category, I thought it was hillarious that a group of us ended up at a bar/club one night down in DC called "The Frontpage".

Thanks to everyone that made the conference possible, especially the organizers- they did an amazing job!


​So today I was in the process of refactoring some InfoPath 2010 forms for wider reuse.  One of the views in the form was an “instructions” page, but rather than having all this “static” formatted text live on the form I wanted it to pull it from a SharePoint list that could then be easily edited and maintained by business users without having to modify the form.

This sounds reasonably easy, and I even already had a data source connection to the list I wanted to use, so all I had to do was add a new field to the SharePoint list as a Multi-Line Text datatype with rich text enabled.  I didn’t want these instructions to load/change every time the form was opened, so my strategy was to create a rule and based on a condition (that told me the form was a new one) I would query the secondary data source and pull the rich text from SharePoint and store it in field located in my Main data source.  I made sure that this new field was of the datatype “Rich Text (XHTML)” and that the actual Rich Text box control placed on the form was set to “Enable enhanced rich text…”, etc…  Problem was that when I previewed the form, the text from the SharePoint list showed up, but all the formatting was stripped.

After a bit of troubleshooting I determined that the RTF formatting was indeed coming over from the SharePoint List data source, in fact if I bound a repeating table with a RTB directly to the secondary data source field it did indeed show the text formatted.  Most other sources on the internet pointed at a faulty implementation of the “Set a Field’s Value…” rule action.  Since in my case this is a pretty complex form and I had a code-behind (sandbox solution) I had the luxury of digging into this a bit further.  What I found is that the problem is not with the Rule Action implementation because the formatting is not lost upon the call to .SetValue() (on the XPathNavigator pointing at your target field).  It is actually lost when you read the value out of the .Value property (of the XPathNavigator pointing at your source field).

So I came up with a work around that appears to be working.  That is instead of reading and writing these field values, if you pull the entire .InnerXML property of the source and set the target’s .InnerXML property to it, all the formatting properly comes over!  Here’s a helper function that illustrates what I’m talking about:

private void CopyFieldValueByXml(DataSource sourceDatasource, string sourceXPath,
                                 DataSource targetDatasource, string targetXPath)
{
    XPathNavigator sourceNav = sourceDatasource.CreateNavigator();
    XPathNavigator sourceField = sourceNav.SelectSingleNode(sourceXPath, NamespaceManager);
    string dataXml = sourceField.InnerXml;
 
    XPathNavigator targetNav = targetDatasource.CreateNavigator();
    XPathNavigator targetField = targetNav.SelectSingleNode(targetXPath, NamespaceManager);
    targetField.InnerXml = dataXml;
}

So now some warnings: First off, I (or you) still need to do a bit more testing on this work around because in my simple use case I only needed to pull static rich text into my document.  I don’t know for sure if there will be any ill effects should we need to later go and edit this rich text within the form.  Second, and more importantly, this little work around will only work if you are using a code behind (doesn’t have to be an admin form, I’m doing this with a sandbox solution).  If this is the ONLY reason you are using a code behind then that’s probably a very unnecessary maintenance burden you are placing on your solution for a very small gain.  I would not recommend you do this in that scenario.  However, if you are in a situation like mine, where you need the code behind anyway, then have at it!


Form Lockdown​Some of you may be familiar with a feature named ViewFormPagesLockdown that ships with MOSS 2007 and SharePoint Server 2010.  It’s automatically enabled when you create a new site using the publishing template.  Basically, if you enable anonymous access then this feature will keep those anonymous users out of application pages and form pages while still allowing them read access to the underlying list/document data.  So for example, they can open a publishing page in a Pages library or download a document from a document library, but they are not able to access the page item property view form or view a list of all the documents in the library.

This feature wasn’t present in WSS, but that makes sense since WSS doesn’t support the publishing infrastructure anyway.  However, now that we have Wiki Page libraries (/SitePages) available in SharePoint 2010 Foundation (SPF from here on) you very well might want to have that same behavior to lock out anonymous users from the “backstage” of a public facing web site.

Take a look at the screenshot below that was taken from the SharePoint Management Shell running on a SharePoint 2010 Server:

ViewFormPagesLockdown Powershell Screen


It’s a hidden feature, but no problem on Server; you can simply activate this feature from PowerShell.  However, if you run the same command on an SPF server you will get NADA.  It’s not installed.  What to do?

Well, it turns out that all that this feature does is change a few of the permissions for the Limited Access Role in the site collection.  There’s not an easy way to get at that via the web UI, but it’s almost trivial with a tiny bit of code.  Here’s a very truncated version of what you need to do:

   1:  string url="http://yoursite";
   2:  using(var site = new SPSite(url))
   3:  {
   4:      SPWeb rootWeb = site.RootWeb;
   5:      SPRoleDefinition guestRole = rootWeb.RoleDefinitions.GetByType(SPRoleType.Guest);
   6:      guestRole.BasePermissions &= ~(SPBasePermissions.EmptyMask | SPBasePermissions.ViewFormPages);
   7:      guestRole.BasePermissions &= ~SPBasePermissions.UseRemoteAPIs;
   8:      rootWeb.AnonymousPermMask64 &= ~(SPBasePermissions.UseRemoteAPIs | SPBasePermissions.ViewFormPages);
   9:      rootWeb.Update();
  10:  }
 
For those that would like something a bit more polished, say a well behaved command line application or an actual SharePoint feature you can turn on and off, contact me and I’ll hook you up.


​I’ve been playing around lately with term stores, both the central global variety as well as the private (aka “local”) site collection type.  This secondary type is very useful if you want to limit access to the term sets or allow the term set’s contents to diverge from site collection to site collection.  Why would you want to do this?  Well, of course the primary power of a term store is to be able to globally manage a set of metadata for your organization, but these more localized term store’s give you more control over the use and management of the term sets, but still let you use all the cool new things SharePoint 2010 has to offer such as metadata navigation, faceted-search, etc…

So knowing all this, you can imagine my surprise when I learned that site collection "term stores" are actually just a special group in an MMS (Managed Metadata Service) term store.  You are probably already familiar with Term Store Groups as they for the top level in the hierarchy within in a term store.  What you may not know is that there are two types of these groups, "regular" and "site collection" (well there's also a third type, "system").  The site collection groups have a property that defines which site collections have access to the group (it’s an ACL that just stores a collection of Guids that are the SPSite.ID’s).  The easiest way to create one of these is to create a new site column within your site, choose the field type of "managed metadata", then change the term set selection to "customize this term set".  This action will both create the special term group in your MMS term store as well as your new term set.

Super, that’s what I want, so let’s package this up as a feature so that I can staple it to a site definition.  Wait a minute!  Not so fast.  You can’t create site collection term store groups in code.  Seriously?  Yes, after checking this out with multiple sources​ as well checking things out for myself with reflector I found that the only public method on the TermStore object is CreateGroup(string groupName) which only creates a normal group CreateGroup from Reflector(note: there is another private overloaded method of CreateGroup that takes a GroupType as a parameter… and there are other internal methods that call this, but there are no public paths to any of these).  Also, once a group is created you cannot change its type.

Well, that’s a big bummer, but not to be deterred I have come up with a workaround.  It’s not the perfect solution to this problem, but it does appear to get the job done.  Also, if you were going to have publishing enabled on the site, then it’s really no extra work.  The biggest downside to this is that I believe you will need to have the full version of Office Server (not just foundation) to pull off this work around.

So it goes like this-  in code (feature receiver, powershell script, or whatever…): 

  1. Activate FeatureID: f6924d36-2fa8-4f0b-b16d-06b7250180fa.  This is the Publishing Infrastructure feature.  One of the side effects of this feature is that it creates a site collection term group in the DefaultSiteCollectionTermStore and adds a term set called “Wiki Categories”.
  2. Look up the created term group by name (you'll just have to iterate over the collection as there's no method for doing this -- look for one with the name equal to the defaultName below:
    string defaultName = "Site Collection - " + (site.Url.Replace("http://", "")).Replace("/", "-");
  3. Doublecheck that the current site’s ID is in the group's SiteCollectionAccessIds property.  If not, it’s ok, you can hi-jack it using the .AddSiteCollectionAccess(Guid ID) and .RemoveSiteCollectionAccess(Guid ID) methods.  I found that the publishing infrastructure feature only creates the new term set if one doesn’t already exist with that default name.  Since it doesn’t clean itself up when you delete the termset you can sometimes find yourself in this state.  This does open up another way to “exploit” this workaround though, because you can actually create a dummy site somewhere else and hi-jack it’s site collection term group (you can always rename the term group to whatever you want later… they key is now you have a group that is the correct type!)
  4. Rename the group if desired.  Also grab the term group’s ID and stash it in your site’s property bag so that later you can fetch it directly using TermStore.GetGroup(Guid id) rather than having to iterate over the groups collection.
  5. Create all your term sets and populate them with default terms if desired.
  6. Optionally you can now deactivate the publishing infrastructure feature if you don’t need it for the site.  You will need to clean up a few things if you want to stay neat including: items added to Style Library, some lists (Content and Structure Reports, Reusable Content, and Workflow Tasks), and the Wiki Categories term set from the site collection term group.
  7.  

Ideally, Microsoft will make a fix in the future that allows us to create a site collection term store group without resorting to all this, but until then, we can exploit the above trick.  I will actually post some code in the next few weeks that essentially does all of the above by creating a “shadow site collection”, hi-jacking it’s site collection term group, and deleting the shadow site collection all encapsulated in a single CreateSiteCollectionTermGroup method call.​​​


​I just spent a bunch of time working around a SharePoint bug related to manipulating the order that navigation items appear in a MOSS Publishing site on the TopNavigationBar.  Here I will spell out what you need to get to get various pieces of this to work- but first, let's define what we are trying to accomplish.

Say you have a site that has been created in some way.  It doesn't really matter if it was declaritively deployed from a Site Definition, restored from a backup, generated by a SharePoint Deployment Wizard CMP, or built up manually from code.  The thing is once it's done being created you need to modify from code various aspects of the top navigation bar including manually setting the order of the items.  Again, it doesn't matter where this code is running from, whether it be a PowerShell script, a command line application, or a suicide webpart placed on the site.  Ok, all that being said, *MY* specific requirements were to:

  • Change Top (global) Nav to Include Subwebs, Hide Pages, and support manual (non-automatic) ordering
  • Hide one of the subwebs
  • Reorder the other subwebs to a custom (ie. arbitrary) order
  • All changes must be done fully in code with no "manual human steps" required as it was part of an automated deployment script.
The good news is that to do the first two bullets, things pretty much work like they are supposed to, although finding exactly how to do some of that took a bit of work, so I'll reproduce them here.  Unfortunately for the third bullet there's a bug that gets in the way and so weird workarounds are required.

First of all, my code segments below will assume you have referenced Microsoft.SharePoint.dll and Microsoft.SharePoint.Publishing.dll and have the following usings at the top of your file:

   1:  using Microsoft.SharePoint;
   2:  using Microsoft.SharePoint.Publishing;
   3:  using Microsoft.SharePoint.Navigation;​
   4:  using System.Collections.Generic;

To set the different properties of the root web's navigation settings simply do the following:

   1:  using (var site = new SPSite(webUrl))
   2:  {
   3:      var rootPubweb = PublishingWeb.GetPublishingWeb(site.RootWeb);
   4:      rootPubweb.NavigationOrderingMethod = OrderingMethod.Manual;
   5:      rootPubweb.IncludeSubSitesInNavigation = true;
   6:      rootPubweb.IncludePagesInNavigation = false;
   7:      rootPubweb.Update();
   8:  }

Now, if you want to hide a particular subweb from the top navigation, you actually need to set the property of the subweb, not a property in the root web or it's navigation collections.  So, you would add the following (inside the SPSite using statement):

   1:  var subwebs = rootPubweb.GetPublishingWebs();
   2:  subwebs["subwebToHide"].IncludeInGlobalNavigation = false;
   3:  subwebs["subwebToHide"].Update();
   4:  rootPubweb.Update();

So far so good.  Now when it gets to actually specifying the order of the nodes, the documentation that you will find will direct you to get an SPNavigationNodeCollection from rootWebInstance.Navigation.TopNavigationBar.  If you iterate over the SPNavigationNode 's in this collection you can set their order by using the .MoveToFirst, .MoveToLast, and .Move methods.  Unfortunately on a newly build publishing site this collection will not contain any of your subwebs, only the top level pages (even though we specified to NOT include pages and to include subwebs).  In scouring the internet I found that others had indeed come across this bug as well.  What's odd is that if you use the SharePoint GUI and go to the root web's navigation settings (/_layouts/AreaNavigationSettings.aspx), select any subweb in the "Global Navigation" tree and click one of the Move buttons, then some magic code runs behind the scenes and the Navigation.TopNavigationBar collection will now be properly populated (and you won't need the following work around any more for that particular site).  However, as my requirement was to not require any manual steps I had to go on to develop the workaround below.

So I embarked on an effort to reproduce the "magic code" that initialized the navigation collection properly.  There was much trial and error involved while making incremental changes and then interogating object properties with PowerShell, etc...  In the end the successful strategy was to create the navigation nodes manually (in code).  The trickiest part though was getting them to be set as "internal" and not "external" links (this is a read-only property on each NavigationNode)-- it turned out the node's Url property had to be set just right for it to work.  Here is my workaround code:

   1:  using (var site = new SPSite(webUrl))
   2:  {
   3:      var rootPubweb = PublishingWeb.GetPublishingWeb(site.RootWeb);
   4:      var subwebs = rootPubweb.GetPublishingWebs();
   5:     
   6:      //Build a temporary hash of the node titles (so we can use them instead of index numbers)
   7:      SPNavigationNodeCollection nodes = site.RootWeb.Navigation.TopNavigationBar;
   8:      Dictionary<string, SPNavigationNode> nodeHash = new Dictionary<string, SPNavigationNode>();
   9:      for (int x = 0; x < nodes.Count; x++)
  10:      {
  11:          nodeHash.Add(nodes[x].Title, nodes[x]);
  12:      }
  13:      
  14:      //This block fixes the evil SharePoint defect ------------------------
  15:      foreach (PublishingWeb subweb in subwebs)
  16:      {
  17:          if (!nodeHash.ContainsKey(subweb.Title))
  18:          {
  19:              string relUrl = subweb.ParentPublishingWeb.Uri.MakeRelativeUri(subweb.Uri).ToString();
  20:              SPNavigationNode nn =
  21:                  Microsoft.SharePoint.Publishing.Navigation.SPNavigationSiteMapNode.CreateSPNavigationNode(
  22:                      subweb.Title, relUrl, NodeTypes.Area, site.RootWeb.Navigation.TopNavigationBar);
  23:              nn.Update();
  24:          }
  25:      }
  26:      site.RootWeb.Update();
  27:      //---------------------------------------------------------------------
  28:   
  29:      //Rebuild the hash table since now we should have the correct nodes!
  30:      nodeHash = new Dictionary<string, SPNavigationNode>();
  31:      for (int x = 0; x < nodes.Count; x++)
  32:      {
  33:          nodeHash.Add(nodes[x].Title, nodes[x]);
  34:      }
  35:   
  36:      string[] subwebTitlesInOrder = {"FirstNavItem", "SecondNavItem", "ThirdNavItem", "FourthNavItem"};
  37:      
  38:      //Order the nodes in the proper order
  39:      SPNavigationNode currentNode;
  40:      SPNavigationNode previousNode=null;
  41:   
  42:      for (int i = 0; i < subwebTitlesInOrder.Length; i++)
  43:      {
  44:          currentNode = nodeHash[subwebTitlesInOrder[i]];
  45:          if (i == 0)
  46:          {
  47:              currentNode.MoveToFirst(nodes);
  48:          }
  49:          else
  50:          {
  51:              currentNode.Move(nodes, previousNode);
  52:          }
  53:          previousNode = currentNode;
  54:      }
  55:      site.RootWeb.Update();
  56:  }
And that's it!
​​

  << Older      [more posts]   

RSS FeedBack to the HomepageMy Twitter Feed and More!Video Chat Now!

Tags

Hide Low Frequency Tags

Archives

Recent Posts