Date Formats in SharePoint 2013 Advanced Search

On my current project we had a problem with Date formats, when users where searching from Advanced Search page by Last Modified date, they were getting a very helpful error: “We didn’t understand your search terms….”

Search_DateFormat_Error

Looking into the log it pretty much had a similar message and was down to the format of the date used, obviously US Format dates worked fine.

Here are the few things I tried to make search recognize UK date formats:

Regional Settings Changes

So first instinct to fix it was to straight away jump to Regional settings of search center site and change the locale to en-GB, obviously changing it right away did not work (Life cannot be that simple with ShitPoint!) so I went a step further and verified that en-GB is used across all site collections reset the index & re-crawl but that made no difference. So I did couple of more things with regional settings in SharePoint itself:

  1. Change the Regional settings of Central Admin, there is no direct link to it you will have to manually type in the Url http://SERVER/ _layouts/15/regionalsetng.aspx.
  2. Set the default Locale for Web Application from General Settings in Central admin

Reset, re-index….. Still no difference.

Web.config globalization Settings
So after regional settings made no difference, I went to each web.config (Web App, central admin & even the Search WCF service!) and update the globalization as shown below.

Web.Config_Globalization

I had no hope of that actually fixing it but still gave it a shot and I was correct it did not fix the issue.

Server Locale

So first two options did not really help and I thought ensuring Server language is set to en-GB should help, and actually my language (Control Panel –> Language) was set to English (United States) and I thought aha that’s it changing this to English (United Kingdom) should work, so I changed it and I even verified the locale with Get-WinSystemLocale powershell command and it indeed was set to en-GB. Re-started the VM, reset and re-crawl, phew and still no difference.

I found this TechNet article about a registry setting to change the Default locale. It was set to en-US on my VM as shown:

RegisterySetting_Locale

And voilà I thought changing this to en-GB got to work but alas it was in vain.

Setting Language Preference in UserProfile

One of the StackOverFlow thread talked about setting the correct language preference in UserProfile (from Edit Profile page), but this also really did not impact the search behaviour

UserProfileLanguageSettings

Ensuring Request headers have correct language

I read a while ago that SharePoint is looking at the request headers to determine user’s locale which makes sense but looking at the request header it was indeed en-GB (Which is based on the language preference set on the OS)

RequestHeader_en-GB

While I was inspecting the headers in Fiddler I noticed that when you are on Search results page (results.aspx) and perform a search, it was doing an AJAX call and the request body had Culture set to 1033 (which is the Id for en-US) , now that looks like is where the problem is

SearchRequest_XML

As a final assault on SharePoint, I cracked open search.debug.js & search.clientcontrols.debug.js and finally worked out that following script is getting emitted on the page (You can do View source of the results page and look for it)

ExecuteOrDelayUntilScriptLoaded(
    function() {
        Srch.ScriptApplicationManager.get_current().states =
        {
            "browserLanguage": 2057,
            "webUILanguageName": "en-US",
            "webDefaultLanguageName": "en-US",
            "contextUrl": "http://KPSP/search",
            "contextTitle": "Search",
            "supportedLanguages": [ /*Removed for Readability*/],
            "navigationNodes": [ /*Removed for Readability*/],
            "searchCenterUrl": "http://KPSP/sites/search/Pages",
            "showAdminDetails": true,
            "defaultPagesListName": "Pages",
            "isSPFSKU": false,
            "userAdvancedLanguageSettingsUrl": "",
            "defaultQueryProperties": {
                "culture": 2057,
                "uiLanguage": 2057,
                "summaryLength": 180,
                "desiredSnippetLength": 90,
                "enableStemming": true,
                "enablePhonetic": false,
                "enableNicknames": false,
                "trimDuplicates": true,
                "bypassResultTypes": false,
                "enableInterleaving": true,
                "enableQueryRules": true,
                "processBestBets": true,
                "enableOrderingHitHighlightedProperty": false,
                "hitHighlightedMultivaluePropertyLimit": -1,
                "processPersonalFavorites": true
            },
            "showSuggestions": true,
            "showPersonalResults": true,
            "openDocumentsInClient": false,
            "languages": [{ "id": 1033, "label": "English" }] /*THIS IS THE PROBLEM LINE*/
        };
        Srch.U.trace(null, 'SerializeToClient', 'ScriptApplicationManager state initialized.');
    }, 'Search.ClientControls.js');

