Auto Publish and Approve Your Solution Files

Applies To: SharePoint 2010

By default, every file you deploy using a sandboxed solution is left checked out. This can lead to problems depending on the type of site you are deploying to and/or the permissions of your end users.

This post focuses on Branding solutions, but anytime you are deploying a sandboxed solution these techniques should help you. This is especially important for Master Pages since these often need an approved/published version in order to be visible to anyone but the site administrators.

I found an interesting approach by Waldek Mastykarz where he suggests using a “Stamp” (Feature ID property) on each file and using that to find and check in each file. This was very cool, but requires you to modify the Elements.xml entry for each file to ensure that property is added and he also never addressed Master Pages which can be a bit of a special case.

For my needs, I generally take a simpler approach of just deploying my resources to one root folder and creating sub folders as needed. This makes it easier to find stuff, but it also means I don’t need to track each file individually. Obviously if you are doing something a little more extensive then you may need to take a hybrid approach of using the featureid property and/or just tracking the various folders you are deploying too. But for a simple branding solution you really just need to:

  1. Apply your branding to each site
  2. Publish and Approve each resource file
  3. Publish and Approve each Master Page

All of this can be done in the FeatureActivating event with a simpler helper method:

    Public Overrides Sub FeatureActivated(ByVal properties As SPFeatureReceiverProperties)
        Dim siteCollection As SPSite = CType(properties.Feature.Parent, SPSite)
        If siteCollection IsNot Nothing Then
            Dim topSite As SPWeb = siteCollection.RootWeb

            'Calculate relative path to site from Web Application root
            Dim WebAppRelativePath As String = topSite.ServerRelativeUrl
            If Not WebAppRelativePath.EndsWith("/") Then WebAppRelativePath &= "/"

            'Enumerate through each site and apply branding
            For Each site As SPWeb In siteCollection.AllWebs
                If Not site.MasterUrl.EndsWith("minimal.master") Then
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSmain.master"
                Else
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSminimal.master"
                End If
                If Not site.CustomMasterUrl.EndsWith("minimal.master") Then
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSmain.master"
                Else
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSminimal.master"
                End If
                site.AlternateCssUrl = WebAppRelativePath & "Style%20Library/BSResources/BS.css"
                site.SiteLogoUrl = WebAppRelativePath & "Style%20Library/BSResources/Images/BSlogo.png"
                site.UIVersion = 4
                site.Update()
            Next

            'Publish and Approve each file
            Dim styleLibrary As SPList = topSite.Lists.TryGetList("Style Library")
            If styleLibrary IsNot Nothing Then
                Dim folders As SPListItemCollection = styleLibrary.Folders
                Dim item As SPListItem = DirectCast((From i In folders Where DirectCast(i, SPListItem).Url = "Style Library/BSResources" Select i).FirstOrDefault(), SPListItem)
                ApproveAndPublish(item.Folder, styleLibrary.EnableModeration)
            End If

            'Publish and Approve the Master Pages
            Dim mpGallery As SPList = siteCollection.GetCatalog(SPListTemplateType.MasterPageCatalog)
            If mpGallery IsNot Nothing Then
                Dim mpages As SPListItemCollection = mpGallery.GetItems(New SPQuery With {.Query = "<Where><Or><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>RegalIC.master</Value></Eq><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>RegalICminimal.master</Value></Eq></Or></Where>"})
                If mpages IsNot Nothing Then
                    For Each i As SPListItem In mpages
                        If Not i.File.CheckOutType = SPFile.SPCheckOutType.None Then
                            i.File.CheckIn("Feature Activation", SPCheckinType.MajorCheckIn)
                            If mpGallery.EnableModeration Then
                                i.File.Approve("Feature Activation")
                            End If
                        End If
                    Next
                End If
            End If

        End If
    End Sub

    Private Sub ApproveAndPublish(folder As SPFolder, Approve As Boolean)
        If folder Is Nothing Then Return
        For Each subfolder As SPFolder In folder.SubFolders
            ApproveAndPublish(subfolder, Approve)
        Next
        For Each file As SPFile In folder.Files
            If Not file.CheckOutType = SPFile.SPCheckOutType.None Then
                file.CheckIn("Feature Activation", SPCheckinType.MajorCheckIn)
                If Approve Then
                    file.Approve("Feature Activation")
                End If
            End If
        Next
    End Sub

