Google Chart API

I can’t believe I have never seen this before – Google has a Chart API available publicly to display bar graphs, pie charts, and various other data visualization tools in HTML5. It’s clean, fast and judging from the examples quite easy to use.

Definitely going to use this on any future web projects that require any kind of graphs.

Link: https://developers.google.com/chart/interactive/docs/index

googlecharts

SharePoint Custom WebParts and list views

It looks as though I’m on a sharepoint-roll; this time I will explain how I put together a custom web part which essentially extends the existing functionality of a list view (SPListView) to execute a custom filter, which in my case required the list view to display only items made by the current user AND users in the same group as the current user (based on a group filter). So for example, let’s say you have a list of grades for a class that has group assignments. I want the list view to display only my own grades and my groupmates’ grades but ignore all others (not as a security thing, but as just a filter thing since I’d have read only access to all the grades in this example).

Starting Off

Add a new “Visual Web Part” to your solution. I think a plain old regular “Web Part” would work as well, but in the examples I had seen on the web it required a visual one, despite not making use of the “ascx” files. Open the feature in the project and add your web part to it – this will be required or else your web part will not show up in the web part gallery on SharePoint. Customize your web part’s name and description as needed then dive right into the override CreateChildControls method body. You will notice that by default there’s some code added for you to create a control based on the ASCX file in your project – remove that code, start with a blank method body. You can remove the privately-scoped variable referencing the ASCX file as well.

Custom Properties

You’ll want your web part to be customizable, so we need to add some properties to it. In my case, I wanted the list name, list view name, and a group prefix to filter on to be customizable. The following code will allow for this. Note that we have to actually define the privately scoped variables and update them, and have a default value or else the web part may cause problems when initially added to a page.

private string listName = "Docs";
[WebBrowsable(true),WebDescription("Set the list to get data from"),WebDisplayName("List Name"),Personalizable(PersonalizationScope.User)]
public string ListName
{
    get { return listName; }
    set { listName = value; }
}

private string listView = "";
[WebBrowsable(true), WebDescription("Set the name of the list view to use"), WebDisplayName("List View"), Personalizable(PersonalizationScope.User)]
public string ListView
{
    get { return listView; }
    set { listView = value; }
}

private string groupFilterPrefix = "";
[WebBrowsable(true), WebDescription("Filter based on this group name prefix"), WebDisplayName("Group Name Prefix"), Personalizable(PersonalizationScope.User)]
public string GroupFilterPrefix
{
    get { return groupFilterPrefix; }
    set { groupFilterPrefix = value; }
}

Now that we have our properties, we can start building the method body and make use of them.

Method Body

In summary you’ll want to do the following:

  1. Call CreateChildControls on the base
  2. Get the site and collection
  3. Create a blank new ListView control
  4. Set up the properties of the list view control as required
  5. Get the current user and all the users in the groups matching the ones of the current user (this is required because items only store the name of the user that created them, they do not store that user’s group – as a result, we will need to know all the relevant users we want to see data from, not just the groups they would be in).
  6. Update the existing selected view by querying it for only users we are interested in
  7. Display the results

Here’s the code I have for that:

