Managed metadata navigation is a new feature that has been part of SharePoint 2013. The basic purpose of managed metadata navigation is to provide Friendly URL for your sites and pages in SharePoint. I have found SharePoint publishing site as a great tool for Content Management through out my work with different organization. There are caveats that comes along but in average i found it helpful for public anonymous sites who needs constant content update.
One bottleneck of the SharePoint public site was the URL that comes by default with Publishing sites. Most of the client who are not familiar with the SharePoint structure didn’t liked the extra term that comes with SharePoint. Most of the SharePoint Internet site URL has “/pages” embedded in their URL. “/pages” will have some meaning and someone familiar with SharePoint can relate to it. But for normal public it is an extra word to type to get to a particular page in the web. IF we browse around most of the web sites don’t have a extra word which doesn’t mean anything.
There are ways to handle a friendly URL for SharePoint site like in this blog FriendlyURL. Since Microsoft introduced managed metadata navigation, it has somehow been a tool that can serve the purpose of adding Friendly URL for sites and pages and sub-sites in a publishing site. But one thing i found little hassle and confusing with Managed metadata navigation is it is manual for sub sites and pages under sub-sites. Till date there is a bug in managed metadata navigation which is more described in this blog ManagedMetadataNavigation Bug.
So in this article i am going to explain about the solutions that i have written for one of my projects which will automate the managed metadata navigation process, that overcomes all these problems and users doesn’t have to do things manually.
This code has been tested and used successfully in my pas work. It will be great if you guys have some ideas to improve the code or the process, it will be a pleasure to adopt that.
Here is the whole process
- Add an Empty project to Visual Studio 2013
2. Make sure you make this solution as Farm Solution
3.Make sure following Dlls are added to your project
- Microsoft.SharePoint.Taxonomy
- Microsoft.SharePoint.Publishing
4.Now add new item to the project and pick the Event receivers template
5. In the list of Events pick the Web Event and Check Web Deleting and Web Provisioning.
6. Repeat the same process for Adding another Event Receiver and this time pick List Item Event Receivers and make sure u pick Pages Library from the event source item.
7. Once we have our Event receivers code files are ready now we can write the actual code that make things happen.
Find the Event receivers code file for pages
Comment out the existing code inside the ItemDeleting and ItemChecking code block.
Following is the function that will be referenced in both ItemDeleting and ItemChecking code block. Put this code inside the event receiver for pages.
This function is the main function that handles the adding and deleting for page items.
private void AddDeleteNavigation(SPWeb currentweb, SPWeb parentweb, SPWeb current_rootweb, Boolean Delete, SPListItem item)
{
try
{
IList urlpartslists = currentweb.ServerRelativeUrl.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
///variable declaration
string parentwebrelative_url = parentweb.ServerRelativeUrl.ToString();
string pagetitle = item.DisplayName;
string currentweb_url = urlpartslists[urlpartslists.Count - 1].ToString();
string currenweb_relativeurl = currentweb.ServerRelativeUrl.ToString().Replace(current_rootweb.ServerRelativeUrl.ToString(), "");
///taxonomy declartion
///
TaxonomySession taxSession = new TaxonomySession(currentweb.Site);
TermStore TermStore = taxSession.TermStores[0]; //Provide your term store name
Group termgroup = TermStore.Groups["Site Navigation"]; // Provide your Site Navigatoin Name
TermSet currenterm = termgroup.TermSets[urlpartslists[0].ToString()];
NavigationTermSet navTermSet = NavigationTermSet.GetAsResolvedByWeb(currenterm, current_rootweb, StandardNavigationProviderNames.GlobalNavigationTaxonomyProvider);
if (currentweb.IsRootWeb)
{
if (Delete && curr_web_term_exists != null)
{
curr_web_term_exists.Delete();
}
else
if (!Delete && curr_web_term_exists == null)
{
curr_web_term_exists = navTermSet.CreateTerm(pagetitle, NavigationLinkType.FriendlyUrl);
curr_web_term_exists.FriendlyUrlSegment.Value = pagetitle;
curr_web_term_exists.TargetUrl.Value = "~sitecollection" + "/" + item.File.Url.ToString();
}
TermStore.CommitAll();
}
else if ((!currentweb.IsRootWeb) && (currentweb.ParentWeb.IsRootWeb))
{
try
{
NavigationTerm curr_web_term_exists = navTermSet.Terms.Where(t => t.Title.ToString() == urlpartslists[1].ToString().Replace("_", "")).FirstOrDefault();
NavigationTerm curr_page_term_exists = curr_web_term_exists.Terms.Where(t => t.Title.ToString() == pagetitle).FirstOrDefault();
if (Delete && curr_page_term_exists != null)
{
curr_page_term_exists.Delete();
}
else if (curr_page_term_exists == null && !Delete)
{
curr_page_term_exists = curr_web_term_exists.CreateTerm(pagetitle, NavigationLinkType.FriendlyUrl);
curr_page_term_exists.FriendlyUrlSegment.Value = pagetitle;
curr_page_term_exists.TargetUrl.Value = "~sitecollection" + "/" + currenweb_relativeurl + "/" + item.File.Url.ToString();
}
TermStore.CommitAll();
}
catch (Exception ex)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("ManagedMetadata Navigation", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected,ex.Message);
}
}
else
if (!currentweb.ParentWeb.IsRootWeb && !currentweb.IsRootWeb)
{
try
{
NavigationTerm first_term = navTermSet.Terms.Where(t => t.Title.ToString() == urlpartslists[1].ToString().Replace("_", "")).FirstOrDefault();
NavigationTerm parent_term = null;
if ((first_term.Title.ToString() == urlpartslists[2].ToString().Replace("_", "")) && urlpartslists.Count == 3)
{
NavigationTerm pageterm = first_term.Terms.Where(t => t.Title.ToString() == pagetitle).FirstOrDefault();
if (Delete != true && pageterm == null)
{
NavigationTerm newterm = first_term.CreateTerm(pagetitle, NavigationLinkType.FriendlyUrl);
newterm.FriendlyUrlSegment.Value = pagetitle;
newterm.TargetUrl.Value = "~sitecollection" + "/" + currenweb_relativeurl + "/" + item.File.Url;
}
else
if (Delete)
{
NavigationTerm delteterm = first_term.Terms.Where(t => t.Title.ToString() == pagetitle).FirstOrDefault();
delteterm.Delete();
}
}
else
{
for (int i = urlpartslists.Count - 1; i > 0; i--)
{
parent_term = first_term.Terms.Where(t => t.Title.ToString() == (urlpartslists[i].ToString().Replace("_", ""))).FirstOrDefault();
if (parent_term != null && i == urlpartslists.Count - 1)
{
if (Delete && curr_page_term_exists != null)
{
curr_page_term_exists.Delete();
}
else
if (!Delete && curr_page_term_exists == null)
{
NavigationTerm newterm = parent_term.CreateTerm(pagetitle, NavigationLinkType.FriendlyUrl);
newterm.FriendlyUrlSegment.Value = pagetitle;
newterm.TargetUrl.Value = "~sitecollection" + "/" + currenweb_relativeurl + "/" + item.File.Url;
}
break;
}
else if (parent_term != null)
{
first_term = parent_term;
parent_term = null;
i = urlpartslists.Count;
}
}
}
TermStore.CommitAll();
}
catch (Exception ex)
{
SPDiagnosticsService.Local.WriteTrace(0,
new SPDiagnosticsCategory("ManagedMetadata Navigation", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected,ex.Message);
}
}
-
}
catch (Exception ex)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("ManagedMetadata Navigation", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected, ex.Message);
}
}
Now Add following code in ItemDeleting of the page item receiver
try { string fileurl = properties.ListItem.File.Url; //Check if the current page is not welcome page and not a system page. //When a web is created then it adds few pages which will trigger these events. //so to avoid them we filter out at here. if (fileurl != properties.ListItem.Web.RootFolder.WelcomePage && !fileurl.Contains("_catalogs")) { SPWeb web = properties.List.ParentWeb; SPListItem item = properties.ListItem; SPWeb current_parentweb = web.ParentWeb; SPWeb current_rootweb = web.Site.RootWeb; AddDeleteNavigation(web, current_parentweb, current_rootweb, true, item); } } catch (Exception ex) { SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("ManageMetadata Navigation", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected, ex.Message); }
Add following code in the ItemCheckingIn code block
try
{
string fileurl = properties.ListItem.File.Url;
if (fileurl != properties.ListItem.Web.RootFolder.WelcomePage && !fileurl.Contains("_catalogs"))
{
SPWeb web = properties.List.ParentWeb;
string listurl = properties.ListItem[SPBuiltInFieldId.EncodedAbsUrl].ToString();
SPListItem item = web.GetListItem(listurl);
SPWeb current_parentweb = web.ParentWeb;
SPWeb current_rootweb = web.Site.RootWeb;
AddDeleteNavigation(web, current_parentweb, current_rootweb, false, item);
}
}
catch (Exception ex)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("ManagedMetadata", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected, ex.Message);
}
Compile the solution. We have the page item events receiver done now let’s do the Web event-receiver code file.
6.Open the eventreceiver.cs file for the web Event-receiver.
There is not much different in the logic of the Site. Most of the logic you will find is just being reused. I guess we can still use only page item event receiver including the welcome page in the event receiver. I hope someone will make a use of that. For now let me write the code for Site Event receiver.
Here is the main function that does the delete and adding of the navigation for Web.
At the top paragraph of this article i was talking about an issues with managed metadta navigation. to fix or workaround with that i added an underscore at the end of my subsite URL so that we can get away with that bug. the following code looks for an underscore and replaces it for the friendly URL.
private void WebAddDeleteNavigation(SPWeb currentweb, SPWeb parentweb, SPWeb current_rootweb, Boolean Delete)
{
try
{
IList urlpartslists = currentweb.ServerRelativeUrl.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
string currentweb_url = urlpartslists[urlpartslists.Count - 1].ToString();
string correctedweb_url = currentweb_url.Replace("_", "");
string currenweb_relativeurl = currentweb.ServerRelativeUrl.ToString().Replace(current_rootweb.ServerRelativeUrl.ToString(), "");
TaxonomySession taxSession = new TaxonomySession(currentweb.Site);
TermStore TermStore = taxSession.TermStores[0];
Group termgroup = TermStore.Groups["Site Navigation"];
String termsetcount = Convert.ToString(termgroup.TermSets.Count);
TermSet currenterm = termgroup.TermSets[0];
NavigationTermSet navTermSet = NavigationTermSet.GetAsResolvedByWeb(currenterm, current_rootweb, StandardNavigationProviderNames.GlobalNavigationTaxonomyProvider);
if ((!currentweb.IsRootWeb) && (currentweb.ParentWeb.IsRootWeb))
{
try
{
if (termgroup != null)
{
NavigationTerm curr_web_term_exists = navTermSet.Terms.Where(t => t.Title.ToString() == correctedweb_url).FirstOrDefault();
if (Delete && curr_web_term_exists != null)
{
curr_web_term_exists.Delete();
}
else
if (!Delete)
{
curr_web_term_exists = navTermSet.CreateTerm(correctedweb_url, NavigationLinkType.FriendlyUrl);
curr_web_term_exists.FriendlyUrlSegment.Value = correctedweb_url;
curr_web_term_exists.TargetUrl.Value = "~sitecollection" + "/" + currenweb_relativeurl + "/Pages/default.aspx";
}
}
TermStore.CommitAll();
}
catch (Exception ex)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Manage Metadata", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected,ex.Message);
}
}
else
if (!currentweb.ParentWeb.IsRootWeb && !currentweb.IsRootWeb)
{
try
{
NavigationTerm first_term = navTermSet.Terms.Where(t => t.Title.ToString() == urlpartslists[1].ToString().Replace("_", "")).FirstOrDefault();
NavigationTerm parent_term = null;
if ((first_term.Title.ToString() == urlpartslists[1].ToString().Replace("_", "")) && urlpartslists.Count == 3)
{
if (Delete != true)
{
NavigationTerm newterm = first_term.CreateTerm(correctedweb_url, NavigationLinkType.FriendlyUrl);
newterm.FriendlyUrlSegment.Value = correctedweb_url;
newterm.TargetUrl.Value = "~sitecollection" + "/" + currenweb_relativeurl + "/Pages/default.aspx";
}
else
if (Delete)
{
NavigationTerm delteterm = first_term.Terms.Where(t => t.Title.ToString() == correctedweb_url).FirstOrDefault();
delteterm.Delete();
}
}
else
{
for (int i = urlpartslists.Count - 1; i > 0; i--)
{
parent_term = first_term.Terms.Where(t => t.Title.ToString() == urlpartslists[i].ToString().Replace("_", "")).FirstOrDefault();
if (parent_term != null && i == urlpartslists.Count - 2)
{
if (Delete)
{
NavigationTerm curr_site_term_exists = parent_term.Terms.Where(t => t.Title.ToString() == correctedweb_url).FirstOrDefault();
if (curr_site_term_exists != null)
{
curr_site_term_exists.Delete();
}
}
else
{
NavigationTerm newterm = parent_term.CreateTerm(correctedweb_url, NavigationLinkType.FriendlyUrl);
newterm.FriendlyUrlSegment.Value = correctedweb_url;
newterm.TargetUrl.Value = "~sitecollection" + "/" + currenweb_relativeurl + "/Pages/default.aspx";
}
break;
}
else if (parent_term != null)
{
first_term = parent_term;
parent_term = null;
i = urlpartslists.Count - 1;
}
}
}
TermStore.CommitAll();
}
catch (Exception ex)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Manage Metadata", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected, ex.Message);
}
}
}
catch (Exception ex)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Manage Metadata", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected, ex.Message);
}
}
Once you have this function added in the site navigation event receiver code file. now lets update the the event receiver methods.
In the Web Deleting method put this code
try { SPWeb web = properties.Web; if (web.Webs.Count == 0) { SPWeb current_parentweb = web.ParentWeb; SPWeb current_rootweb = web.Site.RootWeb; AddDeleteNavigation(web, current_parentweb, current_rootweb, true); } } catch (Exception ex) { SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Managed Metadata Navigation", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected, "web deleting " + ex.Message); }
In the WebProvisioned method add following code
try {
SPWeb web = properties.Web;
web.Navigation.UseShared = true;
SPWeb current_parentweb = web.ParentWeb;
SPWeb current_rootweb = web.Site.RootWeb;
AddDeleteNavigation(web, current_parentweb, current_rootweb, false);
}
catch (Exception ex)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Managed Metadata", TraceSeverity.Unexpected, EventSeverity.Information), TraceSeverity.Unexpected, "web provisioning " + ex.Message);
}
Compile the solutions and Deploy to your Web Application.
Few things to be noted before activating the feature
1. Make sure your terms store name is correct int he service application and in the code
2. Site collection is publishing site template
3. Change navigation to managed metadata navigation
4. Users who will be adding new pages and new site need enough permission at least contribute in the term store and term set in the managed metadata service application.
Here is the codeplex link to this solutions Enable Auto SharePoint Friendly URL