if you notice some of the language properties are set to en-GB and some to en-US! Anyways after trial and error it seems that 1033 in “languages”:[{“id”:1033,”label”:”English”}], Makes all the difference, I quickly hacked up something in JavaScript to change 1033 to 2057 (en-GB Locale) and boom now it takes UK date format as a valid entry and shows the results, happy days…..But soon I realized that fix only works when SharePoint is performing a search via AJAX. If you load the resuts.aspx page with search QueryString i.e. /Pages/Results.aspx?k=Write<=31/03/2104 it still fails, so obviously there is something else going on when the search is performed on the server side.

Proper Fix!

In order to properly fix it, we need to first make sure 1033 is changed to 2057 in the JavaScript above and also ensure when the search is performed on the server side it still takes en-GB as the locale.

Fixing the Search Query when performed AJAX Call

So I went fishing trying to work out what’s generating above JavaScript in a hope to try and change it’s behaviour. Thanks to JetBrains dotPeek it was fairly easy to work out that Microsoft.Office.Server.Search.WebControls.ScriptApplicationManager is the class rendering that JS. ScriptApplicationManager class has got a States dictionary object which gets serialized into above mentioned JavaScript and to my delight States property is public so it’s easy to change the value for “language” key in it to whatever your want.

Fixing the Search Query when performed on the Server side

That proved to be little more challenging! after spending good few hours going through the Out-of-Box code using dotPeek, I found following internal method of ScriptApplicationManager class which was eventually working out the Effective Language to use.

GetLanguageMethod

I could not really work out which if construct was actually invoked to return 1033, anyways thanks to the Blog Post about changing language token in Content Search WebPart, I learned about DataProviderScriptWebPart and I realized that FallbackLanguage (which is the first thing checked in above internal method) is a public property on DataProviderScriptWebPart  and It is always set to -1, I can set it to 2057 and above internal method will basically use the FallbackLanguage.

Putting it all together (Final Code)

The best way I thought to put the fix together would be to do a custom Search Results web-part which overrides the Locale settings. Following class inherits from ResultScriptWebPart and just changes few things to use the Locale based on SiteSettings ONLY

[ToolboxItem(false)]
    public class SearchResults : ResultScriptWebPart
    {
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            if (this.AppManager.States.ContainsKey("languages"))
            {
                List<LanguagePreference> preferences =
                    this.AppManager.States["languages"] as List<LanguagePreference>;
                if (preferences != null && preferences.FirstOrDefault() != null)
                {
                    /*This will fix the language id in JavaScript which emitted on the page*/
                    preferences[0].id = (int)SPContext.Current.RegionalSettings.LocaleId;
                }
            }
        }
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            if (this.AppManager != null)
            {
                if (this.AppManager.QueryGroups.ContainsKey(this.QueryGroupName) &&
                    this.AppManager.QueryGroups[this.QueryGroupName].DataProvider != null)
                {
                    this.AppManager.QueryGroups[this.QueryGroupName].DataProvider.BeforeSerializeToClient +=
                        ChangeLanguage;
                }
            }
        }

        private void ChangeLanguage(object sender, BeforeSerializeToClientEventArgs e)
        {
            DataProviderScriptWebPart scriptWebPart = e.ScriptWebPart as DataProviderScriptWebPart;
            if (scriptWebPart != null)
            {
                /*This will fix the language used when search is performed on page load*/
                scriptWebPart.FallbackLanguage = (int)SPContext.Current.RegionalSettings.LocaleId;
            }
        }
    }

Generating Unique IDs for Folders in SharePoint Document Library using Document ID Service

Document Id Service that comes with SharePoint Out-of-the-box provides neat feature to assign Unique Document Ids to Documents in SharePoint Libraries, but obviously only works with mmmm Documents! What if you had do something similar for Folders also and stick to Of-the-box format like {Some-Prefix}-{Id}-{ItemId} without worrying about where each part of Id is coming from where.

Here’s a quick How to and PoC Code:

When you set up Document ID Service on a site collection, following two columns are added to the Document Libraries:

_dlc_DocId and _dlc_DocIdUrl

_dlc_DocId just stores the display text of the Document ID and _dls_DocIdUrl stores a Url which is in format of
{SPWeb Url}/_layouts/15/DocIdRedir.aspx?ID={DocId}

In SharePoint this columns are also available to really any item in he Document Library (see the screen below) and we are going to hijack above two columns and populate Document Ids for Folders.

FolderId-1

So now how do we actually generate the Id, one option I initially thought was to provide a custom DocumentId Provider and also generate a Id for Folder Item but following method on DocumentProvider never fires for folders.

public abstract string GenerateDocumentId(SPListItem listItem);

So we would need to provide our own Event receiver to handle the generation of IDs for Folder Id fair which is fair enough but goal is to stick to absolutely with the Id which Out-of-the-box generates, so I went digging to work out how out-of-the-box does it and thanks to dotPeek I found OobProvider (Gotta love the name!) and to my delight it was public so I am going to use it, albeit it’s in an Internal namespace