private Microsoft.SharePoint.WebPartPages.ListViewWebPart lvSPListViewControl;
protected override void CreateChildControls()
{
	base.CreateChildControls();
	SPSite oSiteCollection = SPContext.Current.Site;
	SPWeb oWebSite = SPContext.Current.Web;
	lvSPListViewControl = new Microsoft.SharePoint.WebPartPages.ListViewWebPart();
	lvSPListViewControl.Visible = true;
	lvSPListViewControl.EnableViewState = true;
	SPList list = oWebSite.Lists[ListName];

	lvSPListViewControl.ListName = list.ID.ToString("B").ToUpperInvariant();
	lvSPListViewControl.WebId = list.ParentWeb.ID;

	lvSPListViewControl.ListId = (System.Guid)list.ID;

	SPView webPartView;

	if (ListView.CompareTo("") != 0)
	{
	webPartView = list.Views[ListView];
	}
	else
	{
	webPartView = list.DefaultView;
	}
	lvSPListViewControl.TitleUrl = webPartView.Url;

	lvSPListViewControl.HelpMode = WebPartHelpMode.Modeless;

	//get current user.
	SPUser currUser = oWebSite.CurrentUser;

	// Create a dictionary of users for which we want to see list items
	Dictionary<string,SPUser> usersToView = new Dictionary<string,SPUser>();

	// Always show the own user's items
	usersToView.Add(currUser.LoginName, currUser);

	//get all groups that the current user is a member
	if (currUser.Groups.Count > 0)
	{

	foreach (SPGroup g in currUser.Groups)
	{
		if (g.Name.Length >= groupFilterPrefix.Length && g.Name.Substring(0, groupFilterPrefix.Length) == groupFilterPrefix)
		{
		foreach(SPUser u in g.Users) {
			if(!usersToView.ContainsKey(u.LoginName)) {
			usersToView.Add(u.LoginName,u);
			}
		}
		}
	}

	SPUser[] userArray = new SPUser[usersToView.Values.Count];
	usersToView.Values.CopyTo(userArray, 0);

	//generate CAML query based on user groups.
	webPartView.Query = buildQuery(userArray);
	oWebSite.AllowUnsafeUpdates = true;
	webPartView.Update();
	oWebSite.AllowUnsafeUpdates = false;
	}

	lvSPListViewControl.ViewGuid = webPartView.ID.ToString("B").ToUpperInvariant();
	Controls.Add(lvSPListViewControl);
}
private string buildQuery(SPUser[] users)
{
	StringBuilder result = new StringBuilder();
	result.Append("");
	int totalUsers = users.Length;
	for(int i=0;i<users.Length;i++) {
	if (i < totalUsers - 1) result.Append("");
	result.AppendFormat("{0}", users[i].Name);
	}
	for (int i = 0; i < users.Length-1; i++)
	{
	result.Append("");
	}
	result.Append("");
	return result.ToString();
}

Note that for the CAML query we can’t use a logical operator on more than two operands. As a result, we have to chain them together as a binary tree. Here are CAML queries to find where the item was created by one, two, or three users:

One User

<Where><Eq><FieldRef Name="Author" /><Value Type="User">MyFirstUser</Value></Eq></Where>

Two Users

<Where><Or><Eq><FieldRef Name="Author" /><Value Type="User">MyFirstUser</Value></Eq><Eq><FieldRef Name="Author" /><Value Type="User">MySecondUser</Value></Eq></Or></Where>

Three Users

<Where><Or><Eq><FieldRef Name="Author" /><Value Type="User">MyFirstUser</Value></Eq><Or><Eq><FieldRef Name="Author" /><Value Type="User">MySecondUser</Value></Eq><Eq><FieldRef Name="Author" /><Value Type="User">MyThirdUser</Value></Eq></Or></Or></Where>

If you’re getting a generic SPException thrown when doing GetItems (or trying to check the ‘Count’ on the resulting SPListItemCollection) then it probably means your CAML query is invalid. You can check that from PowerShell by using Get-SPSite to get your site, finding the list in the resulting object’s “Lists” property – this will have all the views and items. Take a view you’re interested in, then update it’s query property and call list.GetItems(query). If you’re getting errors with the same CAML query as you’re using in your code then you know it is the query to blame.

Closing Thoughts

The generic SPException from the invalid CAML query was by far the most annoying part of figuring this out. The fact that there was no error message of any kind made it that much more confusing than it should have been.

SharePoint Custom Claims Provider

Recently one of our customers needed a way to move incoming claims from SiteMinder/ADFS into the user properties of the person logging in. In our case, it was critical to have the e-mail claim be passed into the “Work E-mail” user property on a private SharePoint portal. While it is true that there exists built-in functionality in SharePoint 2010 that will map user properties from Active Directory to user properties and synchronize them, it will not work for the ADFS authentication claims. In our case the users we were concerned with were not the users on our customer’s active directory server, but on an external server. For this reason, any incoming claims, including the e-mail claim, were just simply ignored by SharePoint.

