Friday, April 30, 2010

Determine whether SPWeb inherits AlternateCssUrl from parent site or not programmatically

Sometimes we need to programmatically determine whether SPWeb has its own alternate css or inherits alternate css from its parent site. In UI we can find this option “Inherit Alternate CSS URL from parent of this site”  in Site Settings > Master page (it is available for publishing sites only):

image

If we will look at SPWeb class we will find that it has public property AlternateCssUrl of string type:

   1: public class SPWeb : IDisposable, ISecurableObject
   2: {
   3:     ...
   4:     public string AlternateCssUrl
   5:     {
   6:         get { ... }
   7:         set { ... }
   8:     }
   9: }

As “Master page” page is available only for publishing sites, my first idea was to check PublishingWeb class for boolean property like InheritAlternateCssUrl (similiar to InheritGlobalNavigation and InheritCurrentNavigation). But there is no such property in PublishingWeb. Instead, it also has AlternateCssUrl property as SPWeb. But instead of string type PublishingWeb . AlternateCssUrl property has InheritableStringProperty type:

   1: public sealed class PublishingWeb
   2: {
   3:     ...
   4:     public InheritableStringProperty AlternateCssUrl
   5:     {
   6:         get { ... }
   7:     }
   8: }

In turn InheritableStringProperty class has boolean IsInheriting property. So in order to determine whether web site inherits alternate css URL from parent site or not we should obtain PublishingWeb object and check its AlternateCssUrl.IsInheriting property:

   1: SPWeb web = ...;
   2:  
   3: var publishingWeb = PublishingWeb.GetPublishingWeb(web);
   4:  
   5: if (publishingWeb.AlternateCssUrl.IsInheriting)
   6: {
   7:     // alternate css is inherited
   8: }
   9: else
  10: {
  11:     // alternate css is not inherited
  12: }

Using this technique you will be able to determine alternate css inheritance programmatically.

Sunday, April 11, 2010

Change current UI locale using query string parameter in Sharepoint

When we need to develop multilanguage solution for Sharepoint the common approach is to keep all language specific resources in resx files (I don’t mention site variations here as they are out of scope of this post). In this case it is useful to have quick testing tool which will allow us to see how page or user control looks like using different locales. In this post I will describe how to implement custom http module which will allow to change current locale dynamically based on query string “lcid” parameter.

In my previous posts (here and here) about localization I described how Sharepoint sets locale of current thread based on locale of requested SPWeb. Summary of these posts: when user requests application _layout page – Sharepoint sets locale in SPRequestModule.PreRequestExecuteAppHandler; when user requests publishing page then Sharepoint initializes current thread locale in earlier steps in SPWeb.InitWeb() method.

The main idea – is to override locale initialized by Sharepoint by our locale using “lcid” query string parameter. From 2 mentioned places – the later one is SPRequestModule.PreRequestExecuteAppHandler. So we need to implement http handler and subscribe to PreRequestHandlerExecute event of http application. Also our custom http handler should be defined after SPRequestModule in <httpModules> section (it is required that SPRequestModule is the 1st module in Sharepoint) so its handler of PreRequestHandlerExecute  will be executed after SPRequestModule.PreRequestExecuteAppHandler and will override locale set by Sharepoint.

The code of the http module is quite simple:

   1: public class ChangeLocaleModule : IHttpModule
   2: {
   3:     private const string KEY_CULTURE = "lcid";
   4:  
   5:     public void Init(HttpApplication application)
   6:     {
   7:         application.PreRequestHandlerExecute += ApplicationPreRequestHandlerExecute;
   8:     }
   9:  
  10:     private void ApplicationPreRequestHandlerExecute(object sender, EventArgs e)
  11:     {
  12:         var application = (HttpApplication)sender;
  13:         var context = application.Context;
  14:  
  15:         var requestedCulture = this.getRequestedCulture(context);
  16:  
  17:         SPUtility.SetThreadCulture(requestedCulture, requestedCulture);
  18:     }
  19:  
  20:     private CultureInfo getRequestedCulture(HttpContext context)
  21:     {
  22:         var cultureFromQueryString =
  23:             this.getCultureFromQueryStringOrNull(context.Request.QueryString);
  24:         if (cultureFromQueryString != null)
  25:         {
  26:             return cultureFromQueryString;
  27:         }
  28:  
  29:         return Thread.CurrentThread.CurrentUICulture;
  30:     }
  31:  
  32:     private CultureInfo getCultureFromQueryStringOrNull(
  33:         NameValueCollection queryString)
  34:     {
  35:         if (string.IsNullOrEmpty(queryString[KEY_CULTURE]))
  36:             return null;
  37:  
  38:         var ci = queryString[KEY_CULTURE];
  39:         int languageId;
  40:         if (!int.TryParse(ci, out languageId))
  41:             return null;
  42:  
  43:         var culture = CultureInfo.GetCultureInfo(languageId);
  44:         return culture;
  45:     }
  46:  
  47:     public void Dispose()
  48:     {
  49:     }
  50: }

