Tuesday, July 30, 2013

Linq 2 Sharepoint works across site collections in Sharepoint 2013

Do you remember that one of the limitation of Linq 2 Sharepoint 2010 was that it didn’t support cross site collections. Developers found workaround for this limitation, see e.g. here: Using LINQ to SharePoint between site collections. But the fact was that Linq 2 Sharepoint didn’t support cross site collections queries OTB. Microsoft.SharePoint.Linq.DataContext class had only one constructor which receives url of the target web site.

   1:  public DataContext(string requestUrl)
   2:  {
   3:      ...
   4:  }

The good news is that in Sharepoint 2013 new overridden constructor was added to the DataContext class with 2nd boolean parameter crossSiteCollection. So if you need context working with different site collection you need to pass true in this parameter:

   1:  public DataContext(string requestUrl, bool crossSiteCollection)
   2:  {
   3:      ...
   4:  }

I didn’t find any additional parameters in SPMetal parameters schema, so SPMetal still generates strongly typed classes for current site collection only. But it is quite easy to fix: open file with generated data context and find where inheritor of DataContext class calls base constructor:

   1:  public partial class MyDataContext : Microsoft.SharePoint.Linq.DataContext {
   2:      public MyDataContext(string requestUrl) : 
   3:          base(requestUrl) {
   4:          ...
   5:      }
   6:      ...
   7:  }

In the example above this is the line 3. You need to add true to the base constructor call manually, so the final code will look like this:

   1:  public partial class MyDataContext : Microsoft.SharePoint.Linq.DataContext {
   2:      public MyDataContext(string requestUrl) : 
   3:          base(requestUrl, true) {
   4:          ...
   5:      }
   6:  }

After this you will be able to use MyDataContext in order to query data from the list from different site collection.

Sunday, July 28, 2013

User defined fields patterns and Sharepoint

Martin Fowler wrote interesting article about implementing user defined fields functionality: UserDefinedField. He summarized several patterns:

  • Variable state;
  • Pre-defined custom fields;
  • Dynamic schema.

Variable state means including a hashmap field for user-defined fields. With pre-defined custom fields you specify predefined fields like integer_1, integer_2, text_1… and then use only those which are needed. With dynamic schema you add the code which dynamically changes the schema of data storage (calls ALTER TABLE if it is SQL data storage).

As you probably know Sharepoint also allows users to construct own custom content types, add fields, specify field types, etc. It is interesting to note that it also uses Pre-defined custom fields pattern. E.g. there is AllUserData table (which stores the data of lists and doclibs) in the content database which contains a lot of columns:

  • Sharepoint 2007 – 193 columns;
  • Sharepoint 2010 – 192 columns;
  • Sharepoint 2013 – 4026 columns.

These columns look like bit1, bit2, …, datetime1, datetime2, …, float1, float2, …, int1, int2, …, etc. Now we have common pattern name used for this schema. It is also interesting to note the big grow of the columns in Sharepoint 2013 comparing with previous versions, which however didn’t change list and columns limits in Sharepoint 2013 comparing with 2010 (see Software boundaries and limits).

Saturday, July 27, 2013

Problem in KQL with date times and client object model in Sharepoint

Keyword query language (KQL) is the one of the basic ways to perform queries to the search index in Sharepoint 2013. When you use it e.g. in Content by search web parts (ContentBySearchWebPart) in order to show results based on specified parameters you may get the following error:

image

(We didn't understand your search terms. Make sure you're using the proper syntax).

Even stranger that the same page may work correctly (i.e. show search results) in one browser, but broken in another. In our case Content by search web part worked in IE10, but didn’t work in FF and Chrome. So what’s the reason of this behavior?

First of all let’s check ULS (as always). In my case there were several interesting records:

QueryTemplateHelper: Query template '(PublishingDate=1/1/{QueryString.Year}..31/12/{QueryString.Year})' transformed to query text '(PublishingDate=1/1/2013..31/12/2013)'

It shows how query transformation component transformed the search query. In this example we use year specified in query string in order to show all news for specific year. Transformed query was the same for IE and FF, so the problem was somewhere else.

Next interesting line was:

ExecuteFlowInternal Flow:Microsoft.SharePointSearchProviderFlow Exception: Microsoft.Office.Server.Search.Query.QueryMalformedException: Query '(PublishingDate=1/1/2013..31/12/2013)' failed: syntax error at position 1, Unable to parse '31/12/2013' as an absolute datetime value

