Good day and welcome to my first Sharepoint post! (I hope you find it useful)
Ok let's do this....I came across a client request whereby there was a requirement to have a document library that would be made available to all subsites of a site collection. As many of you already know OOTB, this is not supported and when embarking on such tasks (non-OOTB tasks that is) there is always some uncertainty whether or not some aspect of the solution will work as expected.
Anyway, I took to the task as a newcomer to Sharepoint, but was determined!
My scenario was as follows:
- The Central Library would sit in the root site and would be converted to a web part and placed on any subsite requiring it.
- The Central Library comprised of a custom content type which included several taxonomy fields.
- The EditForm was to be customised (ordering, hiding and positioning of fields).
- Users should be able to upload documents to the central library.
- Uploaded documents would automatically have a Taxonomy field populated whose Term value would be equal to the subsite name where the Central Library web part resided.
Not too big a deal I thought.... errr.... maybe not...
What I did:
- Created a Visual Studio solution which created all the necessary fields, the custom content type for the Central Library along with list definition.
- Used Sharepoint designer to export the Central Library (Document Library from root) as a web part.
- Created an Event Receiver to add populate the Taxonomy term to the list item being added. (Note: if the subsite Term does not exist I would add it in the receiver event first).
- Added the web part to a test page.
**Important Note - in order for your exported Document Library web part to display on a subsite this way - you MUST TURN OFF Allow Content Type Management in the Advanced Settings of the List Settings page.
The issues I encountered (as I am new to Sharepoint, maybe I overlooked something here - please feel free to enlighten me):
- As the Central Library web part is essentially a link to the Central Library in the root site, I do NOT have anyway to access the current site's Name property - bummer - if I try to access the parent site it always returns the root site. So my event receiver could not update the Taxonomy value with a Term value of the subsite's name.
- During development, my feature receiver was deleting the existing Central Library library everytime I deployed and so the list id kept changing therefore breaking the web part on the subsites. I would need to re-export the Central Library from the Root and upload it to the web parts gallery.
Now I needed to change whole process and in the end, I came up with the following:
- I created a Visual Web part which would mimic the "Add Document" link as seen on any document library.
- When this web part is added to a site, it immediately creates a copy of the Central Library (if it doesnt already exist) in the current site called "Central Library - Staging" and also sets it's expiration policy to expire after 1 day, anything in the list.
- So on my page where I have the Add Document web part - right above it - I place the Central Library web and set it's Toolbar to "None" essentially hiding it's own "Add Document" link.
- Now when a document is added using the new Add Document web part link, the document is tagged in the event receiver with the current subsite's name (as the Central Library copy is now in the subsite itself).
- In the same event receiver I then copy the item to the root Central Library.
**Note - I did initially attempt to copy the item and delete the existing one, but here lies another issue.... even though I deleted the item, Sharepoint still seemed redirect to it (I tried changing the redirect url and all that) so the modal window which, OOTB closes after deletion, stays open with an error message. For this reason I gave up on deletion and set the expiration date.
Lastly, I was getting slightly annoyed by having to export the Central Library library as a web part time and time again as mentioned in the above "issues I encountered". So I decided to fix this by making it easier to redeploy the Central Library web part, as this would also help whoever so happens to delete the Central Library and re-create it (for whatever reason!).
This is what I did:
- I downloaded the existing (working) .webpart file (Central Library web part), opened it in a text editor and added some placeholder text by replacing all instances of the listid guid with ***LISTID*** and replacing the one instance of the ViewID guid with ***VIEWID***. Save file!
- I added it to my solution as a Module to be deployed to the feature directory within the hive.
- When deploying the solution and after the creation of the Central Library in the root (during feature activated) the code would read in the contents of the .webpart file and replace the above mentioned placeholder text with the newly available IDs.
- Then some minor code to upload the newly modified web part to the gallery.
Job done! (I think)
Now for some code samples:
Event Receiver to tag an uploaded document with a Term value.
#region ItemAdded
public override void ItemAdded(SPItemEventProperties properties)
{
if (Convert.ToInt32(properties.List.BaseTemplate) >= 10000)
{
if (IsCentralLibraryContent(properties))
{
System.Diagnostics.Debug.WriteLine("Added");
TagItem(properties);
}
}
base.ItemAdded(properties);
}
#endregion
#region tag an item
protected void TagItem(SPItemEventProperties properties)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
SPListItem ListItemAdded = properties.ListItem;
string targetTerm = "root";
if (!properties.Web.IsRootWeb)
{
targetTerm = properties.Web.Name;
}
SPWeb web = properties.Web.Site.RootWeb;
web.Site.AllowUnsafeUpdates = true;
if (properties.ListItem.Fields[Settings.SUBSITEMETADATA] != null)
{
TaxonomyField field = (TaxonomyField)properties.ListItem.Fields[Settings.SUBSITEMETADATA];
//get term/s
TermStore termStore = GetTermStore(web);
var terms = termStore.GetTermSet(field.TermSetId).Terms;
bool siteNameFound = false;
Term tagTerm = null;
if (terms.Count > 0)
{
foreach (Term term in terms)
{
if (term.Name.ToLower() == targetTerm.ToLower())
{
tagTerm = term;
siteNameFound = true;
}
}
}
if (terms.Count == 0 || siteNameFound == false)
{
tagTerm = termStore.GetTermSet(field.TermSetId).CreateTerm(targetTerm, properties.Web.UICulture.LCID);
termStore.CommitAll();
}
EventFiringEnabled = false;
var listTerms = new List<Term>();
listTerms.Add(tagTerm);
field.SetFieldValue(properties.ListItem, listTerms);
properties.ListItem.SystemUpdate();
EventFiringEnabled = true;
web.Site.AllowUnsafeUpdates = false;
}
});
}
#endregion
#region move tagged document
protected void CopyTaggedDocument(SPItemEventProperties properties)
{
SPList srcLib = (SPDocumentLibrary)properties.List;
SPList destLib = (SPDocumentLibrary)properties.Web.Site.RootWeb.Lists[Settings.CENTRALIBRARY];
SPListItem item = properties.ListItem;
byte[] fileBytes = item.File.OpenBinary();
string destUrl = destLib.RootFolder.Url + "/" + item.File.Name;
SPFile destFile = destLib.RootFolder.Files.Add(destUrl, fileBytes, true);
if (destFile.Exists)
{
SPListItem destListItem = destFile.Item;
TaxonomyField destTax = destFile.Item.Fields[Settings.SUBSITEMETADATA] as TaxonomyField;
TaxonomyFieldValueCollection currentValues = properties.ListItem[Settings.SUBSITEMETADATA] as TaxonomyFieldValueCollection;
EventFiringEnabled = false;
//update metadata
destTax.SetFieldValue(destListItem, currentValues);
destListItem["CL_Description"] = item["CL_Description"];
destListItem["CL_Source"] = item["CL_Source"];
destListItem["CL_Language"] = item["CL_Language"];
destListItem["CL_Type"] = item["CL_Type"];
destListItem["CL_FiscalYear"] = item["CL_FiscalYear"];
destListItem["CL_Topic"] = item["CL_Topic"];
destListItem["CL_Discipline"] = item["CL_Discipline"];
destListItem["CL_SiteTags"] = item["CL_SiteTags"];
destListItem["CL_DocumentTags"] = item["CL_DocumentTags"];
destListItem["CL_Rating"] = item["CL_Rating"];
destListItem["CL_Country"] = item["CL_Country"];
destListItem.Update();
EventFiringEnabled = true;
}
}
#endregion
#region get term store
private static TermStore GetTermStore(SPWeb web)
{
return GetTermStore(web.Site);
}
/// <summary>
/// Get the term store for current site
/// </summary>
/// <param name="site">Site to get term store for</param>
/// <returns>Term store object</returns>
private static TermStore GetTermStore(SPSite site)
{
try
{
TaxonomySession session = new TaxonomySession(site);
TermStore termStore = session.DefaultSiteCollectionTermStore;
return termStore;
}
catch (Exception)
{
return null;
}
}
#endregion
Document Add Visual Web part code behind:
Feature Receiver (Install Central Library, setup taxonomy fields, delete staging libraries, upload modified web part):
I think that's about it - with this set up I can easily modify my Central Library list and have it updated on all subsite automatically.
I am currently still playing with this solution and plan to do the following:
During feature activation, create a copy of the existing Central Library document library (with contents), delete the original, create a new Central Library and then transfer the documents back again whilst preserving creation dates etc.
Will get back to you with that!
Bye for now - peace!
public partial class DocumentAddUserControl : UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
string html = string.Empty;
string libName = Settings.GetCentralLibStaging();
SPWeb web = SPContext.Current.Web;
SPList lib = web.Lists.TryGetList(libName);
if (lib == null)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
web.AllowUnsafeUpdates = true;
SPListTemplate template = web.Site.RootWeb.ListTemplates[Settings.CLTEMP_LISTTEMPLATE];
var listID = web.Lists.Add(libName, "Central Library staging area. Files get routed to the main library at the root.", template);
web.Update();
//get new list and set properties
lib = web.Lists[listID];
lib.NoCrawl = true;
lib.ContentTypesEnabled = true;
lib.Update();
//set expiration policy
ListPolicySettings pol = new ListPolicySettings(lib);
string settings = "<Schedules nextStageId=\"2\">" +
"<Schedule type=\"Default\">" +
"<stages>" +
"<data stageId=\"1\">" +
"<formula id=\"Microsoft.Office.RecordsManagement.PolicyFeatures.Expiration.Formula.BuiltIn\">" +
"<number>1</number>" +
"<property>Created</property>" +
"<propertyId>8c06beca-0777-48f7-91c7-6da68bc07b69</propertyId>" +
"<period>days</period>" +
"</formula>" +
"<action type=\"action\" id=\"Microsoft.Office.RecordsManagement.PolicyFeatures.Expiration.Action.MoveToRecycleBin\" />" +
"</data>" +
"</stages>" +
"</Schedule>" +
"</Schedules>";
pol.UseListPolicy = true;
pol.SetRetentionSchedule(settings, "Central Library Content Deletion");
pol.Update();
web.AllowUnsafeUpdates = false;
});
}
html = "   <span class=\"s4-clust\" style=\"position: relative; width: 10px; display: inline-block; height: 10px; overflow: hidden\">"
+ "<img src=\"/_layouts/images/fgimg.png\" alt=\"\" style=\"position: absolute; top: -128px !important; left: 0px !important\"/>"
+ "</span> <a class=\"ms-addnew\" id=\"idHomePageNewDocument\" onclick=\"javascript:NewItem2(event, "" + web.Url
+ "/_layouts/Upload.aspx?List={" + lib.ID.ToString() + "}&RootFolder=");javascript:return false;\" href=\"/" + web.Name
+ "/_layouts/Upload.aspx?List={" + lib.ID.ToString() + "}&RootFolder=\" target=\"_self\">Add document</a>";
lt.Text = html.ToString();
}
}
Feature Receiver (Install Central Library, setup taxonomy fields, delete staging libraries, upload modified web part):
#region set note fields for taxonomy fields
private void SetTaxField(SPSite site, Guid FieldID, string TermGroup, Guid NoteFieldID, bool RemoveNote)
{
if (site.RootWeb.Fields.Contains(FieldID))
{
TaxonomySession session = new TaxonomySession(site);
if (session.TermStores.Count != 0)
{
TermStore termStore = session.DefaultSiteCollectionTermStore;
Group grp = null;
foreach (var group in termStore.Groups)
{
if (group.Name == "Company Enterprise Taxonomy")
{
grp = group;
}
}
if (grp != null)
{
TermSet ts = null;
foreach (TermSet termset in grp.TermSets)
{
if (termset.Name == TermGroup)
{
ts = termset;
}
}
if (ts != null)
{
TaxonomyField field = site.RootWeb.Fields[FieldID] as TaxonomyField;
// Connect to MMS
field.SspId = ts.TermStore.Id;
field.TermSetId = ts.Id;
field.TargetTemplate = string.Empty;
field.AnchorId = Guid.Empty;
field.TextField = NoteFieldID;
if (RemoveNote) field.TextField = Guid.Empty;
field.Update();
}
}
}
}
}
#endregion
#region delete staging libraries
protected void DeleteInstancesOfStaging(SPSite root)
{
SPWebCollection siteWebs = root.AllWebs;
foreach (SPWeb web in siteWebs)
{
try
{
SPList libStaging = web.Lists.TryGetList(Settings.GetCentralLibStaging(root));
if (libStaging != null)
{
try
{
libStaging.Delete();
web.Update();
}
catch (SPException ex)
{
EventLog.WriteEntry("WVDocLibEvent Feature Install", "Staging library in " + web.Name + " could not be deleted." + ex.ToString());
continue;
}
}
}
finally
{
if (web != null)
web.Dispose();
}
}
}
#endregion
#region update Central Library webpart with new IDs
protected void ReConfigureWebpart(string ListID, string ViewID, SPSite site, string filename)
{
try
{
if (System.IO.File.Exists(filename))
{
var doc = XDocument.Load(filename);
XNamespace nm = "http://schemas.microsoft.com/WebPart/v3/";
var n = doc.Descendants()
.Where(x => (string)x.Attribute("name") == "ListName")
.FirstOrDefault();
{
Console.WriteLine(n.Value);
n.Value = ListID;
}
var o = doc.Descendants()
.Where(x => (string)x.Attribute("name") == "ListId")
.FirstOrDefault();
{
Console.WriteLine(o.Value);
o.Value = ListID;
}
var p = doc.Descendants()
.Where(x => (string)x.Attribute("name") == "XmlDefinition")
.FirstOrDefault();
{
string viewMarkup = p.Value;
System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(@"(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}", System.Text.RegularExpressions.RegexOptions.Compiled);
p.Value = reg.Replace(p.Value, ViewID);
}
doc.Save(filename);
doc = null;
}
else
{
EventLog.WriteEntry("WVDocLibEvent", "Central Library Webpart could not be updated - file not found.");
}
}
catch (Exception ex)
{
EventLog.WriteEntry("WVDocLibEvent", "Central Library Webpart could not be updated - " + ex.ToString() + " " + ex.StackTrace);
}
}
#endregion
#region upload updated Central Library webpart
protected void UploadCentralLibWebpart(SPSite site, string filename)
{
System.IO.FileStream fs = null;
try
{
System.IO.FileInfo file = new System.IO.FileInfo(filename);
fs = file.Open(System.IO.FileMode.Open, System.IO.FileAccess.Read);
}
catch (Exception ex)
{
EventLog.WriteEntry("WVDocLibEvent", "Error uploading Central Library webpart: " + ex.ToString());
}
try
{
using (SPWeb web = site.OpenWeb())
{
web.AllowUnsafeUpdates = true;
SPList list = web.Lists["Web Part Gallery"];
SPFolder folder = list.RootFolder;
SPFile webpart = folder.Files.Add("Central Library.webpart", fs, true);
webpart.Update();
web.AllowUnsafeUpdates = false;
}
}
catch (SPException ex)
{
EventLog.WriteEntry("WVDocLibEvent", "Error uploading Central Library webpart: " + ex.ToString());
}
}
#endregion
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
//Setup metadata column notefield mappings
SPSite site = properties.Feature.Parent as SPSite;
SetTaxField(site, new Guid("{DD1CF443-657B-4d32-95AB-1B134A8F6DB9}"), "Country", new Guid("{869DB01E-C573-4b7f-9985-F8BC6BDF5ED3}"), false);
SetTaxField(site, new Guid("{3519C3BF-F553-4fed-B2F1-ABA7E22F3CC4}"), "Language", new Guid("{6936F2B0-C0B8-4bb1-909D-63373C1B39D8}"), false);
SetTaxField(site, new Guid("{1E0B08D4-336C-4c68-A331-6003F46D2B2D}"), "Subsites", new Guid("{025F38F3-59D1-4fe7-B063-7CA77CC979A4}"), false);
SetTaxField(site, new Guid("{AA360E18-A607-4443-96CD-6DC5D92C49F6}"), "DocumentTags", new Guid("{96CABFAE-64CC-4250-9221-F905EE4CC60B}"), false);
string libName = Settings.CENTRALIBRARY;
using (SPWeb web = site.OpenWeb())
{
web.AllowUnsafeUpdates = true;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
//report any lists using this content type
IList<SPContentTypeUsage> usages = SPContentTypeUsage.GetUsages(web.ContentTypes[Settings.CONTENTTYPE]);
if (usages.Count > 0)
{
string cUse = string.Empty;
foreach (SPContentTypeUsage usage in usages)
{
cUse += usage.Url + "| ";
}
EventLog.WriteEntry("WVDocLibEvent", Settings.CONTENTTYPE + " is in use at: " + cUse);
}
SPList lib = web.Lists.TryGetList(libName);
if (lib == null)
{
SPListTemplate template = web.Site.RootWeb.ListTemplates[Settings.CLTEMP_LISTTEMPLATE];
var listID = web.Lists.Add(libName, "Marketing Hub Central Library.", template);
web.Update();
//get new list and set properties
lib = web.Lists[listID];
lib.Update();
}
string webpartfile = properties.Definition.RootDirectory + @"\WebpartXML\Central Library.webpart";
ReConfigureWebpart(lib.ID.ToString(), lib.DefaultView.ID.ToString(), site, webpartfile);
UploadCentralLibWebpart(site, webpartfile);
});
web.AllowUnsafeUpdates = false;
}
}
I think that's about it - with this set up I can easily modify my Central Library list and have it updated on all subsite automatically.
I am currently still playing with this solution and plan to do the following:
During feature activation, create a copy of the existing Central Library document library (with contents), delete the original, create a new Central Library and then transfer the documents back again whilst preserving creation dates etc.
Will get back to you with that!
Bye for now - peace!