1

SharePoint で、ユーザーがハード ドライブからアップロードするファイルを選択し、ファイルのタイトルを入力し、必要に応じてコメントを追加し、コンテンツを指定できるように、ファイル アップロード コントロールとカスタム フィールド タイプを組み合わせたカスタム アップロード ページを作成することは可能ですか?いくつかのカスタム フィールドに追加データを入力し、新しい SPListItem を作成し、ファイルをアップロードして新しい SPListItem に関連付け、最後にカスタム フィールドに入力されたすべての値を新しく作成した SPListItem に正常に保存しますか?

注:私は、SharePoint カスタム フィールド タイプのみを使用してこのタスクを実行しようとしています。単純に多数の UserControls を含むカスタム ASPX ページを使用するのではありません。

カスタム フィールド タイプを使用する際に存在する根本的な問題は、SharePoint ドキュメント ライブラリのファイル アップロード イベントが非同期イベントであることです。SPListItemEventReceiver で使用可能な ItemAdding メソッドの既定の動作をオーバーライドできます。このメソッドを使用すると、ファイルが「アップロード中」のときに特定の情報にアクセスできます。同様に、ItemAdded から新しく作成された SPListItem に関する情報にアクセスできます。メソッドは「アイテムが既に追加された後に」呼び出されますが、このメソッドは別のスレッドで発生し、カスタム フィールドまたはそれぞれの値に関連する情報をまったく知らずに非同期で実行されるため、ユーザーが入力したデータはまったく取得されません。これらのフィールドでは、正常に保存されます。

ユーザーが EditFormTemplate を使用してカスタム フィールドの値を編集することにより、ドキュメントに関する情報を更新したい場合、初期化中に各フィールドの SPListItem プロパティが設定されます。このような場合、ListItem は既に存在するため、これはすべて正常に機能します。問題は、ユーザーが初めてドキュメントをアップロードしたいとき、明らかに ListItem がまだ存在しないため、各フィールドが SPListItem プロパティを「null」に設定して初期化され、永久に null のままになることです。ファイルがアップロードされた後に、新しく作成されたListItemへの参照を使用して、各フィールドの ListItem プロパティをさかのぼって更新する方法ではないようです。

Microsoft がユーザーに 1 つの画面でファイルをアップロードし、ファイルが正常にアップロードされた後に編集フォームにリダイレクトすることを強制することを主張したのは、この理由だけでした。2 つのページを分割することにより、Microsoft はユーザーに、ファイルに関するその他の情報を保存する前に、ファイルをアップロードしてListItemを作成するように強制します。ファイルがアップロードされ、ListItem が作成されると、ListItem は既に存在するため、個々のカスタム フィールドの値をそれぞれ ListItem に保存することに問題はありません。

注: BaseFieldControl は、FormComponent を継承する FieldMetadata を継承します。FormComponent には、フィールドが属する基になる SPListItem に対応するItemというプロパティがあります。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 プロパティの null 参照が含まれます。 ItemAdded メソッドは非同期で起動され、新しく作成された ListItem は ItemUpdating/ItemUpdated メソッドでは使用できません。

4

2 に答える 2

0

さて、ファイル アップロード入力コントロールとライブラリのカスタム フィールドを 1 つ以上の OOTB またはカスタムSPListFieldIteratorsと組み合わせたカスタム アップロード フォームを作成するのは簡単な作業ではありません。これがおそらく、Microsoft がプロセスを 2 つの別個の完全に分離することを決定した理由です。関係のない操作。

それにもかかわらず、この種の機能を許可することには固有の価値があります。ユーザーがファイルを同時にアップロードし、単一のアトミック操作でメタデータを永続化できるため、「エーテル」に存在するドキュメントがライブラリに存在することはありません。識別情報なしで。

それで、それは何でしたか?いくつかのこと。

1 つ目は、「 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);
        }
    }
}

それから、自分の ASPX ページの分離コードである" CustomUpload " クラスでそれを使用しました。

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を使用することです。 CustomUpload クラスで使用されているSPContext.Current.ItemContextと同期される ItemContext オブジェクトおよび SPListItem.Id 値。有効で新しく更新された ItemContext を取得したら、SPListFieldIterator (カスタム フィールドを管理している) によって使用されている既存の ItemContext を、FileUploadで作成された ItemContext と等しく設定することができます。クラスに渡され、新しく作成された ListItem への参照を実際に持つCustomUploadクラスに戻されます!!!

ここでさらに注意すべき点は、SPContext.Current.FormContextSPListFieldIteratorの両方のコントロール モードを"New" から "Edit" に設定する必要があることです。そうしないと、ItemContext および ListItem プロパティを設定できず、データが保存されません。また、コントロール モードの値を「編集」に設定して開始することもできません。これは、FormContext と SPListFieldIterator が既存の ItemContext を期待するためです。これは、最初のページまたはコントロールのライフ サイクルのどの時点でも存在しないためです。実際にはまだファイルをアップロードしていません!!!

上記のコードはすべて、 CustomUploadクラスの OnInit メソッドから実行する必要があります。この理由は、新しく作成された ItemContext を SPListFieldIterator に挿入してから、それ自体を初期化し、それが子 SPField コントロール (つまり、カスタム コントロール!!!) になるようにするためです。SPListFieldIterator が新しく作成された ItemContext への参照を取得すると、そのすべての子 SPField コントロールを前述の ItemContext で初期化できます。これにより、 FileUploadコントロールをカスタム フィールドとマージするカスタム アップロード ページを、1 つ以上の SPListFieldIterator と共に使用することができます。ファイルを正常にアップロードし、単一のアトミック操作でカスタム フィールドのすべての値を保存します。

やった!

注: この解決策は、「技術的に」単一またはアトモイックな操作ではありませんが、仕事は完了します。

于 2014-01-29T19:28:30.790 に答える
0

ファイルをアップロードしてリスト アイテムにリンクするには、Sparqube ドキュメントフィールド タイプを使用できます。注: 商用アドオンです。

于 2014-01-21T14:25:42.407 に答える