Thursday, December 16, 2010

Use C# 3.0 features in application layout pages in Sharepoint

During everyday Sharepoint development we often use application layout pages (aspx pages which are located in 12/Templates/Layouts folder on file system of web server) for tracing and debugging. It is useful technique, as you can include server-side script directly into the page and show results on the page in context of the living site. Suppose that we have sub folder 12/Templates/Layouts/Test and we created simple aspx page here with the following code:

   1: <%@ Page Language="C#" %>
   2: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   3: <html xmlns="http://www.w3.org/1999/xhtml" >
   4: <head id="Head1" runat="server">
   5:   <title>Test</title>
   6: </head>
   7: <body>
   8:   <form id="form1" runat="server">
   9:     <%
   1:  int i = 1;
   2:        this.lbl.Text = i.ToString();
   3:     
%>
  10:     <asp:Label ID="lbl" runat="server" />
  11:   </form>
  12: </body>
  13: </html>

Look at the code inside <%%>. It is very simple example: it just assigns integer value to variable “i” and shows it in the label. In real life you will execute some meaningful code and trace its result in the label or in another control (e.g. display current user identity).

However it is annoying that by default you are limited with C# 2.0 features, and can’t use features available in C# 3.0 (see Overview of C# 3.0 for full list). E.g. if instead of “int i = 1;” you will write “var i = 1;” (i.e. if you will use Implicitly Typed Local Variables), then you will get the following error when you will try to access your page:

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS\test\test.aspx(9): error CS0246: The type or namespace name 'var' could not be found (are you missing a using directive or an assembly reference?)
   at System.Web.Compilation.AssemblyBuilder.Compile()
   at System.Web.Compilation.BuildProvidersCompiler.PerformBuild() 
   … 
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Recently my colleague showed me the tip how to avoid this issue (unfortunately he doesn’t blogging so he is not able to publish it by himself). So in order to enable C# 3.0 features in application layout pages you should create the following web.config file in your sub folder (12/Templates/Layouts/Test in our case):

   1: <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   2: <configuration>
   3: <system.codedom>
   4:   <compilers>
   5:     <compiler
   6:       language="c#;cs;csharp"
   7:       extension=".cs"
   8:       type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0,
   9:             Culture=neutral, PublicKeyToken=b77a5c561934e089"
  10:       warningLevel="4">
  11:       <providerOption name="CompilerVersion" value="v3.5"/>
  12:       <providerOption name="WarnAsError" value="false"/>
  13:     </compiler>
  14:   </compilers>
  15: </system.codedom>
  16: </configuration>

This web.config overrides compiler version (see <providerOption name="CompilerVersion" value="v3.5"/>) used by ASP.Net, so you will be able to write C# 3.0 code in your pages after that. It helped us to write application layouts page more effectively and I hope it will simplify your everyday Sharepoint development and debugging too.

Friday, December 3, 2010

Install Sharepoint designer 2007 and 2010 on single machine

Today I encountered with interesting problem: I needed to install both versions of Sharepoint Designer (2007 and 2010) on single machine. The problem was in architectural versions of the application. On my host I have Windows 7 x64 and Office Professional Plus 2010 x64 installed. Sharepoint Designer 2007 doesn’t have x64 version so I just installed available x86 version first (by going from earlier version 2007 to the most recent 2010 version). In opposite Sharepoint Designer 2010 exists for x86 and x64 architectures. As I have most of software for x64, I tried to install SPD 2010 x64 first. But during installation I encountered with the following error message:

image

I will provide also text here for search engines:

You cannot install the 64-bit version of Office 2010 because you have 32-bit Office products installed. These 32-bit products are not supported with 64-bit installations:

    Microsoft Office SharePoint Designer 2007

If you want to install 64-bit Office 2010, you must uninstall all 32-bit Office products first, and then run setup.exe in the x64 folder. If you want to install 32-bit Office 2010, close this Setup program. and then either go to the x86 folder at the root of your CD or DVD and run setup.exe, or get the 32-bit Office 2010 from the same place you purchased 64-bit Office 2010.

