Recently I had to create a new Site Collection and add multiple Site Collection Administrators. I searched the internet offcourse and found a solution by Keith Richie. You can merge step 1 and 2 by using the EnsureUser method provided by the OM.

user = rootWeb.EnsureUser(“domain\loginname”)
user.IsSiteAdmin = True
user.Update()

You can use the same method to set the Primary and Secondary Site Collection Administrator.

site.Owner = rootWeb.EnsureUser(“domain\loginname”)
site.SecondaryContact = rootWeb.EnsureUser(“domain\loginname”)

This way you don’t have to pass the name and email of the users.

There are generally two ways to get a collection of WebApplications in a farm, so you can loop over them. When you use one of the methods below …

SPWebService.ContentService.WebApplications
– or
SPFarm.Local.Services.GetValue<SPWebService>().WebApplications

…, you will notice that the Central Administration WebApplication is not included.

To get a reference to that one, you need to use the WebApplications Collection of the SPWebService.AdministrationService. Most developers can easily make a merge of both collections to loop over all WebApplications.

To boost the loadtime of your WebParts (and so the page on which the WebPart is added) in SharePoint, you can use caching methodologies for content that doesn’t change that often. You can find excellent tutorials on the internet about WebPart Caching Options, for example on this blog. But often these WebParts can be customized via properties (through ‘Modify Shared Web Part’) and you want to clear the cache when you do so to reflect the changes you have made a moment ago. As long as you stick to the standard WebBrowsable (& Personalizable) properties, you will have a hard time defining when to clear the cache and when not. The problem is that you can’t clear the cache everytime you call the setter of a property! As soon as one property is changed, these values are stored in the content database. Everytime the WebPart is added on the page, it’s initialized with the default values. The content database is queried and if new values could be found, these are set on the WebPart. Simple to follow, the set method for each property is called and so is the clear cache function (overhead since it’s called multiple times, but this could be solved with a simple boolean). There are however some techniques to avoid this:

1. You could store an object with the actual property values whenever you store your content in cache. Everytime you are loading the cache, you verify that the property values of the WebPart haven’t changed.

2. You create a custom editorpart for your WebPart and leave it almost empty. You have to override at least two methods, ‘ApplyChanges’ and ‘SyncChanges’. The ‘ApplyChanges’ method is called everytime OK or Apply is pressed. Inside this method, you get a reference to the WebPart via a cast from the WebPartToEdit property. If not nothing, you can just call a Clear Cache method you have written inside the WebPart class. The other method, ‘SyncChanged’ can be left blank. Don’t forget to implement the default and paramaterized constructor, you will need them later. In the WebPart class, you simply override the ‘CreateEditorParts’ method and add the new editorpart to the collection.

Although both techniques work, I prefer the second one, since it’t more robust to changes. You don’t have to change any line of code on the clear cache principle if you add a new property, standard or custom. In the first technique, the property value store and the compare have to change.

If you want to add a SPGridView to an Application Page in SharePoint and link it to a DataSource, you will have to write some extra code for sorting and filtering. I found quite some examples on different blogs, but still had to find out things on my own. Especially because I couldn’t find any code example that used a DataView as return type. So I’m not going to describe in detail the complete process, but only the steps related to a DataView return type.

In your Application Page you will have to add a reference to your assembly containing the DataSource. Next you add a tag for the DataSource, give it an ID and reference this ID in the SPGridView DataSourceID attribute. If you want sorting and filtering, like I do in this example, you set AllowSorting and AllowFiltering to true. For the filtering you have to define the FilterDataFields, FilteredDataSourcePropertyName and the FilteredDataSourcePropertyFormat. Suppose I only display four fields (Name, Type, Status and Description) of our DataSource and I only want to filter on Type and Status.

(more…)

While writing a tool for automated solution deployment accross a DOTAP street (Development, Ontwikkeling, Test, Acceptatie, Productie), I had to loop over the solutions in the farm. If a job existed for a solution, I had to acquire more information about the job and especially what kind of solution jobtype it was (Deploy, Retract or Upgrade). In debug (Quickwatch), I found an instance of SPSolutionDeploymentJobDefinition, but I was unable to cast my SPJobDefintion to a SPSolutionDeploymentJobDefinition. After some research with .NET Reflector, my mystery was solved. It was an internal sealed class.

Internal means the member is accessible to other types that are defined in the same assembly. A Sealed class is sort of the oppositie of abstract. It can be instantiated but cannot serve as a base class. The primary reason to seal a class is to prevent your users from fiddling around with it and breaking it. It’s also the case that sealing a class permits certain compiler optimizations that are not possible with non-sealed classes. Obviously, a class cannot be both sealed and abstract.

So conclusion, no cast and I had to find another way to do my thing.

Some error messages (not to say most) don’t say much at first glance. But when you take a closer look at what you just changed in your code, you mostly get a clue what could cause the error message.

