Friday, June 25, 2010

Build dynamic CAML queries based on query string parameters

In this article I would like to show one of the practical usage of free and open source Camlex.NET library in real life scenarios of Sharepoint development. In various Sharepoint forums I often see questions where people ask how they achieve mentioned goal, i.e. how they can build dynamic CAML queries based on set of parameters specified e.g. in query strings. In most cases I answered by giving a link to Camlex.NET project on codeplex with some brief example. But with this I would like to have more descriptive blog post which will help people in their every-day work. This is one of the purpose of this post. Another purpose is to show how easy such tasks can be achieved with Camlex.NET – because this library was created exactly for simplifying dynamic CAML queries building (see also my previous articles which cover dynamic CAML queries: http://sadomovalex.blogspot.com/2010/02/build-dynamic-expressions-for-caml.html (this article for Camlex.NET 1.0. One the moment of writing this post version 2.0 is current and recommended release which solves mentioned problems more simpler) and http://sadomovalex.blogspot.com/2010/04/camlexnet-20-for-sharepoint-released.html).

So lets start from the more or less common scenario: there is number of fields and we want to be able to retrieve items from Sharepoint list based on values specified in query string, e.g. if we will enter the following URL http://example.com/_layouts/Custom/Search.aspx?Title=Meeting we want to see only those items which have Title field equal to “Meeting”. Assume that all query string parameters are optional – i.e. if some parameter is not specified in query string search engine should ignore it. Also if several query string parameters are specified we will treat them as single boolean expression joined using logical And (&&), i.e. http://example.com/_layouts/Custom/Search.aspx?Title=Meeting&Description=Sharepoint means that we want to retrieve item which have Title=Meeting && Description=Sharepoint. This is quite common scenario where we applied useful pattern when dealing with web search: search is performed using HTTP GET request so users will be able to bookmark search result in their browsers (that’s how most popular search engines works also). Note that in real life you probably want to join conditions using logical Or (||) – it is not problem also.

At first download Camlex.NET assembly from http://camlex.codeplex.com, and install Camlex.NET.dll into GAC. Now you are ready to use Camlex.NET library in your project. For test purposes I created simple Sharepoint list “TestList” based on Custom list template and added one single line text field Description in addition to existing Title field. And then I added several test items in it. In order to test results I created new application _layouts page Search.aspx in 12/template/layouts/custom folder with included server code (application _layouts pages are good tool for testing because they are compiled on the fly and don’t require reinstall assemblies in GAC with app pool recycling). I will provide full code of Search.aspx page, but the real work is only 2 lines of code – the rest is just infrastructure stuff:

   1: <%@ Page Language="C#" %>
   2: <%@ Assembly Name="Camlex.NET, Version=2.0.2.0, Culture=neutral,
   3: PublicKeyToken=831792d54d5285b7" %>
   4: <%@ Import Namespace="CamlexNET" %>
   5: <%@ Import Namespace="System.Linq" %>
   6: <%@ Import Namespace="System.Linq.Expressions" %>
   7: <%@ Import Namespace="Microsoft.SharePoint" %>
   8: <%@ Import Namespace="System.Text" %>
   9:  
  10: <html xmlns="http://www.w3.org/1999/xhtml" >
  11: <head>
  12:     <title>Camlex.NET search example</title>
  13: </head>
  14: <body>
  15:     <form id="form1" runat="server">
  16:     <script runat="server">
   1:  
   2:         protected override void OnLoad(EventArgs e)
   3:         {
   4:             var queryString = HttpContext.Current.Request.QueryString;
   5:  
   6:             if (queryString.Count == 0)
   7:                 return;
   8:                 
   9:             var conditions =
  10:                 queryString.AllKeys.Select<string, Expression<Func<SPListItem, bool>>>(
  11:                     field => x => (string) x[field] == queryString[field]);
  12:             string queryText = Camlex.Query().WhereAll(conditions).ToString(false);
  13:  
  14:  
  15:             using (var site = new SPSite("http://example.com"))
  16:             {
  17:                 using (var web = site.OpenWeb())
  18:                 {
  19:                     var list = web.Lists["TestList"];
  20:  
  21:                     var query = new SPQuery { Query = queryText };
  22:                     var items = list.GetItems(query);
  23:  
  24:                     showListItems(items);
  25:                 }
  26:             }
  27:         }
  28:         
  29:         private void showListItems(SPListItemCollection items)
  30:         {
  31:             var sb = new StringBuilder();
  32:             foreach (SPListItem item in items)
  33:             {
  34:                 sb.AppendFormat("{0}<br/>", item.Title);
  35:             }
  36:             this.lit.Text = sb.ToString();
  37:         }
  38:     
</script>
  17:   
  18:       <asp:Literal ID="lit" runat="server" />
  19:  
  20:     </div>
  21:   </form>
  22: </body>
  23: </html>

Note the following lines of code:

   1: var conditions =
   2:     queryString.AllKeys.Select<string, Expression<Func<SPListItem, bool>>>(
   3:         field => x => (string) x[field] == queryString[field]);
   4: string queryText = Camlex.Query().WhereAll(conditions).ToString(false);

All work is done in these lines: at first we construct list of expressions based on query string parameters (i.e. we build expression tree representation for each key value pair in query string) and then we call WhereAll() method of Camlex.NET library which joins expressions using logical And (&&). So when we specify URL http://example.com/_layouts/Custom/Search.aspx?Title=Meeting&Description=Sharepoint Camlex.NET will build the following CAML query:

   1: <Where>
   2:   <And>
   3:     <Eq>
   4:       <FieldRef Name="Title" />
   5:       <Value Type="Text">Meeting</Value>
   6:     </Eq>
   7:     <Eq>
   8:       <FieldRef Name="Description" />
   9:       <Value Type="Text">Sharepoint</Value>
  10:     </Eq>
  11:   </And>
  12: </Where>

It is scallable code, i.e. if we will add new field (Foo) and append it to URL: http://example.com/_layouts/Custom/Search.aspx?Title=Meeting&Description=Sharepoint&Foo=bar then Camlex.NET will automatically build valid CAML query with this new condition:

   1: <Where>
   2:   <And>
   3:     <And>
   4:       <Eq>
   5:         <FieldRef Name="Title" />
   6:         <Value Type="Text">Meeting</Value>
   7:       </Eq>
   8:       <Eq>
   9:         <FieldRef Name="Description" />
  10:         <Value Type="Text">Sharepoint</Value>
  11:       </Eq>
  12:     </And>
  13:     <Eq>
  14:       <FieldRef Name="Foo" />
  15:       <Value Type="Text">bar</Value>
  16:     </Eq>
  17:   </And>
  18: </Where>

That’s how building of dynamic CAML queries can be achieved with Camlex.NET library. In the future posts I will show how to use another data types (i.e. not just strings) and various operations (like >, <, Contains and other).

Thursday, June 24, 2010

Fix “Console Configuration File Error: XML Exception: Access is denied” in Sharepoint

This error can occur on publishing sites in Sharepoint when you have site collection and subsites with unique permissions. The following fix helped me:

  1. Go to Site Settings of the root web site of the site collection and select Master pages and page layouts
  2. Go to Document library settings inside _catalogs/masterpage doclib and click Permissions for this document library
  3. Grant “Read” permissions for “NT AUTHORITY\authenticated users” (use “Give users permission directly” option when you will grant permissions. I had problems when tried to add “NT AUTHORITY\authenticated users” to Sharepoint group – this is another available option)

After that users will be able to successfully login into subsite.

Thursday, June 17, 2010

How to determine when users accounts were created in Active Directory

If you use Active Directory as users storage for your Sharepoint site with LdapMembershipProvider and LdapRoleProvider then sometimes it will be useful to know when user or group account was created in your AD. It can be achived by using LDIFDE command line utility:

   1: ldifde -d ou=myusers,dc=example,dc=com -l whencreated -p onelevel
   2: -r "(ObjectCategory=user)" -f log.txt

You should specify your own root LDAP search path after “-d” param. It will list all your users and date when they were created.

ReSharper live template for Sharepoint utilities

In everyday Sharepoint development we often need to create many console utilities for various maintenance tasks which should be performed on production or staging environments (e.g. install bugfix on living Sharepoint site, trace some information from production site, etc). Before to run program on remote server we test it on our development environment and then copy it to remote server and run it there with different site URL (in most cases there are different URLs on production and dev environment). So we have to write the following code each time we need to create such utility:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         if (args.Length != 1)
   6:         {
   7:             Console.WriteLine("Usage: exe <site_collection_url>");
   8:             return;
   9:         }
  10:  
  11:         using (var site = new SPSite(args[0]))
  12:         {
  13:             using (var web = site.OpenWeb())
  14:             {
  15:                 // code goes here
  16:             }
  17:         }
  18:  
  19:     }
  20: }