This module reads value specified in “lcid” query string parameter and if it represents valid locale id – changes locale of current thread using SPUtility.SetThreadCulture(…) method.

In order to make it work you need to add this custom http module in your web.config:

   1: <httpModules>
   2:   <clear />
   3:   <add name="SPRequest" type="Microsoft.SharePoint.ApplicationRuntime.SPRequestModule,
   4:     Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
   5:     PublicKeyToken=71e9bce111e9429c" />
   6:   ...
   7:   <add name="ChangeLocaleModule" type="NamespaceName.ChangeLocaleModule,
   8:     AssemblyName" />
   9: </httpModules>

Now if you specify URL like http://example.com/_layouts/page.aspx – locale of http://example.com site will be used. But if you will append “lcid”query string parameter e.g. like this http://example.com/_layouts/page.aspx?lcid=1033 – then English locale (id = 1033) will be used to display content (of course you can use any valid locale id). It is not necessary to have single “lcid” query string param. You can use it with another parameters as well http://example.com/_layouts/page.aspx?param1=value1&lcid=1033.

Friday, April 2, 2010

Camlex.NET 2.0 for Sharepoint released

I’m glad to announce that next version of Camlex.NET project is released. About 2 months ago I introduced release of Camlex.NET on codeplex. During this time we received feedback from community and took into account applied developers needs when worked over 2.0 version. Yesterday we published Camlex.NET 2.0. In this post I will describe new features and differences from previous version.

1. Dynamic filtering conditions

This is the most excited feature added in 2.0 version. In every day Sharepoint development we often need to build CAML query based on predefined set of values. E.g. we need to search items which have Ids contained in array {1, 2, 3} (analog of IN operator in SQL), or we need to retrieve all items which contains one of the substrings (or all of them) in their Title { “hello”, “world” }. This was not easy with classical approach of building CAML queries based on strings. It was also not so easy with Camlex.NET 1.0: I showed example how to build dynamic expression based on set of values and pass it to Camlex in one of my previous posts. Nevertheless it required extra “infrastructure” work from developer. By infrastructure I mean those work which is not related directly with business task – to retrieve items which match some conditions. In version 2.0 we addressed this issue and added 2 additional methods in IQuery interface WhereAll and WhereAny (by analogy with All and Any methods in Linq):

   1: public interface IQuery
   2: {
   3:     IQuery Where(Expression<Func<SPListItem, bool>> expr);
   4:     IQuery WhereAll(IEnumerable<Expression<Func<SPListItem, bool>>> expressions);
   5:     IQuery WhereAny(IEnumerable<Expression<Func<SPListItem, bool>>> expressions);
   6:     ...
   7: }

Method Where exists from 1.0 version. I showed it here to compare signatures of new methods with old one. The main difference is that WhereAll and WhereAny methods receive list of lambda expressions (or more accurate – IEnumerable) in contrast to Where method which receives single lambda expression.

So what these methods do? As I said they were named by analogy with Linq methods. And by analogy with them WhereAll method constructs CAML query for retrieving those items which satisfy all conditions specified in argument. In other words WhereAll method contructs CAML query using <And> logical join (see And element). WhereAny method returns CAML query which can be used to retrieve items which satisfy at least one of specified conditions – it uses <Or> logical join (see Or element).

Lets see examples. Suppose that we need to retrieve all items which contain at least one of the values {“hello”, “greeting”, “hi”} in Title field. I.e. we need to use the following CAML query:

   1: <Where>
   2:   <Or>
   3:     <Or>
   4:       <Contains>
   5:         <FieldRef Name="Title" />
   6:         <Value Type="Text">hello</Value>
   7:       </Contains>
   8:       <Contains>
   9:         <FieldRef Name="Title" />
  10:         <Value Type="Text">greeting</Value>
  11:       </Contains>
  12:     </Or>
  13:     <Contains>
  14:       <FieldRef Name="Title" />
  15:       <Value Type="Text">hi</Value>
  16:     </Contains>
  17:   </Or>
  18: </Where>

That’s how it can be done in Camlex.NET 2.0:

   1: // list of tokens
   2: var tokens = new List<string> { "hello", "greeting", "hi" };
   3: var expressions = new List<Expression<Func<SPListItem, bool>>>();
   4:  
   5: // create lambda expression for each token in list
   6: foreach (string t in tokens)
   7: {
   8:     string token = t;
   9:     expressions.Add(x => ((string)x["Title"]).Contains(token));
  10: }
  11:  
  12: // prepare query
  13: string query = Camlex.Query().WhereAny(expressions).ToString();

