Article Page Definition and URL Routing

Custom functionality may require that URLs be defined that can be referenced by code to enable specific workflows (editing content, viewing content, listing content, etc). New pages can be declared in code at custom routed URLs to provide reliable URLs for specific functionality to custom plugins and UI extensions.

Routed Pages vs. Custom Pages

Custom pages are useful for custom UI specific to a single blog, group, or site that is not required as part of a larger application. When a page/URL must exist to enable a workflow, a routed page may be a better option. The table below shows all differences between routed pages and custom pages:

  Routed Pages Custom Pages
Supports parsing the URL into a programmatic context Yes

No

Defined within a specific theme instance (a single blog, group, site, etc) No

Yes

Has a factory default implementation Yes

No

Created in code Yes

No

Created in the user interface No

Yes

Requires /p/ in URL path No

Yes

Defining Site Pages

The INavigable plugin type is used to define routed site pages. INavigable extends IPlugin and adds the RegisterUrls() method that provides a controller to register new URL routes.

Example 1:  Site Page

The Site Page example will create a blank page at the root of the site with the URL "custom."  For example, if your community URL was  http://mycommunity, the new page would be found at http://community/custom.

using Telligent.Evolution.Extensibility.Urls.Version1;

namespace Telligent.Evolution.Examples
{
	public class CustomSitePageExample : INavigable
	{
		#region IPlugin

		public string Name
		{
			get { return "Custom Site Page Example"; }
		}

		public string Description
		{
			get { return "Define a custom site page"; }
		}

		public void Initialize()
		{
		}

		#endregion

		#region INavigable

		public void RegisterUrls(IUrlController controller)
		{
			controller.AddPage("page-custom", "custom", null, null, "page-custom", new PageDefinitionOptions());
		}

		#endregion
	}
}

We use the controller.AddPage method to add a new widget page.  For additional information on the IUrlController and its methods see the IUrlController documentation .

Example 2: Members Page

This members page plugin adds a page for each user on the site at http://mycommunity/{username}/custompage.  

using Telligent.Evolution.Extensibility;
using Telligent.Evolution.Extensibility.Api.Version1;
using Telligent.Evolution.Extensibility.Urls.Version1;
using Telligent.Evolution.Extensibility.Version1;

namespace Telligent.Evolution.Examples
{
	public class CustomPageExamples : INavigable, ITranslatablePlugin
	{
		private ITranslatablePluginController _translationController;

		#region IPlugin

		public string Name
		{
			get { return "Custom User Page Example"; }
		}

		public string Description
		{
			get { return "Define a custom user page"; }
		}

		public void Initialize()
		{
		}

		#endregion

		#region INavigable