Microsoft advised us to develop a custom claims provider, and the task ultimately came down to me. This was entirely alien to me as I had never done anything like this before, but I managed to pull it together in about a week’s time. Given how much documentation I had to search through to achieve this seemingly simple goal, I would like to save someone out there the trouble by disclosing some of the techniques I used to achieve this.

Understanding Claims-Based Authentication

There’s a decent Wikipedia page about claim-based identity that can help you out more on this but essentially it distills down to a system similar to OAuth where you have an authentication provider, a server that you provide credentials to which does not have anything to do with your service aside from the fact that you’re using their user database. Your client authenticates through that server and the server will provide your web service with whatever claims are configured (claims in this case being properties about the authenticating user that you wish to know or validate against).

Intercepting the default SharePoint authentication behavior for claims

After authenticating, the user will be redirected, along with a SAML document that is passed along, to the SharePoint site’s /_trust/ URL which will verify the token and run any customized claims processing. This is our entry point for the custom claims provider.

There’s another Wikipedia article about SAML (Security Assertion Markup Language) which you may find useful. It shows you some of the basic structure and flow used.

Overview of writing a Custom Claims Provider

The process is ultimately quite simple, after you get it.

  1. Create an empty SharePoint project in Visual Studio
  2. Create a class that implements the abstract SPClaimProvider
  3. Implement a couple of methods and properties (the main ones you will need are FillClaimsForEntity, and the DisplayName property)
  4. Add a SharePoint “feature” into the project and set up its properties
  5. Compile and deploy the application either through visual studio itself or by running some PowerShell magic. In my case, I had to make use of both since VS2010 is not exactly the most stable piece of software.

That’s essentially it. Sounds simple enough? Let’s get started.

The Set-up

In Visual Studio, create a new project in your solution. It should be an “Empty Sharepoint Project”. Create a class that will implement SPClaimProvider. At this point I won’t bother explaining too much detail because you will basically need to create a skeleton of all the methods you are going to implement. Follow the examples shown in the “Additional References” section below. Here’s the most important things out of what you will need to know, read it carefully!

  • The method that will be called for you when the user is authenticating is FillClaimsForEntity. This is your entry point to work with the authentication system and where you will put most of your logic.
  • The method FillClaimsForEntity will only be called if you have the property SupportsEntityInformation set to true.
  • In most situations when you use custom claims providers, you’re going to want to take some external claims and then add them to SharePoint claims. This is done by inserting them into the ‘claims’ list parameter that is passed along to you in FillClaimsForEntity. However, in my case I needed to update user properties with the data stored in the e-mail claim. If you are trying to accomplish something similar, you will not need that parameter.
  • Make sure your feature is set as a farm feature and not a web feature
  • Most of the other methods you will see in custom claim providers such as FillClaimTypes and FillClaimValueTypes you can just leave blank as for the functionality I described they are not needed. These two methods in specific that I just mentioned are required if you’re passing along the claims into SharePoint via the claims parameter.
  • All the “Supports” properties like “SupportsSearch” should be false except for the one I already mentioned, SupportsEntityInformation which should be true.

The Code

For obvious reasons I am not going to disclose the exact code I used as it is confidential to our customers but I will provide helpful snippets and links to some of the places I got them from.

The bulk of your code is going to be in the FillClaimsForEntity method, so let’s start off by looking at the process for it. At a high level you’re going to want to extract the incoming claims from the SAML document, locate the user associated with those claims in SharePoint, verify if any updates need to occur, and then perform the updates on the user properties that need updating. For the first part of these tasks, there is a very, incredibly helpful post about it on TechNet. Essentially, it distills down to using System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString to get the string representation of the SAML document with all the claims in it, then just navigating through  the XML DOM using System.Xml. There’s a full code snippet in that link. Some customizations I made were changing the incomingClaims into a dictionary instead of a list since we were only concerned with one claim of each type (e.g. only one e-mail) and found using a dictionary less bulky for retrieving the claim afterwards.

