Tuesday, June 3, 2014

Test Strategy in SharePoint: Part 3 – Event Receiver as procedural, untestable feature

copyright from: blog.goneopen.com

Here I cover procedural, untestable code and to do so will cover three pieces of SharePoint code. The first is sample code that suggest just how easy it is to write SharePoint code. The second is production quality I code written in the style of the first and yet it becomes unmaintainable. The third piece of code is what we refactored the second piece to become and we think it’s maintainable. But it will come at cost (and benefit) that I explore in “Test Strategy in SharePoint: Part 4 – Event Receiver as layered Feature”.

Sample One: sample code is easy code

Here’s a current sample that we will find on the web that we find is actually the code that makes its way into production code rather than remaining sample code – so no offence to the author. Sharemuch writes:
When building publishing site using SharePoint 2010 it’s quite common to have few web parts that will make it to every page (or close to every page) on the site. An example could be a custom secondary navigation which you may choose to make a web part to allow some user configuration. This means you need to provision such web part on each and every page that requires it – right? Well, there is another solution. What you can do is to define your web part in a page layout module just like you would in a page. In MOSS this trick would ensure your web part will make it to every page that inherits your custom layout; not so in SharePoint 2010. One solution to that is to define the web part in the page layout, and programmatically copy web parts from page layout to pages inheriting them. In my case I will demonstrate how to achieve this by a feature receiver inside a feature that will be activate in site template during site creation. This way every time the site is created and pages are provisioned – my feature receiver will copy web parts from page layout to those newly created pages.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    SPWeb web = properties.Feature.Parent as SPWeb;
 
    if (null != web)
    {
        PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);
        SPList pages = pubWeb.PagesList;
 
        foreach (SPListItem page in pages.Items)
        {
            PublishingPage pubPage = PublishingPage.GetPublishingPage(page);
            pubPage.CheckOut();
            CopyWebParts(pubPage.Url, web, pubPage.Layout.ServerRelativeUrl, pubPage.Layout.ListItem.Web);
            pubPage.CheckIn("Webparts copied from page layout");
        }
    }
}
 
private void CopyWebParts(string pageUrl, SPWeb pageWeb, string pageLayoutUrl, SPWeb pageLayoutWeb)
{
    SPWeb web = null;
    SPWeb web2 = null;
    SPpageWebPartManager pageWebPartManager = pageWeb.GetpageWebPartManager(pageUrl, PersonalizationScope.Shared);
    SPpageWebPartManager pageLayoutWebPartManager = pageLayoutWeb.GetpageWebPartManager(pageLayoutUrl, PersonalizationScope.Shared);
    web2 = pageWebPartManager.Web;
    web = pageLayoutWebPartManager.Web;
    SPLimitedWebPartCollection webParts = pageLayoutWebPartManager.WebParts;
    SPLimitedWebPartCollection parts2 = pageWebPartManager.WebParts;
    foreach (System.Web.UI.WebControls.WebParts.WebPart part in webParts)
    {
        if (!part.IsClosed)
        {
            System.Web.UI.WebControls.WebParts.WebPart webPart = parts2[part.ID];
            if (webPart == null)
            {
                string zoneID = pageLayoutWebPartManager.GetZoneID(part);
                pageWebPartManager.AddWebPart(part, zoneID, part.ZoneIndex);
            }
        }
    }
}
This sample code sets a tone that this is maintainable code. For example, there is some abstraction with the CopyWebParts method remaining separate from the activation code. Yet, if I put it against the four elements of simple design, the private method maximises clarity but won’t get past passing tests.
Let’s take a look at some production quality code that I have encountered then refactored to make it maintainable code.

Sample Two: easy code goes production