		public void RegisterUrls(IUrlController controller)
		{
			object userNameConstraints = new { UserName = @"^[a-zA-Z0-9\-\._]+$" };

			controller.AddPage("page-members-custom", "members/{UserName}/custompage", null, userNameConstraints, "page-members-custom", new PageDefinitionOptions()
			{
				DefaultPageXml = @"<contentFragmentPage pageName=""page-members-custom"" isCustom=""false"" layout=""Content"" themeType=""0c647246-6735-42f9-875d-c8b991fe739b"">
      <regions>
        <region regionName=""Content"">
          <contentFragments>
            <contentFragment type=""Telligent.Evolution.ScriptedContentFragments.ScriptedContentFragment, Telligent.Evolution.ScriptedContentFragments::796a5296bbb74d24b4113eee9352d431"" showHeader=""False"" cssClassAddition=""no-wrapper with-spacing responsive-1"" isLocked=""False"" configuration="""" />
         </contentFragments>
        </region>
      </regions>
      <contentFragmentTabs />
    </contentFragmentPage>",
				TitleFunction = () => _translationController.GetLanguageResourceValue("page-members-custom"),
				DescriptionFunction = () => _translationController.GetLanguageResourceValue("page-members-custom-description"),
				ParseContext = ParseUserContext,
				Validate = ValidateUserContext
			});
		}

		#endregion

		#region ITranslatablePlugin

		public Translation[] DefaultTranslations
		{
			get
			{
				var t = new Translation("en-us");

				t.Set("page-members-custom", "Sample User Page");
				t.Set("page-members-custom-description", "Shows a page at the url members/{username}/custompage.");

				return new Translation[] { t };
			}
		}

		public void SetController(ITranslatablePluginController controller)
		{
			_translationController = controller;
		}

		#endregion

		private void ParseUserContext(PageContext context)
		{
			var userName = context.GetTokenValue("UserName");

			if (userName != null)
			{
				var usersApi = Apis.Get<IUsers>();
				string decodedUserName = Apis.Get<IUrl>().DecodeFileComponent(userName.ToString());
				var user = usersApi.Get(new UsersGetOptions() { Username = decodedUserName });

				if (user != null && !user.HasErrors())
				{
					context.ContextItems.Put(new ContextItem()
					{
						TypeName = "User",
						ApplicationId = user.ContentId,
						ApplicationTypeId = usersApi.ContentTypeId,
						ContainerId = user.ContentId,
						ContainerTypeId = usersApi.ContentTypeId,
						ContentId = user.ContentId,
						ContentTypeId = usersApi.ContentTypeId,
						Id = user.Id.ToString()
					});
				}
			}
		}

		private void ValidateUserContext(PageContext context, IUrlAccessController urlAccessController)
		{
			if (context.ContextItems.Find(item => item.TypeName == "User") == null)
				urlAccessController.Redirect(Apis.Get<ICoreUrls>().LogIn(new CoreUrlLoginOptions { ReturnToCurrentUrl = true }));
		}
	}
}

In this example the Url parameter is defined as "members/{UserName}/custompage", the  new { UserName = @"^[a-zA-Z0-9\-\._]+$" } constraint has also been applied to the UserName variable.  The Url will only respond if the requested Url matches the format and constraints that are defined when adding the page.

The example also uses some of the options available in the PageDefinitionOptions  parameter.  DefaultPageXml is used to define the factory default set of widgets for this page.  The TitleFunction and DescriptionFunction options determine the page's name and description when working with the page in the theme editing panels.  ParseContext defines the method that is used to examine the Url and determine what item are in context for this page.  ValidateContext is used to validate the parsed context is valid to render the page (is required data present, does the user have access to the context, etc).

Defining Application Pages

The IApplicationNavigable  plugin type is used to extend existing applications (and groups) by adding new routed pages.  In addition to the base IPlugin members, IApplicationNavigable adds the ApplicationTypeId to define which application the URL/route belongs to and RegisterUrls() to register URLs to the related application type.

Example 3: Blog Raw Page

This sample adds a url to a blog that is handled directly (not through page/widget rendering).  The URL defined in the sample would have the following URL pattern: http://community/{grouppath}/{b}/{blogappkey}/raw.

using System;
using System.Linq;
using Telligent.Evolution.Extensibility;
using Telligent.Evolution.Extensibility.Api.Version1;
using Telligent.Evolution.Extensibility.Urls.Version1;

namespace Telligent.Evolution.Examples
{
	public class CustomBlogRawExample : IApplicationNavigable
	{
		#region IPlugin

		public string Name
		{
			get { return "Custom Blog Pages"; }
		}

		public string Description
		{
			get { return "Define custom blog raw url"; }
		}

		public void Initialize()
		{
		}

		#endregion

		#region IApplicationNavigable

		public Guid ApplicationTypeId
		{
			get { return Apis.Get<IBlogs>().ApplicationTypeId; }
		}

		public void RegisterUrls(IUrlController controller)
		{
			controller.AddRaw(
				"blog-raw",
				"{WeblogApp}/raw",
				null,
				null,
				async (a, p) =>
				{
					var blogApi = Apis.Get<IBlogs>();
					var blog = p.ContextItems.GetAllContextItems().FirstOrDefault(b => b.ContentTypeId == blogApi.ContentTypeId);
					if (blog != null)
					{
						var posts = Apis.Get<IBlogPosts>().List(new BlogPostsListOptions() { BlogId = int.Parse(blog.Id) });
						foreach (var post in posts)
							await a.Response.WriteAsync(post.Title + "<br />");
					}
				}, new RawDefinitionOptions() { ParseContext = ParseBlogContext });
		}

		#endregion

		private void ParseBlogContext(PageContext context)
		{
			var appKey = context.GetTokenValue("WeblogApp");
			var blogsApi = Apis.Get<IBlogs>();
			var groupsApi = Apis.Get<IGroups>();

			if (appKey != null)
			{
				var groupItem = context.ContextItems.GetAllContextItems().FirstOrDefault(a => a.ContentTypeId == groupsApi.ContentTypeId);
				if (groupItem != null)
				{
					var blog = blogsApi.Get(new BlogsGetOptions() { GroupId = int.Parse(groupItem.Id), Key = appKey.ToString() });
					if (blog != null)
					{
						context.ContextItems.Put(new ContextItem()
						{
							TypeName = "Blog",
							ApplicationId = blog.ApplicationId,
							ApplicationTypeId = blogsApi.ApplicationTypeId,
							ContainerId = blog.Group.ApplicationId,
							ContainerTypeId = groupsApi.ContentTypeId,
							ContentId = blog.ApplicationId,
							ContentTypeId = blogsApi.ApplicationTypeId,
							Id = blog.Id.ToString()
						});
					}
				}
			}
		}
	}
}

The URL is defined as {WeblogApp}/raw, since we are adding a page to the blog application, the portion of the URL up to and including the blog application identifier ('/b/') accounted for.  You are only required to define the portion of the url inside the group or application. 

What is the difference between adding a widget page and adding a raw page?

The IUrlController provides the option to add a widget page or a raw page.  A widget page is a themeable page that supports a widget-based layout.  Raw pages give complete control over the output when defining the Url.  The typical use case for raw pages are RSS Feeds, file downloads or Xml.