1

是否可以在 SharePoint 中创建将文件上传控件与自定义字段类型相结合的自定义上传页面,以便用户可以从硬盘驱动器中选择要上传的文件、输入文件标题、可选地添加评论、指定内容在几个自定义字段中键入并输入其他数据并创建新的 SPListItem,上传文件并将其关联到新的 SPListItem,最后将所有输入自定义字段的值成功保存到新创建的 SPListItem?

注意:我希望使用 SharePoint 自定义字段类型来执行此任务,而不是简单地使用带有一堆用户控件的自定义 ASPX 页面。

使用自定义字段类型时存在的根本问题是 SharePoint 文档库中的文件上传事件是异步事件。您可以覆盖 SPListItemEventReceiver 中可用的 ItemAdding 方法的默认行为,该方法可用于在文件“正在上传”时访问某些信息,并且您同样可以从 ItemAdded 访问有关新创建的 SPListItem 的信息被称为“在已添加项目之后”的方法 - 但由于此方法发生在单独的线程中并且在不知道与自定义字段或其各自值相关的任何内容的情况下异步执行,因此用户输入的任何数据都没有在那些领域被成功保存。

当用户希望通过使用 EditFormTemplate 编辑自定义字段中的值来更新有关文档的信息时,会在初始化期间设置每个字段的 SPListItem 属性。这一切都很好,因为在这种情况下 ListItem 已经存在!问题是,当用户第一次希望上传文档时,ListItem 显然还不存在,因此每个字段都使用设置为“null”的 SPListItem 属性进行初始化,并且将永远保持为 null,因为只是没有'似乎没有任何方法可以在文件上传后使用对新创建的 ListItem 的引用来追溯更新每个字段的 ListItem 属性!

正是出于这个原因,也正是这个原因,为什么微软坚持强迫用户在一个屏幕上上传他们的文件,然后在文件成功上传后将他们重定向到编辑表单。通过拆分这两个页面,Microsoft 强制用户上传文件并在保存有关文件的任何其他信息之前创建ListItem 。上传文件并创建 ListItem 后,将每个自定义字段的值保存回 ListItem 没有问题,因为 ListItem 已经存在

注意: BaseFieldControl 继承自 FieldMetadata,后者继承自 FormComponent。FormComponent 有一个名为Item的属性,它对应于该字段所属的基础 SPListItem。BaseFieldControl 有一个名为ListItemFieldValue的属性,它存储保存回 ListItem 的字段的实际值,它还有一个名为UpdateFieldValueInItem()的可覆盖方法,可用于在将数据分配给之前执行附加逻辑(例如验证) ItemFieldValue属性

更新现有的SPListItem时,以下代码有效,并且自定义字段值将被保存,因为 SPListItem 已经存在!

var item = MyDocLib.Items[0] as SPListItem;
item["MyCustomFieldName"] = "some value";
item.Update();

在 SPListItemEventReceiver 中,在初始文件上传期间,在创建 ListItem 并且各个自定义字段值“尝试保存”之后,ItemUpdating/ItemUpdated 方法将包含对 SPItemEventProperties 属性的空引用。ListItem 因为如前所述, ItemAdded 方法被异步触发,并且新创建的 ListItem 在 ItemUpdating/ItemUpdated 方法中不可用。

4

2 回答 2

0

对于上传文件并将它们与列表项链接,您可以使用Sparqube 文档字段类型。注意:它是商业插件。

于 2014-01-21T14:25:42.407 回答
0

好的,因此创建一个自定义上传表单,它将文件上传输入控件和来自库的自定义字段与一个或多个 OOTB 或自定义SPListFieldIterators 相结合并不是一件容易的事——这可能是微软决定将流程分成两个不同且完全的原因无关的操作。

然而,允许这种功能具有内在价值,因为它使用户能够同时上传文件并在单个原子操作中持久化元数据,因此您的库中永远不会存在位于“以太”中的文档没有任何识别信息。

那么它采取了什么呢?几件事。

第一个是创建一个实用程序类,我称之为“ FileUploader ”,这就是它的样子。

public class FileUploader
{
    #region Fields

    private readonly SPList list;
    private readonly FileUpload fileUpload;
    private string contentTypeId;
    private string folder;
    private SPContext itemContext;
    private int itemId;

    #endregion

    #region Properties

    public bool IsUploaded
    {
        get
        {
            return this.itemId > 0;
        }
    }

    public SPContext ItemContext
    {
        get
        {
            return this.itemContext;
        }
    }

    public int ItemId
    {
        get
        {
            return this.itemId;
        }
    }

    public string Folder
    {
        get
        {
            return this.folder;
        }

        set
        {
            this.folder = value;
        }
    }

    public string ContentTypeId
    {
        get
        {
            return this.contentTypeId;
        }

        set
        {
            this.contentTypeId = value;
        }
    }

    #endregion

    public FileUploader(SPList list, FileUpload fileUpload, string contentTypeId)
    {
        this.list = list;
        this.fileUpload = fileUpload;
        this.contentTypeId = contentTypeId;
    }

