Workaround: PnP Sites Core not provisioning Sub Web Security!

Introduction

Let’s start by reminding ourselves how SharePoint groups work: when you add a SP group to a sub web, it is actually added to the Site Collection SharePoint groups. And when you assign some permissions (e.g. ‘Contribute’) to that group in the sub web, two things may happen, depending on whether the sub web inherits parent permissions or not:

  • permissions are inherited – the group is given ‘Contribute’ permissions in the Site Collection and all sub webs receive it too, implicitly.
  • permissions inheritance is broken – the group is given ‘Contribute’ permissions only on the Sub Web! In the Site Collection root you will still see the group under ‘People & Groups’ page (because as we said, the groups are provisioned on Site Collection level), but you will not see in the ‘Permissions’ page, because it is given permissions only in the sub web.

The issue

Ok, now consider the following scenario: you are provisioning a PnP Template against a sub web. e.g.:

<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2015/12/ProvisioningSchema">
  <pnp:Preferences Generator="OfficeDevPnP.Core, Version=2.3.1604.0, Culture=neutral, PublicKeyToken=3751622786b357c2" />
  <pnp:Templates ID="CONTAINER-TEMPLATE-XXX">
    <pnp:ProvisioningTemplate ID="TEMPLATE-XXX" Version="1">
      <pnp:Security>
        <pnp:SiteGroups>
          <pnp:SiteGroup Title="My Group" Owner="SHAREPOINT\system" AllowMembersEditMembership="false" AllowRequestToJoinLeave="false" AutoAcceptRequestToJoinLeave="false" OnlyAllowMembersViewMembership="true" />
        </pnp:SiteGroups>
        <pnp:Permissions>
          <pnp:RoleAssignments>
            <pnp:RoleAssignment Principal="My Group" RoleDefinition="Full Control" />
          </pnp:RoleAssignments>
        </pnp:Permissions>
      </pnp:Security>
    </pnp:ProvisioningTemplate>
  </pnp:Templates>
</pnp:Provisioning>

what would you expect to happen when the PnP engine runs this? Personally, I would expect that the engine sees the ‘My Group’, creates it under the Site Collection and assigns ‘Full Control’ permission level to it on the Sub Web level. Now, as we explained, if the Sub Web permission inheritance is not broken, that would actually mean giving the ‘My Group’ ‘Full Control’ rights within the entire site collection. But this is the exact reason why the PnP provisioning engine ignores the Security element for sub webs! Because the PnP team has a principle of ‘provisioning a site can never change settings in other sites‘. Yes, PnP provisioning engine will simply ignore your Security instructions, when executed against a sub web.

However, in the case when the sub web has broken permission inheritance, I disagree! When the inheritance is broken, the Group will be created on the Site Collection level, but it will only be given rights on the sub web, hence – not actually changing anything in the other (parent) site!

Solution

To overcome this issue, I have created a PnP extension, which will perform the Security element provisioning on the sub web exactly the way I think it should be – if the web is a sub web and the permission inheritance is broken – go ahead with the Security provisioning, instead of ignoring it.

Template

First you have to change your template to:

<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2015/12/ProvisioningSchema">
  <pnp:Preferences Generator="OfficeDevPnP.Core, Version=2.3.1604.0, Culture=neutral, PublicKeyToken=3751622786b357c2" />
  <pnp:Templates ID="CONTAINER-TEMPLATE-XXX">
    <pnp:ProvisioningTemplate ID="TEMPLATE-XXX" Version="1">
      <pnp:Security>
        <pnp:SiteGroups>
          <pnp:SiteGroup Title="My Group" Owner="SHAREPOINT\system" AllowMembersEditMembership="false" AllowRequestToJoinLeave="false" AutoAcceptRequestToJoinLeave="false" OnlyAllowMembersViewMembership="true" />
        </pnp:SiteGroups>
        <pnp:Permissions>
          <pnp:RoleAssignments>
            <pnp:RoleAssignment Principal="My Group" RoleDefinition="Full Control" />
          </pnp:RoleAssignments>
        </pnp:Permissions>
      </pnp:Security>
      <pnp:Providers>
        <pnp:Provider Enabled="true" HandlerType="My.Assembly.NameSpace.MyClassName, My.Assembly" />
      </pnp:Providers>
    </pnp:ProvisioningTemplate>
  </pnp:Templates>
</pnp:Provisioning>