It is very annoying. So I created ReSharper live template which slightly simplify this task. Just add the following code to User Template (in Visual Studio menu ReSharper > Live Templates > User Templates > New Template):

   1: if (args.Length != 1)
   2: {
   3:     Console.WriteLine("Usage: exe <site_collection_url>");
   4:     return;
   5: }
   6:  
   7: using (var site = new SPSite($site$))
   8: {
   9:     using (var web = site.OpenWeb($web$))
  10:     {
  11:     }
  12: }

and specify “Shortcut” for it (e.g. “spsite”). Now you are able to create the above code by typing “spsite”. ReSharper will offer spsite template for you (like it does for “for” or “foreach” templates):

image

After selecting of “spsite” template ReSharper will add this code for you and it will allow to specify parameters in SPSite() constructor and OpenWeb() method by clicking Enter key (which is more convenient in comparison with VS code snippets):

image

This is a little automation which slightly simplifies creation of console utilities for Sharepoint.

Friday, June 4, 2010

Restore publishing site via Sharepoint Designer into root web site of site collection

In one of my previous posts I summarized ways to copy sites in Sharepoint. In this post I will describe how to copy site into root web site of site collection (SPSite.RootWeb). Sharepoint Designer allows to backup and restore sites with its subsites. In order to do it you need perform the following steps:

  1. Open site you want to backup in Sharepoint Designer
  2. Select Site > Administration > Backup Web Site and check "Include subsites in archive"
  3. Select location for backup file (.cmp)