1. Apply your branding to each site

After gathering basic information about where the feature is being deployed and getting the correct reference URLs, we begin looping through every site in the sitecollection and setting the master page, sitelogo, and CSS settings to use our custom branding beginning in line 11.

The only thing different than the approach described in the Microsoft article, Deploying Branding Solutions for SharePoint 2010 Sites Using Sandboxed Solutions is that I am checking if the current master page is the minimal.master and if so, using my BSminimal.master file instead. This allows me to have both master pages deployed correctly, but it also allows me to restore these settings more accurately in the deactivating event (See my previous post).

2. Publish and Approve each resource file

To keep things simple, I keep all of my resource files in a single root folder within the Style Library. This makes looping through each subfolder and resource very simple to ensure that each one gets checked in and/or approved as necessary.

Lines 29-34 get a reference to the resource folder within the Style Library and pass that information over to a helper method called ApproveAndPublish. This method takes an SPFolder reference and a boolean indicating if approval is necessary or not. For the initial call, the folder is our resource folder and the approval setting comes directly from the Style Library and is found in the EnableModeration property of the SPList object.

The ApproveAndPublish method (Lines 55-68) is a recursive function that loops through every subfolder and checks in every file found. If Approval is required, it also marks them as approved.

This means you don’t have to track each file (either through stamping or keeping a list). This really cuts down on all the plumbing that is often necessary when working on a SharePoint solution.

3. Publish and Approve each Master Page

Unfortunately, Master Pages aren’t usually deployed to a sub folder and so the above technique for approval and check in has to be tweaked slightly. Lines 37-50 take care of this. Basically, we get a reference to the Master Page Catalog and use some basic CAML to isolate our master pages and then loop through them to check them in and/or activate them if required.

That’s all that’s required. You now have your files successfully deployed and ready to be used. Be sure to check out my previous post Branding Solution Cleanup. In that post I describe how to remove all of your solution files when your solution gets deactivated.

Branding Solution Cleanup

Applies To: SharePoint 2010

I followed the Microsoft article, Deploying Branding Solutions for SharePoint 2010 Sites Using Sandboxed Solutions and I was able to quickly get the bones of a Branding project put together. Unfortunately, I found that when the solution was deactivated all the files I deployed remained exactly where they were.

I found various solutions for removing your files ranging from individual file lists to marking every file with your feature ID, but for a simple Branding project all you really need to do is:

  1. Remove usage of your Master Pages from every site referencing them
  2. Remove your files from the Style Library
  3. Remove your Master Page files from the Master Page Catalog

The first two can be done in the FeatureDeactivating event handler and the third can be done in the FeatureUninstalling event handler. For those that just want the code, here it is:

    Public Overrides Sub FeatureDeactivating(ByVal properties As SPFeatureReceiverProperties)
        Dim siteCollection As SPSite = CType(properties.Feature.Parent, SPSite)
        If siteCollection IsNot Nothing Then
            Dim topSite As SPWeb = siteCollection.RootWeb

            'Calculate relative path to site from Web Application root
            Dim WebAppRelativePath As String = topSite.ServerRelativeUrl
            If Not WebAppRelativePath.EndsWith("/") Then WebAppRelativePath &= "/"

            'Enumerate through each site and remove branding
            For Each site As SPWeb In siteCollection.AllWebs
                If Not site.MasterUrl.EndsWith("minimal.master") Then
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/v4.master"
                Else
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/minimal.master"
                End If
                If Not site.CustomMasterUrl.EndsWith("minimal.master") Then
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/v4.master"
                Else
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/minimal.master"
                End If
                site.AlternateCssUrl = String.Empty
                site.SiteLogoUrl = String.Empty
                site.Update()
            Next

            'Kill Style Library Folder
            Dim styleLibrary As SPList = topSite.Lists.TryGetList("Style Library")
            If styleLibrary IsNot Nothing Then
                Dim folders As SPListItemCollection = styleLibrary.Folders
                Dim item As SPListItem = DirectCast((From i In folders Where DirectCast(i, SPListItem).Url = "Style Library/BSResources" Select i).FirstOrDefault(), SPListItem)
                item.Delete()
            End If

        End If
    End Sub

    Public Overrides Sub FeatureUninstalling(ByVal properties As SPFeatureReceiverProperties)
        Dim siteCollection As SPSite = properties.UserCodeSite
        If siteCollection IsNot Nothing Then

            'Kill Master Pages
            Dim mpGallery As SPList = siteCollection.GetCatalog(SPListTemplateType.MasterPageCatalog)
            If mpGallery IsNot Nothing Then
                Dim mpages As SPListItemCollection = mpGallery.GetItems(New SPQuery With {.Query = "<Where><Or><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>BSmain.master</Value></Eq><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>BSminimal.master</Value></Eq></Or></Where>"})
                If mpages IsNot Nothing Then
                    For i As Integer = mpages.Count - 1 To 0 Step -1
                        mpages(i).Delete()
                    Next
                End If
            End If

        End If
    End Sub

