SharePoint 2010 - 客户端对象模型 - 向列表项添加附件

14

我使用客户端对象模型向 SharePoint 列表中添加新的列表项。添加列表项不是问题,可以正常工作。

现在我想添加附件。

我使用以下方式的 SaveBinaryDirect:

File.SaveBinaryDirect(clientCtx, url.AbsolutePath + "/Attachments/31/" + fileName, inputStream, true);
只要我尝试为已通过SharePoint网站添加而不是使用客户端对象模型添加附件的项添加附件,它就能正常工作,没有任何问题。
当我尝试将附件添加到尚未具有附件的项目时,我会收到以下错误(两个错误都会发生,但不是针对同一文件-但这两个消息始终出现):
远程服务器返回错误:(409)冲突
远程服务器返回错误:(404)未找到
我想也许我需要先为此项创建附件文件夹。当我尝试以下代码时:
clientCtx.Load(ticketList.RootFolder.Folders);
clientCtx.ExecuteQuery();
clientCtx.Load(ticketList.RootFolder.Folders[1]);             // 1 -> Attachment folder
clientCtx.Load(ticketList.RootFolder.Folders[1].Folders);
clientCtx.ExecuteQuery();
Folder folder = ticketList.RootFolder.Folders[1].Folders.Add("33");
clientCtx.ExecuteQuery();

我收到一个错误消息,内容为:

无法创建文件夹“Lists/Ticket System/Attachment/33”

我对SharePoint站点/列表拥有完整的管理员权限。

你有什么想法吗?我可能做错了什么吗?

谢谢,Thorben

6个回答

12

我也曾经长时间与这个问题纠缠,所以我想发布一个完整的代码示例,展示如何成功创建列表项并添加附件。

我使用客户端对象API来创建列表项,并使用SOAP Web服务来添加附件。这是因为,正如网络上其他地方所指出的那样,客户端对象API只能用于向已经存在上传目录的项目添加附件(例如,如果该项目已经有附件)。否则,它会失败并显示409错误或其他错误。而SOAP Web服务可以很好地处理这种情况。

请注意,我不得不克服的另一件事是,尽管我使用了以下URL添加SOAP引用:

https://my.sharepoint.installation/personal/test/_vti_bin/lists.asmx

但VS实际上添加到app.config中的URL是:

https://my.sharepoint.installation/_vti_bin/lists.asmx

我不得不手动将app.config更改回正确的URL,否则我会收到以下错误:

列表不存在。 您选择的页面包含一个不存在的列表。它可能已被另一个用户删除。 0x82000006

以下是代码:

    void CreateWithAttachment()
    {
        const string listName = "MyListName";
        // set up our credentials
        var credentials = new NetworkCredential("username", "password", "domain");

        // create a soap client
        var soapClient = new ListsService.Lists();
        soapClient.Credentials = credentials;

        // create a client context
        var clientContext = new Microsoft.SharePoint.Client.ClientContext("https://my.sharepoint.installation/personal/test");
        clientContext.Credentials = credentials;

        // create a list item
        var list = clientContext.Web.Lists.GetByTitle(listName);
        var itemCreateInfo = new ListItemCreationInformation();
        var newItem = list.AddItem(itemCreateInfo);

        // set its properties
        newItem["Title"] = "Created from Client API";
        newItem["Status"] = "New";
        newItem["_Comments"] = "here are some comments!!";

        // commit it
        newItem.Update();
        clientContext.ExecuteQuery();

        // load back the created item so its ID field is available for use below
        clientContext.Load(newItem);
        clientContext.ExecuteQuery();

        // use the soap client to add the attachment
        const string path = @"c:\temp\test.txt";
        soapClient.AddAttachment(listName, newItem["ID"].ToString(), Path.GetFileName(path),
                                  System.IO.File.ReadAllBytes(path));
    }

希望这能帮助到某人。


我正在使用Visual Studio 2012并尝试将列表项附件保存到SharePoint 2010。我在我的SharePoint 2010服务器上找不到任何代理类和方法与上面的示例匹配的Web服务。 - Zarepheth
2
好的,我找到了问题所在。在Visual Studio 2012中,我们需要添加“Web引用”而不是“服务引用”。从“添加服务引用”对话框中,点击“高级...”按钮。然后点击“添加Web引用...”按钮。现在,之前版本的Visual Studio中熟悉的“添加Web引用”对话框将被显示出来。 - Zarepheth

9

我已经与微软讨论过这个问题。看起来,创建远程附件的唯一方法是使用 List.asmx Web 服务。我也尝试过创建这个子文件夹,但没有成功。


谢谢你的回答,MaxBeard。至少知道这是不可能的比试图失败要好——看起来应该是可能的。顺便说一下,博客写得很好。 - Thorben
这似乎确实是这种情况...请查看我的答案,了解如何完成此操作的完整代码示例。 - Mike Chamberlain

6

在Sharepoint 2010中,使用COM方式没有办法上传第一个附件到列表项中。建议使用Lists Web服务。

但是,在Sharepoint 2013中可以实现这一功能。

AttachmentCreationInformation newAtt = new AttachmentCreationInformation();
newAtt.FileName = "myAttachment.txt";
// create a file stream
string fileContent = "This file is was ubloaded by client object meodel ";
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] buffer = enc.GetBytes(fileContent);
newAtt.ContentStream = new MemoryStream(buffer);

// att new item or get existing one
ListItem itm = list.GetItemById(itemId);
ctx.Load(itm);   
// do not execute query, otherwise a "version conflict" exception is rised, but the file         is uploaded
// add file to attachment collection
newAtt.ContentStream = new MemoryStream(buffer);
itm.AttachmentFiles.Add(newAtt); 
AttachmentCollection attachments = itm.AttachmentFiles;
ctx.Load(attachments);
ctx.ExecuteQuery(); 
// see all attachments for list item
// this snippet works if the list item has no attachments

