Saturday, May 25, 2013

Provision list view web parts declaratively and modify their views in Sharepoint

You may provision list views web parts as any other web part by adding it to Module > File > AllUsersWebPart in the feature’s element manifest. In this case you have to add xml declaration of the web part inside AllUsersWebPart (you may get it if e.g. will add list view web part on the page manually and then will export it to the file on disk). However there is more simple way to provision list view web parts using View element:

   1: <Module Name="MyPages" Url="$Resources:cmscore,List_Pages_UrlName;" Path="pages">
   2:   <File Url="default.aspx" Type="GhostableInLibrary" Level="Published">
   3:     <View
   4:         List="Lists/MyList"
   5:         BaseViewID="3"
   6:         Name="MyList"
   7:         WebPartZoneID="RightWebPartZone"
   8:         WebPartOrder="0" />
   9:   ...
  10:   </File>
  11: </Module>

In this example we provision list view web part on the default.aspx page into RightWebPartZone. This web part will use list view with BaseViewID=3 for list with url Lists/MyList on the current web site. BaseViewID can be retrieved from query string when you choose create new view of specific type from the list settings of ribbon.

However this web part will use default settings for the selected list view, e.g. it will have default columns and sorting settings. In some cases we may need to modify the list view used by web part, e.g. add new columns, remove columns which were added by default, change sorting settings, etc. In order to do that we need to understand what happens behind the scene when we provision list view web part like this.

If you will check the views for the list for which we provision web part e.g. in Sharepoint Manager and then compare them with list, which was created by the same template, but without provisioned list view web part, you will find that during provisioning web part creates additional view with specified BaseViewID and with Hidden=true. I.e. this view won’t be visible from UI. So if we want to modify this view, we need to get instance of appropriate SPListView object and then manipulate its properties. We can do it e.g. in feature receiver:

   1: public class MyFeatureReceiver : SPFeatureReceiver
   2: {
   3:     public override void FeatureActivated(SPFeatureReceiverProperties properties)
   4:     {
   5:         ...
   6:         var list = web.Lists.Cast<SPList>().FirstOrDefault(l => l.Title == "MyList");
   7:         if (list != null)
   8:         {
   9:             var view = list.Views.Cast<SPView>().LastOrDefault(v => v.BaseViewID == "3" && v.Hidden);
  10:             if (view != null)
  11:             {
  12:                 view.ViewFields.DeleteAll();
  13:                 view.ViewFields.Add("Title");
  14:                 view.ViewFields.Add("Modified");
  15:                 view.Query = "<OrderBy><FieldRef Name=\"Modified\" Ascending=\"FALSE\" /></OrderBy>";
  16:                 view.Update();
  17:             }
  18:         }
  19:         ...
  20:     }
  21: }

In this example at first we get instance of the list (line 6), then find hidden view with specified BaseViewID and (line 9). After that we delete all columns which were added by default and add columns which we need: Title and Modified. Also we specify sort order: by Modified field descending. After that your web part will show the view with specified settings, instead of default ones.

Saturday, May 18, 2013

Problems with uninstalling external dlls from the GAC with wsp retract in Sharepoint and ways to solve it

In this post there won’t be technical solutions. Instead it will be kind of analysis of the problems which we currently have in regular wsp deployment process and how they can be solved. So what is the actual problem?

On the same Sharepoint farm there may be many web applications implemented by single or different vendors. These web applications may have own set of wsp packages, single web app may have several packages deployed to it or it may use functionality defined in wsp packages deployed globally:

image

In the example above there are 2 web apps, both use wsp 1 and wsp 2, which install the same dll (with the same fully strong name) into the GAC (this dll is located inside wsp and defined in their manifest.xml files).

Once we retract e.g. wsp 2, dll is also uninstalled from GAC, and functionality defined in wsp 1 will be most probably broken (wsp 1 and wsp 2 may be implemented by completely different vendors and don’t know about each other):

image

How this situation can be improved? The first idea which comes to the mind is to implement reference counter for external dlls. Note, that we are talking about external dlls, which are installed into the GAC with wsp. Each wsp may have own dll with code compiled for its artifacts. This is different story: such dlls should be always uninstalled from the GAC when parent wsp is retracted. During retracting of wsp the process should check this counter and uninstall dll only when it becomes 0 (similar to the reference counters in old COM technology):

image

When wsp is retracted reference count is decremented:

image

When wsp 2 is deployed again, it may update existing dll or leave it as is. 1st way is preferrable from my point of view. It will override existing dll only if it contains the same fully qualified strong name, which only should happen when dlls are the same. So it won’t break wsp 1, but with updating we will ensure that new dll will always be installed in GAC during wsp installation.

Looks easy, but what is the cost which we should pay for this? The problem is that in real life wsp deployment may fail as well as wsp retracting. And the main problem which I see in these kind of solutions is reliability of wsp deployments. E.g. suppose that retracting of wsp 2 failed:

image

What we can say about reference count in this case? Nothing definite. Ref count depends on further actions of administrator. He may remove failed wsp from solution store, add it back and deploy with –force key. In this case if we would decrement ref during retracting error, we need to increment it back, so at the end we would have ref count = 2. However administrator may always deploy wsp package with –force attribute, and if he will do it e.g. with wsp 1 which is correctly deployed already, ref count should not be incremented. As you can see it is almost impossible to design reliable wsp deployment process with reference counts.

The simpler solution in this case from my point of view would be adding one more switch parameter to the retractsolution stsadm command and Uninstall-SPSolution powershell cmdlet (e.g. LeaveInGAC), which when set will mean to leave external dlls in GAC. I.e. by default these commands will still uninstall dlls from GAC in order to keep backward compatibility for the deployment process, but also it will be possible to leave dlls in GAC if needed. It should be clearly noticed that this parameter only affects external dlls, not own wsp dlls which should be retracted always regardless of this parameters. What will you say about such feature?

