Simplify reading and writing field values of type SPFieldUser, SPFieldUrl and SPFieldLookup using extension methods
To read or write values to fields of type SPFieldUser or SPFielLookup is an annoying task because you have to create field value objects bevore you can write your values into a field. The following post descripes how you can encapsulate those tasks to extension methods and slim your code to make it more readable.
SPFieldUser
The following code displays a couple of extension methods extending the SPListItem class for simple reading and writing access to fields of type SPFieldUser.
using System; using System.Collections.Generic; using Microsoft.SharePoint; namespace ExtensionMethods { public static class SPListItemExtensions { /// <summary> /// Returns the login name of an User-Field. /// </summary> public static string GetFieldValueUserLogin(this SPListItem item, string fieldName) { if (item != null) { SPFieldUserValue userValue = new SPFieldUserValue( item.Web, item[fieldName] as string); return userValue.User.LoginName; } else { return string.Empty; } } /// <summary> /// Sets the value of a User-Field to a login name. /// </summary> public static void SetFieldValueUser(this SPListItem item, string fieldName, string loginName) { if (item != null) { item[fieldName] = item.Web.EnsureUser(loginName); } } /// <summary> /// Sets the value of a User-Field to an SPPrincipal /// (SPGroup or SPUser). /// </summary> public static void SetFieldValueUser(this SPListItem item, string fieldName, SPPrincipal principal) { if (item != null) { item[fieldName] = principal; } } public static void SetFieldValueUser(this SPListItem item, string fieldName, IEnumerable<SPPrincipal> principals) { if (item != null) { SPFieldUserValueCollection fieldValues = new SPFieldUserValueCollection(); foreach (SPPrincipal principal in principals) { fieldValues.Add( new SPFieldUserValue( item.Web, principal.ID, principal.Name)); } item[fieldName] = fieldValues; } } /// <summary> /// Sets the value of a multivalue User-Field to /// a list of user names. /// </summary> public static void SetFieldValueUser(this SPListItem item, string fieldName, IEnumerable<string> loginNames) { if (item != null) { SPFieldUserValueCollection fieldValues = new SPFieldUserValueCollection(); foreach (string loginName in loginNames) { SPUser user = item.Web.EnsureUser(loginName); fieldValues.Add( new SPFieldUserValue( item.Web, user.ID, user.Name)); } item[fieldName] = fieldValues; } } } }
The following lines of code demonstrate how these extension methods can be used.
using ExtensionMethods; SPList list = web.Lists["MyList"]; SPListItem item = list.Items[0]; // Get a user login of a SPFieldUser field string userLogin = item.GetFieldValueUserLogin("Author"); // Set a SPFieldUser field to a user using the login item.SetFieldValueUser("Person", "alexander.bruett"); // Set a SPFieldUser field that allows multiple values to a list of users string[] persons = { "alexander.bruett", "paul.panzer" }; item.SetFieldValueUser("Mehrere Personen", persons); // Set a SPFieldUser field to a SPUser SPUser user = web.Users[1]; item.SetFieldValueUser("Person", user); // Set a SPFieldUser field to a list of SPPrincipal SPGroup group = web.Groups[0]; SPUser user = web.Users[0]; SPUser user2 = web.EnsureUser("alexander.bruett"); SPUser user3 = web.EnsureUser("Domain Users"); ; SPPrincipal[] principals = { group, user, user2, user3 }; item.SetFieldValueUser("AssignedTo", principals);
Especially setting fields that allow multiple values now needs only a couple of code lines.
SPFieldLookup
The SPFieldLookup field is a little bit more complicated if you want to set the field only by the lookup string. You have to query the ID of the lookup value before you can set the field value. The following extension methods simplify this task.
/// <summary> /// Returns the value of a Lookup Field. /// </summary> private static string GetFieldValueLookup(this SPListItem item, string fieldName) { if (item != null) { SPFieldLookupValue lookupValue = new SPFieldLookupValue(item[fieldName] as string); return lookupValue.LookupValue; } else { return string.Empty; } } /// <summary> /// Returns the value of a Lookup-Field with multiple values. /// </summary> public static IEnumerable<string> GetFieldValueLookupCollection( this SPListItem item, string fieldName) { List<string> result = new List<string>(); if (item != null) { SPFieldLookupValueCollection values = item[fieldName] as SPFieldLookupValueCollection; foreach (SPFieldLookupValue value in values) { result.Add(value.LookupValue); } } return result; } /// <summary> /// Returns the SPFieldLookupValue instance of a lookup value. /// The ID value will be obtained using SPQuery. /// </summary> private static SPFieldLookupValue GetLookupValue( SPWeb web, SPFieldLookup field, string lookupValue) { string queryFormat = @"<Where> <Eq> <FieldRef Name='{0}' /> <Value Type='Text'>{1}</Value> </Eq> </Where>"; string queryText = string.Format(queryFormat, field.LookupField, lookupValue); SPList lookupList = web.Lists[new Guid(field.LookupList)]; SPListItemCollection lookupItems = lookupList.GetItems(new SPQuery() { Query = queryText }); if (lookupItems.Count > 0) { int lookupId = Convert.ToInt32(lookupItems[0][SPBuiltInFieldId.ID]); return new SPFieldLookupValue(lookupId, lookupValue); } else { return null; } } /// <summary> /// Sets the value of a Lookup-Field. /// </summary> public static void SetFieldValueLookup( this SPListItem item, string fieldName, string lookupValue) { if (item != null) { SPFieldLookup field = item.Fields.GetField(fieldName) as SPFieldLookup; item[fieldName] = GetLookupValue(item.Web, field, lookupValue); } else { item[fieldName] = null; } } /// <summary> /// Set the values of a Lookup-Field with multiple values allowed. /// </summary> public static void SetFieldValueLookup(this SPListItem item, string fieldName, IEnumerable<string> lookupValues) { if (item != null) { SPFieldLookup field = item.Fields.GetField(fieldName) as SPFieldLookup; SPFieldLookupValueCollection fieldValues = new SPFieldLookupValueCollection(); foreach (string lookupValue in lookupValues) { fieldValues.Add( GetLookupValue(item.Web, field, lookupValue)); } item[fieldName] = fieldValues; } }
That’s been a lot of code but take a look at the following lines of code. Especially the setting task becomes very easy now.
// Reads the value of a SPFieldLookup field string value = item.GetFieldValueLookup("LookupFieldName"); // Read lookup values of a SPFieldLookup field // with multiple values allowed IEnumerable<string> values = item.GetFieldValueLookupCollection("MultiLookupFieldName"); // Set the value of a SPFieldLookup field item.SetFieldValueLookup("LookupFieldName", "LookupValue"); //Set the value of a SPFieldLookup field with multiple values allowed string[] values = {"Hamburg", "London" }; item.SetFieldValueLookup("MultiLookupFieldName", values);
SPFieldUrl
Setting and getting SPFieldUrl field values is very simple:
/// <summary> /// Returns the value of an Url-Field. /// </summary> public static string GetFieldValueUrl( this SPListItem item, string fieldName) { if (item != null) { SPFieldUrlValue urlValue = new SPFieldUrlValue(item[fieldName] as string); return urlValue.Url; } else { return string.Empty; } } /// <summary> /// Sets the value of an URL-Field. /// </summary> public static void SetFieldValueUrl(this SPListItem item, string fieldName, string url, string description) { if (item != null) { item[fieldName] = new SPFieldUrlValue() { Description = description, Url = url }; } }
The following lines of code show how to get and set values of the SPFieldUrl field using the extension methods above:
// Get the url of a SPFieldUrl field string url = item.GetFieldValueUrl("URL"); // Set the url and description of a SPFieldUrl field item.SetFieldValueUrl("URL", "http://www.alexbruett.net", "Alex' Blog");
Conclusion
Using extension methods to simplify the access to complex and SharePoint list fields makes your code more readable and reduces the lines of code.
You could also use helper classes instead of extension methods but you will have to write more code to call a helper method instead of calling a extension method.
Februar 9th, 2009 at 5:40 am
Some great examples! One suggestion…for GetFieldValueLookupCollection you could use an iterator instead of allocating your own list:
SPFieldLookupValueCollection values =
item[fieldName] as SPFieldLookupValueCollection;
foreach (SPFieldLookupValue value in values)
yield return value.LookupValue;
Or just use LINQ:
return from SPFieldLookupValue v in values select v.LookupValue;
Cheers ~
Keith
März 31st, 2009 at 5:05 am
You may need to add the code below, in case you get an error message (Cannot define a new extension method because the compiler required type ‘System.Runtime.CompilerServices.ExtensionAttribute’ cannot be found.)
namespace System.Runtime.CompilerServices {
[AttributeUsage(AttributeTargets.Method)]
public class ExtensionAttribute : Attribute {
}
}
April 9th, 2009 at 7:48 am
It’s really nice to see SharePoint Developers working with the extension methods to enhance the SharePoint Object Model! Great work. I have added this code snippet (referencing this post) to the SharePointDevWiki.com as it’ll be great to get some collaboration on other methods that could be added!
(let me know if you want me to take it down)
Thanks!
April 15th, 2009 at 8:42 pm
Hi Alex,
Sharepoint lookup field mapping to a remote lookup list
Doing research and experimenting with a lookup field I’ve found that a lookup field (a site column which gets their data from a list) and a lookup list (from which a lookup field gets their data) must belong to the same site (or different sites if sites are a parent and child) within a site collection. There are also the cross-sites lookup field solutions where a lookup field and a lookup list might belong to different sites but again within the same website/site collection. All these known implementations do not allow separate a lookup field and a lookup list meaning to have them on different web applications and working together.
Is this correct or am I missing something?
There might be another scenario where a lookup field should be deployed on a few hundred client’s sites and these clients’ lookup fields must get their data from a remote lookup list being resided on separate admin web application. In this case remote lookup list might be maintained at one central place (admin web app) and feeds updated data for all clients’ lookup fields. The point here is to avoid creation of hundreds instances of a remote lookup list on the clients’ sites. Client sites’ lookup fields should map directly to a remote lookup list being resided on separate admin web application.
Any idea, advise, discussion or references about a lookup field mapping to a remote lookup list implementation using CAML/C#/webservices will be appreciated.
Thanks.
Vadim.
April 16th, 2009 at 12:09 am
Hi Vadim,
if you want to create a cross-site lookup field you have to create a custom lookup field based on the SPFieldLookup field. In that custom field you can offer the user to configure the website-url and a list name for the lookup list and use these values to fetch the data from that list.
If you haven’t created a custom field yet, try it
http://msdn.microsoft.com/en-us/library/bb684919.aspx
I’d also give a shot on the Business Data Catalog. I haven’t used it already but I once saw a Web Cast where a guy showed how to import an external data source into SharePoint and use its data as a lookup column. Everything without coding.
Maybe this information helps you to go into the right direction
Regards,
Alex
April 24th, 2009 at 8:02 am
I just wanted to drop a line letting you know how much this has helped me. I’m in the process of creating a Sharepoint ORM tool and was currently handling all three of these fields incorrectly. Keep up the good work.
Regards,
Ramone
Juni 7th, 2009 at 6:42 pm
Your post was extremely helpful in getting other code to work. I apologise for bothering you with a problem, but this custom field code (to allow selection of only groups that does not appear to be available any other way) I cannot get it to work: on new it just creates a value that displays like 10;#
I am am ashamed that this is VB code but I have not bitten the bullet to change to c# yet:
Option Explicit On
Option Strict On
Imports System
Imports System.Runtime.InteropServices
Imports Microsoft.SharePoint
Imports Microsoft.SharePoint.WebControls
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls
Imports Microsoft.VisualBasic
Namespace pccCustomFields2
‘ TODO: Replace, as needed, “TextField” with some other class derived from Microsoft.SharePoint.WebControls.BaseFieldControl.
_
_
Public Class pccAllGroupsFieldControl
Inherits UserField
Private glist As ListBox
Private table As HtmlTable
Protected Overrides Sub CreateChildControls()
MyBase.CreateChildControls()
Me.table = New HtmlTable
Dim row As New HtmlTableRow
table.Rows.Add(row)
Dim cell As HtmlTableCell = Nothing
If Me.ControlMode = SPControlMode.Edit Or Me.ControlMode = SPControlMode.[New] Then
cell = New HtmlTableCell
cell.ColSpan = 2
cell.Attributes(“class”) = “ms-formdescription”
cell.InnerText = “Choose a group:”
row.Cells.Add(cell)
row = New HtmlTableRow
cell = New HtmlTableCell
Me.glist = New ListBox
With Me.glist
Dim site As SPSite = SPContext.GetContext(Me.Context).Site
For Each g As SPGroup In site.RootWeb.SiteGroups
.Items.Add(g.Name)
Next
End With
If IsNothing(Me.ItemFieldValue) Then
If Me.glist.Items.Count > 0 Then
Me.glist.SelectedIndex = 0
End If
Else
Dim lsName As String = CType(Me.ItemFieldValue, SPFieldUserValue).User.Name
If Not String.IsNullOrEmpty(lsName) Then
Me.glist.SelectedValue = lsName
End If
End If
cell.Controls.Add(Me.glist)
row.Cells.Add(cell)
table.Rows.Add(row)
End If
cell = New HtmlTableCell
Dim lit As New LiteralControl
Dim group As String = String.Empty
Dim gObj As Object = Me.ItemFieldValue
If Not IsNothing(gObj) Then
If Not IsNothing(CType(gObj, SPFieldUserValue).User) Then
group = CType(gObj, SPFieldUserValue).User.Name
End If
End If
cell.Controls.Add(lit)
row.Cells.Add(cell)
MyBase.Controls.Add(table)
End Sub
Protected Overrides Sub Render(ByVal output As System.Web.UI.HtmlTextWriter)
Me.table.RenderControl(output)
End Sub
Public Overrides Sub UpdateFieldValueInItem()
Me.EnsureChildControls()
If glist.Items.Count > 0 Then
Dim site As SPSite = SPContext.GetContext(Me.Context).Site
For Each g As SPGroup In site.RootWeb.SiteGroups
If g.Name = glist.SelectedValue Then
Dim lsValue As String = g.ID & “;#” & g.Name
Dim x As New SPFieldUserValue(site.RootWeb, g.ID, g.Name)
‘ Me.Value = lsValue
Me.Value = x
MyBase.UpdateFieldValueInItem()
Exit For
End If
Next
End If
End Sub
End Class
End Namespace
Juni 12th, 2009 at 11:50 am
fantastic. this is what i was looking for. for hours i was stranded. i wanted to update values in a look up field. jus the cod i was looking for
keep going.. great !!!!!!!!! expect much more code snippets from u
Februar 9th, 2010 at 9:21 am
Your post is really helpful. It saves me a lot of time in reading and setting the values of a Lookup-Field with multiple values allowed. Thank you!
April 6th, 2010 at 1:50 am
Hi Alex,
Firstly – very cool stuff.
I do have a question. The extension methods are doing a null check on the item for which they are extending.
Is that necessary? If the item itself is null, the calling of the method is impossible anyway. Is there a Microsoft “Best Practice for this scenario?”
Mai 13th, 2010 at 3:48 pm
Nice work, thanks for sharing.
-mano