Quite interesting. Ok lets try to install SPD 2010 x86. No luck either:

image

You cannot install the 32-bit version of Office 2010 because you have 64-bit Office products installed. These 64-bit products are not supported with 32-bit installations:

    Microsoft Office Professional Plus 2010

If you want to install 32-bit Office 2010, you must uninstall all 64-bit Office products first, and then run setup.exe in the x86 folder. If you want to install 64-bit Office 2010, close this Setup program, and then either go to the x64 folder at the root of your CD or DVD and run setup.exe, or get the 64-bit Office 2010 from the same place you purchased 32-bit Office 2010.

So I’m not able to install SPD 2010 x64 because of SPD 2007 x86 is already installed and I can’t install SPD 2010 x86 because I have Office 2010 x64 installed. In order to proceed I uninstalled SPD 2007 and installed SPD 2010 x64 first. It was installed successfully in this time. And after this I successfully installed SPD 2007 x86. Now I have both SPD 2007 and 2010 running on the single machine. Hope it will help someone who will encounter the same issue.

Dynamic OrderBy CAML statements via Camlex.NET

Today new version of Camlex.NET was released. A new feature was added to it which is called dynamic OrderBy statements. Camlex.NET was created for Sharepoint developers and its purpose to simplify creation of CAML queries. As many other features dynamic OrderBy statements was initiated by discussion in Camlex project page on Codeplex, i.e. it is initiated by developers feedback (thanks to umike user for the feedback). So if you think that some feature will be useful for Camlex, don’t hesitate to contact us in discussions board. This is the end of little preface. Now lets say a few words about new feature.

