SharePoint Pitfalls: GUID Formats

SharePoint uses GUIDs extensively. The problem is, it accepts them in different formats, in different locations:

  • The ListId and ListName properties of the DataFormWebPart, feature, site column definitions, solution manifests, all expect a GUID in the {A1ADD3D1-B21F-4F93-9B86-B1FE332424D0} format (inside { });
  • In an Elements.xml file or in a Parameter inside a data source, use B8381C7D-3B8D-46D8-8E40-C96E1FF4C308;
  • In the URL, you need to replace for %2D, { for %7B and } for %7D, as in %7BA1ADD3D1%2DB21F%2D4F93%2D9B86%2DB1FE332424D0%7D;
  • In feature receivers tokens and in New-SPTrustedSecurityTokenIssuer and the likes, GUIDs have to be in lowercase ($SharePoint.Type.edd0669b-2393-4fe6-988d-17a2De06c6e4.FullName$).

SharePoint Pitfalls: Master Pages in Page Layouts

When you deploy a page layout, you may want to add your own master page as well. I find it useful to add my own custom master pages and reference them directly, in the same or a dependent feature. You might be surprised, however, that it doesn’t work exactly how you’d expect!

The issue is, page layouts will ignore silently anything in the MasterPageFile attribute if it isn’t one of the standard tokens for the system or custom master pages. ~masterurl/default.master and ~masterurl/custom.master. The solution is to have a code behind class and specify the master page in the OnPreInit method (anywhere else won’t work):

protected override void OnPreInit(EventArgs e)

{

    base.OnPreInit(e);

 

    this.MasterPageFile = "~site/_catalogs/masterpage/CustomMasterPage.master":

}

SharePoint Pitfalls Index

This page will list all of my posts dedicated to SharePoint pitfalls. It will be updated regularly.

  1. Creating a Visual Studio Project Without SharePoint Locally Installed
  2. Save Publishing Site as Template Option Missing
  3. Publishing Pages in Document Libraries Other Than Pages

SharePoint Pitfalls: Publishing Pages in Document Libraries Other Than Pages

This one is a classic: the SharePoint Publishing feature in a Publishing Site creates a document library by the name of Pages; this is where you can store your publishing pages.

A common request is to have more of these document libraries, that is because we cannot create publishing pages anywhere else. The problem is, it is unsupported, God knows why!

What you can do is rename the document library to something else. SharePoint will look for the library id in the site’s property bag, under __PagesListId, so only need to update this value accordingly:

web.AllProperties["__PagesListId"] = newDocLibId.ToString();

web.Update();

Now, there are some solutions over the Internet that claim to go around this limitation, but none of them is supported, so proceed with care!

SharePoint Pitfalls: Save Publishing Site as Template Option Missing

If you want to save a publishing site as a template, so that it can be used to create other sites, you may find it surprising that the option is missing from the site settings page:

image

I don’t know exactly why, but publishing sites hide this option, however, it’s not difficult to get around it: just navigate to /_layouts/15/savetmpl.aspx. But, wait, what if you get this?

image

Easy, easy. Open your site in SharePoint Designer, click Site Options:

image

and change the value of property SaveSiteAsTemplateEnabled from false to true:

image

And now you will be able to access savetmpl.aspx and save your site.

SharePoint Pitfalls: Creating a Visual Studio Project Without SharePoint Locally Installed

This is the first on a (huge) collection of posts on SharePoint pitfalls. Hope you enjoy, and, please, do send me your feedback!

If you do not have SharePoint locally installed, Visual Studio will not let you create a SharePoint project:

sp-not-installed

Fortunately, it is easy to go around this in two simple steps:

  1. RegistryIn the Registry, add the following key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\15.0\SharePoint

    In it, add two string values: Location=C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ and SharePoint=Installed

  2. Global Assembly CacheCopy the following assemblies from a machine where SharePoint is installed (C:\Windows\Microsoft.NET\Assembly\GAC_32) into your development machine and add them to the GAC (gacutil):
    • Microsoft.SharePoint.dll
    • Microsoft.SharePoint.Client.dll
    • Microsoft.SharePoint.Client.Publishing.dll
    • Microsoft.SharePoint.Client.Runtime.dll
    • Microsoft.SharePoint.Client.ServerRuntime.dll
    • Microsoft.SharePoint.Library.dll
    • Microsoft.SharePoint.Linq.dll
    • Microsoft.SharePoint.Portal.dll
    • Microsoft.SharePoint.Publishing.dll

    Of course, you may need others as well.