Which basically tells the PnP engine to search for the My.Assembly.dll and within it, it shall find My.Assembly.NameSpace.MyClassName class, which implements IProvisioningExtensibilityHandler interface. Search Google for more information on PnP extension engine.

Extension

Here is the C# code for your extension class, which is basically the original PnP method, but with modifications to provision Security against a sub web, when the sub web has broken inheritance.

click here to see code!

Conclusion & Download

With this extension in place, you can now provision Security elements against a sub web, when the sub web has broken inheritance. It overrides the default PnP provisioning engine, which ignores the Security element, as soon as it sees that it’s a sub web.

Again, my opinion is that sub web Security should not be ignored, when the sub web has broken inheritance, because it doesn’t change in any way any other site, except adding the Group container on the Site Collection level (but without giving it any permissions!).

You can find the code samples as a convenient zip file here.

Cheers!

SharePoint 2013 People & Groups field as a dropdown with predefined choices

If you are in the SharePoint consulting business, by no doubt you have already heard the following customer requirement:

We need a SharePoint field, which allows the user to pick one of the few predefined groups we have defined there

That’s not possible out of the box, although possible with some custom development. There is many different options here, but the one I recommend and I find it to be a ‘best practice’, is the Field JSLink approach.

Below I will show you how to implement a JSLink, which changes the People & Groups field from this:

1

to this:

2

Ok, first thing first, you probably already have somewhere in your project the definition of the Field. Something like:

<Field ID="{GUID_Here}" Name="GroupsAsDropdownTest" StaticName="GroupsAsDropdownTest" DisplayName="GroupsAsDropdownTest" Type="User" List="UserInfo" EnforceUniqueValues="FALSE" ShowField="ImnName" UserSelectionMode="PeopleAndGroups" UserSelectionScope="0" 
/>

You need to add JSLink attribute to it. So:

<Field ID="{GUID_Here}" Name="GroupsAsDropdownTest" StaticName="GroupsAsDropdownTest" DisplayName="GroupsAsDropdownTest" Type="User" List="UserInfo" EnforceUniqueValues="FALSE" ShowField="ImnName" UserSelectionMode="PeopleAndGroups" UserSelectionScope="0" 
        JSLink="~site/SiteAssets/jquery-1.11.3.min.js|~site/SiteAssets/JSLink/MyCompany.JSLink.Fields.GroupsAsDropdownTest.js" />

You will find the full source code of the JSLink at the bottom of the post, but let’s quickly go over it.

First, we have the standard

SPClientTemplates.TemplateManager.RegisterTemplateOverrides(FieldCtx);

which tells SharePoint to use our custom JavaScript functions to render the field in the different display modes (Edit, New, etc).

Let’s review the function which renders the field in New/Edit mode, as it is the most interesting one. What it basically does is, render the field as SharePoint would render it OOB:

var html = jQuery(SPClientPeoplePickerCSRTemplate(ctx));
html.hide();
 
return html.outerHTML() + dropdownHTML;

but hide it, and next to it render our custom dropdown list, which is driven by the configuration specified in

Fields_Renderer.DropdownOptions

Important note: the groups listed in this structure should be valid ones!

Whenever the value in the custom dropdown list changes, we run the following function:

Fields_Renderer.SelectionChange

which uses the standard SharePoint people picker functionality to clear the current value in the hidden OOB people field and set a new one (defined as value attribute in the dropdown option).

In summary:

We use JSLink on the People & Groups field to hide the standard field control and render a custom dropdown next to it. Whenever the dropdown changes, it updates the value in the hidden OOB field. When the user presses ‘Save’ on the form, SharePoint persists the user field as it normally would.

Here is the source code:

https://app.box.com/s/8e5q02b324zwrry05xem33rohe001ytm

Happy coding!

How to override the NewDocSet.aspx redirect

If you are opening the NewDocSet.aspx page in a dialog, similarly to this:

options.url = L_Menu_BaseUrl + "/_layouts/15/NewDocSet.aspx?List=" + {listGuid} + "&ContentTypeId=" + {CT} + "&RootFolder=" + L_Menu_BaseUrl + "/Lists/YourList";
options.title = "New Document Set";
options.autoSize = true; 
// Show dialog
SP.SOD.execute('sp.ui.dialog.js', 'SP.UI.ModalDialog.showModalDialog', options);