All things dev puts up the sample for Customising SharePoint 2010 MySites with Publishing Sites. Here is the code below that follows the same patterns: clarity is created through class scope refactoring of private methods. But we still still see magic string constants, local error handling, procedural style coding against the SharePoint API (in SetupMySite(). The result is code that is easy to write, easy to deploy, manual to test and reuse is through block-copy inheritance (ie copy and paste).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using System;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using System.Linq;
 
namespace YourCompany.SharePoint.Sites.MySites
{
 
   [Guid("cd93e644-553f-4486-91ad-86e428c89723")]
   public class MySitesProvisionerReceiver : SPFeatureReceiver
   {
 
         private const string MySiteCreated = "MySiteCreated";
         private const string ResourceFile = "MySiteResources";
         private const uint _lang = 1033;
 
         public override void FeatureActivated(SPFeatureReceiverProperties properties)
         {
 
             using (SPWeb web = properties.Feature.Parent as SPWeb)
             {
                //run only if MySite hasn't been created yet as feature could be run after provisioning as well
                if (web.Properties.ContainsKey(MySiteCreated))
                  return;
 
                ActivatePublishingFeature(web);
                SetUpMySite(web);
             }
         }
 
         private void ActivatePublishingFeature(SPWeb web)
         {
 
               //Activate Publishing Web feature as the stapler seems to not do this consistently
               try
               {
                   web.Features.Add(new Guid("94C94CA6-B32F-4da9-A9E3-1F3D343D7ECB"));
               }
               catch (Exception)
               {
                   //already activated
                }
 
         }
 
         private void SetUpMySite(SPWeb web)
         {
 
               //turn off versioning, optional but keeps it easier for users as they are the only users of their MySite home page
               var pubWeb = PublishingWeb.GetPublishingWeb(web);
               var pages = pubWeb.PagesList;
               pages.EnableVersioning = false;
               pages.ForceCheckout = false;
               pages.Update();
 
               //set custom masterpage
               var customMasterPageUrl = string.Format("{0}/_catalogs/masterpage/CustomV4.master", web.ServerRelativeUrl);
               web.CustomMasterUrl = customMasterPageUrl;
               web.MasterUrl = customMasterPageUrl;
 
               var layout = pubWeb.GetAvailablePageLayouts().Cast()
                                                        .Where(p => p.Name == "HomePage.aspx")
                                                        .SingleOrDefault();
 
               //set default page
               var homePage = pubWeb.GetPublishingPages().Add("Home.aspx", layout);
               homePage.Title = "Home Page";
               homePage.Update();
               pubWeb.DefaultPage = homePage.ListItem.File;
 
               //Add initial webparts
               WebPartHelper.WebPartManager(web,
               homePage.ListItem.File.ServerRelativeUrl,
               Resources.Get(ResourceFile, "MySiteSettingsListName", _lang),
               Resources.Get(ResourceFile, "InitialWebPartsFileName", _lang));
 
               web.AllowUnsafeUpdates = true;
               web.Properties.Add(MySiteCreated, "true");
               web.Properties.Update();
               pubWeb.Update();
 
               //set the search centre url
               web.AllProperties["SRCH_ENH_FTR_URL"] = Resources.Get(ResourceFile, "SearchCentreUrl", _lang);
               web.Update();
 
               //delete default page
               var defaultPageFile = web.GetFile("Pages/default.aspx");
               defaultPageFile.Delete();
               web.AllowUnsafeUpdates = false;
 
         }
     }
}
  
There is for me one more key issue. What does it really do? I was struck by the unreadability of this code and was concerned that there are so many working parts here and how they would all be combined.

Sample Three: wouldn’t this be nice?

Here’s what we refactored that code to. Hopefully there is some more intention in this. You may read it like this: I have a Personal Site that I create and process with a new page, removing and exsiting page, being activated and then getting a couple of master pages.
I like this because I can immediately ask simple questions, why do I have to remove an existing page and why are there two master pages. It’s SharePoint and there’s of course good reasons. But I now am abstracting away what SharePoint has to do for me to get this feature activated. It’s not perfect but is a good enough example to work on.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System.Runtime.InteropServices;
using YourCompany.SharePoint.Domain.Model.Provisioning;
using YourCompany.SharePoint.Infrastructure;
using YourCompany.SharePoint.Infrastructure.Configuration;
using Microsoft.Office.Server.UserProfiles;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
 
namespace YourCompany.SharePoint.MySites.Features.WebFeatureProvisioner
{
    [Guid("cb9c03cd-6349-4a1c-8872-1b5032932a04")]
    public class SiteFeatureEventReceiver : SPFeatureReceiver
    {
        [NewPage("Home.aspx", "Home Page", "HomePage.aspx")]
        [ActivateFeature(PackageCfg.PublishingWebFeature)]
        [RemovePage("Pages/default.aspx")]
        [MasterPage("CustomV4.master", MasterPage.MasterPageType.User)]
        [MasterPage("CustomMySite.master", MasterPage.MasterPageType.Host)]
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            PersonalSiteProvisionerFactory
              .Create(properties.Feature.Parent as SPWeb)
              .Process();
        }
    }
}
What I want to suggest is that this code is not necessarily easy to write given the previous solution. We are going to have to bake our own classes around the code: there’s the factory class and the attributes too – we’ll also find that if there are the classes that the factory returns too. In the end, we are should have testable code and my hunch is that we are likely to get some reuse too.
The next entry, “Test Strategy in SharePoint: Part 4 – Event Receiver as layered Feature”, will look at how we can layer the code to make this code become a reality.

No comments: