SharePoint 2013开发入门探索(二)- 列表操作
我们如何用代码对SharePoint列表做些例如增删改查的操作呢?如果您的程序可以部署到服务器上,就可以使用 服务器对象模型,因为服务器对象模型提供的功能最多,限制最少;否则可能要选择客户对象模型等其他方式,这可能会遇到一些功能限制;另外还有一些其他的访问方式,例如Web服务等。如何在 SharePoint 2013 中选择正确的 API 集请参考链接 http://msdn.microsoft.com/zh-cn/library/jj164060.aspx。
我们首先研究下服务器对象模型。使用服务器对象模型需要到服务器端部署,所以没有服务器部署权限的话就不能使用了。使用服务对象模型要引用程序集Microsoft.SharePoint —— 如果您创建的是SharePoint项目,这个引用默认就会有的。
在服务器对象模型中我经常使用的几个.NET类是SPSite、SPWeb、SPList还有SPListItem,下面简单介绍下这几个类。
网站集(由一个首要网站以及该网站下的所有网站组成,参考:http://technet.microsoft.com/zh-cn/library/cc262410.aspx)对应的类是SPSite,可以传参网站集URL给SPSite的构造方法创建对象。一般您可以这样用。
using (SPSite spSite = new SPSite("http://SharePointServer/")) { //TODO...... }
有时候你可能要提升一下权限以场管理员身份执行,则可以用下面的写法——这是服务器对象模型的优势,但是如果项目选择为“部署为沙盒解决方案”则不能提升到场管理员权限。
SPSecurity.RunWithElevatedPrivileges(() => { using (SPSite spSite = new SPSite("http://SharePointServer/")) { //TODO...... } });
SharePoint里的用户对应的是SPUser类,也可以使用SPUserToken构造网站集对象实现模拟用户身份执行代码。
SPSecurity.RunWithElevatedPrivileges(() => { using (SPSite spSite = new SPSite("http://SharePointServer/")) { SPUser imitatedUser = spSite.RootWeb.EnsureUser(@"contoso\sanzhang"); SPUserToken token = imitatedUser.UserToken; using (SPSite siteWithUser = new SPSite("SharePointServerUrl", token)) { //TODO...... } } });
网站对应的类是SPWeb,SPSite有一个RootWeb属性,是网站集的根网站;SPSite还有个AllWebs属性,是它的所有网站集合(可以用索引访问),要得到SPWeb对象还可以调用SPSite对象的OpenWeb方法,传给一个URL参数(根网站的URL和网站集是相同的,所以下面的代码传空字符串,其他子网站要传标识网站的URL部分)。每个SPWeb对象也有个Webs属性,是这个网站的子网站(也可以用索引访问)。
using (SPSite spSite = new SPSite("http://SharePointServer/")) { //获取网站集根网站对象 SPWeb spRootWeb = spSite.RootWeb; Console.WriteLine("网站集的根网站是{0}", spRootWeb.Title); //用网站ID获取子网站对象 Guid webId = new Guid("{4a106421-ae78-40fd-ad62-77fecb67cf27}"); SPWeb spWeb = spSite.AllWebs[webId]; Console.WriteLine("该网站是{0}", spWeb.Title); spWeb = spRootWeb.Webs[webId]; Console.WriteLine("该网站是{0}", spWeb.Title); //用网站名称获取子网站对象 string name = "mysubweb1"; spWeb = spSite.AllWebs[name]; Console.WriteLine("该网站是{0}", spWeb.Title); spWeb = spRootWeb.Webs[name]; Console.WriteLine("该网站是{0}", spWeb.Title); //用网站URL获取子网站对象 string url = @"/mysubweb1"; spWeb = spSite.OpenWeb(url); Console.WriteLine("该网站是{0}", spWeb.Title); }
获取对网站和其他关键对象的引用请参考:http://msdn.microsoft.com/zh-cn/library/ms468609(v=office.14).aspx。
列表对应的类是SPList,其实SPList类对应的不仅仅是狭义的自定义列表,文档库、图片库、日历、通知、任务都可以用它来操作,还有网站资产、用户信息列表、解决方案库、列表模版库、Web部件库等等。SPWeb对象有个Lists属性,是SPListCollection类型,遍历它或者用ID、Title做索引可得到SPList对象。想了解网站中有哪些List可以用下面的代码查看:
//获取网站所有列表 foreach (SPList list in spWeb.Lists) { Console.WriteLine("Title:{0} BaseType:{1} BaseTemplate:{2}", list.Title, list.BaseType, list.BaseTemplate); } //用列表标题获取列表对象 string title = @"我的客户列表"; SPList spList = spWeb.Lists[title]; Console.WriteLine("该列表是{0}。", spList.Title); //用列表ID获取列表对象 Guid id = new Guid(@"{3824b091-c7b8-409c-bcc0-7cce487d6b49}"); spList = spWeb.Lists[id]; Console.WriteLine("该列表是{0}。", spList.Title); //用列表URL获取列表对象 string listUrl = string.Format(@"{0}/{1}", spWeb.Url, "Lists/MyCustomerList/AllItems.aspx"); spList = spWeb.GetList(listUrl); Console.WriteLine("该列表是{0}。", spList.Title);
您还是否记得这些列表属性,我们在开发自定义列表时都设置过?
列表项对应的类是SPListItem,SPList对象的Items属性是它的项目的集合,是SPListItemCollection类型的,枚举它的元素就是SPListItem对象了。不过您想访问列表项的时候一般不要直接通过SPList对象的Items属性,特别是已经存储了很多项目的大数据列表,因为可能会有不必要的数据加载影响性能。例如您想知道列表中已存在多少个项目,可以直接访问SPList对象的ItemCount属性,而不是SPList对象的Items属性的Count;如果您想得到列表中的某个项目可以调用SPList对象的GetItemById、GetItemByUniqueId等方法。
//用列表标题获取列表对象 SPList spList = spWeb.Lists["我的客户列表"]; //获取用户信息列表的项目数量 int userCount = spList.ItemCount; Console.WriteLine("我的客户列表共有{0}条项目。", userCount.ToString()); //用项目ID获取项目对象 int itemId = 18; SPListItem spListItem = spList.GetItemById(itemId); Console.WriteLine("该客户是{0}。", spListItem.Title); //用项目UniqueID获取项目对象 Guid itemUniqueId = new Guid("{83815a27-6291-416d-8db6-a77bcae4bb86}"); spListItem = spList.GetItemByUniqueId(itemUniqueId); Console.WriteLine("该客户是{0}。", spListItem.Title);
列表对象还有一个Fields的属性,是SPFieldCollection类型,遍历这个字段集合可以得到SPField对象。这就是列表的字段信息,包括字段内部名称、显示名称、类型等等。我们要访问列表项目的某个属性时就可以用SPField对象的ID,InternalName或Title。
string title = @"我的客户列表"; SPList spList = spWeb.Lists[title]; SPFieldCollection spFields = spList.Fields; //获取列表的字段信息 foreach (SPField field in spFields) { Console.WriteLine("Title:{0} InternalName:{1} TypeDisplayName:{2} TypeAsString:{3}", field.Title, field.InternalName, field.TypeDisplayName, field.TypeAsString); } Guid fieldId = new Guid("{a943ca8c-a2ad-4a90-8a78-2f6a202f6553}"); SPField spField = spFields[fieldId]; SPListItem spListItem = spList.Items[0]; //用字段的ID访问列表项目属性 Console.WriteLine("该用户的消费金额是{0}。", spListItem[spField.Id].ToString()); //用字段的InternalName访问列表项目属性 Console.WriteLine("该用户的消费金额是{0}。", spListItem[spField.InternalName].ToString()); Console.WriteLine("该用户的消费金额是{0}。", spListItem.GetFormattedValue(spField.InternalName)); //用字段的Title访问列表项目属性 Console.WriteLine("该用户的消费金额是{0}。", spListItem[spField.Title].ToString()); Console.WriteLine("该用户的消费金额是{0}。", spListItem.GetFormattedValue(spField.Title));
您还记得开发自定义列表栏的时候我们一起设置的那些字段属性吗?
SPList对象还有个GetItemByIdAllFields方法和GetItemByIdSelectedFields方法。看来我们可以按需索取项目字段了。
//用列表标题获取列表对象 string title = @"我的客户列表"; SPList spList = spWeb.Lists[title]; int itemId = 1; SPListItem spListItem = spList.GetItemByIdSelectedFields(itemId, new[] { "CustomerName", "Recency", "Frequency", "Monetary" }); //用项目的ID和字段的InternalName访问列表项目 Console.WriteLine("客户{0}的最近消费时间是{1}消费频率是{2}消费金额是{3}。", spListItem["CustomerName"].ToString(), spListItem["Recency"].ToString(), spListItem["Frequency"].ToString(), spListItem["Monetary"].ToString());
不过,我把GetItemByIdSelectedFields方法的数组参数减掉了两个项"Frequency"和"Monetary"再运行,竟然没有看到我期待的、闪电般美丽的异常!不是说只要两个字段吗?而且随后我把SPListItem对象的Xml属性也输出时有看到了好多不是我指定索取的字段信息!
string title = @"我的客户列表"; SPList spList = spWeb.Lists[title]; int itemId = 1; //删掉"Frequency"、 "Monetary" SPListItem spListItem = spList.GetItemByIdSelectedFields(itemId, new[] { "CustomerName", "Recency"}); //用项目的ID和字段的InternalName访问列表项目 Console.WriteLine("客户{0}的最近消费时间是{1}消费频率是{2}消费金额是{3}。", spListItem["CustomerName"].ToString(), spListItem["Recency"].ToString(), spListItem["Frequency"].ToString(), spListItem["Monetary"].ToString()); Console.WriteLine(@"XML:{0}",spListItem.Xml);
查看一下GetItemByIdSelectedFields方法的源代码,发现在拼接CAML查询字段时有个对SPField对象MustFetchByDefault的判断,如果这个属性是True,没有指定也会查询,而且这个SPField对象MustFetchByDefault属性是internal的。
public SPListItem GetItemByIdSelectedFields(int id, params string[] fields) { if (fields == null) { throw new ArgumentNullException("fields"); } StringBuilder builder = new StringBuilder(); foreach (string str in fields) { if (str != null) { builder.Append("<FieldRef Name=\"" + str + "\"/>"); } } foreach (SPField field in this.Fields) { bool flag = false; foreach (string str2 in fields) { if (str2 == field.InternalName) { flag = true; break; } } if (!flag && field.MustFetchByDefault) { builder.Append("<FieldRef Name=\""); builder.Append(field.InternalName); builder.Append("\"/>"); } } return this.GetItemById(id, null, false, builder.ToString()); }
如何读取列表项中字段的值请参考:http://msdn.microsoft.com/zh-cn/library/ff521580(v=office.14).aspx。
在读取项目属性时,有些类型是比较特殊的:例如多个用户或用户组,它可以转为SPFieldUserValueCollection类型,这是个SPFieldUserValue对象的集合; 用户或用户组 、超链接或图片这样的字段取出来时String类型,您要解析可以用字符串截取方式,也可以对应构造成SPFieldUserValue、SPFieldUrlValue对象,直接访问它们的属性。
SPListItem spListItem = spList.GetItemById(itemId); //直接ToString,用户是[ID]+[;#] + [显示名];链接是[URL] + [,] + [显示文本] Console.WriteLine("客户的分享者是{0} 所有者是{1} 业务系统链接是{2}。", spListItem["Sharers"], spListItem["Owner"].ToString(), spListItem["BusinessPage"].ToString()); //截取字符串取属性 Console.WriteLine("客户的所有者的显示名是{0} 业务系统链接的URL是{1}。", spListItem["Owner"].ToString().Split(new[] { @";#" }, StringSplitOptions.None)[1], spListItem["BusinessPage"].ToString().Split(',')[0]); //转为相应对象取属性 SPFieldUserValueCollection shares = (SPFieldUserValueCollection)spListItem["Sharers"]; foreach (SPFieldUserValue share in shares) { if (share.User != null) { SPUser user = share.User; Console.WriteLine(@"找到个用户{0}", user.Name); } else { SPGroup spGroup = spWeb.Groups.GetByID(share.LookupId); Console.WriteLine(@"找到个用户组{0}", spGroup.Name); } } SPFieldUserValue owner = new SPFieldUserValue(spWeb, spListItem["Owner"].ToString()); SPFieldUrlValue businessPage = new SPFieldUrlValue(spListItem["BusinessPage"].ToString()); Console.WriteLine("客户的业务系统链接的URL是{0} 业务系统链接的描述是{1}。", businessPage.Url, businessPage.Description);
如果您想按某些条件得到列表中的几个项目,则可以使用CAML列表查询了。上文中我们查看SPList的源代码,看到了一段XML的拼接,这就是CAML。协作应用程序标记语言 (CAML) 简介请参考:http://msdn.microsoft.com/zh-cn/library/ms426449.aspx。
如果要直接使用CAML做查询可以使用SPList对象的GetItems方法,这个方法的参数是一个SPQuery对象。SPQuery对象的ViewFields是返回字段(个人理解ViewFieldsOnly要设置为True时ViewFields才有效,但是我尝试中发现不设置“ViewFieldsOnly=true”返回的结果是一样的。另外,ID、Created、Modified等属性一直会被返回的),Query是查询条件,这就相当于SQL语句里的"SELECT ..."和“WHERE...”了。GetItems方法的返回值是一个SPListItemCollection对象,如果喜欢用DataTable的话,可以调用它的GetDataTable()对象。举个简单的查询例子,查询名称为张三的客户:
SPList spList = spWeb.Lists[title]; SPQuery spQuery = new SPQuery(); spQuery.ViewFields = @"<FieldRef Name='CustomerName'/><FieldRef Name='Gender'/><FieldRef Name='Monetary'/><FieldRef Name='BusinessPage'/><FieldRef Name='Owner'/><FieldRef Name='Sharers'/>"; //spQuery.ViewFieldsOnly = true; spQuery.Query = @"<Where><Eq><FieldRef Name='CustomerName'/><Value Type='Text'>张三</Value></Eq></Where>"; SPListItemCollection spListItemCollection = spList.GetItems(spQuery); //将结果保存到DataTable DataTable dataTable = spListItemCollection.GetDataTable(); Console.WriteLine(spQuery.ViewFieldsOnly.ToString()); if (spListItemCollection.Count > 0) { foreach (SPListItem spListItem in spListItemCollection) { Console.WriteLine(spListItem["CustomerName"]); Console.WriteLine(spListItem.Xml); } }
CAML查询条件有很多比较符:Eq(=)、Gt(>)、Lt(<)、Geq(>=)、Leq(<=)、Neq(<>)、Contains(Like)、IsNull(Null)、IsNotNull(NotNull)等,多个查询条件可以使用关系运算符And或Or(http://msdn.microsoft.com/zh-cn/library/ms467521(v=office.14).aspx)。再举一个例子:自定义列表-我的客户列表有一个Sharers字段(表示分享用户列表),这个字段可存储多个用户和组,现在有一个用户甲,要查询分享给这个用户或这个用户所在的组的数据则使用如下代码:
string title = @"我的客户列表"; SPList spList = spWeb.Lists[title]; SPUser user = spWeb.EnsureUser(loginName); SPGroup[] groups = user.Groups.Cast<SPGroup>().ToArray(); SPQuery spQuery = new SPQuery(); spQuery.ViewFields = @"<FieldRef Name='CustomerName'/><FieldRef Name='Gender'/><FieldRef Name='Monetary'/><FieldRef Name='BusinessPage'/><FieldRef Name='Owner'/><FieldRef Name='Sharers'/>"; spQuery.ViewFieldsOnly = true; if (groups.Length > 0) { StringBuilder query = new StringBuilder(); query.AppendFormat(@"<Where><Or><Includes><FieldRef Name='Sharers' LookupId=""TRUE"" /><Value Type='User'>{0}</Value></Includes>", user.ID.ToString()); Array.ForEach(groups, g => query.AppendFormat(@"<Includes><FieldRef Name='Sharers' LookupId='TRUE' /><Value Type='Group'>{0}</Value></Includes>", g.ID.ToString())); query.Append(@"</Or></Where>"); spQuery.Query = query.ToString(); } else { spQuery.Query = string.Format(@"<Where><Includes><FieldRef Name='Sharers' LookupId='TRUE' /><Value Type='User'>{0}</Value></Includes></Where>", user.ID.ToString()); } DataTable dataTable = spList.GetItems(spQuery).GetDataTable();
SPQuery对象的ViewAttributes属性可以设置检索范围;RowLimit是返回数量ListItemCollectionPosition是查询位置,用这两个属性可以实现分页查询;列表中如果包含文件夹并且查询要在指定文件夹下执行,可以使用Folder属性。
SPQuery类使用请参考http://technet.microsoft.com/zh-cn/library/microsoft.sharepoint.spquery.aspx ;如果您需要跨网站多列表混合查询,可以使用SPSiteDataQuery 类,http://msdn.microsoft.com/zh-cn/library/microsoft.sharepoint.spsitedataquery.aspx;查询列表项请参考http://msdn.microsoft.com/zh-cn/library/ms456030(v=office.14).aspx。如果您觉得拼写CAML有些麻烦,也可以到网站找一些小工具下载使用,例如:http://sharepointcamlhelper.codeplex.com/。关于查询性能,有几篇个人觉得不错的文章:http://msdn.microsoft.com/zh-cn/subscriptions/ee557257.aspx,http://www.infoq.com/cn/articles/SharePoint-Andreas-Grabner。
新增列表项目可以先调用SPList对象的AddItem方法,获取一个新的SPListItem对象,在给SPListItem的所需字段赋值后再调用它的Update方法即可保存新增(SPListItem对象还有个SystemUpdate方法,这个方法在修改时不会影响修改时间和修改者,另外还可以用参数指定是否创建新版本)。
SPUser user1 = spWeb.EnsureUser(loginName1); SPUser user2 = spWeb.EnsureUser(loginName2); SPGroup group1 = spWeb.Groups[loginName3]; string title = @"我的客户列表"; SPList spList = spWeb.Lists[title]; SPListItem spListItem = spList.AddItem(); spListItem["CustomerName"] = "赵六"; spListItem["Gender"] = "女"; spListItem["EMail"] = "[email protected]"; spListItem["CellPhone"] = "13456435678"; spListItem["WorkAddress"] = "西直门"; spListItem["Recency"] = DateTime.Now; spListItem["Frequency"] = 0.5; spListItem["Monetary"] = 100000; spListItem["BusinessPage"] = new SPFieldUrlValue() { Url = "http://zhaoliu.com", Description = "赵六的个人主页" }; spListItem["CustomerType"] = "钻石"; spListItem["Owner"] = new SPFieldUserValue(spWeb, user1.ID, user1.Name); spListItem["Sharers"] = new SPFieldUserValueCollection { new SPFieldUserValue(spWeb, user1.ID, user1.Name), new SPFieldUserValue(spWeb, user2.ID, user2.Name), new SPFieldUserValue(spWeb, group1.ID, group1.Name) }; spListItem.Update();
在给项目的字段赋值时,对于用户或用户组、超链接或图片这样的字段,也可以直接以字符串赋值。用户或用户组的ID和显示名要以“;#”连接,超链接或图片则以“,”连接URL和描述。
spListItem["BusinessPage"] = string.Format(@"{0},{1}", "http://sunqi.com", "孙七的个人主页"); spListItem["Owner"] = new SPFieldUserValue(spWeb, string.Format(@"{0};#{1}", user2.ID.ToString(), user2.Name)); spListItem["Sharers"] = string.Join(@";#", user1.ID.ToString(), user1.Name, user2.ID.ToString(), user2.Name, group1.ID.ToString(), group1.Name);
如果项目有个附件文件如何上传呢?这个还很简单,只要使用SPListItem对象的Attachments属性就可以了。这是一个SPAttachmentCollection类型的SPAttachment集合,它有一个Add方法。
spListItem.Attachments.Add(Path.GetFileName(@"C:\1.txt"), File.ReadAllBytes(@"C:\1.txt"));
上文我们提到,对文档库的操作也要使用SPList类,可是我看了一下SPListItem对象的File属性——它是只读的。 那么如何给文档库创建文件夹和上传文件呢?这次创建文件夹或文件的项目要用到SPList对象的AddItem重载方法,相关的SPFolder和SPFile也可以通过列表的网站SPWeb对象获取。
string title = @"我的文档库"; SPList spList = spWeb.Lists[title]; //创建一个文件夹的SPListItem对象 SPListItem folderListItem = spList.AddItem(spList.RootFolder.ServerRelativeUrl, SPFileSystemObjectType.Folder, "文件夹2"); folderListItem.Update(); //找到网站对应文件夹SPFolder对象 SPFolder spFolder = spList.ParentWeb.GetFolder(folderListItem.UniqueId); bool allowUnsafeUpdates = spWeb.AllowUnsafeUpdates;//如果AllowUnsafeUpdates是False可能下面操作会有异常 spWeb.AllowUnsafeUpdates = true; //在指定文件夹下添加文件 spFolder.Files.Add(Path.GetFileName(@"C:\1.txt"), File.ReadAllBytes(@"C:\1.txt")); spWeb.AllowUnsafeUpdates = allowUnsafeUpdates;
再看看修改和删除列表项目就非常简单了。(参考资料:http://msdn.microsoft.com/zh-cn/library/ms467435(v=office.14).aspx)
string title = @"我的客户列表"; SPList spList = spWeb.Lists[title]; //修改: int itemId = 5; SPListItem spListItem = spList.GetItemById(itemId); spListItem["CustomerName"] = "孙七七"; spListItem.Update(); //删除: //spListItem.Delete();
不过要高效的批量执行增删改操作还是需要SPWeb对象的ProcessBatchData方法。命令语法请参考:http://msdn.microsoft.com/zh-CN/library/ms455433(v=office.12).aspx、http://msdn.microsoft.com/zh-cn/library/microsoft.sharepoint.spweb.processbatchdata.aspx、http://msdn.microsoft.com/zh-cn/library/cc404818.aspx。下面代码是插入、修改和删除的例子,对于多个用户和组、超链接或图片还是使用的字符串拼接(用户和组用“;#”连接ID和显示名称,超链接或图片用空格+逗号+空格连接URL和描述),还有时间类型字段,要转换为ISO8601格式,可以调用SPUtility类的CreateISO8601DateTimeFromSystemDateTime方法创建(http://msdn.microsoft.com/zh-cn/library/ms197282(v=office.14).aspx)。
SPUser user1 = spWeb.EnsureUser(loginName1); SPUser user2 = spWeb.EnsureUser(loginName2); SPGroup group1 = spWeb.Groups[loginName3]; string listId = @"3824b091-c7b8-409c-bcc0-7cce487d6b49"; StringBuilder strBatchData = new StringBuilder(@"<?xml version=""1.0"" encoding=""UTF-8""?><Batch>"); //命令头: string setListText = string.Format(@"<SetList Scope=""Request"">{0}</SetList>", listId); //插入数据: strBatchData.AppendFormat(@"<Method ID=""Insert,1"">{0}<SetVar Name=""ID"">New</SetVar><SetVar Name=""Cmd"">Save</SetVar>", setListText); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "CustomerName", "蒋九"); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "Gender", "男"); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "EMail", "[email protected]"); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "CellPhone", "13656435678"); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "WorkAddress", "菜市口"); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "Recency", SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Now.AddDays(-1))); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "Frequency", "0.3"); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "Monetary", "30000"); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "BusinessPage", string.Format(@"{0} , {1}", "http://jiangjiu.com", "蒋九的个人主页")); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "CustomerType", "青铜"); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "Owner", string.Format(@"{0};#{1}", user2.ID.ToString(), user2.Name)); strBatchData.AppendFormat(@"<SetVar Name=""urn:schemas-microsoft-com:office:office#{0}"">{1}</SetVar>", "Sharers", string.Join(@";#", user1.ID.ToString(), user1.Name, user2.ID.ToString(), user2.Name, group1.ID.ToString(), group1.Name)); strBatchData.Append("</Method>"); //修改数据: string updateItemId = "2"; strBatchData.AppendFormat(@"<Method ID=""Update,1"">{0}<SetVar Name=""ID"">{1}</SetVar><SetVar Name=""Cmd"">Save</SetVar><SetVar Name=""urn:schemas-microsoft-com:office:office#{2}"">{3}</SetVar></Method>", setListText, updateItemId, "CustomerName", "李四四"); //删除数据: string deleteItemId = "3"; strBatchData.AppendFormat(@"<Method ID=""Delete,1"">{0}<SetVar Name=""ID"">{1}</SetVar><SetVar Name=""Cmd"">Delete</SetVar></Method>", setListText, deleteItemId); //命令尾: strBatchData.Append(@"</Batch>"); //执行: Console.WriteLine(spWeb.ProcessBatchData(strBatchData.ToString()));
服务端对象模型还有一个很重要的上下文对象--SPContext,在开发Web部件时经常会用到。我们可以通过SPContext.Current获取到当前上下文SPContext对象,用该对象可以直接获取当前网站、当前列表、当前项目......(http://msdn.microsoft.com/zh-cn/library/microsoft.sharepoint.spcontext.aspx)
SPContext spContext = SPContext.Current; //当前网站集 SPSite spSite = spContext.Site; //当前网站 SPWeb spWeb = spContext.Web; //当前用户 SPUser spUser = spWeb.CurrentUser; //当前列表 SPList spList = spContext.List; //当前列表ID Guid listId = spContext.ListId; //当前项目 SPItem spItem = spContext.Item; //当前项目ID int itemId = spContext.ItemId; //当前项目ID(字符串) string itemIdAsString = spContext.ItemIdAsString; //当前页面上下文 SPContextPageInfo contextPageInfo = spContext.ContextPageInfo; //......
上文花了较大篇幅研究服务端对象模型,接下来我们研究托管客户端对象模型(托管客户端对象模型是用.NET开发的,另有ECMAScript客户端对象模型以后会介绍,http://msdn.microsoft.com/zh-cn/library/ee539429(v=office.14).aspx)。客户端对象模型不需要部署到服务器端,当然功能也会有一定限制。使用客户端对象模型代码需要引用Microsoft.SharePoint.Client程序集和Microsoft.SharePoint.Client命名空间。
首先,我们要构建一个客户端上下文对象——ClientContext(相当于服务端对象模型的Microsoft.SharePoint.SPContext)。如果需要显式以某个用户的 Windows 凭据运行,可以给ClientContext对象的Credentials赋值。
ClientContext clientContext = new ClientContext(webFullUrl); //NetworkCredential networkCredential = CredentialCache.DefaultNetworkCredentials; //clientContext.Credentials = networkCredential; NetworkCredential networkCredential = new NetworkCredential(@"contoso\administrator", "password"); clientContext.Credentials = networkCredential;
使用过了服务器端对象模型再理解客户端对象模型比较容易,与Microsoft.SharePoint.SPList对应的是Microsoft.SharePoint.Client.List、与Microsoft.SharePoint.SPListItem对应的是Microsoft.SharePoint.Client.ListItem,只是客户端对象模型要写更多代码,通过查询取得的对象不能直接使用,需要调用ClientContext对象的Load方法,并显式调用ExecuteQuery方法。(http://msdn.microsoft.com/zh-cn/library/ee534956(v=office.14).aspx、http://msdn.microsoft.com/zh-cn/library/gg277498.aspx)
string webFullUrl = "http://SharePointServer/"; using (ClientContext clientContext = new ClientContext(webFullUrl)) { List list = clientContext.Web.Lists.GetByTitle("我的客户列表"); CamlQuery camlQuery = new CamlQuery(); //指定过滤条件 camlQuery.ViewXml = @"<View><Query><Where><Eq><FieldRef Name='CustomerName'/><Value Type='Text'>蒋九</Value></Eq></Where></Query></View>"; ListItemCollection listItems = list.GetItems(camlQuery); //加载查询,并指定加载字段: clientContext.Load(listItems, items => items.Include( item => item.Id, item => item["CustomerName"], item => item["Gender"], item => item["EMail"], item => item["CellPhone"], item => item["WorkAddress"], item => item["Recency"], item => item["Frequency"], item => item["Monetary"], item => item["BusinessPage"], item => item["CustomerType"], item => item["Owner"], item => item["Sharers"])); //执行查询 clientContext.ExecuteQuery(); int count = listItems.Count; foreach (ListItem item in listItems) { Console.WriteLine("ID: {0} CustomerName: {1} Gender: {2} EMail: {3} CellPhone: {4} WorkAddress: {5} Recency: {6} Frequency: {7} Monetary: {8} BusinessPage: {9} CustomerType: {10} Owner: {11} Sharers: {12} ", item.Id, item["CustomerName"], item["Gender"], item["EMail"], item["CellPhone"], item["WorkAddress"], item["Recency"], item["Frequency"], item["Monetary"], ((FieldUrlValue)item["BusinessPage"]).Url, item["CustomerType"], ((FieldUserValue)item["Owner"]).LookupValue, string.Join(",", ((FieldUserValue[])item["Sharers"]).Select(v => v.LookupValue).ToArray())); } }
新增项目时也多了一个步骤,要先创建一个ListItemCreationInformation对象(添加附件还要创建AttachmentCreationInformation对象),再调用List的AddItem方法;为ListItem对象的属性赋值后,要调用Update和ExecuteQuery方法。
Guid listId = new Guid(@"{3824b091-c7b8-409c-bcc0-7cce487d6b49}"); List list = clientContext.Web.Lists.GetById(listId); ListItemCreationInformation listItemCreationInformation = new ListItemCreationInformation(); ListItem listItem = list.AddItem(listItemCreationInformation); listItem["CustomerName"] = "李四"; listItem["Gender"] = "女"; listItem["EMail"] = "[email protected]"; listItem["CellPhone"] = "13456435678"; listItem["WorkAddress"] = "西直门"; listItem["Recency"] = DateTime.Now; listItem["Frequency"] = 0.5; listItem["Monetary"] = 100000; listItem["BusinessPage"] = new FieldUrlValue() { Url = "http://lisi.com", Description = "李四的个人主页" }; listItem["CustomerType"] = "钻石"; listItem["Owner"] = FieldUserValue.FromUser(loginName1); listItem["Sharers"] = new [] { FieldUserValue.FromUser(user1.LoginName), FieldUserValue.FromUser(user2.LoginName), FieldUserValue.FromUser(group1.LoginName) }; listItem.Update(); AttachmentCreationInformation attachmentCreationInformation = new AttachmentCreationInformation(); using (FileStream fileStream = new FileStream(fileFullName, FileMode.Open)) { attachmentCreationInformation.ContentStream = fileStream; attachmentCreationInformation.FileName = Path.GetFileName(fileFullName); listItem.AttachmentFiles.Add(attachmentCreationInformation); clientContext.ExecuteQuery(); }
如果要上传一个文档到文档库则需要创建FileCreationInformation对象。
string title = @"我的文档库"; List list = clientContext.Web.Lists.GetByTitle(title); Folder folder = list.RootFolder.Folders.Add("文件夹1"); folder.Update(); FileCreationInformation fileCreationInformation = new FileCreationInformation(); fileCreationInformation.Content = System.IO.File.ReadAllBytes(fileFullName); fileCreationInformation.Overwrite = true; fileCreationInformation.Url = Path.GetFileName(fileFullName); folder.Files.Add(fileCreationInformation); clientContext.ExecuteQuery();
将项目修改或删除的代码很简单,也是操作后要调用ListItem对象的Update和ExecuteQuery方法。(http://msdn.microsoft.com/zh-cn/library/ee539976(v=office.14).aspx)
Guid listId = new Guid(@"{3824b091-c7b8-409c-bcc0-7cce487d6b49}"); int itemId = 14; List list = clientContext.Web.Lists.GetById(listId); ListItem item = list.GetItemById(itemId); clientContext.Load(item); clientContext.ExecuteQuery(); //修改 item["CustomerName"] = "蒋九九"; item.Update(); clientContext.ExecuteQuery(); //删除 item.DeleteObject(); clientContext.ExecuteQuery();
关于Web Services(http://msdn.microsoft.com/zh-cn/library/dd878586(v=office.12).aspx)的方式操作列表,首先要添加Web引用(解决方案资源管理器-〉引用-〉添加服务引用-〉高级(左下角)-〉添加Web引用(左下角)-〉URL输入框输入 http://<site>/_vti_bin/Lists.asmx
.)。
这里的操作命令语法和服务器端对象模型、客户端对象模型里的CAML非常相似,只是Web服务方法的参数和返回值多是XmlElement对象,查询列表项目shiyongLists.GetListItems 方法(请参考:http://msdn.microsoft.com/zh-cn/library/websvclists.lists.updatelistitems(v=office.12).aspx),下面是一个简单的查询例子。
string listName = "我的客户列表"; using (Lists listService = new Lists()) { listService.Credentials = CredentialCache.DefaultCredentials; //或者: //listService.Credentials = new NetworkCredential(@"contoso\administrator", "password"); XmlDocument xmlDocument = new XmlDocument(); XmlNode queryNode = xmlDocument.CreateNode(XmlNodeType.Element, "Query", string.Empty); XmlNode viewFieldsNode = xmlDocument.CreateNode(XmlNodeType.Element, "ViewFields", string.Empty); viewFieldsNode.InnerXml = @"<FieldRef Name='ID' /><FieldRef Name='CustomerName' /><FieldRef Name='Gender' /><FieldRef Name='EMail' /><FieldRef Name='CellPhone' />"; queryNode.InnerXml = string.Format(@"<Where><Eq><FieldRef Name='CustomerName'/><Value Type='Text'>{0}</Value></Eq></Where>", "蒋九"); XmlNode resultNode = listService.GetListItems(listName, null, queryNode, viewFieldsNode, null, null, null); Console.WriteLine(resultNode.InnerXml); Console.ReadLine(); }
新增、修改、删除列表项目使用Lists.UpdateListItems 方法执行批量命令(请参考:http://msdn.microsoft.com/zh-cn/library/websvclists.lists.updatelistitems(v=office.12).aspx)。对于多个用户和组、超链接或图片等特殊的字段类型还是使用的和服务器端对象模型相同的字符串拼接。
string listName = "我的客户列表"; using (Lists listService = new Lists()) { listService.Credentials = CredentialCache.DefaultCredentials; //或者: //listService.Credentials = new NetworkCredential(@"contoso\administrator", "password"); XmlDocument xmlDocument = new XmlDocument(); XmlElement updatesBatch = xmlDocument.CreateElement("Batch"); updatesBatch.SetAttribute("OnError", "Continue"); StringBuilder methodsText = new StringBuilder(); //删除数据: methodsText.AppendFormat(@"<Method ID='Delete,1' Cmd='Delete'><Field Name='ID'>{0}</Field></Method>", deleteItemId.ToString()); //插入数据: methodsText.Append(@"<Method ID='Insert,1' Cmd='New'><Field Name='ID'>New</Field>"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "CustomerName", "蒋九"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Gender", "男"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "EMail", "[email protected]"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "CellPhone", "13656435678"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "WorkAddress", "菜市口"); DateTime recency = DateTime.Now.AddDays(-1); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Recency", string.Format("{0}-{1}-{2}T{3}:{4}:{5}Z", recency.Year.ToString("0000"), recency.Month.ToString("00"), recency.Day.ToString("00"), recency.Hour.ToString("00"), recency.Minute.ToString("00"), recency.Second.ToString("00"))); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Frequency", "0.4"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Monetary", "10000"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "BusinessPage", string.Format(@"{0} , {1}", "http://jiangjiu.com", "蒋九的个人主页")); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "CustomerType", "青铜"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Owner", string.Format(@"{0};#{1}", user2.ID.ToString(), user2.Name)); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Sharers", string.Join(@";#", user1.ID.ToString(), user1.Name, user2.ID.ToString(), user2.Name, group1.ID.ToString(), group1.Name)); methodsText.Append("</Method>"); methodsText.Append(@"<Method ID='Insert,2' Cmd='New'><Field Name='ID'>New</Field>"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "CustomerName", "孙七"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Gender", "女"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "EMail", "[email protected]"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "CellPhone", "13656437456"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "WorkAddress", "恭王府"); DateTime recency2 = DateTime.Now.AddDays(-3); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Recency", string.Format("{0}-{1}-{2}T{3}:{4}:{5}Z", recency2.Year.ToString("0000"), recency2.Month.ToString("00"), recency2.Day.ToString("00"), recency2.Hour.ToString("00"), recency2.Minute.ToString("00"), recency2.Second.ToString("00"))); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Frequency", "0.6"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Monetary", "10000"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "BusinessPage", string.Format(@"{0} , {1}", "http://sunqi.com", "孙七的个人主页")); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "CustomerType", "黄金"); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Owner", string.Format(@"{0};#{1}", user2.ID.ToString(), user2.Name)); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "Sharers", string.Join(@";#", user1.ID.ToString(), user1.Name, user2.ID.ToString(), user2.Name, group1.ID.ToString(), group1.Name)); methodsText.Append("</Method>"); //修改数据: methodsText.AppendFormat(@"<Method ID='Update,1' Cmd='Update'><Field Name='ID'>{0}</Field>", updateItemId.ToString()); methodsText.AppendFormat(@"<Field Name='{0}'>{1}</Field>", "CustomerName", "赵六六"); methodsText.Append("</Method>"); updatesBatch.InnerXml = methodsText.ToString(); XmlNode returnNode = listService.UpdateListItems(listName, updatesBatch); Console.WriteLine(returnNode.InnerXml); }
如果您觉得Web Services+XmlElement不够先进,还可以使用WCF+LINQ TO XML的方式,使用WCF直接添加服务引用就可以了。
使用WCF访问SharePoint列表和使用Web Services访问的SharePoint的方法应该是同出一辙的,只是这里把参数XmlElement对象换成了XElement对象。可是开始的时候我还是遇到了一点小异常。
对于这个MessageSecurityException类型的异常——“HTTP 请求未经客户端身份验证方案'Anonymous'授权。从服务器收到的身份验证标头为‘NTLM’。”,我们可以用添加两句代码,设置BasicHttpBinding.Security.Mode和BasicHttpBinding.Security.Transport.ClientCredentialType解决。
BasicHttpBinding basicHttpBinding = (BasicHttpBinding)listsSoapClient.Endpoint.Binding; basicHttpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly; basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm; listsSoapClient.ClientCredentials.Windows.ClientCredential = new NetworkCredential(@"username", "password", "domain"); XElement queryXElement = XElement.Parse(string.Format(@"<Query><Where><Eq><FieldRef Name='CustomerName'/><Value Type='Text'>{0}</Value></Eq></Where></Query>", "赵六六")); XElement viewFieldsXElement = XElement.Parse(@"<ViewFields><FieldRef Name='CustomerName'/><FieldRef Name='Gender'/><FieldRef Name='Monetary'/><FieldRef Name='BusinessPage'/><FieldRef Name='Owner'/><FieldRef Name='Sharers'/></ViewFields>"); XElement resultXElement = listsSoapClient.GetListItems(listName, null, queryXElement, viewFieldsXElement, null, null, null); Console.WriteLine(resultXElement.ToString());
如果您不喜欢加代码,还可以修改应用程序配置文件(.config)中的binding节点。
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="ListsSoap" ><security mode="TransportCredentialOnly"><transport clientCredentialType="Ntlm" /></security></binding> </basicHttpBinding> </bindings> <client> <endpoint address="http://SharePointServer/_vti_bin/Lists.asmx" binding="basicHttpBinding" bindingConfiguration="ListsSoap" contract="WebsvcLists.ListsSoap" name="ListsSoap" /> </client> </system.serviceModel>