you may have noticed that after creation of the docset, the dialog redirects your page to the welcome page of the newly created doc set. If you are opening this dialog from some kind of a dashboard, this is usually an undesired behavior.

I have taken a look at the NewDocSet.aspx backend with ILSpy and I have determined that when the new Document Set is provisioned, the code end with a calls to this function – ‘window.frameElement.navigateParent’. Turns out this is a function defined by SharePoint, when you open a dialog. It basically redirects the parent window to a new URL. And NewDocSet.aspx uses this function to redirect the window to the Welcome page of the new doc set.

So, to override this behavior, we simply override the dialog’s window frame function (make sure you execute this AFTER the dialog is opened):

// Override the redirect behavior of NewDocSet.aspx
for (var i = 0; i < frames.length; i++) {
    // Find the frame of the dialog
    var frmWindow = frames[i];
    if (frames[i].location.href.indexOf(‘/_layouts/15/NewDocSet.aspx’) > -1) {
        // Override the function
        var prxy = frmWindow.frameElement.navigateParent;
        frmWindow.frameElement.navigateParent = function () {
            // Disregard the original function’s URL, refresh the page we are currently on
            prxy.apply(this, [window.location.href]);
        };
    }
}

The patter is called proxy pattern, by the way.  This code will refresh the page that opened the NewDocSet.aspx dialog, instead of redirecting to the doc set’s welcome page.

Good luck (with SharePoint, you need it).

SharePoint 2013 Refiner Multi-Value (‘Contains’ instead of an ‘Equals’)

You are in a situation where your Managed Property contains multiple values (e.g. you programmatically populate a Property Bag, which is added to the IndexedPropertyKeys and then exposed as a Managed Property). You add a Content Search Web Part on your page and also a refiner. In the Refiner panel you add your managed property and you are horrified by the following end result:

1

If you are reading this page you already know that such rendering causes a problem – the entries within the multi-value field are not ‘normalized’ to single entries, hence, you now can’t search only for items that have a property which contains only (e.g.) one of the values. You have already probably tried setting the ‘multi-value’ property of the Managed Property, but alas, no success.

Fear not, for I have a solution! It is delivered as a custom Refiner filter, which is based on the Filter_MultiValue.html OOB Refiner filter. Here it is: click here.

How to use it? Simple:

1. Drop the file into your Filters folder (Site Collection -> Settings -> Master pages and page layouts -> Display Templates -> Filters)

2. Make sure you Save, Check In and Publish the file

3. Go to your refiner and edit the web part properties -> Choose refiners

4. Select the newly added Filter

2

5. Ok -> Save page

And the result now is:

3

Much better! Selecting a refiner value and applying actually works too🙂

4

For the curious, how does the custom Filter work? Well:

1. We set the var useContains = true; Although in my experience it has no effect on the end result

2. More importantly, we add one more for(;;) cycle in the code, which splits every filter item into separate filters:

5

So, instead of having:

[ ] Value 1, Value 2, Value 3, …

you will have:

[ ] Value 1

[ ] Value 2

[ ] Value 3

3. But MOST IMPORTANTLY, instead of hex-ing the refiner token, as the OOB filter does, we use the plain-text value:

6

This is crucial! Without this part the refiner will not work as expected.

So let’s compare an original query URL generated by the OOB Filter_MultiValue.html with the custom Filter_Contains_MultiValue.html.

Before:

#Default=
{
“k”:””,
“r”:[{
“n”:”HahnAirPMOProjectManagerDisplayValue”,
“t”:[“\”ǂǂ4c41425c41646d696e6973747261746f72\””],
“o”:”OR”,
“k”:false,
“m”:{“\”ǂǂ4c41425c41646d696e6973747261746f72\””:”LAB\\Administrator”}}]}

After:

Default=
{
“k”:””,
“r”:[{
“n”:”HahnAirPMOProjectManagerDisplayValue”,
“t”:[“LAB\\Administrator”],
“o”:”OR”,
“k”:false,
“m”:{“LAB\\Administrator”:”LAB\\Administrator“}
}]
}

That specific part seems to cause SharePoint to do ‘contains’ search, instead of an ‘equals’ search.

Hope this helps!

PS

Don’t forget to set your ‘delimiter’ variable to whatever string you use for separating the values within the field. It is currently set to ‘, ‘ (coma and space).

Update 1:

I have updated the source code to handle strings with backslash in them differently. i.e.:

– If the string has a backslash (e.g. ‘domain\admin’) – don’t put it withing brackets

– if the string has no backslash (e.g. ‘Farm Admin’) – put it in brackets, otherwise the KQL is invalid and causes an error

Fixing ‘The SPListItem being updated was not retrieved with all taxonomy fields’ or how to provision taxonomy fields correctly

If someone wants to provision a taxonomy field in a declarative way, the following pattern should be followed:

      <Field Type="Note" DisplayName="Strategy_0" StaticName="Strategy_0" Name="Strategy_0" ID="{the GUID of your hidden NOTE field here}" ShowInViewForms="FALSE" Required="FALSE" Hidden="TRUE" />
      <Field Type="TaxonomyFieldType" DisplayName="Strategy" ShowField="Term1033" Required="FALSE" EnforceUniqueValues="FALSE" ID="{6f1ce2c9-9c40-407f-a0b9-7785220a14ff}" StaticName="Strategy" Name="Strategy">
        <Customization>
          <ArrayOfProperty>
            <Property>
              <Name>TermSetId</Name>
              <Value xmlns:q2="http://www.w3.org/2001/XMLSchema" p4:type="q2:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">{the GUID of your termset here}</Value>
            </Property>
            <Property>
              <Name>AnchorId</Name>
              <Value xmlns:q3="http://www.w3.org/2001/XMLSchema" p4:type="q3:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">00000000-0000-0000-0000-000000000000</Value>
            </Property>
            <Property>
              <Name>TargetTemplate</Name>
              <Value xmlns:q6="http://www.w3.org/2001/XMLSchema" p4:type="q6:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance"></Value>
            </Property>
            <Property>
              <Name>TextField</Name>
              <Value xmlns:q6="http://www.w3.org/2001/XMLSchema" p4:type="q6:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">{the GUID of your hidden NOTE field here}</Value>
            </Property>
            <Property>
              <Name>IsPathRendered</Name>
              <Value xmlns:q7="http://www.w3.org/2001/XMLSchema" p4:type="q7:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
            </Property>
          </ArrayOfProperty>
        </Customization>
      </Field>

That would be sufficient, if it were not for the SspId property (the GUID of your keyword term store). Unfortunately there is no token for this and SharePoint is not smart enough to use the default one, if you leave it empty! You will get an error ‘The SPListItem being updated was not retrieved with all taxonomy fields‘. So, a little code work-around is needed.

You have to create a feature receiver or something else, which should run the following code against your field:

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            var web = properties.Feature.Parent as SPWeb;

            // Strategy
            var strategyField = web.Lists["YourList"].Fields["YourTaxonomyField"] as TaxonomyField;
            TaxonomyHelper.ConnectTaxonomyField(web.Site, strategyField);
        }
        /// <summary>
        /// Fix up a taxonomy field
        /// </summary>
        /// <param name="site"></param>
        /// <param name="field"></param>
        public static void ConnectTaxonomyField(SPSite site, TaxonomyField field)
        {
            //Create a Taxonomy Session
            TaxonomySession session = new TaxonomySession(site);
            if (session.DefaultKeywordsTermStore != null)
            {
                field.SspId = session.DefaultKeywordsTermStore.Id;
                field.Update();
            }
            else
            {
                throw new Exception(string.Format("DefaultKeyWordTermStore not found in this site {0}", site.Url));
            }
        }

Again, if SharePoint used the default keyword store if you leave out the SspId empty or if there was a token for it, that would not be necessary.

Still, I am happy with the approach, as it is 95% declarative and I need the code only to link my field to the keyword store. The TermsetId is part of my declaration, and not the code, so, goal achieved!

SharePoint document library list view webpart drag and drop

The following article assumes that you know how to add programmatically list view web parts on your page.

You might find yourself being in a situation in which you are programmatically adding a List View Webpart pointing to a document library on your site and you wonder why:

  • ‘add document’ link does not show
  • drag & drop does not show up or does not work

Basically, you have ended up with something along the lines of:

drag drop not working

First, and foremost, you have to change your code and instead of adding ListViewWebPart, you have to add XsltListViewWebPart (source).

var webPartToAdd = new XsltListViewWebPart
                        {
                            ChromeState = PartChromeState.Normal,
                            ChromeType = PartChromeType.None,
                            Title = "Documents List View",
                            ListName = web.Lists["Documents"].ID.ToString("B").ToUpper(),
                            XmlDefinition = web.Lists["Documents"].DefaultView.GetViewXml()
                        }