    public FileUploader(SPList list, FileUpload fileUpload, string contentTypeId, string folder)
    {
        this.list = list;
        this.fileUpload = fileUpload;
        this.contentTypeId = contentTypeId;
        this.folder = folder;
    }

    public event EventHandler FileUploading;
    public event EventHandler FileUploaded;

    public event EventHandler ItemSaving;
    public event EventHandler ItemSaved;

    public void ResetItemContext()
    {
        //This part here is VERY, VERY important!!!
        //This is where you "trick/hack" the SPContext by setting it's mode to "edit" instead
        //of "new" which gives you the ability to essentially initialize the
        //SPContext.Current.ListItem and set it's ItemId value. This of course could not have
        //been accomplished before because in "new" mode there is no ListItem. 
        //Once you've done all that then you can set the FileUpload.itemContext 
        //equal to the SPContext.Current.ItemContext. 
        if (this.IsUploaded)
        {
            SPContext.Current.FormContext.SetFormMode(SPControlMode.Edit, true);
            SPContext.Current.ResetItem();
            SPContext.Current.ItemId = itemId;

            this.itemContext = SPContext.Current;
        }
    }

    public bool TryRedirect()
    {
        try
        {
            if (this.itemContext != null && this.itemContext.Item != null)
            {
                return SPUtility.Redirect(this.ItemContext.RootFolderUrl, SPRedirectFlags.UseSource, HttpContext.Current);
            }
        }
        catch (Exception ex)
        {
            // do something
            throw ex;
        }
        finally
        {
        }

        return false;

    }

    public bool TrySaveItem(bool uploadMode, string comments)
    {
        bool saved = false;
        try
        {
            if (this.IsUploaded)
            {
                //The SaveButton has a static method called "SaveItem()" which you can use
                //to kick the whole save process into high gear. Just right-click the method
                //in Visuak Studio and select "Go to Definition" in the context menu to see
                //all of the juicy details.
                saved = SaveButton.SaveItem(this.ItemContext, uploadMode, comments);

                if (saved)
                {
                    this.OnItemSaved();
                }
            }
        }
        catch (Exception ex)
        {
            // do something
            throw ex;
        }
        finally
        {
        }

        return saved;
    }

    public bool TrySaveFile()
    {
        if (this.fileUpload.HasFile)
        {
            using (Stream uploadStream = this.fileUpload.FileContent)
            {
                this.OnFileUploading();

                var originalFileName = this.fileUpload.FileName;

                SPFile file = UploadFile(originalFileName, uploadStream);

                var extension = Path.GetExtension(this.fileUpload.FileName);

                this.itemId = file.Item.ID;

                using (new EventFiringScope())
                {
                    file.Item[SPBuiltInFieldId.ContentTypeId] = this.ContentTypeId;
                    file.Item.SystemUpdate(false);

                    //This code is used to guarantee that the file has a unique name.
                    var newFileName = String.Format("File{0}{1}", this.itemId, extension);

                    Folder = GetTargetFolder(file.Item);

                    if (!String.IsNullOrEmpty(Folder))
                    {
                        file.MoveTo(String.Format("{0}/{1}", Folder, newFileName));
                    }

                    file.Item.SystemUpdate(false);
                }

                this.ResetItemContext();

                this.itemContext = SPContext.GetContext(HttpContext.Current, this.itemId, list.ID, list.ParentWeb);
                this.OnFileUploaded();

                return true;
            }
        }

        return false;
    }

    public bool TryDeleteItem()
    {
        if (this.itemContext != null && this.itemContext.Item != null)
        {
            this.ItemContext.Item.Delete();

            return true;
        }

        return false;
    }

    private SPFile UploadFile(string fileName, Stream uploadStream)
    {
        SPList list = SPContext.Current.List;

        if (list == null)
        {
            throw new InvalidOperationException("The list or root folder is not specified.");
        }

        SPWeb web = SPContext.Current.Web;

        SPFile file = list.RootFolder.Files.Add(fileName, uploadStream, true);

        return file;
    }

    private string GetTargetFolder(SPListItem item)
    {
        var web = item.Web;
        var rootFolder = item.ParentList.RootFolder.ServerRelativeUrl;
        var subFolder = GetSubFolderBasedOnContentType(item[SPBuiltInFieldId.ContentTypeId]);

        var folderPath = String.Format(@"{0}/{1}", rootFolder, subFolder);
        var fileFolder = web.GetFolder(folderPath);

        if (fileFolder.Exists) return folderPath;

        return Folder;
    }