OOB Document ID Provider

Its simply now matter of calling GenerrateDocumentId method of OobProvider from your event receiver for Folder Items and updating two OOB columns with appropriate values:

 OobProvider p = new OobProvider();
string folderUId = p.GenerateDocumentId(properties.ListItem);
item["_dlc_DocId"] = folderUId;
item["_dlc_DocIdUrl"] = new SPFieldUrlValue()
{
Url = string.Format("/_layouts/15/DocIdRedir.aspx?ID={0}", folderUId),
Description = folderUId
};
item.SystemUpdate();

Sample PoC to generate Folder Ids using above discussed approach can be found at Git Hub SharePoint Folder Id Repository. There are few things the PoC Provides:

  • Site collection level Folder Id Feature, Feature adds a Sync Item Added Event Receivers to Document Library and it has dependency on OOB Document Id Service feature so you want be able to activate Folder ID feature without the OOB Document ID feature in place.
  • Provides a Pausable timer job to reset the Folder Ids or set the Folder Ids of existing folders in a site collection, TimerJob is based on something similar to OOB timer job and makes use of  SPWorkItemJobDefinition and rather unknown TimerJobUtility (which basically inherits from ContentIterator), this ensures that Timerjob will work with Large lists (I have tested it with a document library containing ~8k items out of which ~3K where folders). Use of TimerJobUtlity probably warrants it’s own Post because it provides very cool functionality which was hidden to me until now!
  • An application page is provided via which a re-assignment of folder id can be triggered which I imagine will be useful in a scenario where Document Id  prefix for the site collection is changed and there is a need to re-generate FodlerIds based on that. (Please note there is no custom action to get to  the page, just go to http://siteCollection/_layouts/15/SharePoint.FolderIds/FolderIds.aspx).
  • It uses exactly same approach to OOB to work out which Document Provider to use in case a custom Document Provider is Set for a Site Collection.

Navigation through FancyTree using TAB Key

Finally back to Blog after a long time, so starting with a short one!

Recently I had to deal with a requirement to provide Tree like navigation menu, as I am very much familiar with DynaTree using FancyTree (DynaTree is apparently being retired) was a no brainer however the tree navigation had to be accessible i.e. users should be able to navigate through the tree using TAB and Arrow key and pressing ENTER on the tree node behaves like a click event.
FancyTree renders ULs and Lis and you can navigate through the tree using Arrow keys but using TAB was a problem also pressing the ENTER key on the Tree node had no effect, so here is a little extension to FancyTree which provides that functionality.

Extension basically Adds tabindex=0 to Tree node and captures ENTER key event to perform action on the node, I only needed to either expand/collapse the node or navigate to the Href.

Ah also first time using Git -:)

Accessible FancyTree Demo
Code @ Git Hub (Look into jquert.fancytree.Tabable.js)

Enhanced SharpSSH – In .NET 3.5 & Support for SFTP Delete

On my current project we had a requirement to drop a file onto an SFTP server.  SharpSSH seems to the only free and open source option around for .NET

Some of the pain points of SharpSSH

  • SharpSSH was written in older version of .NET and relies on algorithms in Org.Mentalis.Security.Cryptography for encryption and hashing which is now natively supported by System.Security.Cryptograph so using SharpSSH as is does not seem right
  • Sftp implementation does not support deleting a file (although it is very simple to add that!)
  • Not complied with a Strong name key

Making above changes looked easy enough, so here is the download of SharpSSH – SFTP component for .NET which:

  • Uses algorithms in System.Security.Cryptograph instead of Org.Mentalis.Security.Cryptography
  • Compiled using  .NET 3.5 with strong name  and Project converted to Visual Studio 2008
  • Supports Deleting a file, directory and renaming over SFTP

Download Enhanced SharpSSH Here

Convert a WebPart Zone in MOSS into Accordion or Tabs Style Interface

This is a web part that turns all the visible web parts with a Title in a Zone into Tab or Accordion interface.  Just drop the webpart into a zone and see it convert all the visible webparts with a title in the zone  into part of Tab or Accordion.  It renders one tab/accordion menu per webpart.

Originally inspired by EasyTabs from PathToSharePoint but instead of embedding some JavaScript with Content editor web part, it’s a custom web part providing a property to control if it renders Tab or Accordion and JavaScript bit is done using jQuery.

Some customary screen shots!

WebPart Zone Converted into Tabs

WebPart Zone Converted into Tabs

WebPart Zone containing the TabAccordion WebPart in Edit mode

WebPart Zone converted into Accordion

WebPart Zone converted into Accordion