This will get you to the point where you see the drag&drop functionality (in IE only, I don’t see it in Firefox):

drop here

However, when you drop the file, you might get the error:

exception

The reason for this error is the fact that sp.js and sp.core.js are not registered on the page (SP.Utilities and SP.Utilities.CommandBlock are undefined)! I think this is a bug in SharePoint, because if you simply click Edit on the page and then save it (without changing anything), the web part will start working (because the scripts will now be registered on the page).

So, how do we overcome this issue? Well, I have determined that if we execute the following script on the page, the drag&drop in the web part will work fine:

SP.SOD.executeFunc('sp.js', 'SP.Utilities.Utility', function() { console.log('sp.js loaded'); });
SP.SOD.executeFunc('sp.core.js', 'SP.Utilities.CommandBlock', function() { console.log('sp.core.js loaded'); });

This code will include the scripts that SharePoint ‘forgets’ to include. You have variety of options how to register those scripts on the page. I have decided to include them as a content of a Content Editor WebPart which I provision on the same page where I provision the (xslt) list view web part:

// add script registration work-around (there is a bug in the framework, which does not register those scripts until we edit the page).
var xmlDoc = new XmlDocument();
var xmlElement = xmlDoc.CreateElement(“HtmlContent”);
xmlElement.InnerText = “<script type=’text/javascript’>SP.SOD.executeFunc(‘sp.js’, ‘SP.Utilities.Utility’, function() { console.log(‘sp.js loaded’); }); SP.SOD.executeFunc(‘sp.core.js’, ‘SP.Utilities.CommandBlock’, function() { console.log(‘sp.core.js loaded’); })</script>”;
var cewp = new ContentEditorWebPart
{
ChromeState = PartChromeState.Normal,
ChromeType = PartChromeType.None,
Title = “Drag and drop script registration workaround”,
Content = xmlElement
};

And then, viola!, the drag&drop works:

viola

 

I hope this helps,

Hristo

When using [TaxonomyField].SetFieldValue you get ‘set AllowUnsafeUpdates to true’ exception

In some situations (such as ajax requests and other non-postback methodologies), you might encounter the following error:

Updates are currently disallowed on GET requests.  To allow updates on a GET, set the ‘AllowUnsafeUpdates’ property on SPWeb

This might happen if you are trying to set a Term type of object in an SPList item’s field. e.g.:

SPListItem myItem = … ;

Term myTerm = … ;

var taxonomyField = myItem[“MyTaxonomyField”] as TaxonomyField;

taxonomyField.SetFieldValue(item, term);

myItem.Update();

If you execute this in the backend during an ajax request (or maybe some other use cases too) this will fail with the exception mentioned above.

If you are reading this article it means that  you have already set the SPWeb’s AllowUnsafeUpdates to true and the problem still persists. For some reason this is the default SharePoint behavior and it doesn’t matter how many times you set the AllowUnsafeUpdates to true, it will still be ignored. So, we need another solution.

You might be tempted to use a solution (1, 2) such as setting a TaxonomyFieldValue to the field, instead of Term directly. Such as:

string termString = String.Concat(thisYear.GetDefaultLabel(1033), TaxonomyField.TaxonomyGuidLabelDelimiter, thisYear.Id);

TaxonomyFieldValue tagValue = new TaxonomyFieldValue(string.Empty);
tagValue.PopulateFromLabelGuidPair(termString);
oField.SetFieldValue(i, tagValue);

 

That would be a mistake because such approach breaks the Search Crawl (i.e. the crawler won’t crawl your field properly). So what is the correct solution? As crazy as it might sound, it is to set the HttpContext.Current to null just prior to setting the term field and then restoring it back.

So, solution:

var httpContext = HttpContext.Current;

HttpContext.Current = null;

SPListItem myItem = … ;

Term myTerm = … ;

var taxonomyField = myItem[“MyTaxonomyField”] as TaxonomyField;

taxonomyField.SetFieldValue(item, term);

myItem.Update();

HttpContext.Current = httpContext;

 

What does this achieve? It lies to SharePoint that you don’t have a HttpContext (i.e. you are not triggering this code from the browser), so it won’t validate your context and won’t throw an exception about AllowUnsafeUpdates.

 

Best Regards,

Hristo

 

Follow

Get every new post delivered to your Inbox.