さて、ファイル アップロード入力コントロールとライブラリのカスタム フィールドを 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.FormContextとSPListFieldIteratorの両方のコントロール モードを"New" から "Edit" に設定する必要があることです。そうしないと、ItemContext および ListItem プロパティを設定できず、データが保存されません。また、コントロール モードの値を「編集」に設定して開始することもできません。これは、FormContext と SPListFieldIterator が既存の ItemContext を期待するためです。これは、最初のページまたはコントロールのライフ サイクルのどの時点でも存在しないためです。実際にはまだファイルをアップロードしていません!!!
上記のコードはすべて、 CustomUploadクラスの OnInit メソッドから実行する必要があります。この理由は、新しく作成された ItemContext を SPListFieldIterator に挿入してから、それ自体を初期化し、それが子 SPField コントロール (つまり、カスタム コントロール!!!) になるようにするためです。SPListFieldIterator が新しく作成された ItemContext への参照を取得すると、そのすべての子 SPField コントロールを前述の ItemContext で初期化できます。これにより、 FileUploadコントロールをカスタム フィールドとマージするカスタム アップロード ページを、1 つ以上の SPListFieldIterator と共に使用することができます。ファイルを正常にアップロードし、単一のアトミック操作でカスタム フィールドのすべての値を保存します。
やった!
注: この解決策は、「技術的に」単一またはアトモイックな操作ではありませんが、仕事は完了します。