Friday, December 19, 2008

“Value cannot be null” error on postback when using Grouping in SPGridView

When you use an SPGridview with grouping enabled, you will receive an exception whenever you perform a postback: Value cannot be null. Parameter name: container.

Patrick Rodgers came up with a solution which you can read here: http://www.thesug.org/blogs/patrickr/Lists/Posts/Post.aspx?ID=2

The solution is to create your own GridView that inherits from SPGridView and override LoadControlState to raise an event whenever the DataSource is null. The page or control that implement the GridView will need to implement an eventhandler and make sure the Grid is bound to it’s datasource.

Friday, November 28, 2008

Microsoft.SharePoint.WebControls.DateTimeControl ignores regional settings

When you use an instance of the Microsoft.SharePoint.WebControls.DateTimeControl in SharePoint (for example in a webpart), the locale will always be set to en-US, even if the web of user have specified a different locale.

To overcome this, you need to explicitly set the LocaleId property of the control to the correct locale.

DateTimeControl dtCtrl =new DateTimeControl();
dtCtrl.ID = "dtCtrl";
dtCtrl.LocaleId = (int)SPContext.Current.RegionalSettings.LocaleId;
this.Controls.Add(dtCtrl);


You should use SPContext.Current.RegionalSettings as this reflects the RegionalSettings for the current user (they might differ from the Web if the user modified this).

Wednesday, November 19, 2008

Sharepoint lookup with picker

Microsoft Sharepoint Server 2007 lookup control with element picker for custom list.
This control is useful if you need to choose lookup data from large lists. This control supports single and multi select mode.

http://www.codeplex.com/lookupwithpicker

Sunday, November 16, 2008

‘Title’ field and property not available on SPItem when using a View to access the SPListItemCollection

You can get an SPListItemCollection from a List using a View with the following code:

SPList list = SPContext.Current.Web.Lists["My List"];

SPListItemCollection items = list.GetItems(list.Views["All Items"]);

When then try to access the Title field of any of the individual SPListItem instances in the collection you will receive an ArgumentExecption


//all these statements will throw an Argument Exception
string titleField = item[0]["Title"];
string titleProperty = items[0].Title;
string titleValue = items[0].GetFormattedValue("Title");

As a workaround, you can access the Title field value using the LinkTitle field:


//this will return the correct value
string title = items[0].GetFormattedValue("LinkTitle");

Ajax Loader Icons Generator

The site http://www.ajaxload.info allows you to freely generate and download ajax loader images. You can choose style, foreground color and background color to create a wide variety of icons. You can also set the background color to ‘transparent’.

ajax-loader      ajax-loader2      ajax-loader3

http://www.ajaxload.info

Wednesday, October 29, 2008

Determine AD Group membership in SQL server

Today, one of the customers I work for, asked me to review the Authentication method used in 2 applications. The group membership of the application's users was stored in tables on a SQL Server and the application called an Stored Procedure to determine if a user was a member of a specific group by passing the username of the logged in user and groupname. To make the applications easier to maintain, the customer preferred to use Active Directory groups. As I did not want to change the code of the application (and test it, and deploy the new version), I thought about modifying the Stored Procedures so they would query the Active Directory instead of the database. As the applications use SQL Server 2000, writing a CRL routine and using that from the SQL Server was not an option. Luckily an provider exists allowing you to connect to Active Directory: the 'OLE DB Provider for Microsoft Directory Services'. I started with adding a Linked server to the SQL server name 'ActiveDirectory' and defined that connections must be made by an existing user account with sufficient access rights to Active Directory. Next I played around a bit in Query Analyzer and managed to execute some simple queries on AD For example, to return all the users for the domain 'somedomain.local' you would execute

SELECT * FROM openquery(ActiveDirectory, 'select ADsPath, sAMAccountName FROM ''LDAP://dc=somedomain,dc=local'' where objectCategory = ''Person'' and objectClass= ''user'' ')

