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 numbersvoid Filter
, this method is passed the page'sXhtmlDocument
and details about the page (in the form of theIPage
data for the page). TheFilter
method is expected to manipulate theXhtmlDocument
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.