该方法用于http://www.mailtosharepoint.net/


哇,非常感谢。这个在SharePoint(2013)在线和MVC应用程序中运行得很好。 - Philippe Lavoie
@sam 除非我漏掉了什么,否则这似乎只适用于使用现有的ListItem。如果您正在通过AddItem方法创建新项目,则此方法不起作用。它会抛出一个错误,说您必须先保存该项,然后再保存附件。 - Thierry
@sam,我收回之前的话,它确实可以工作 :) 但并不完全是你在代码片段中描述的方式。你需要先调用AddItem添加项目,然后从GetItemById调用代码,这样它就可以非常好地工作了。 - Thierry

4

微软SharePoint团队没有公开承认这个问题,也没有提供可用的解决方案,这对他们的形象造成了不小的影响。以下是我处理这个问题的方法:

我正在使用随产品一起提供的新的SharePoint 2010托管客户端。因此,我已经拥有带有凭据的SharePoint ClientContext。以下函数向列表项添加附件:

private void SharePoint2010AddAttachment(ClientContext ctx, 
                                     string listName, string itemId, 
                                     string fileName, byte[] fileContent)
{
    var listsSvc = new sp2010.Lists();
    listsSvc.Credentials = _sharePointCtx.Credentials;
    listsSvc.Url = _sharePointCtx.Web.Context.Url + "_vti_bin/Lists.asmx";
    listsSvc.AddAttachment(listName, itemId, fileName, fileContent);
}

上面代码的唯一前提是项目(我使用的是Visual Studio 2008)中添加一个我称之为“sp2010”的_web_reference_,该引用是从以下URL创建的:http:///_vti_bin/Lists.asmx。
祝你好运...

0

我在我的CSOM(SharePoint客户端对象模型)应用程序中使用并尝试过这个,它对我有效。

using (ClientContext context = new ClientContext("http://spsite2010"))
                {

                    context.Credentials = new NetworkCredential("admin", "password");
                    Web oWeb = context.Web;
                    List list = context.Web.Lists.GetByTitle("Tasks");
                    CamlQuery query = new CamlQuery();
                    query.ViewXml = "<View><Where><Eq><FieldRef Name = \"Title\"/><Value Type=\"String\">New Task Created</Value></Eq></Where></View>";
                    ListItemCollection listItems = list.GetItems(query);
                    context.Load(listItems);
                    context.ExecuteQuery();
                    FileStream oFileStream = new FileStream(@"C:\\sample.txt", FileMode.Open);
                    string attachmentpath = "/Lists/Tasks/Attachments/" + listItems[listItems.Count - 1].Id + "/sample.txt";
                    Microsoft.SharePoint.Client.File.SaveBinaryDirect(context, attachmentpath, oFileStream, true);
                }

注意:仅在项目文件夹已经创建的情况下才能正常工作。

0

HTML:

<asp:FileUpload ID="FileUpload1" runat="server" AllowMultiple="true" />

后台代码中的事件:

protected void UploadMultipleFiles(object sender, EventArgs e)
{
    Common.UploadDocuments(Common.getContext(new Uri(Request.QueryString["SPHostUrl"]),
    Request.LogonUserIdentity), FileUpload1.PostedFiles, new CustomerRequirement(), 5);
}

public static List<string> UploadDocuments<T>(ClientContext ctx,IList<HttpPostedFile> selectedFiles, T reqObj, int itemID)
{
    List<Attachment> existingFiles = null;
    List<string> processedFiles = null;
    List<string> unProcessedFiles = null;
    ListItem item = null;
    FileStream sr = null;
    AttachmentCollection attachments = null;
    byte[] contents = null;
    try
    {
        existingFiles = new List<Attachment>();
        processedFiles = new List<string>();
        unProcessedFiles = new List<string>();
        //Get the existing item
        item = ctx.Web.Lists.GetByTitle(typeof(T).Name).GetItemById(itemID);
        //get the Existing attached attachments
        attachments = item.AttachmentFiles;
        ctx.Load(attachments);
        ctx.ExecuteQuery();
        //adding into the new List
        foreach (Attachment att in attachments)
            existingFiles.Add(att);
        //For each Files which user has selected
        foreach (HttpPostedFile postedFile in selectedFiles)
        {
            string fileName = Path.GetFileName(postedFile.FileName);
            //If selected file not exist in existing item attachment
            if (!existingFiles.Any(x => x.FileName == fileName))
            {
                //Added to Process List
                processedFiles.Add(postedFile.FileName);
            }
            else
                unProcessedFiles.Add(fileName);
        }
        //Foreach process item add it as an attachment
        foreach (string path in processedFiles)
        {
            sr = new FileStream(path, FileMode.Open);
            contents = new byte[sr.Length];
            sr.Read(contents, 0, (int)sr.Length);
            var attInfo = new AttachmentCreationInformation();
            attInfo.FileName = Path.GetFileName(path);
            attInfo.ContentStream = sr;
            item.AttachmentFiles.Add(attInfo);
            item.Update();
        }
        ctx.ExecuteQuery();
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        existingFiles = null;
        processedFiles = null;
        item = null;
        sr = null;
        attachments = null;
        contents = null;
        ctx = null;

    }
    return unProcessedFiles;
}

仅仅发布一个代码块并不是对一个问题的好回答。请解释*您的解决方案如何解决OP的任务以及它对已经存在于问题上的其他答案做出了哪些贡献。 - TZHX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接