Enjoy!

SharePoint Reference Document Updated

Just updated my SharePoint reference document Excel (references: here and here) with the default content placeholders.

It now contains:

  • Fields: all the built-in SharePoint field types;
  • Content types;
  • List templates;
  • Site templates;
  • SPDataSource returned fields;
  • List fields;
  • Document library fields;
  • Search content classes;
  • Content placeholders in default master pages.

If you have any corrections or if I missed something, please let me know!

SharePoint Filter Web Part

SharePoint includes a couple of useful web parts that can be used to filter other web parts. The problem is, they are only available in the Enterprise edition. It is easy, however, to build our own filters.

The key interface here is ITransformableFilterValues; it defines the contract for passing one or more values into another web part for the purpose of filtering it. Another useful interface is IDefaultFilterValue, which, guess what, defines a default value to be returned in case no one exists to be returned. Let’s see an example that retrieves values from the query string:

public class QueryStringFilter : WebPart, ITransformableFilterValues, IDefaultFilterValue

{

    public QueryStringFilter()

    {

        this.ChromeType = PartChromeType.None;

        this.Hidden = true;

        this.MultiValueHandling = MultiValueHandling.First;

        this.MultiValueSeparator = String.Empty;

    }

 

    [WebDisplayName("Default Value")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Default Value")]

    [DefaultValue("")]

    [Description("")]

    [Personalizable(PersonalizationScope.Shared)]

    public String DefaultValue { get; set; }

 

    [WebDisplayName("Allow All Value")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Allow All Value")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue(false)]

    [Description("")]

    public Boolean AllowAllValue { get; set; }

 

    [WebDisplayName("Allow Empty Value")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Allow Empty Value")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue(false)]

    [Description("")]

    public Boolean AllowEmptyValue { get; set; }

 

    [WebDisplayName("Allow Multiple Values")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Allow Multiple Values")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue(false)]

    [Description("")]

    public Boolean AllowMultipleValues { get; set; }

 

    [WebDisplayName("Query String Parameter Name")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Query String Parameter Name")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue("")]

    [Description("")]

    public String ParameterName { get; set; }

 

    [WebDisplayName("Multi Value Handling")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Multi Value Handling")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue(MultiValueHandling.First)]

    [Description("")]

    public MultiValueHandling MultiValueHandling { get; set; }

 

    [WebDisplayName("Multi Value Separator")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Multi Value Separator")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue("")]

    [Description("")]

    public String MultiValueSeparator { get; set; }

 

    ReadOnlyCollection<String> ITransformableFilterValues.ParameterValues

    {

        get

        {

            var list = new List<String>();

 

            if (String.IsNullOrWhiteSpace(this.ParameterName) == false)

            {

                if (this.AllowMultipleValues == false)

                {

                    list.Add(this.Context.Request.QueryString[this.ParameterName]);

                }

                else

                {

                    var index =

                        Array.IndexOf(

                            this.Context.Request.QueryString.AllKeys.Select(x => (x ?? String.Empty).ToLowerInvariant()).ToArray(),

                            this.ParameterName.ToLowerInvariant());

 

                    if (index >= 0)

                    {

                        if (this.MultiValueHandling == MultiValueHandling.First)

                        {

                            list.Add(this.Context.Request.QueryString.GetValues(index).First());

                        }

                        else if (this.MultiValueHandling == MultiValueHandling.All)

                        {

                            list.AddRange(this.Context.Request.QueryString.GetValues(index));

                        }

                        else

                        {

                            list.Add(String.Join(this.MultiValueSeparator, this.Context.Request.QueryString.GetValues(index)));

                        }

                    }

                }

 

                if (list.Count == 0)

                {

                    if (String.IsNullOrWhiteSpace(this.DefaultValue) == false)

                    {

                        list.Add(this.DefaultValue);

                    }

                    else

                    {

                        if (this.AllowAllValue == false)

                        {

                            if (this.AllowEmptyValue == true)

                            {

                                list.Add(String.Empty);

                            }

                        }

                        else

                        {

                            list.Add(null);

                        }

                    }

                }

            }

 

            return new ReadOnlyCollection<String>(list);

        }

    }

 

    [ConnectionProvider("Query String Filter", "ITransformableFilterValues", AllowsMultipleConnections = true)]

    public ITransformableFilterValues GetFilterValues()

    {

        return this;

    }

}

There are a couple of public properties:

  • ParameterName: the query string key whose value is to be returned;
  • DefaultValue: the default value to be returned, in case the query string does not contain a value for the ParameterName key;
  • AllowAllValue: whether to allow the all (null) value;
  • AllowEmptyValue: whether to allow the empty (“”) value;
  • AllowMultipleValues: whether to allow multiple values or not;
  • MultiValueHandling: what to do if multiple values are found for the ParameterName key: select the first only, return all or combine them all into one;
  • MultiValueSeparator: the separator for combining multiple values.

The web part is invisible and will only show the chrome when the page is in edit mode. After you add it to a page, you can add a connection of type filter to another web part and select the field on the other web part that you want to filter by. The actual

ITransformableFilterValues implementation is returned by the GetFilterValues method, which is marked with the ASP.NET ConnectionProvider attribute so as to make it a connection provider, and can feed several other web parts. The logic inside ParameterValues is a bit tricky because of the AllowAllValue and AllowEmptyValue properties but I think you’ll have no problems following it.

You would normally apply a filter using the browser interface, but you can also do it in markup:

<WebPartPages:SPProxyWebPartManager runat="server">

    <SPWebPartConnections>

        <WebPartPages:SPWebPartConnection ConsumerConnectionPointID="DFWP Filter Consumer ID" ConsumerID="listViewWebPart" ProviderConnectionPointID="ITransformableFilterValues" ProviderID="queryStringFilterWebPart">

            <WebPartPages:TransformableFilterValuesToParametersTransformer ConsumerFieldNames="LinkTitle" ProviderFieldNames="TaskName"/>

        </WebPartPages:SPWebPartConnection>

    </SPWebPartConnections>

</WebPartPages:SPProxyWebPartManager>

In this example, I am binding the LinkTitle field of a list view web part to the TaskName query string parameter provided by the QueryStringFilter web part. If the query string contains a TaskName parameter that matches the list view’s LinkTitle, it will show these records.

SharePoint Reference Document

I updated the reference document for SharePoint that I first mentioned in a previous post.

Right now, I have the following lists:

  • Fields: all the built-in SharePoint field types;
  • Content types;
  • List templates;
  • Site templates;
  • SPDataSource returned fields;
  • List fields;
  • Document library fields;
  • Search content classes.

You can find it in Excel format here: https://mscblogs.blob.core.windows.net/media/ricardoperes/Documents/Reference.xlsx.

If you spot anything wrong, please let me know!

Querying SharePoint

Introduction

SharePoint, being a content management system, of course, offers a couple of ways to query its contents programmatically. Here we will explore some of these options.

Web Parts

First, of course, there are the web parts. These allow us to configure queries visually on a page. The most important web parts are:

ContentByQueryWebPart: use this for simple queries that do not return much contents, on a single site collection. Can be used in SharePoint Online. Can only be customized through XSLT.

ContentBySearchWebPart (introduced in SharePoint 2013): more powerful, but does not exist in SharePoint Online. Can handle more complex queries that can span multiple site collections and multiple levels of sorting. Can only be customized through HTML and JavaScript templates. Results can be refined.

XsltListViewWebPart/DataFormWebPart: can be used to view of a specific list. The display can be totally configured. XsltListViewWebPart is a bit more powerful.

ListViewByQuery: requires that you pass an SPList and a SPQuery instance. Can only display pre-defined views.

APIs

There are several APIs for querying, either directly or using the search index.

SPQuery: can only be applied to a single list:

var query = new SPQuery();

query.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>Bla Bla</Value></Eq></Where>";

 

var list = site.RootWeb.Lists["Some List"];

 

var table = list.GetItems(query).GetDataTable();

SPSiteDataQuery: can be applied to several lists across a site collection. Has some issues, for example, won’t return values for multi-value fields:

var query = new SPSiteDataQuery();

query.Lists = "<List Name='Tasks'/>";

query.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>Bla Bla</Value></Eq></Where>";

query.Webs = "<Web Scope='SiteCollection'/>";

 

var table = site.RootWeb.GetSiteData(query);

KeywordQuery: uses the search index to search for keywords, so it requires that contents are indexed beforehand, and the search service is functioning:

using (var query = new KeywordQuery(site))

{

    query.QueryText = "Bla Bla";

    query.ResultsProvider = SearchProvider.Default;

 

    var searchExecutor = new SearchExecutor();

 

    var resultTableCollection = searchExecutor.ExecuteQuery(query);

 

    var searchResult = resultTableCollection.Filter("TableType", KnownTableTypes.RelevantResults).Single();

 

    var table = new DataTable();

    table.TableName = "Result";

    table.Load(searchResult, LoadOption.OverwriteChanges);

}

FullTextSqlQuery: uses SharePoint Search SQL to execute queries, which makes it generally more powerful than KeywordQuery:

using (var query = new FullTextSqlQuery(site))

{

    query.QueryText = "SELECT * FROM scope() WHERE Title = 'Teste'";

    query.ResultsProvider = SearchProvider.Default;

 

    var searchExecutor = new SearchExecutor();

 

    var resultTableCollection = searchExecutor.ExecuteQuery(query);

 

    var searchResult = resultTableCollection.Filter("TableType", KnownTableTypes.RelevantResults).Single();

 

    var table = new DataTable();

    table.TableName = "Result";

    table.Load(searchResult, LoadOption.OverwriteChanges);

}

CrossListQueryInfo and CrossListQueryCache: performs queries in a single site collection but multiple sites, with optional audience targeting. CrossListQueryCache caches the results for a period of time:

var crossListQueryInfo = new CrossListQueryInfo();

crossListQueryInfo.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>Bla Bla</Value></Eq></Where>";

crossListQueryInfo.Lists = "<List Name='Some List'/>";

crossListQueryInfo.Webs = "<Webs Scope='SiteCollection' />";

crossListQueryInfo.UseCache = true;

 

var crossListQueryCache = new CrossListQueryCache(crossListQueryInfo);

 

var table = crossListQueryCache.GetSiteData(site.RootWeb);

Client KeywordQuery: this is the client counterpart to KeywordQuery. It uses the SharePoint Client Components, or Client Side Object Model (CSOM), which means, it accesses SharePoint through its web services. It is basically similar to the server KeywordQuery, but can be used from a remote machine:

var keywordQuery = new Microsoft.SharePoint.Client.Search.Query.KeywordQuery(ctx);

keywordQuery.QueryText = "SharePoint";

 

var searchExecutor = new Microsoft.SharePoint.Client.Search.Query.SearchExecutor(ctx);

 

var results = searchExecutor.ExecuteQuery(keywordQuery);

 

ctx.ExecuteQuery();

 

var searchResult = results.Value.Where(x => x.TableType == KnownTableTypes.RelevantResults.ToString()).Single();

Web Services

Search.asmx: SOAP web service that takes a SharePoint SQL query. Part of the SharePoint Foundation 2010 Web Services, now somewhat obsolete.

Lists.asmx: Another SOAP web service that can return and update items in a list.

ListData.svc: WCF Data Service REST/OData that can be used for querying (including OData queries) or modifying contents of lists.

SharePoint 2013 REST Services: new REST/OData web services introduced in SharePoint 2013. Includes _api/web, _api/search, _api/web/lists, for searches, web or list operations.

SharePoint Foundation RPC Protocol

The SharePoint Foundation RPC Protocol, now obsolete, this allowed querying, exporting contents and performing a number of other operations through the OWSSVR.DLL handler. Although almost unused nowadays, still offers the only out of the box way to, for example, export a list in XML format.

JavaScript

In SharePoint 2010 the JavaScript Side Object Model (JSOM) was introduced and in version 2013 it was enhanced. It is now possible to do anything that the SharePoint Client API allows.

Conclusion

You see, lots of ways to get contents from SharePoint, as usual. Make sure you chose the one that best suits your needs.

References

SharePoint 2013 .NET Server, CSOM, JSOM, and REST API index

When to use the Content Query Web Part or the Content Search Web Part in SharePoint

Choose the right API set in SharePoint 2013

Use OData query operations in SharePoint REST requests

SharePoint Server 2013 Client Components SDK

SharePoint Search SQL Syntax Reference