In order to restore backuped site you need:

  1. Create empty destination web site (in Sharepoint Designer open site to which subsites collection you want to restore backuped site and select File > New > Web Site tab > General > Empty Web Site)
  2. With opened empty web site in Sharepoint Designer select Site >Administration > Restore Web Site and select backup file

It is important that destination site should be empty. In other case Sharepoint Designer will not restore site with message that destination site is not empty. But what if we need to restore site into root site of site collection? The problem is that you can’t create site collection without root web site via UI. Although it is possible via stsadm –o createsite command without sitetemplate parameter (see http://technet.microsoft.com/en-us/library/cc262594(office.12).aspx). But there is possibility to create site collection with “Empty Site” template for root web site in Central Administration. Although in this case root web site will not be empty Sharepoint Designer will allow to restore backuped site in such destination site.

Unfortunately if your backuped site is publishing site (site with activated publishing infrastructure features – e.g. Publishing Portal) Sharepoint Designer will not activate publishing infrastructure on restored site in this case. So event if your web site was restored successfully you will not have functionality added by publishing infrastructure (e.g. Site Master Page Settings). You can try to activate it after restoring in Site Settings > Manage Site Features > Office SharePoint Server Publishing Infrastructure but it can fail or can lead to unpredictable issues (as you already used some publishing artefacts like Pages doclib during restoring).

So I suggest to do one more step after you created new site collection with “Empty Site” template for root web site: activate Office SharePoint Server Publishing Infrastructure on this empty site. And only after this restore site in Sharepoint Designer. You can’t create site collection with some existing publishing sites templates for root web site as Sharepoint Designer will not allow to restore site in it with message that destination site is not empty.

After performed steps you will be able to restore publishing site into root web site of site collection via Sharepoint Designer.

Update 2010-06-09: restoring of regular web site (SPWeb) into root web site of site collection is not enough. There are many other issues which should be fixed after that (e.g. activate the same features of site scope, fix navigation, etc). Check this article of Gary Lapointe for details: Convert a Sub-site to a Site Collection. Shortly you should run the following command on your server after web site was restored:

stsadm -o gl-repairsitecollectionimportedfromsubsite –sourceurl http://example.com/source -targeturl http://example.com/target

It should fix most of problems. But several issues may exists after it. For example, you should manually fix DataFormWebPart and ContentByQueryWebPart web parts in order to retarget them on new location.

Tuesday, June 1, 2010

How Sharepoint determines whether or not to show Anonymous Access menu item in Site Permissions page

As you know in Sharepoint it is possible to configure anonymous access to your site. In order to do it you should login under some administrative account into appropriate web application zone, which allows anonymous access, and go to Site Settings > People and groups > Site Permissions > Settings > Anonymous Access and select what option is suitable for you:

  • Entire Web site
  • Lists and libraries
  • Nothing

But sometimes you will not find Anonymous Access option in menu as it is hidden because of some reasons. In forums people say that in order to make it visible you should allow anonymous access for your web application. I want to go a little deeper and show underlying mechanism used by Sharepoint when it determines whether or not to show Anonymous Access item in menu. It may help with troubleshooting when you investigate issues with anonymous access.

Anonymous Access menu item is shown in user.aspx page which is located in 12/template/layouts folder on file system:

   1: <SharePoint:MenuItemTemplate id="MenuItemAnonAccess" runat="server"
   2:         Text="<%$Resources:wss,people_anonaccess%>"
   3:         Description="<%$Resources:wss,people_anonaccessdesc_site%>"
   4:         Visible=false
   5:         MenuGroupId="300"
   6:         Sequence="300"
   7:         />

As you can see it has initially Visible = false. Lets go inside codebehind class of user.aspx page – UserRoles class located in Microsoft.SharePoint.ApplicationPages assembly (this assembly is not located in GAC – it is located in _app_bin folder inside inetpub folder for each Sharepoint site). So how Visibility of MenuItemAnonAccess item is controlled? It becomes clear after analyzing of UserRoles class in Reflector:

   1: protected override void InitPage()
   2: {
   3:     ...
   4:     this.MenuItemAnonAccess.Visible =
   5: base.Site.IISAllowsAnonymous && SPSecurity.WebConfigAllowsAnonymous;
   6:     ...
   7: }

So Anonymous Access item is appeared when the following conditions are true: anonymous access is enabled in IIS and web.config is configured to allow anonymous access. These conditions are not the same – sometimes they may differ (i.e. anonymous access may be configured in web.config, but not allowed in IIS). You can control anonymous access during creation or extending of web application:

image

Or for existing web applications in Central Administration > Application Management > Authentication Providers and click on provider name:

image

In order to test these settings I created test application _layouts page which shows these settings for the current site:

   1: <%@ Page Language="C#" %>
   2: <%@ Import Namespace="Microsoft.SharePoint" %>
   3:  
   4: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   5:  
   6: <html xmlns="http://www.w3.org/1999/xhtml" >
   7: <head id="Head1" runat="server">
   8:   <title>test</title>
   9: </head>
  10: <body style="font-family:Arial">
  11:   <form id="form1" runat="server">
  12:     <div>
  13:       <%
   1:  
   2:         this.lbl1.Text = SPContext.Current.Site.IISAllowsAnonymous.ToString();
   3:         this.lbl2.Text = SPSecurity.WebConfigAllowsAnonymous.ToString();
   4:        
%>
  14:  
  15:       IISAllowsAnonymous:&nbsp;<asp:Label ID="lbl1" runat="server" />
  16:       <br />
  17:       <br />
  18:       <br />
  19:       WebConfigAllowsAnonymous:&nbsp;<asp:Label ID="lbl2" runat="server" />
  20:  
  21:     </div>
  22:   </form>
  23: </body>
  24: </html>

In order to use it copy paste content into test.aspx page and copy this page into 12/template/layouts folder on file system. After that you will be able to see anonymous settings if you will enter the following URL into browser: http://example.com/_layouts/test.aspx. Hope it will be useful.