The openquery statement has one major drawback: you have to provide a string contstant for the query and cannot use variables. You have to use 'sp_executesql' instead. Also, in Active Directory, it is possible to have nested groups (i.e. a group within a group). To make sure all the users that belong to certain group are returned, a recursive method was needed. After banging my head on my desk several times to get the quoting right, I ended up with the 2 stored procedures listed below. The stored procedure 'sp_getADGroupUsers' expects 2 parameters:

  • @LDAPRoot: the root of the Active Directory domain. (e.g. 'ldap://dc=somedomain,dc=local/')
  • @FriendlyGroupName: the name of the group to list the users for (e.g. 'domain users').

The basic flow of the SP is as follows:

  • Create a temp table (#ADSI) to stored the users while the other Stored Procedures is called recursively
  • Determine the full ADsPath for the specified group
  • Call 'sp_getadgroupusers_recursive' passing the LDAPRoot and the full ADsPath

The Stored Procedure 'sp_getadgroupusers_recursive' expects 2 parameters:

  • @LDAPRoot: the root of the Active Directory domain.
  • @group_name: the full ADsPath (without the LDAP:// portion) of the group to list the users for.

The basic flow of the SP is as follows:

  • Insert all the users of the specified group into the temp table (#ADSI)
  • Create a cursor with all the Groups within the specified group
  • Call 'sp_getadgroupusers_recursive' (itself) for each childgroup

 

sp_getADGroupUsers (click 'expand source' to open)

CREATE PROCEDURE [dbo].[sp_getADGroupUsers]
@LDAPRoot nvarchar(200),
@FriendlyGroupName nvarchar(200)

AS

BEGIN
DECLARE @SQLString nvarchar(4000)
DECLARE @group_name nvarchar(500)
--Create a temp table to hold the results
CREATE TABLE #ADSI (
ADsPath nvarchar(500),
SamAccountname nvarchar(100))

--build an SQL string to return the ADsPath for the requested group
--(the 'LDAP://' part will be stripped off)
set @SQLString = N'SELECT @group_name_int = SUBSTRING(ADsPath,8,LEN(ADsPath))
FROM OPENQUERY(ACTIVEDIRECTORY,''SELECT ADsPath FROM ''''' + @LDAPRoot + '''''
WHERE SamAccountname=''''' + @FriendlyGroupName + ''''' '')'

--Execute the statement (will return the ADsPath in @group_name
EXEC sp_executesql @SQLString,
N'@group_name_int nvarchar(200) OUTPUT',
@group_name_int=@group_name OUTPUT

--if the group_name is null, give up (raise error?)
IF @group_name is null
BEGIN
RETURN -1
END

--recursively get all the users for the group
EXEC sp_getadgroupusers_recursive @LDAPRoot, @group_name

--return the resultset
--(use DISTINCT as a user might be a member of several groups)
SELECT DISTINCT * FROM #ADSI

--explicitely drop the temp table
DROP TABLE #ADSI

END

sp_getadgroupusers_recursive (click 'expand source' to open)


CREATE PROCEDURE [dbo].[sp_getadgroupusers_recursive]
@LDAPRoot nvarchar(200),
@group_name nvarchar(500)

AS

BEGIN
DECLARE @SQLString nvarchar(4000)
DECLARE @LDAPPath nvarchar(500)

--build an statement to insert all the user from the specified
--group into the temp table (temp table is created in parent sp)
SET @SQLString = N'INSERT INTO #ADSI SELECT ADsPath,SamAccountname
FROM OPENQUERY(ACTIVEDIRECTORY,''SELECT ADsPath,
SamAccountname FROM ''''' + @LDAPRoot + '''''
WHERE objectCategory = ''''Person'''' and objectClass = ''''user''''
and memberOf=''''' + @group_name + ''''' '') ';

--execute the statement
EXECUTE sp_executesql @SQLString;

--declare a variable to hold a cursor (need a variable because we
--need a Local cursor since the SP is called recursively)
DECLARE @group_cursor CURSOR

--Load all the groups within the specified group into the cursor
SET @SQLString = N'SET @group_cursor = CURSOR STATIC FOR
SELECT SUBSTRING(ADsPath, 8, 200)
FROM OPENQUERY(ACTIVEDIRECTORY,''SELECT ADsPath
FROM ''''' + @LDAPRoot + '''''
WHERE objectClass = ''''group''''
and memberOf=''''' + @group_name + ''''' '')
FOR READ ONLY; OPEN @group_cursor';

--execute
EXECUTE sp_executesql @SQLString, N'@group_cursor CURSOR OUTPUT',
@group_cursor OUTPUT

--loop through the Cursor (it's opened in the sp_excecutesql statement above)
--the only column in the cursor contains the ADsPath with the 'LDAP://' part
--already stripped off)

FETCH NEXT FROM @group_cursor INTO @LDAPPath
WHILE @@FETCH_STATUS = 0
BEGIN
--recursive call using the 'inner group'
EXEC sp_getadgroupusers_recursive @LDAPRoot, @LDAPPath
--get next
FETCH NEXT FROM @group_cursor INTO @LDAPPath
END

--clean up cursor
CLOSE @group_cursor
DEALLOCATE @group_cursor
END

Tuesday, October 28, 2008

Upgrading ContentType Features

Upgrading contenttypes after they are deployed using a feature is quite a challenge. According to this MS article (http://msdn.microsoft.com/en-us/library/ms479975.aspx), it's a big no-no to modify the Content Type definitions after deployment.

Under no circumstances should you update the content type definition file for a content type after you have installed and activated that content type. Windows SharePoint Services does not track changes made to the content type definition file. Therefore, you have no method for pushing down changes made to site content types to the child content types.

Off course, from a developers perspective, this is major drawback because it makes tasks that should be very trivial (like adding a column) virtually impossible.

After searching around the internet for a solution on how to update content types, I stumbled upon this post on Gary Lapointe's STSADM blog.

Gary created an STSADM extenstion based on a post by Søren Nielsen that will update a content type by comparing all the fields in the content type in the lists where the content type is used.

The code works great, but is a bit limited as you have to run the stsadm command for each ContentType that you have deployed (of course, you could script the commands).

I've created an SPFeatureReceiver that uses Gary's/Søren's code to update all the ContentTypes that are deployed using the feature.

To update the ContentTypes, I just have to re-install the (updated) feature and re-activate it.

I will post the source as soon a I find some extra spare time.

PagerRow not showing in SPGridView when setting AllowPaging=true

When you enable paging in an SPGridView, the PagerRow doesn't show up by default. To work around this, you have to set the PagerTemplate to 'null' AFTER adding the control to the controls collection but BEFORE calling DataBind().

e.g.

protected override void CreateChildControls() {
SPGridView grid = new SPGridView;
grid.AllowPaging=true;
this.Controls.Add(grid);
//set PagerTemplate to null
grid.PagerTemplate = null;
grid.PageIndexChanging +=
new GridViewPageEventHandler(grid_PageIndexChanging);
grid.DataBind();
}

Friday, October 24, 2008

Showing the StackTrace in SharePoint

Instead of being stuck with the default errors in sharepoint (usually 'unexpected error'), you can force SharePoint to show the StackTrack.

To do so, you must make 2 changes to the web.config file of each site where you wish to enable this.

First of all, set the 'Mode' value of the 'CustomErrors' element to 'Off' (or 'RemoteOnly')

Second, set the 'CallStack' value for the 'SafeMode' element to 'true'

Thursday, October 23, 2008

Error 'SPSearch ([accountname]) when trying to activate WSS Search

I ran into the error shown below when I tried to activate WSS Search on my development server:  When examining the WSS Log file, I found the following error:

The call to SPSearchServiceInstance.Provision (server '[servername]') failed. Setting back to previous status 'Disabled'. System.ComponentModel.Win32Exception: SPSearch (mossadmin) at Microsoft.SharePoint.Win32.SPAdvApi32.ChangeServiceConfiguration(String strServiceName, String strAccountName, SecureString sstrPassword, IdentityType identityType, Boolean bDontRestartService) at Microsoft.SharePoint.Administration.SPProvisioningAssistant.ProvisionProcessIdentity(String strUserName, SecureString secStrPassword, IdentityType identityType, Boolean isAdminProcess, Boolean isWindowsService, String strServiceName, Boolean dontRestartService) at Microsoft.SharePoint.Administration.SPProcessIdentity.ProvisionInternal(SecureString sstrPassword, Boolean isRunningInTimer) at Microsoft.SharePoint.Administration.SPProcessIdentity.Provision() at Microsoft.SharePoint.Administration.SPWindowsServiceInstance.ProvisionCredentials() at Microsoft.SharePoint.Administration.SPWindowsServiceInstance.Provision(Boolean start) at Microsoft.SharePoint.Administration.SPWindowsServiceInstance.Provision() at Microsoft.SharePoint.Search.Administration.SPSearchServiceInstance.Provision() at Microsoft.SharePoint.Search.Internal.UI.SPSearchServiceInstanceSettings.BtnSubmit_Click(Object sender, EventArgs args)

After fiddeling around with the settings, I figured out that you must enter the name of the service and content accounts in the format [SERVERNAME]\[AccountName].

Wednesday, July 23, 2008

Adding a 'Send to Gac' option to the shortcut menu

When working on assemblies that are deployed to the GAC, copying a new version to the GAC is something you do several times a day.

To speed this process up, having a 'send to gac' option on the context menu of a dll file can be very handy.

Use the following reg file to add the 'send to gac' option to the context menu. Note that also an 'open' option is added without a command specified. This is to have a default (i.e. doubleclick) action to avoid sending a dll to the GAC when you accidentally doubleclick on it.

The path of the gacutil.exe utility may differ. You might prefer to add the location of the gacutil.exe utility to the PATH environment variabele and simply use @="gacutil.exe /i \"%1\"" as the command.

Windows Registry Editor Version 5.00 
[HKEY_CLASSES_ROOT\dllfile\shell]
@="Open"
[HKEY_CLASSES_ROOT\dllfile\shell\Open]
@="&Open"
[HKEY_CLASSES_ROOT\dllfile\shell\Open\command]
@=""
[HKEY_CLASSES_ROOT\dllfile\shell\Send to GAC]
@="Send to &Gac"
[HKEY_CLASSES_ROOT\dllfile\shell\Send to GAC\command]
@="\"C:\\Program Files\\Microsoft SDKs\\Windows\\v6.0A\\Bin\\gacutil.exe\" /i \"%1\""

Thursday, July 10, 2008

Zeta Resource Editor

This is a free, open source utility application that enables you to edit string resources from multiple different resource files together inside one single data grid. See the website here

Friday, May 16, 2008

Troubleshooting VSTO Add-ins

During development By default, if an add-in created with Visual Studio Tools for Office throws an exception, the Office application continues without displaying the exception. Set the debugger to break on all exceptions to see when add-in exceptions are thrown. After deployment VSTO can write all errors that occur during startup to a log file or display each error in a message box. By default, these options are turned off for application-level projects. You can turn the options on by adding and setting environment variables. To display each error in a message box, set the VSTO_SUPPRESSDISPLAYALERTS variable to 0 (zero). You can suppress the messages by setting the variable to 1 (one). To write the errors to a log file, set the VSTO_LOGALERTS variable to 1 (one). Visual Studio Tools for Office creates the log file in the folder that contains the application manifest. The default name is .manifest.log. To stop logging errors, set the variable to 0 (zero).

Thursday, April 17, 2008

Virtual PC and MAC addresses

When you copy a VPC, only copy the VHD images and do no copy and reuse the VMC file. When a VMC is created, VPC generates a MAC address for the virtual ethernet adapter(s), and stores this in the VMC file. If you copy the VMC file, and used networking with your VPC, you will end up with duplicate MAC address within your network.

Monday, January 7, 2008

Connect to the Console using Remote Desktop Connection

Whenever you are using Remote Desktop to manage your server, you will sooner or later run into the problem that the maximum number of connections has been reached (Windows Server 2003 allows 2 RDP connections active). Usually, you'll end up blaming your co-workers for not logging of. Fortunately, RDP allows you to connect directly to the console, instead of using another sessoin. To force RDC to connect to the console start it via the command line (start > run): mstsc /console then select the computer to connect to as normal. Note that connecting to the console session will take over the session from whoever is currently logged in.