So idea is quite simple – create list of lambda expressions and pass this list into Camlex. It will join expressions using And or Or joins – so it will have one big expression as result and pass it in old Where method which you familiar with from 1.0 version.

Notice that you can create various expressions – i.e. not necessary to create the same expression for each value. You can even provide different argument names in expressions – Camlex doesn’t restrict you to have the same argument names for all provided expressions. For example we need to select item which have ID = 1 and which Title = “Hello world”:

   1: <Where>
   2:   <And>
   3:     <Eq>
   4:       <FieldRef Name="ID" />
   5:       <Value Type="Integer">1</Value>
   6:     </Eq>
   7:     <Eq>
   8:       <FieldRef Name="Title" />
   9:       <Value Type="Text">Hello world</Value>
  10:     </Eq>
  11:   </And>
  12: </Where>

It can be done using the following code:

   1: var expressions = new List<Expression<Func<SPListItem, bool>>>();
   2: expressions.Add(x => (int)x["ID"] == 1);
   3: expressions.Add(y => (string)y["Title"] == "Hello world");
   4:  
   5: string query = Camlex.Query().WhereAll(expressions).ToString();

As you can see Camlex may successfully translate queries with different arguments names “x” and “y”.

2. Search by field Ids

Camlex 1.0 allowed only to search items using field Title. But often developers use field Id when prepare CAML query. In Camlex.NET 2.0 it is available now. Let see how to search items which have Title = “Hello world” using field Id of standard Title field:

   1: <Where>
   2:   <Eq>
   3:     <FieldRef ID="fa564e0f-0c70-4ab9-b863-0177e6ddd247" />
   4:     <Value Type="Text">Hello world</Value>
   5:   </Eq>
   6: </Where>

The following code produces this CAML query:

   1: string query = Camlex.Query().Where(
   2:     x => (string) x[SPBuiltInFieldId.Title] == "Hello world").ToString();

SPBuiltInFieldId.Title is OTB property of System.Guid type. You can also provide your own variable of Guid type in indexer.

One drawback is that with this feature we’ve lost drawback compatibility with 1.0 version. In previous version we used SPItem in all expressions which have only 2 indexers with int and string arguments. We changed SPItem –> SPListItem which has additional indexer with System.Guid parameter. It became a breaking change in 2.0 version.

3. Search by lookup id and lookup value

In CAML you can search items by lookup value and lookup id using undocumented LookupId attribute  of <FieldRef> element: see comments to FieldRef Element MSDN article. Now it is also available in Camlex.NET 2.0. For example there is “Status” lookup field in some list and we need to retrieve those items which have Status = “Completed”. We can do it either using lookup value:

   1: <Where>
   2:   <Eq>
   3:     <FieldRef Name="Status" />
   4:     <Value Type="Lookup">Completed</Value>
   5:   </Eq>
   6: </Where>

or using lookup id (suppose that look up item which has Title = “Completed” has ID = 1):

   1: <Where>
   2:   <Eq>
   3:     <FieldRef Name="Status" LookupId="True" />
   4:     <Value Type="Lookup">1</Value>
   5:   </Eq>
   6: </Where>

In order to do it in Camlex we introduced 2 new types for string-based syntax: DataTypes.LookupId and DataTypes.LookupValue. Previous DataTypes.Lookup was made internal (this is one more breaking change in 2.0 version). So CAML queries mentioned above can be created using the following code:

   1: // lookup value
   2: string query = Camlex.Query().Where(
   3:     x => x["Status"] == (DataTypes.LookupValue)"Completed").ToString();
   4:  
   5: // lookup id
   6: query = Camlex.Query().Where(
   7:     x => x["Status"] == (DataTypes.LookupId)"1").ToString();

4. Support for native System.Guid type for values

One more native type is supported in Camlex.NET 2.0: System.Guid. In previous version developers needed to cast variable of Guid type to string in order to specify Guid as value in CAML query ((DataTypes.Guid)val.ToString()). Now you can just pass Guid variable into expression:

   1: var guid = new Guid("a30bcb5a-c614-4dc0-8df0-8741e2aebfd9");
   2: string query = Camlex.Query().Where(x => (Guid)x["UniqueId"] == guid).ToString();

So with 2.0 you can specify Guid both in indexer and in value.

This was an overview of new features in Camlex.NET 2.0. Hope it will be useful in Sharepoint development and will allow developers to increase their productivity. Thanks to all guys who provide us feedback on Camlex (especially butaji, Olegas, mihailsamson and Dmitry Pichugin). We very appreciate your efforts and hope that you will provide us your feedback in the future as well. It allow us to make Camlex better!