Wow code! Alright Scriptkitties, copy away! Everyone else, here’s what we’re doing and why:

1. Remove usage of your Master Pages from every site referencing them

After gathering basic information about where the feature was deployed and figuring out the correct reference URLs, we begin looping through every site in the sitecollection and resetting the master page to the defaults beginning in line 11.

We are just undoing what was done in the FeatureActivating event. The only thing of note is that I hate when a Branding solution replaces every MasterPage with theirs and then just blindly restores v4.master. Mostly this is fine, but if one of your subsites is an Enterprise Search site or anything else using the minimal.master you’ve just wrecked it. Obviously if you know you aren’t using minimal.master then you can simplify this section. Also, I always name my minimal.master replacement in the form [Something]minimal.master to ensure this works out.

2. Remove your files from the Style Library

To keep things simple, I keep all of my resource files in a single root folder within the Style Library. Obviously I have subfolders to organize images, fonts, etc. but all of those are within my one folder. This makes finding stuff much easier, but more than that it makes removing the files super easy – Just delete that folder.

Lines 27-33 do just that. After getting a reference to the Style Library (Every sitecollection in SharePoint 2010 has one of these), grab the folder (just replace the “Style Library/BSResources” string in line 31 with your folder path) and delete.

3. Remove your Master Page files from the Master Page Catalog

There are lots of guides for deleting deployed master pages and I didn’t find any that worked. Basically every time I tried to delete a master page from within the FeatureDeactivating event I got an error about them still being used. I’m sure there’s a good reason for this (feel free to let me know in the comments), but it doesn’t really matter because as long as you followed step 1 above, it’ll work in the FeatureUninstalling event.

We simply grab a reference to the sitecollection’s Master Page Gallery and use some simple CAML to grab references to our Master Pages. Then we walk through them and delete them.

That’s it, you now have a self-cleaning solution and you are a responsible member of the SharePoint community.

WP7 Accent Shapes

Applies To: Windows Phone 7

The new Windows Phone 7 (WP7) introduces Microsoft’s Metro style (Okay, it’s not the first Microsoft product to use it but it will probably be the first time many users encounter it).  We’ve gone back and forth on it.  It seems most people (by people I mean developer’s and tech gurus) are convinced of it’s genius.

It is simple and there is a sort of beauty to it when done correctly.  However, it will be interesting to see how main stream consumers who are used to the styles introduced through the iOS and Android devices take to it.  As developers of multiple platforms we’ve struggled with it a bit.  It seems to fit some Apps remarkably well, but others not at all.

If  you have developed apps for other software platforms like iOS it can be tempting to try and port your apps to look and behave as it does on the other platforms.  Admittedly this was our first approach and we found it didn’t work too well.  Ultimately we’ve come to the conclusion that existing apps were made to match the OS they were created for and the same should be true of the various versions we create.

In other words, if the consumer wanted an iOS style app they would have bought an iOS device.  By purchasing a WP7 device the consumer has shown their preference for WP7 styles, which inevitably means Metro.

This isn’t always appropriate and should be decided on an App by App basis.  WP7 style is also a moving target and we shouldn’t hold back on innovation just to match existing patterns.  But there should be an evolution to our innovations so that they don’t clash with those patterns but rather enhance them.

</soapbox>