In many real life scenarious when we need to create CAML query programmatically in compile time we don’t know what actual fields will be used for sorting. E.g. suppose that we have a grid view and it is bind to items’ collection which is retrieved from Sharepoint list using CAML. Users have possibility to sort grid view by several columns in ascending or descending order. So we can’t say what CAML query will be used in each particular case. For such scenarious new Camlex feature was implemented. It allows developers to build dynamic OrderBy statements using parameters provided e.g. from user input. In order to do it a new method was added to IQuery interface which receives list of expressions for OrderBy statement (each expression have the following syntax x => x[“Title”] or x => x[“Title”] as Camlex.Asc – see http://camlex.codeplex.com – Scenario 6. Query with sorting (OrderBy)):

   1: public interface IQuery
   2: {
   3:     ...
   4:     IQuery OrderBy(IEnumerable<Expression<Func<SPListItem, object>>> expressions);
   5: }

So now you can dynamically create list of expressions for OrderBy statement in runtime and pass it directly to Camlex as shown in the following example:

   1: var orderByList = new List<Expression<Func<SPListItem, object>>>();
   2: orderByList.Add(x => x["Title"] as Camlex.Asc);
   3: orderByList.Add(y => y["Date"] as Camlex.Desc);
   4:  
   5: var caml = Camlex.Query().OrderBy(orderByList).ToString();

It will produce the following CAML:

   1: <OrderBy>
   2:   <FieldRef Name="Title" Ascending="True" />
   3:   <FieldRef Name="Date" Ascending="False" />
   4: </OrderBy>

You can also specify single field for sorting by adding single element to the list. I hope this new feature will be useful in real life development scenarios.

Thursday, November 25, 2010

Build and deploy Sharepoint solutions using bat functions

Today for solution administration in Sharepoint we can use great opportunities of PowerShell. However there is interesting feature available in simple cmd (or bat) files which can be used in every day development – bat functions. Of course it is not so powerful as PowerShell, but is it very simple and doesn’t require any additional components in the system. We used this approach in several projects in conjunction with WSPBuilder and it proofed its reliability and value. If you don’t familiar with this concept you can check this link for example: Routines functions methods in .bat.

Assume that you have several class library projects (e.g. Project1 and Project2) in Visual Studio which are built into separate wsp packages using WSPBuilder. Project1 doesn’t contain web application specific resources and should be deployed globally. Project2 in contrast contains web application specific resources and should be deployed into particular web application. You need to automate build process. In order to do that you can use the following cmd file which uses bat functions technique:

   1: @cd ..
   2:  
   3: @call :install Project1 global
   4: @call :install Project2
   5:  
   6: @pause
   7: @exit
   8:  
   9: rem Makes wsp package, add it to solution store, deploys it and waits some time in order to allow sharepoint successfully install wsp
  10: :install
  11: @cd %1
  12:  
  13: @call :create_wsp %1
  14:  
  15: @call :add_solution %1
  16:  
  17:  
  18: @if "%2" == "global" (
  19:     @call :deploy_solution_globally %1
  20: ) else (
  21:     @call :deploy_solution_web_specific %1
  22: )
  23:  
  24: @call :delay 5
  25:  
  26: @cd ..
  27: @exit /b
  28:  
  29:  
  30: rem Creates wsp based on current directory
  31: :create_wsp
  32: wspbuilder -BuildWSP true -ResetWebServer true -TraceLevel Warning
  33: @exit /b
  34:  
  35:  
  36: rem Adds wsp file in the current directory to solution store
  37: :add_solution
  38: stsadm -o addsolution -filename %1.wsp
  39: @exit /b
  40:  
  41:  
  42: rem Deploys wsp from current directory to specific web applications (i.e. not globally)
  43: :deploy_solution_web_specific
  44: stsadm -o deploysolution -local -allowgac -allcontenturls -name %1.wsp
  45: @exit /b
  46:  
  47:  
  48: rem Deploys wsp from current directory globally
  49: :deploy_solution_globally
  50: stsadm -o deploysolution -local -allowgac -name %1.wsp
  51: @exit /b
  52:  
  53:  
  54: rem Delays execution
  55: :delay
  56: @echo Off
  57: PING -n %1 127.0.0.1>nul
  58: @echo On
  59: @exit /b

I added comments to the script so it is more or less self descriptive. Note that every function begins with colon “:” and ends with “@exit /b” command which returns execution to the caller. So we call “install” function for Project1 and Project2. What “install” function does? First of all it creates wsp package using wspbuilder (see “create_wsp” function). After that it adds solution to the solution store (“add_solution” function) and deploys solution to the server. Note that for Project1 we specified additional param “global” which is used in the code in order to determine how solutions should be deployed – globally (“deploy_solution_globally” function) or web application specific (“deploy_solution_web_specific”).

After solution is deployed “install” function waits some time in order to give Sharepoint time to proceed the request. Note how “delay” function is implemented: it uses “-n” parameter of well known “ping” command in order to wait specified amount of seconds

That’s how this feature can be used in Sharepoint development to automate simple scenarios using common cmd files. Of course for more complicated things you should prefer PowerShell, however for many real world cases described technique can be useful as well.

Sunday, November 14, 2010

Retain object identity during export and import of subsites in Sharepoint into different site collection hierarchy location

As you know Sharepoint object model contains several classes which allow to perform export and import of sites. Most important of them are SPExport and SPImport. If you don’t familiar with content deployment API then before to read this article I highly recommend you to read series of articles of Stefan Goßner: Deep Dive into the SharePoint Content Deployment and Migration API.

Mentioned classes allow you to export content of single SPWeb as well as whole site collection and import it into another location (probably on another server). You can find list of another features of content deployment here. In this post I want to describe problem with retaining objects identities during import of subsites into different location rather they had in source site.

When site is exported we can choose from 2 alternatives for import:

  • import this site with retaining object identities;
  • import site without retaining object ids.

With first method all child objects of exported site like lists, doclibs, list items, etc. will have the same GUIDs and location after import as they had in source site. This method has many advantages as you don’t need to make extra work after import. E.g. you don’t have a headache with retargeting of content by query web parts (if you don’t familiar with this problem I recommend you to read this post of Gary Lapointe: Retarget Content Query Web Part). Also you won’t have problems with lookup fields retargeting, etc. But with advantages this method has limitation: you should import exported subsite into the same URL location on target site relative with site collection URL. I.e. if your site2 was located at the following URL http://example.com/sites/sitecollection/site1/site2, then it should be imported to the same location on target site collection related to site collection root site: http://example1.com/site1/site2, but it can be imported for example to http://example1.com/site2 (where http://example1.com is root site of site collection).

Second method can be used when you need to copy objects into another site collection hierarchy (exactly 2nd method is used in site manager copy operations and in stsadm import operation). If you will open stsadm utility in reflector and see SPImportOperation class you will see:

   1: public override void Run(StringDictionary keyValues)
   2: {
   3:     ...
   4:     SPImportSettings settings;
   5:     ...
   6:     settings.RetainObjectIdentity = false;
   7:     ...
   8: }
So it just sets RetainObjectIdentity property to false without any conditions. But in opposite to 1st method you will have all problems with retargeting which are avoided in 1st method.

It is not very good as we need to select a compromise when we use one of these approaches. But is there a way to use advantages of both methods, i.e. retain objects GUIDs and have possibility to import site into any site collection hierarchy? Well, there is such method, but I need to warn that approach described below is actually a workaround and it is not supported by MS. You should keep it in mind when you will make decision whether to use it or not.

Before to show exact workaround I would describe the reason of this issue. The root of the problem is in SPImport class which can’t properly import SPWebs which are childs of another SPWebs (i.e. not childs of site collection root site) with preserving objects identities. It occurs because during exporting of web site it becomes orphan as we don’t import its parent. One of they way to handle such situation is to re-parent exported web during import process using SPImport.Started event – see Deep Dive into the SharePoint Content Deployment and Migration API - Part 3. Unfortunately this method doesn’t work with RetainObjectIdentity = true. If you saw provided link you know that in order to identify all orphaned objects args.RootObjects collection is used (where args is of type SPDeploymentEventArgs and this is parameter of OnImportStarted delegate). Lets see how this collection is initialized in SPImport class via reflector:

   1: public override void Run()
   2: {
   3:     try
   4:     {
   5:         ...
   6:         SPImportObjectCollection rootObjects = this.DeserializeRootObjectMap();
   7:         this.OnStarted(new SPDeploymentEventArgs(SPDeploymentStatus.Started, base.ObjectsProcessed, base.ObjectsTotal, base.DataFileManager.DataFilePath, rootObjects));
   8:         ...
   9:     }
  10:     catch (Exception exception3)
  11:     {
  12:         ...
  13:     }
  14:     ...
  15: }

So RootObjects collection is result of DeserializeRootObjectMap() method. Lets see this method as well:

   1: private SPImportObjectCollection DeserializeRootObjectMap()
   2: {
   3:     SPImportObjectCollection objects = new SPImportObjectCollection();
   4:     FileInfo rootObjectMapFile = base.DataFileManager.RootObjectMapFile;
   5:     if (!rootObjectMapFile.Exists)
   6:     {
   7:         return objects;
   8:     }
   9:     if (this.Settings.RetainObjectIdentity)
  10:     {
  11:         return objects;
  12:     }
  13:     ...
  14: }

Quite impressive: if we use RetainObjectIdentity = true then empty collection is always returned! That's why we can’t re-parent orphaned objects using Started event of SPImport class.

I found the following way to fix it. When we exported site using SPExport class we have a bunch of xml files with objects definitions (assume that we didn’t use compressed export into cab files). And there is a lot of relative URL paths of exported site like “/site1/site2”. You need to replace all such strings with name of the target site. I.e. if you need to import site2 not under site1 on target site collection, but under root site of site collection, you need to replace all strings “/site1/site2” by “/site2” in all files which you get after export. Doing this we made a trick – we replaced real parent of site2 (/site1) with root site of site collection (/). After that you can use SPImport with RetainObjectIdentity = true and it will work like if you would export site2 from subsite of root site of site collection.

As I already mentioned this solution should be considered as workaround. There is no guarantee that it will work for all cases. However it helped me to solve real life issue on one of the projects – with it I was able to copy sites using SPExport and SPImport classes into any site collection hierarchy and avoid problems with retargeting of Sharepoint artifacts. I hope that it will be useful for you in real life Sharepoint development as well.