Update 2013-05-20: this post become interesting for developers. Here are some ideas of how you may try to handle this problem without requesting new switch param in Uninstall-SPSolution powershell cmdlet:

  1. From me: Include external components as a source code to your wsp and use own copy of this component. As other web applications will continue to use original component when you will retract your wsp, it won’t affect them. The problem with this approach is that source code is not often available, not all components which we use are opensource.
  2. From avishnyakov: Include external components as dll into separate wsp, which will contain only these dlls. Advantage of this approach is that such wsp is most probably won’t be retracted so often as regular wsps with Sharepoint artifacts. So it is safer than including assemblies into regular wsp. However at some moment administrator may deploy wsp from other supplier, which may have the same dll as in your common wsp. After retracting of their wsp your functionality will be broken.
  3. From Jarno Leikas: instead of full trust assemblies which are installed into the GAC, install 3rd party assemblies to the local bin folders of the application virtual directory. It will also require code access security section in manifest.xml (see Deploy DLL to Local app folder (Bin folder) in sharepoint 2010). This is the MS recommended way btw now. However be ready for additional work and problems with this approach. Personally I try to avoid changing content inside virtual folders (custom http application in global.asax, custom web.config modifications, even App_GlobalResources if it require additional actions from our side), because in most cases it caused some problems afterwards.

If you have other ideas, you can share them in comments.

Sunday, May 12, 2013

Fix File not found error after upgrade from Sharepoint 2010 to 2013

In one of my previous posts I wrote about one major problem which we faced after performing site collections upgrade from 2010 to 2013 mode (see File not found error after upgrade of customizations from Sharepoint 2010 to 2013). On the moment of writing the previous post we had one working workaround, which required direct modification of the content database (change values in SetupPathVersion column in AllDocs table from 4 to 15). Also I wrote that we opened a primary support ticket in MS, but unfortunately all suggestions which we got from MS support didn’t work in our case.

As sites should go to production soon and we didn’t find exact reason and solution for this problem, we decided to go by other way: we decided to replace all page layouts and master pages by new ones, which will be installed only to 15 folders. And this workaround really worked. So what we did:

  1. Created list of all page layouts and masterpages, used in the migrated sites.
  2. Copied all of them as is, without modifications, but with new file names. It is important to not change web part zones during this process, because if e.g. Id of web part zone would be changed, we would lost the content after replacing of the original page layouts. For file names we used the following rule: [old file name] + “2013” + [old extension], i.e. if old file name was foo.aspx, then new is foo.2013.aspx.
  3. Created new feature which contains new page layouts and masterpages, provisioned it and activated on the sites.
  4. Created PowerShell scripts which recursively changed masterpages on all sub sites and page layouts of all publishing pages.

There were several new problems, caused by this workaround (e.g. we needed to change available page layouts setting for the sub sites, in order to give content producers possibility to create new pages using new page layouts), but original File not found error has gone after this. And since this solution didn’t require direct modifications of the content database, it was accepted for production use.

Hope that this information will help you. And we are still interested in the actual reasons of this problem, so if you know another solution, please share it in the comments.

Update 2013-05-13. Rainer D├╝mmler shared another solution in the email conversation, and I with his approval put it here (already asked, but one more time: Rainer, create own blog :) ). His solution contains from several steps:

  • When you migrate the SP 2010 Solution to SP 2013 give the SP 2013 solution a new Solution ID.
  • Install SP 2010 solution to 14 hive and SP 2013 solution to 15 hive on SP2013 server.
  • Give all site definitions (onet.xml) in SP 2013 solution a higher Revision number for example:
   1: <Project Title="Migrationtest" Revision="3" .../>
  • Then you need an Upgrade xml file (destination: {SharePointRoot}\CONFIG\UPGRADE\) where you add a webtemplate element for every site definition you use:
   1: <?xml version='1.0'?>
   2: <!-- Copyright (c) Microsoft Corporation. All rights reserved. -->
   3: <Config xmlns="urn:Microsoft.SharePoint.Upgrade">
   4:   <WebTemplate
   5:       ID="..."
   6:       LocaleId="*"
   7:       FromProductVersion="4"
   8:       BeginFromSchemaVersion="0"
   9:       EndFromSchemaVersion="2"
  10:       ToSchemaVersion="3">
  11:   </WebTemplate>
  12: </Config>
  • Attach SP 2010 content database to SP 2013.
  • Do visual upgrade:
    Upgrade-SPSite -Identity <url>
  • Close and reopen powershell console:
    Upgrade-SPSite -Identity <url> -VersionUpgrade
  • Have a look at the database - paths of the upgraded sitecollection have changed
Interesting solution from my point of view.

Update 2013-07-07. Solution described above fixes problem for publishing pages and masterpages. However there is one more issue which we found after upgrade. All images in Site collection images document library, which were in the library before upgrade, returned HTTP 404 File not found error when tried to open them by direct URL. Interesting that inside Site collection images doclib all images thumbnails were shown.

The problem occurs only when you try to open specific file. Also new images uploaded to Site collection images after upgrade work properly, i.e. they are opened successfully without File not found. As these images were used before upgrade on the publishing pages (and in other content) by URL, in order to fix this issue it was enough to delete all upgraded images and then re-upload them to the Site collection images with the same file names (we copied original images from old SP2010 sites, because it was also not possible to copy files from Site collection images doclib on upgraded SP2013 site using explorer view). After that they started to work properly.