For the next part, you will need to know the username of the authenticating user. For this, I found the following useful snippet:

string userLoginEncoded = SPUtility.FormatAccountName("i", entity.Value);
string userLogin = SPClaimProviderManager.Local.DecodeClaim(userLoginEncoded).Value;

You may not need both for your purposes. In any case, userLoginEncoded represents the “i:…” format of username where as userLogin represents simply the username portion of it. To locate and update the user in Sharepoint we use the following code. Note that users don’t always have enough permissions to update their own properties, so you may need to run the code with elevated privileges like I have.

using (SPSite _site = new SPSite(*YOUR_SITE_URL_HERE*))
{
	using (SPWeb _web = _site.OpenWeb())
	{
		SPSecurity.RunWithElevatedPrivileges(delegate()
		{
			SPUser _myUser = _web.AllUsers[userLoginEncoded];
			if (String.Compare(_myUser.Email.ToLower(), incomingClaims[IncomingEmailClaimType].claimValue.ToLower()) != 0)
			{
				_myUser.Email = incomingClaims[IncomingEmailClaimType].claimValue;
				_myUser.Update();
			}
		});
	}
}

The rest of my methods are empty. I implement the Name property as per the name I wish to give my claim provider, and set SupportsEntityInformation to true, and the other Supports properties to false. Note that for the *YOUR_SITE_URL_HERE* you will need to provide the actual site URL and not just the host. Even though your SharePoint site may be rooted at http://whatever/, you will need to input http://whatever/sites/mysite/ instead here. To confirm that you have the right URL, you can use PowerShell and run Get-SPUser on the site you are implementing this for and see if the relevant users are there or not (e.g. if I’m trying to login as user1 and user1 does not exist in the list of users when I do Get-SPUser on http://foo/, then my code to find the user profile will also not work).

Make sure you add your Feature’s EventReceiver and update the properties in it (the instructions for this are outlined in MSDN).

Useful Tips for Debugging

  • Use Debug->Attach To Process and attach to all “w3wp.exe” processes (IIS app pool); this way you can make use of System.Diagnostics.Debug.WriteLine() in your code and see the output in Visual Studio.
  • When you run the “Deploy” command on your project, one of the steps VS will take is to recycle the application pool for your SharePoint website. However, claims and authentication are not run in the application pool of the SharePoint site but in a separate app pool called SecurityTokenApplication. In order to make sure your code is running up-to-date in SharePoint you will need to recycle that app pool as well manually.
  • When “Deploy”ing your application from VS you may encounter errors that the application pool could not be restarted because it couldn’t find the site. This may happen during the “Recycle Application Pool” step of deployment (in which case, try running Remove-SPClaimProvider in powershell before deploying) or during “activating features” in which case you don’t even need that step because farm features are automatically activated (so you can just change your deploy mode to “without activation” in that case).
  • You may want to increase the timeout period in IIS because while debugging you will probably hit the timeout often.

Useful tips for Deploying

In the SharePoint powershell, you can use Get-SPWebApplication to get the list of SharePoint applications. Locate the display name of the one you are developing for, and note the name. Then, execute Get-SPClaimProvider and find the claim provider you just wrote in the list (note that this will require doing Add-SPSolution (on the *.wsp project that will be in your project folder under /bin/*/*.wsp) and Add-SPClaimProvider beforehand). Given these two, you will then need to do something like the following to add your claim provider to the site you are working with:

$WebApplication = Get-SPWebApplication *YOUR_APPLICATION_NAME*
$IisSettings = $WebApplication.GetIisSettingsWithFallback("Internet")
$providers = $IisSettings.ClaimsProviders
$providers += *REFERENCE_TO_YOUR_PROVIDER_FROM_Get-SPClaimProvider*
Set-SPWebApplication -Identity $WebApplication -Zone "Default" -AdditionalClaimProvider $providers
Set-SPWebApplication -Identity $WebApplication -Zone "Internet" -AdditionalClaimProvider $providers

Additional References