I was adding a Regular Expression Validator to a textbox where I had to validate the input on illegal characters. When I deployed the solution, I got an error message telling me: ‘The server tag is not well formed.’ After some trial and error, I found out that a quote was causing the problem. I created a new solution with an ASP.NET Web Application to test out the regular expression ([^\|\\""\/:+\?\*#])+. Strange, no errors, everything was working fine and doing what it was supposed to do. So I searched the internet on more information about Regular Expressions in .NET and found that you can use HEX values as well. I changed the “” inside the ValidationExpression attribute to \x22 (22 is the HEX value for a quote). Build, deploy and argghhh still the same error message. I quickly scanned throught the code and found a quote in the ErrorMessage. You have the option to replace the quote with the HTML number (&#34;) or name (&quot;). Build, deploy and gone with the error message.

Code example:

<wssawc:InputFormTextBox ID="siteUrl" Runat="server" />
<wssawc:InputFormRegularExpressionValidator ID="ifrevSiteUrl"
  runat="server" ControlToValidate="siteUrl" Text="Error"
  Display="dynamic" EnableClientScript="true"
  ErrorMessage="An Url cannot contain any of the following
                characters: | \ &#34; / :  + ? * #"
  ValidationExpression="([^\|\\\x22\/:+\?\*#])+">
</wssawc:InputFormRegularExpressionValidator>

When you want to add a pager to a grid inside a WebPart to implement paging, you will notice that some event handlers can be left blank. Suppose you create a new SPGridView in the CreateChildControls, set paging to true and PageSize to something less than the amount of records you are going to add and you add that grid to the WebPart controls. Next you add a toolbar to the WebPart controls and create a new SPGridViewPager. Assign the Id of the grid to the GridViewId of the pager, add some handlers for the ClickNext and ClickPrevious event and add the control to the ToolBar controls. You can just implement the Click functions and leave them empty. In the OnPreRender you add the datasource to the grid and bind the grid. Easy to implement, nothing to worry about.

Code example:
Protected Overrides Sub CreateChildControls()
  grid = New SPGridView()
  grid.ID = Me.ID + “Grid”
  grid.AllowPaging = true
  grid.PageSize = 10

  pager = New SPGridViewPager()
  pager.ID = Me.ID + “Pager”
  pager.GridViewId = grid.ID
  AddHandler pager.ClickNext, AddressOf pager_ClickNext
  AddHandler pager.ClickPrevious, AddressOf pager_ClickPrevious

  gridToolBar = Page.LoadControl(“~/_controltemplates/ToolBar.ascx”)
  gridToolBar.RightButtons.Controls.Add(pager)

  Me.Controls.Add(grid)
  Me.Controls.Add(gridToolBar)
End Sub

Protected Overrides Sub OnPreRender(ByVal e as EventArgs)
  MyBase.OnPreRender(e)

  ‘No further details about the method below.
  ’It adds a datasource to the grid with more
  ’than 10 records.

  grid_AddDataSource()

  grid.DataBind()
End Sub

Private Sub pager_ClickNext(ByVal sender As Object, ByVal e As EventArgs)
  ‘Intentionally left blank, grid is binded in OnPreRender
End Sub
Private Sub pager_ClickPrevious(ByVal sender As Object, ByVal e As EventArgs)
  ‘Intentionally left blank, grid is binded in OnPreRender
End Sub

You will see that first the CreateChildControls is called, next a click handler and as last the OnPreRender. During the click handler, the current pageindex is updated automatically.

Suppose you have a list in SharePoint with a column ‘User’ of type ‘Person or Group’ in it and you are writing custom EventReceivers to add to the list (eg ItemAdded, ItemUpdated, etc). As long as you work on the properties.ListItem and you get the value from properties.ListItem.Item(“User”), you will receive a typical string formatted as <siteusers.id>;#<displayname>. This string can be easily split to separate the ID from the DisplayName. This DisplayName can be passed to SPWeb.EnsureUser() method to retrieve a SPUser object. This object can’t be passed to the UserProfileManager, so sometimes you even have to get the SPUser.LoginName to pass to the UserProfileManager.GetUserProfile() method to get a UserProfile object.

Code example:
Dim web As SPWeb = properties.OpenWeb()
Dim formattedUser As String = properties.ListItem.Item(“User”)
Dim splitUser() As String = formattedUser.Split(“#”)
Dim userDisplayName As String = splitUser(1)
Dim user As SPUser = web.EnsureUser(userDisplayName)

But as soon as you start working with the AfterProperties and BeforeProperties (of type SPItemEventDataCollection) and you get the value of ‘User’, you will notice that you only get the <siteusers.id> (without the DisplayName). You now have two options to continue:

  1. You get the value of ‘display_urn:schemas-microsoft-com:office:office#User’, which contains the DisplayName and continue like in the above example.
  2. Or, you work with the ID and you use the SPWeb.SiteUsers.GetByID() method to retrieve a SPUser object.

Code example:
Dim web As SPWeb = properties.OpenWeb()
Dim beforeProps As SPItemEventDataCollection = properties.BeforeProperties
Dim user As SPUser = web.SiteUsers.GetByID(beforeProps.Item(“User”))

Personally I would suggest to use the second way and rewrite the code above to get the ID out of the formatted string instead of the DisplayName. This way you can write a universal method where you pass the ‘User’ value without bothering about the format of the string you pass. If you can find a ‘;’ you have to split the string on the ‘;’ to retrieve the ID, otherwise you only have the ID in the string.

Recently I also wanted to add breadcrumb navigation to some Application Pages for SharePoint, which I was using in our Central Administration, like the out-of-the-box Application Pages have. But it didn’t worked out the way I wanted to. Google showed me the way to Jan Tielens’ blogpost about Adding breadcrumb navigation to SharePoint Application Pages, The easy way.

But after some research I found out that Application Pages that you want to show in the SharePoint Central Administration Site should be placed in different folders than normal Application Pages (in collaboration or publishing sites for example). When you are building up your 12-hive structure you should use 12\TEMPLATE\ADMIN for the Application Pages (*.aspx) and sitemap (admin.sitemap.*.xml) files, instead of the 12\TEMPLATE\LAYOUTS. If you mix up the .aspx and .sitemap files between the ADMIN and LAYOUTS folder, the breadcrumbs just don’t appear at all and if you place everything in the LAYOUTS folder, the breadcrumbs don’t work like you expect them to do. Further you have to reference the correct MasterPageFile from the ADMIN folder as well.

To deploy your feature and let SharePoint add the sitemap entries, you can use the methods described in Jan’s article.

(more…)

« Previous Page