Wednesday, January 02, 2013

Converting multiple SharePoint 2010 projects to SharePoint 2013

Converting/ migrating SharePoint 2010 projects to SharePoint 2013 involves changing TargetOfficeVersion to be 15.0, and changing TargetFrameworkVersion to be v4.0 or v4.5 in the Visual Studio csproj file.

When you have multiple SharePoint 2010 projects in your solution, it will not be an easy task!

The following PowerShell script is my trial to simplify converting multiple projects, hope it will help.
Just change the value of the $path variable to be your solution path (the folder containing your SharePoint projects).

# Path containing SharePoint 2010 projects 
$path = "<Your Solution Folder Path>"

cd $path
$files = get-childitem -recurse -filter *.csproj

foreach ($file in $files)
{
    "Filename: {0}" -f $($file.Name)
    $proj = [xml](Get-Content $file.FullName)

    $ns = new-object Xml.XmlNamespaceManager $proj.NameTable
    $ns.AddNamespace("dns", "http://schemas.microsoft.com/developer/msbuild/2003")
    $projectTypeGuids = $proj.SelectSingleNode("//dns:Project/dns:PropertyGroup/dns:ProjectTypeGuids", $ns)
 
 # Check to see if the project type is SharePoint 
 if ($projectTypeGuids."#text" -like "*BB1F664B-9266-4fd6-B973-E1E44974B511*")
 {
  $targetOfficeVersion = $proj.SelectSingleNode("//dns:Project/dns:PropertyGroup/dns:TargetOfficeVersion", $ns)
  if($targetOfficeVersion -eq $null)
  {
   # Create TargetOfficeVersion element if not exist
   $targetOfficeVersion = $proj.CreateElement("TargetOfficeVersion")
   $targetOfficeVersion = $proj.SelectSingleNode("//dns:Project/dns:PropertyGroup", $ns).AppendChild($targetOfficeVersion)
  }

  # Change target office version
  $targetOfficeVersion.InnerText = "15.0"

  # Change target framework version
  $targetFrameworkVersion = $proj.SelectSingleNode("//dns:Project/dns:PropertyGroup/dns:TargetFrameworkVersion", $ns)
  $targetFrameworkVersion.InnerText = "v4.5"

      # Remove empty namespaces
  $proj = [xml] $proj.OuterXml.Replace(" xmlns=`"`"", "")
  $proj.Save($file.FullName)
 }
}

Saturday, December 22, 2012

Ghost un-ghosted SharePoint 2010 files

Recently, I faced some performance problems in a big customized SharePoint solution. One of the reasons was that some of the page layouts and master pages are un-ghosted by SharePoint designer users, and some other page layouts were created directly in the SharePoint designer without having a feature to deploy them.

I had to fix that up by re-ghosting the customized pages and create a ghosted version for the files that have been created inside the SharePoint designer without a deployment feature.

Assessing the current situation I have the following cases:

Case 1 Some pages had been deployed using SharePoint features, but after that were edited in SharePoint designer. So, they are now customized (un-ghosted) SharePoint Designer displays the icon beside these files.

Case 2 Some pages had been deployed using SharePoint features, but after that they were replaced in SharePoint designer by uploading another file with the same name and overwriting the existing one. So, they are now customized (un-ghosted) SharePoint Designer doesn't display anything to show that these file are customized.

Case 3 Some pages had been created directly by importing files to the SharePoint designer using import option, which means they have never been ghosted before. Again, SharePoint Designer doesn't display anything to show that these file are customized. (Actually, they are not customized because they have never been ghosted before) but I need to make them ghosted.


So, I created a Windows application small tool, SPGhostFilesManager, to help doing the job.

First, I want to know what are the customized files (the three types above)
Second, I have to revert the un-ghosted files that already have ghosted files deployed, but after updating the ghosted files by the customized files content.
Third, I have to create ghosted files for the files created directly in the SharePoint designer and re-ghost those files to keep everything working as is while having all the files ghosted.

Here are the steps I followed to handle each of the above cases:

For Case 1:
For Case 2:

  • Using SPGhostFilesManager, Save each file content to the disk using the "Save" button.
  • Back to the SharePoint feature in Visual Studio update the content of the original deployable files.
  • Redeploy the feature containing the files to have the ghosted files updated.
  • Using SPGhostFilesManager, Click "Revert" button beside each file to revert it back to the ghosted file.
For Case 3: (These are the files with Status None on the SPGhostFilesManager view)
  • Using SPGhostFilesManager, Save each file content to the disk using the "Save" button.
  • Create a SharePoint feature (or use an existing one) to deploy these files as ghosted versions but you have to choose other names for the files to be able to link the old un-ghosted files to these new ghosted files.
  • Deploy the created feature to get your ghosted files in the SharePoint environment.
  • Using SPGhostFilesManager, Click "Ghost" button beside each file to ghost it to the corresponding one you deployed using the feature.

Read more »


Thursday, November 10, 2011

Paginating SharePoint List Items

Paginating SharePoint list items is not a straight forward task. You have to do some plumbing to make it work. especially, if you have sorting and filtering applied to the query you want to get your items with.

This is my trial to make the task easier:
SPPagedListItemsRetriever is a class that we need to instantiate by specifying the SPList we need to work with, and SPQuery we will execute.
SPPagedListItemsRetriever pagedItemsRetriever = new SPPagedListItemsRetriever(list, query);

Then, we have 2 public methods:
public SPListItem[] GetItems(int? startIndex, int? maxRowsCount); 
public int GetTotalItemsCount();

The drawback of the used technique in this implementation, is that it always get the items from the beginning! So, if you are asking to get the items starting from item 200 and with page size 10, It will get the first 199 items and throw them away, then begin getting the required items from 200 to 210.

Here is the complete post of the code:
/// <summary> 
/// Retrieves paginated items of SPList with the specified SPQuery. 
/// </summary> 
public class SPPagedListItemsRetriever
{ 
  private SPQuery _query;
  private SPList _list;
  private const int MaxRowLimit = 2000;
  private static SPListItemCollectionPosition _emptySPListItemCollectionPosition = new SPListItemCollectionPosition(string.Empty); 

  /// <summary>
  /// Constructs a new instance of SPPagedListItemsRetriever 
  /// </summary> 
  /// <param name="list" />The list to get the items from
  /// <param name="query" />The query by which the items should be retrieved
  public SPPagedListItemsRetriever(SPList list, SPQuery query) 
  { 
    _list = list; 
    _query = query;
  }
  
  /// <summary> 
  /// Get the items of the list with the specified query begining from a specified startIndex and with maxRowsCount (PageSize) 
  /// </summary> 
  /// <param name="startIndex" /> 
  /// <param name="maxRowsCount" /> 
  /// <returns></returns>
  public SPListItem[] GetItems(int? startIndex, int? maxRowsCount)
  {
    SPListItemCollectionPosition listItemCollectionPosition = null; 
    uint actualStartIndex = startIndex.HasValue ? (uint)startIndex.Value : 0;
    //If we need items beginning from a specific index (greater that 0, the first one) 
    //Create a dummy query to begin getting the items from the first one (0) till we reach the specified startIndex 
    if (actualStartIndex > 0)
    {
      SPQuery dummyQuery = new SPQuery();
      //Change the ViewFields returned from this dummy query to minimal, actually we dont need these items so selelct the ID only to minimize the view fields
      dummyQuery.ViewFields = "<fieldref name='ID'>"; 
      dummyQuery.Query = _query.Query;
      
      if (null != _query.Folder)
        dummyQuery.Folder = _query.Folder; 
      
      int gotDummyItems = 0;
      do
      { 
        //Minimize the number of items not to exceed the recommended 2000 MaxRowLimit for SPQuery
        dummyQuery.RowLimit = Math.Min((uint)(actualStartIndex - gotDummyItems), MaxRowLimit); 
        if (null == listItemCollectionPosition)
          listItemCollectionPosition = _emptySPListItemCollectionPosition;
        
        dummyQuery.ListItemCollectionPosition = listItemCollectionPosition;
        SPListItemCollection items = _list.GetItems(dummyQuery); gotDummyItems += items.Count; 
        listItemCollectionPosition = items.ListItemCollectionPosition;
      }
      while (gotDummyItems < actualStartIndex && listItemCollectionPosition != null);
    } 
    
    //Now we will get the actual items we need 
    SPQuery query = new SPQuery();
    query.Query = _query.Query;
    if (null != _query.Folder)
      query.Folder = _query.Folder;
    query.ViewFields = _query.ViewFields;
    List<splistitem> returnedItemsList = new List<splistitem>();
    uint actualMaxRowCount = maxRowsCount.HasValue ? (uint)maxRowsCount.Value : (uint)_list.ItemCount; 

    do
    { 
      //Minimize the number of items not to exceed the recommended 2000 MaxRowLimit for SPQuery
      query.RowLimit = Math.Min(actualMaxRowCount, MaxRowLimit); 
      if (null == listItemCollectionPosition)
        listItemCollectionPosition = _emptySPListItemCollectionPosition;
      query.ListItemCollectionPosition = listItemCollectionPosition;
      SPListItemCollection listItems = _list.GetItems(query);
      returnedItemsList.AddRange(listItems.Cast<splistitem>().Select(i=>i));
      listItemCollectionPosition = listItems.ListItemCollectionPosition;
    }
    while(returnedItemsList.Count < actualMaxRowCount && listItemCollectionPosition != null); 
     
    return returnedItemsList.ToArray();
  } 

  /// <summary>
  /// Gets the total items count using the specified query
  /// </summary>
  /// <returns></returns>
  public int GetTotalItemsCount()
  {
    SPQuery query = new SPQuery();
    //Change the ViewFields returned from this dummy query to minimal, actually we dont need these items so selelct the ID only to minimize the view fields
    query.ViewFields = "<fieldref name='ID'>";
    query.Query = _query.Query;
    SPFolder folder = _query.Folder;
    if (null != folder)
      query.Folder = folder;
    return _list.GetItems(query).Count; 
  } 
} 

Read more »


Sunday, November 06, 2011

Grouping XML elements using XSLT version 1.0

Suppose you have an XML like:
<Rows>
  <Row Category="Sweet" Value="Chocolate"/>
  <Row Category="Salty" Value="Potato Chips"/>
  <Row Category="Salty" Value="Pop Corn"/>
  <Row Category="Sour" Value="Lemon"/>
  <Row Category="Sweet" Value="Biscuits"/>
  <Row Category="Salty" Value="Fries"/>
</Rows>

And you want to have these rows grouped by the 'Category' attribute like:

Salty

Fries
Pop Corn
Potato Chips

Sour

Lemon

Sweet

Biscuits
Chocolate

You can use Muenchian grouping just like the following:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <!-- First, define a key that uses our element or attribute we need to group by. In our case it's the Category attribute. -->
  <xsl:key name="food-by-Category" match="Row" use="@Category" />
  <xsl:template match="Rows">
  <!-- Now, we need to iterate on the Categories, This can be done by iterating on the first XML Row item for each Category. We will do that by create a node set for the current Row item (.) and the first item in the Key of the Current item  Category. Then we check the count to see is it 1 or 2.. If 1, So we've the same item in the node set; We have a new Category.-->
    <xsl:for-each select="Row[count(. | key('food-by-Category', @Category)[1]) = 1]">
      <!-- Sort by the Category -->
      <xsl:sort select="@Category" />
      <xsl:value-of select="@Category" />
      <hr />
      <!-- Now loop on the items of this Category, We get them from the Key we defined -->
      <xsl:for-each select="key('food-by-Category', @Category)">
        <!-- Sort by the item Value -->
        <xsl:sort select="@Value" />
        <xsl:value-of select="@Value" />
        <br/>      </xsl:for-each>
      <br/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Read more »


Wednesday, February 23, 2011

DotNetNuke Resources keys

In DotnetNuke Localization support, every resource key (in APP_LocalResources or APP_GlobalResources) should end with an extension.
resourceName.Text – Represents a string used for a common purpose
resourceName.Help – Used mostly in conjuncture with the DNN:Label control, which supplies a help icon () and tooltip.
resourceName.Tooltip – Used for a control’s Tooltip property.
resourceName.Action – Used to represent the Title property of a ModuleAction item shown in an actions menu.
resourceName.Confirm – Text that is displayed in a JavaScript’s confirmation dialog (such as a dialog to confirm deleting a module).
resourceName.Error – Text that is displayed as an error message. This might be for a label or, more commonly, for one of the ASP.NET validation controls.
resourceName.Detail – Text that gives more detail about an error message.

If we add a resource key in a resx file of the APP_LocalResources or APP_GlobalResources and didn’t give it an extension (like one of the above or any other one), we cannot get its value back by the DNN Localization logic of the:
DotNetNuke.Services.Localization.Localization.GetString(key, this.LocalResourceFile);
Or:
DotNetNuke.Services.Localization.Localization.GetString(key, <global resource file name>);

Further, if we call the Localization.GetString function with a key without an extension, it will assume that we are asking about the one with the extension ".Text".
So the call of Localization.GetString("SomeKey", LocalResourceFile) is equivalent to Localization.GetString("SomeKey.Text", LocalResourceFile)!

Read more »


Sunday, February 13, 2011

Localization support in DotNetNuke

In general, DNN disables the ASP.NET localization (the resources files) by disabling the build providers of the extensions (.resx and .resources) in the web.config.
<buildProviders>
  <remove extension=".resx"/>
  <remove extension=".resources"/>
</buildProviders>
So the calls of HttpContext.GetGlobalResourceObject() and HttpContext.GetLocalResourceObject() will always return null! If we need to support localization in DNN solutions, we have to use the DNN localization class (DotNetNuke.Services.Localization.Localization) to do so.

Read more »


Sunday, March 29, 2009

DllImport 32bit dlls in .net assemblies throws BadImage exception on 64bit OS


When you compile your .Net executable with "AnyCPU" platform, it will select the CLR at runtime depending on the currently running platform.
Which means that your application will run as a 64-bit application on x64 machine and as 32-bit application on x86 machine.

On x64 machines if you use any DllImport in your .Net executable to load a 32-bit native dll, while you compile your executable with "AnyCPU" platform, it will crash at runtime because no 64-bit application can run 32-bit dll.
So if you want to load a 32-bit native dll with DllImport in a .Net executable you have to compile this .Net executable for "x86" platform to make this executable run under the WOW64 as a 32-bit application this will make it capable to load native 32-bit dlls.

Read more »