    private void OnFileUploading()
    {
        EventHandler handler = this.FileUploading;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    private void OnFileUploaded()
    {
        EventHandler handler = this.FileUploaded;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    private void OnItemSaving()
    {
        EventHandler handler = this.ItemSaving;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    private void OnItemSaved()
    {
        EventHandler handler = this.ItemSaved;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

然后我在我的“ CustomUpload ”类中使用它,这是我的 ASPX 页面的 CodeBehind。

public partial class CustomUpload : LayoutsPageBase
{
    #region Fields

    private FileUploader uploader;

    #endregion

    #region Properties

    public SPListItem CurrentItem { get; set; }
    public SPContentType ContentType { get; set; }
    public int DocumentID { get; set; }

    private SPList List;

    #endregion

    public CustomUpload()
    {
        SPContext.Current.FormContext.SetFormMode(SPControlMode.New, true);
    }

    protected override void OnInit(EventArgs e)
    {
        if (IsPostBack)
        {
            // Get content type id from query string.
            string contentTypeId = this.Request.QueryString["ContentTypeId"];
            string folder = this.Request.QueryString["RootFolder"];

            //ALL THE MAGIC HAPPENS HERE!!!
            this.uploader = new FileUploader(SPContext.Current.List, this.NewFileUpload, contentTypeId, folder);

            //These event handlers are CRITIAL! They are what enables you to perform the file
            //upload, get the newly created ListItem, DocumentID and MOST IMPORTANTLY...
            //the newly initialized ItemContext!!!
            this.uploader.FileUploading += this.OnFileUploading;
            this.uploader.FileUploaded += this.OnFileUploaded;
            this.uploader.ItemSaving += this.OnItemSaving;
            this.uploader.ItemSaved += this.OnItemSaved;
            this.uploader.TrySaveFile();
        }

        base.OnInit(e);
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        //put in whatever custom code you want...
    }

    protected void OnSaveClicked(object sender, EventArgs e)
    {
        this.Validate();

        var comments = Comments.Text;

        if (this.IsValid && this.uploader.TrySaveItem(true, comments))
        {
            this.uploader.TryRedirect();
        }
        else
        {
            this.uploader.TryDeleteItem();
        }
    }

    private void OnFileUploading(object sender, EventArgs e)
    {
    }

    private void OnFileUploaded(object sender, EventArgs e)
    {
        //This is the next VERY CRITICAL piece of code!!!
        //You need to retrieve a reference to the ItemContext that is created in the FileUploader
        //class and then set your SPListFieldIterator's ItemContext equal to it.
        this.MyListFieldIterator.ItemContext = this.uploader.ItemContext;

        ContentType = this.uploader.ItemContext.ListItem.ContentType;

        this.uploader.ItemContext.FormContext.SetFormMode(SPControlMode.Edit, true);
    }

    private void OnItemSaving(object sender, EventArgs e)
    {
    }

    private void OnItemSaved(object sender, EventArgs e)
    {
        using (new EventFiringScope())
        {
            //This is where you could technically set any values for the ListItem that are
            //not tied into any of your custom fields.
            this.uploader.ItemContext.ListItem.SystemUpdate(false);
        }
    }
}

好的......那么所有这些代码的要点是什么?

好吧,如果您不热衷于实际查看我提供的评论,我会给您一个简短的摘要。

本质上,代码所做的是使用FileUploader帮助程序类执行整个文件上传过程,并使用一系列附加到各种 SPItem 和 SPFile 相关事件(即保存/保存和上传/上传)的EventHandler ,这些事件允许新创建的 SPListItem 和与CustomUpload类中使用的 SPContext.Current.ItemContext同步的 ItemContext 对象和 SPListItem.Id 值。一旦您拥有一个有效且新刷新的 ItemContext,您就可以“偷偷摸摸地”将您的SPListFieldIterator (管理您的自定义字段)正在使用的现有 ItemContext 设置为等于在FileUpload中创建的 ItemContext类并传回CustomUpload类,该类实际上具有对新创建的 ListItem 的引用!!!

这里要注意的另外一点是,您需要将SPContext.Current.FormContextSPListFieldIterator的控制模式从“New”设置为“Edit”。如果您不这样做,那么您将无法设置 ItemContext 和 ListItem 属性,并且您的数据将不会被保存。您也不能通过将控制模式值设置为“编辑”来开始,因为这样 FormContext 和 SPListFieldIterator 将期望现有的 ItemContext 在初始页面或控件生命周期的任何时间点当然不会存在,因为您没有还没有上传文件!!!

上述所有代码必须从 CustomUpload 类的 OnInit 方法执行。这样做的原因是您可以在初始化自身之前将新创建的 ItemContext 注入您的 SPListFieldIterator 并且它是子 SPField 控件(即您的自定义控件!!!)。一旦SPListFieldIterator具有对新创建的 ItemContext 的引用,它就可以使用所述 ItemContext 初始化它的所有子 SPField 控件,这就是您可以使用自定义上传页面的方式,该页面将 FileUpload 控件与自定义字段以及一个或多个 SPListFieldIterators 合并成功上传文件并将自定义字段中的所有值保存在单个原子操作中!

完成并完成!

注意:这个解决方案在“技术上”不是单一的或大气的操作,但它可以完成工作。

于 2014-01-29T19:28:30.790 回答