WebPart Zone containing the TabAccordion WebPart in Edit mode

WebPart Zone containing the TabAccordion WebPart in Edit mode

Properties of the WebPart

Properties of the WebPart

Download Full Source Code. Hopefully it’s commented just enough to understand what’s going on in the JavaScript.

WSP Solution can be downloaded from here. Once the package is deployed, Activate the Kark SharePoint Web Parts feature at Site Collection level and Tab Accordion web part will be available in the Web Part Gallery.

UPDATE
Have changed the JavaScript to render tabs at the top

Download WSP Here

Here is a screenshot


Please excuse the ugly looking Tabs, but hopefully CSS should be really easy to change

Created Date in SPListItemVersion does not respect locale settings?

A Quick post! On a recent project we had a custom control that was showing document version history information such as “Created By” and “Date Created” in a table format, it was looking all good. After the day light savings time (British Summer Time) one of the tester spotted a bug in document version history control where “Date Created” was still showing the GMT time, it looked like SPListItemVersion.Created was not respecting day light savings time.

TimeZone.xml looked good and every other Dates on the site were showing correct time. It turns out that SPListItemVersion.Created was only showing time in GMT (Don’t know why!) so a simple fix was to convert the time into local time at the render time.


lblDateCreated.Text = version.Created.ToLocalTime().ToString();

Various MOSS Enterprise Search Stuff – Part I

Recently I have been working on a large Content Management solution that required 300+ custom Managed/Metadata Properties in MOSS Search and had requirements to allow user to search on all of the Metadata Properties. MOSS Search Index was also used to run a number of reports such as “What Asset (Image, Video, Document etc) is used on what page?”

While working with MOSS Search I came across a number of things that I think are worth sharing.

Programmatically setting up Shared Scope

APIs to work with MOSS Search related Administration tasks such as creating Scopes, Managed Properties etc are very straight forward and well documented under Getting Started with the Enterprise Search Administration Object Model

But if you want to set up a shared scope, then it’s not very obvious from API how to do it. ScopeCollection.Create method as shown below takes owningSiteUrl as a parameter and to set up a shared scope just pass in null for owningSiteUrl and the scope will be created as a Shared Scope!

public Scope Create (string name,string description, Uri owningSiteUrl,   bool displayInAdminUI,string alternateResultsPage,ScopeCompilationType compilationType)

Programmatically setting up “Include values from a single crawled property based on order specified”

Programmatically setting up mappings to crawled properties is straight forward, if you want to add multiple crawled properties and include value only from a single crawled property based on the order than from the UI, you can simply check the radio button next “Include values from a single crawled property based on order specified”.

Managed Properties Mappings

To do it programmatically add multiple Crawled properties to MappingCollection and then set ManagedProperty.RespectPriority property to true, here is some code (pseudo code really!) to add multiple crawled property mappings


Schema propertiesSchema = new Schema(SearchContext.Current);
ManagedPropertyCollection managedProperties = propertiesSchema.AllManagedProperties;
ManagedProperty managedProperty = managedProperties["Test Property"];
MappingCollection mappingCollection = managedProperty.GetMappings();//Create Crawled Property Mappings
Mapping propMapping = new Mapping(new Guid("00130329-0000-0130-c000-000000131346"),  "ows_Modified_x0020_By", "31",managedProperty.PID);
Mapping propMapping1 = new Mapping(new Guid("b725f130-47ef-101a-a5f1-02608c9eebac"), "11","31",managedProperty.PID);//Add in the order in which you want it to appear in the Admin UI
mappingCollection.Add(propMapping);

mappingCollection.Add(propMapping1);

managedProperty.SetMappings(mappingCollection); // You have to do this for Mappings to work

//Tell it to respect priority!

managedProperty.RespectPriority = true;

managedProperty.Update();

It takes a Guid (crawledPropset) which is the category of crawled property (SharePoint, People, etc) and also takes an int (crawledPropertyVariantType) which represents the DataType of crawled property, I find it annoying that list of possible values crawledPropset and crawledPropertyVariantType is not very handy so here are the possible values

Some Possible Values of crawledPropertyVariantType

Data Type crawledPropertyVariantType
string 31
bool 11
integer 20
datetime 64
Something to do with Custom field 4127

*Please note that above values are what I have encountered till now *

Commonly Used crawledPropset

Category crawledPropset
Sharepoint 00130329-0000-0130-c000-000000131346
Basic 0b63e343-9ccc-11d0-bcdb-00805fccce04
Mail aa568eec-e0e5-11cf-8fda-00aa00a14f93
Office f29f85e0-4ff9-1068-ab91-08002b27b3d9
People 00110329-0000-0110-c000-000000111146