So now that you are all about Metro, here is at least one technique we use to take advantage of this design pattern. The SDK comes with a variety of StaticResources that map directly to the user’s theme settings. Ironically, these “Static” resources can be used to dynamically match a user’s design preferences.  Using these allows you to match a style your user obviously likes and allows their personalization to carry all the way through the phone into your apps.  This is a powerful and easy way to provide customization to your app for “Free”.

Note – By default your app will be doing this and if you wish to explicitly control your colors you will need to do so on each control.  Be sure to adjust your theme and accent color multiple times during the testing of your app to ensure your app remains readable and reasonably pretty.

This can be done from either ExpressionBlend or directly in the XAML.  The nice thing about doing it in ExpressionBlend is that it allows you to choose the resource from a list:

Doing this with text or built in shapes is pretty simple.  For instance, to create a textblock control that matches a user’s accent color (PhoneAccentBrush) you can just use this XAML:

<TextBlock Text="I'm so Pretty!"
 Foreground="{StaticResource PhoneAccentBrush}"/>

You could also create a Circle filled with the accent color using this XAML:

<Ellipse Fill="{StaticResource PhoneAccentBrush}" Width="25" Height="25"/>

There are some other basic shapes and a lot can be done with these.  But what do you do when you want something a little more complex?  You could draw it from shapes, include an image for each possible theme, or try this technique using an OpacityMask combined with an ImageBrush:

<Rectangle x:Name="Star"
 Fill="{StaticResource PhoneAccentBrush}"
 Width="15" Height="15">
  <Rectangle.OpacityMask>
    <ImageBrush ImageSource="/Images/Star.png"/>
  </Rectangle.OpacityMask>
</Rectangle>

Note – the image used above has it’s Build Action property set to Content.

This allows us to use an image with transparency to determine our shape.  The image itself doesn’t need to be any particular color it just needs to have an alpha layer.  This allows for complex shapes that match a user’s theme without a whole lot of work!

Both of the above screenshots use the same XAML:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Width="456">
    <TextBlock TextWrapping="Wrap" Text="PhoneForegroundBrush"
     Margin="0,0,-75,0" d:LayoutOverrides="Width"
     Foreground="{StaticResource PhoneForegroundBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneAccentBrush"
     Margin="0,0,15,0" d:LayoutOverrides="Width"
     Foreground="{StaticResource PhoneAccentBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneContrastBackgroundBrush"
     Foreground="{StaticResource PhoneContrastBackgroundBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneDisabledBrush"
     Foreground="{StaticResource PhoneDisabledBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneSubtleBrush"
     Foreground="{StaticResource PhoneSubtleBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneBorderBrush"
     Foreground="{StaticResource PhoneBorderBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneChromeBrush"
     Foreground="{StaticResource PhoneChromeBrush}"/>
    <Line X2="400" StrokeThickness="4" Height="4"
     Stroke="{StaticResource PhoneAccentBrush}" Margin="0,50,0,0"
     HorizontalAlignment="Center"/>
    <Ellipse Fill="{StaticResource PhoneAccentBrush}"
     Width="25" Height="25" Margin="0,50,0,0" HorizontalAlignment="Center"/>
    <Rectangle x:Name="Star" Fill="{StaticResource PhoneAccentBrush}"
     Width="15" Height="15" VerticalAlignment="Center"
     HorizontalAlignment="Center" Margin="0,50,0,0">
      <Rectangle.OpacityMask>
        <ImageBrush ImageSource="/Images/Star.png"/>
      </Rectangle.OpacityMask>
    </Rectangle>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"
     Margin="0,50,0,0">
      <Image Source="/Images/Bear.png" Stretch="Fill"
       Width="63" Height="92" HorizontalAlignment="Center"/>
      <Rectangle x:Name="Bear" Fill="{StaticResource PhoneAccentBrush}"
       Width="63" Height="92" HorizontalAlignment="Center" Margin="50,0,0,0">
        <Rectangle.OpacityMask>
          <ImageBrush ImageSource="/Images/Bear.png"/>
        </Rectangle.OpacityMask>
      </Rectangle>
    </StackPanel>
  </StackPanel>
</Grid>

You can use this technique in all sorts of situations, but our favorite is to combine it with databinding and use a little icon to mark something about the entry (such as unread in an article reader).  This is an easy way to wow your users while maintaining consistency with the Metro style.

Originally Published on WireBear.com/blog on October 29, 2010