Custom output for EPiServer pages

What I needed
I wanted to be able to render different output formats for a page created in EPiServer.
My requirements were:

  • Render the different output through a segment in the url, not through the querystring.
  • Control over what gets rendered in the output.

The solution
I started with creating the output formats I wanted.

  • JsonOutputFormat, handles the output for JSON.
  • XmlOutputFormat, handles the output for XML.
  • TxtOutputFormat, handles the output for TXT.
  • PdfOutputFormat, handles the output for PDF.

I created an Attribute, [UseInOutput(Order = 10)], to add to PageType properties, used to indicate whether the content should be rendered in the output and in which order. 
You can add it to a ContentArea , in which case it renders the properties, with the attribute, of the content added to the area.

You can add two properties “`PdfHeader“` and “`PdfFooter“` to your startpage if you want a header and footer to be rendered in the pdf.

You can enable/disable formats in the settings. You can also set the location of a print stylesheet, which will be used while rendering the pdf.

The code
For the basics of partial routing read the [SDK].

The router checks if there is an output segment in the url, checks if the requested output is enabled, renders the output and returns the url without the format.

public PartialRouteData GetPartialVirtualPath(
            string content, string language, RouteValueDictionary routeValues, RequestContext requestContext)
        {
            return new PartialRouteData
                       {
                           BasePathRoot = requestContext.GetContentLink(), 
                           PartialVirtualPath =
                               string.Format(CultureInfo.InvariantCulture, "{0}/", content)
                       };
        }
public object RoutePartial(PageData content, SegmentContext segmentContext)
        {
            if (segmentContext == null)
            {
                return content;
            }

            // Use helper method GetNextValue to get the next part from the URL
            SegmentPair nextSegment = segmentContext.GetNextValue(segmentContext.RemainingPath);

            string output = nextSegment.Next;

            if (string.IsNullOrWhiteSpace(output))
            {
                return content;
            }

            segmentContext.RemainingPath = nextSegment.Remaining;

            if (output.Equals(OutputConstants.PDF, StringComparison.OrdinalIgnoreCase) && OutputSettings.Instance.EnablePDF)
            {
                PdfOutputFormat pdfOutputFormat = new PdfOutputFormat();
                pdfOutputFormat.HandleFormat(content, new HttpContextWrapper(HttpContext.Current)); 
            }

            if (output.Equals(OutputConstants.Text, StringComparison.OrdinalIgnoreCase) && OutputSettings.Instance.EnableTXT)
            {
                TxtOutputFormat txtOutputFormat = new TxtOutputFormat();
                txtOutputFormat.HandleFormat(content, new HttpContextWrapper(HttpContext.Current));
            }

            if (output.Equals(OutputConstants.XML, StringComparison.OrdinalIgnoreCase) && OutputSettings.Instance.EnableXML)
            {
                XmlOutputFormat xmlOutputFormat = new XmlOutputFormat();
                xmlOutputFormat.HandleFormat(content, new HttpContextWrapper(HttpContext.Current));
            }

            if (output.Equals(OutputConstants.JSON, StringComparison.OrdinalIgnoreCase) && OutputSettings.Instance.EnableJSON)
            {
                JsonOutputFormat jsonOutputFormat = new JsonOutputFormat();
                jsonOutputFormat.HandleFormat(content, new HttpContextWrapper(HttpContext.Current));
            }

            return content;
        }

The partial router is initialized in an InitializationModule

OutputPartialRouter partialRouter = new OutputPartialRouter();
RouteTable.Routes.RegisterPartialRouter(partialRouter);

The rest of the code is pretty straight forward, and uses some great libraries like JSON.Net for the JSON rendering, the HTMLAgilityPack, iTextSharp for the PDF rendering, ExCSS for creating the styles from the print stylesheet for the PDF.

Unit testing

For the unit tests I needed to fake the HttpContext.

HttpRequestBase httpRequestBase = A.Fake<HttpRequestBase>();
A.CallTo(() => httpRequestBase.HttpMethod).Returns("POST");
A.CallTo(() => httpRequestBase.Headers).Returns(this.requestHeaderCollection);
A.CallTo(() => httpRequestBase.Form).Returns(this.formCollection);
A.CallTo(() => httpRequestBase.QueryString).Returns(this.queryStringCollection);

HttpResponseBase httpResponseBase = A.Fake<HttpResponseBase>();
A.CallTo(() => httpResponseBase.Clear()).Invokes(this.ClearResponse);

A.CallTo(() => httpResponseBase.Write(A<string>.Ignored))
             .Invokes(m => this.responseOutput.Append(m.GetArgument<string>(0)));
A.CallTo(() => httpResponseBase.Output).Returns(new StringWriter(this.responseOutput));

A.CallTo(() => httpResponseBase.AddHeader(A<string>.Ignored, A<string>.Ignored))
             .Invokes(m => this.responseHeaderCollection.Add(m.GetArgument<string>(0), m.GetArgument<string>(1)));
A.CallTo(() => httpResponseBase.Headers).Returns(this.responseHeaderCollection);

HttpContextBase httpContextBase = A.Fake<HttpContextBase>();
A.CallTo(() => httpContextBase.Request).Returns(httpRequestBase);
A.CallTo(() => httpContextBase.Response).Returns(httpResponseBase);

The tests themselves are pretty straight forward. Have a look at them if you are interested. The code can be found on GitHub.

I will also submit the package to EPiServer NuGet

6 thoughts on “Custom output for EPiServer pages

      1. Nice pt2 🙂

        While I do agree that people using normal browsers will have an easier time using querystrings or URL-segments for this, the Accept-header is indeed the preferred way of doing content negotiation when consuming external APIs or web services. It is, after all, the very reason the Accept header exists.

        Like

Leave a comment