Filtering Page Output

Template output, page content and most C1 Functions - all emit XHTML, represented in the run time as Composite.Core.Xml.XhtmlDocument, an XDocument. In this guide, we will discuss the ways you can access this markup - either as a whole page or as specific components - and transform it, before it is sent to the client.

Transforming the markup of the whole page using IPageContentFilter

IPageContentFilter allows you to change any XHTML on a page, after all functions have executed, before it is served to users / cached.

The overall strategy is creating a class implementing IPageContentFilter and registering it in the C1 service collection, like in this sample:

using System.Xml.Linq;
using Composite.Core.WebClient.Renderings.Page;
using Composite.Core.Xml;


[ApplicationStartup]
public class Startup
{
   public static void ConfigureServices(IServiceCollection services)
   {
      services.AddSingleton<IPageContentFilter>(new MyPageContentFilter();
   }
}

class MyPageContentFilter : IPageContentFilter
{
    public void Filter(XhtmlDocument container, IPage page)
    {
        var titleElement = document.Head.Element(Namespaces.Xhtml + "title");
        if (titleElement != null)
        {
            titleElement.Value = "Title overwritten by global filter. Was: " + titleElement.Value;
        }
    }

    public int Order => 100;
}
  • int Order, the integer used to order content filters when more than one exists. Lower numbers run before higher numbers
  • void Filter, this method is passed the page's XhtmlDocument and details about the page (in the form of the IPage data for the page). The Filter method is expected to manipulate the XhtmlDocument in-place. Any changes made are passed on to the next filter/visitor.

Transforming specific content elements

The C1 Function layer allows you to pass the output of one function call (or a content element) on to another C1 Function via a parameter.

Below are two examples, one taking the output of a C1 Function, the other working on a page template content element. In general, this approach can be mixed and matched as you please, and you can thus target whole templates, specific content areas etc.

Sample function that transforms one XHTML document into another

The following code can be used as a Razor Function in C1 CMS. See below the examples of calling this function. This sample will take all body elements of an XHTML document and wrap a div with a pink background around it.

@inherits RazorFunction
@functions {
    [FunctionParameter(Label = "XHTML document input", Help = "We will wrap the body of this in pretty pink!")]
    public XhtmlDocument Document { get; set; }
}
@{
	var newBody = new XElement("div", new XAttribute("style", "background-color:pink"), Document.Body.Elements());
	Document.Body.Elements().Remove();
	Document.Body.Add( newBody );
}
@Html.Raw(@Document)

Below are 3 examples of calling this function (named Test.Transform) from within a Razor Page Template. The principle works for pure markup and other situations where C1 Functions can be called.

@inherits RazorPageTemplate
@functions {
    public override void Configure()
    {
        TemplateId = new Guid("1db33abc-cbb9-42bc-b746-4ea71f102d49");
        TemplateTitle = "Minimal";
    }

    [Placeholder(Id = "content", Title = "Content", IsDefault = true)]
    public XhtmlDocument Content { get; set; }
}
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://www.composite.net/ns/function/1.0" >
<head>
    @*you can add template specific head elements here*@
</head>
<body>
    <div class="example-1">
		<f:function name="Test.Transform">
			<f:param name="Document">
				<html xmlns="http://www.w3.org/1999/xhtml">
				<head>
				</head>
				<body>
					<h1>I am a nested document</h1>
					<p>this is a constant value, in the form of an xhtml document...</p>
				</body>
				</html>
			</f:param>
		</f:function>
	</div>
	
    <div class="example-3">
		<f:function name="Test.Transform">
			<f:param name="Document">
				<f:function name="Composite.Pages.QuickSitemap" />
			</f:param>
		</f:function>
	</div>

	<div class="example-3">
		@this.Function("Test.Transform",
			new { Document = Content })
	</div>
	
</body>
</html>

The sample function used here takes in an XhtmlDocument, which limits/ensures that you are only passed complete documents, with a head, body etc.

If you want to be able to use user function across any type of markup (single elements, fragments and complete documents) you can change the type of the input parameter from XhtmlDocument to IEnumerable<XNode> or the like to match your needs.