it appeared only in FF where query didn’t work. So the problem was in the datetime format: Sharepoint couldn’t parse datetime string in FF and Chrome. But why it happened? Very often such problems occur because of different locales used for parsing the string. I checked languages settings in IE10 (there is no separate setting for languages now in IE. It uses languages specified in OS) and FF: there was difference. In IE there was Russian and English (US) languages, while in FF there was only English (US). Accordingly in http requests fiddler showed the following Accept-Language headers in the browsers:

  • Accept-Language: ru,en-us – in IE
  • Accept-Language: en-us – in FF

When I added Russian language to FF, search queries started to work, but we couldn’t use this solution in production of course: site shouldn’t rely on the client’s browser language setting, it should work in all cases. So I started deeper investigation.

First of all in Sharepoint 2013 there is convenient way to test search queries from UI against particular search result source: go to Site settings > Search result sources > select appropriate result source and click Edit. Then click Launch query builder button:

image

In the opened window write your query and click Test query. Here is the result in FF:

image

If we will check what happens in fiddler when you click on the Test query button, we will find that Sharepoint makes request to the client object model end point to the following URL:

http://example.com//_vti_bin/client.svc/ProcessQuery

It returns response in JSON format. In case of FF it returned exception:

[
{
"SchemaVersion":"15.0.0.0","LibraryVersion":"15.0.4420.1017","ErrorInfo":null,"TraceCorrelationId":"9213339c-4c53-3023-9599-62e1b6279428"
},185,{
"IsNull":false
},193,{
"IsNull":false
},197,{
"IsNull":false
},205,{
"IsNull":false
},219,{
"IsNull":false
},220,{
"HasException":true,"ErrorInfo":{
"ErrorMessage":"We didn't understand your search terms. Make sure they're using proper syntax.","ErrorValue":null,"TraceCorrelationId":"9213339c-4c53-3023-9599-62e1b6279428","ErrorCode":-1,"ErrorTypeName":"Microsoft.Office.Server.Search.Query.QueryMalformedException"
}
}
]

And in IE it returned actual search results. I.e. it seems like that client object model uses browser language for some operations. For example it uses it for datetimes parsing when KQL contains datetimes. Don’t know why MS did it like this, but it often will be a problem because you don’t want to rely on client browsers settings, functionality should work regardless of it.

But how to fix the problem? First of all I tried different syntaxes for the range operator in KQL:

PublishingDate=1/1/2013..31/12/2013
PublishingDate=1.1.2013..31.12.2013
PublishingDate=1/1/2013..12/31/2013

All of them had similar problem: depend on the browser language. The I tried to add the following line to the web.config in order to disabled changing UI settings based on the browser language:

<globalization fileEncoding="utf-8" enableClientBasedCulture="false" />

(tried to change it both in site virtual folder and in 15\isapi folder which is mapped to _vti_bin virtual sub folder in Sharepoint sites).

I already started to think about hard fix: implement custom http module which will change Accept-Language header for requests to the client object model end point (client.svc), but then tried to use one more syntax for datetimes:

PublishingDate=2013/1/1..2013/12/31

And fortunately it worked regardless of the browser language:

image

When I changed the year in the query it correctly showed the news published in this changed year, so logic was correct.

Important summary from this post is that Sharepoint uses browser language settings in client object model which may lead to different problems. In the example above solution was quite easy and doesn’t have side effects which would affect other functionality, but it may appear in other cases. Notice it when you will work with search-driven sites in Sharepoint 2013.

Sunday, July 7, 2013

How to get friendly URL of the current page in Sharepoint programmatically

Friendly URLs is the new feature of Sharepoint 2013. It allows to show urls like http://example.com/products/ instead of http://example.com/products/pages/default.aspx in browser address bar, which is shorter and more user friendly. For developers it adds several challenges. I wrote about several of them already in the following posts (here, here and here). In web development in general it is often needed to get URL of the current page. When you use structural navigation and regular URLs it is quite easy:

   1: string url = HttpContext.Current.Request.Uri.OriginalString;

But how to get friendly URL of the current page when managed metadata navigation is used? The answer is TaxonomyNavigationContext class. It contains several properties which will help you to work with friendly URLs programmatically, e.g. HasFriendlyUrl and ResolvedDisplayUrl (all properties can be found here). Using this class you can get friendly URL using the following code:

   1: string url = "";
   2: if (TaxonomyNavigationContext.Current != null &&
   3:     TaxonomyNavigationContext.Current.HasFriendlyUrl)
   4: {
   5:     url = SPUtility.GetFullUrl(SPContext.Current.Site,
   6:         TaxonomyNavigationContext.Current.ResolvedDisplayUrl); 
   7: }

If you are on the page with real URL http://example.com/products-site/info-site/pages/default.aspx this code will return friendly URL (e.g. http://example.com/products/info) and you will be able to use it in your code.