Skip to content

GetToAnAnswer (GTAA) Integration Guide

Primary path: Manual embed route your app can use immediately.
Secondary path: Contentful-driven embed via rich text.

flowchart LR
  subgraph "Manual Path (Primary)"
    M1["Client requests /gtaa/{slug}"] --> M2[Controller builds GTAA model]
    M2 --> M3["Hydrate (optional)"]
    M3 --> M4[Renderer injects BaseUrl from config]
    M4 --> M5[View renders iframe + external script]
    M5 --> M6["Runner loads /questionnaires/{slug}/start?embed=true"]
  end

  subgraph "Contentful Path (Secondary)"
    C1[Author GTAA entry in CMS] --> C2[Embed in Page rich text]
    C2 --> C3[App fetches Page + rich text]
    C3 --> C4[Renderer detects embedded GTAA in document]
    C4 --> C5[Renderer injects BaseUrl from config]
    C5 --> C6[View renders iframe + external script]
  end

What you’ll add

  • A model for the content type with Title, questionnaireSlug, BaseUrl.
  • A renderer that supports embedded GTAA and sets BaseUrl from config.
  • A shared view that renders the iframe and external script.
  • An optional controller endpoint for manual embedding by slug.
  • Config: GetToAnAnswer:BaseUrl.
  • CSP: allow the runner host to be framed.

Manual Path (Primary)

Use this when you don’t need CMS authorship. It exposes an embed route that renders a page with the questionnaire iframe.

1) Model

// C#
namespace MyApp.Web.Models.Content;

public class GetToAnAnswer : ContentfulContent
{
    public static string ContentType => "getToAnAnswer";
    public string? Title { get; set; }
    public string? questionnaireSlug { get; set; }
    public string? BaseUrl { get; set; }
}

2) Renderer

// C#
using Contentful.Core.Models;
using Microsoft.Extensions.Configuration;
using MyApp.Web.Models.Content;

public class GDSGetToAnAnswerRenderer(IServiceProvider sp) : GDSRazorContentRenderer(sp)
{
    private readonly IConfiguration _cfg = sp.GetRequiredService<IConfiguration>();

    public override bool SupportsContent(IContent content)
    {
        if (content is EntryStructure s && s.NodeType == "embedded-entry-block")
            return s.Data.Target is GetToAnAnswer;

        return content is GetToAnAnswer;
    }

    public override Task<string> RenderAsync(IContent content)
    {
        GetToAnAnswer? model =
            content as GetToAnAnswer ??
            (content as EntryStructure)?.Data.Target as GetToAnAnswer;

        if (model is not null)
            model.BaseUrl = _cfg["GetToAnAnswer:BaseUrl"];

        return RenderToString("GetToAnAnswer", model);
    }
}

Register it with your renderer setup:

// C#
renderer.AddRenderer(new GDSGetToAnAnswerRenderer(serviceProvider));

3) View (shared partial to render the iframe)

// Razor
@model MyApp.Web.Models.Content.GetToAnAnswer

<div class="gtaa-wrapper" style="margin:0 auto; max-width:100%; width:100%;">
  <iframe id="gtaaFrame"
          title="@Model.Title"
          src="@Model.BaseUrl/questionnaires/@Model.questionnaireSlug/start?embed=true"
          allow="autoplay"
          referrerpolicy="strict-origin"
          sandbox="allow-scripts allow-top-navigation allow-forms"
          style="width:100%; height:100%; border:none;"></iframe>
</div>
<script asp-add-nonce="true" src="@Model.BaseUrl/js/gtaa.external.js"></script>

Optional CSS (if not inlined):

/* CSS */
.gtaa-wrapper iframe { width:100%; height:100%; border:none; }

4) Controller (convenience endpoint)

// C#
using Contentful.Core.Models;
using Microsoft.AspNetCore.Mvc;
using MyApp.Web.Contentful;
using MyApp.Web.Models.Content;

[Route("gtaa")]
public class GetToAnAnswerController(IContentService content) : Controller
{
    [HttpGet("{slug}")]
    public async Task<IActionResult> Embedded(string slug)
    {
        var stub = new GetToAnAnswer { questionnaireSlug = slug, Sys = new SystemProperties { Id = slug } };
        var hydrated = await content.Hydrate(stub);
        var page = new Page { MainContent = new Document { Content = [hydrated] } };
        return View("EmbeddedGetToAnAnswer", page);
    }
}

5) Configuration

// JSON
{
  "GetToAnAnswer": {
    "BaseUrl": "https://your-questionnaire-host.example.com"
  }
}

6) CSP (frames)

// C#
x.AllowFraming.FromSelf();
x.AllowFrames.From("https://your-questionnaire-host.example.com");

7) How to use - Navigate to /gtaa/{slug} to render the runner using that slug. - Or render a GetToAnAnswer model directly in any page’s rich text; the renderer will output the iframe.


Contentful Path (Secondary)

Use this when you want editors to manage the block and embed it in rich text.

1) Migration

// JavaScript
module.exports = function (migration) {
  const gtaa = migration.createContentType('getToAnAnswer')
    .name('GetToAnAnswer')
    .displayField('title')
    .description('Embeddable iframe that renders a questionnaire runner');

  gtaa.createField('title').name('Title').type('Symbol').required(true);
  gtaa.createField('questionnaireSlug')
    .name('Questionnaire Slug')
    .type('Symbol')
    .validations([{ regexp: { pattern: '^([a-zA-Z0-9|-]).+' }, message: 'Must be a questionnaire slug' }]);
  gtaa.changeEditorInterface('questionnaireSlug', 'singleLine');

  const page = migration.editContentType('page');
  page.editField('mainContent').validations([
    { enabledMarks: ['bold', 'italic'] },
    { enabledNodeTypes: ['heading-2','heading-3','heading-4','ordered-list','unordered-list','embedded-entry-block','embedded-asset-block','entry-hyperlink','hyperlink','embedded-entry-inline','hr','table'] },
    { nodes: { 'embedded-entry-block': [{ linkContentType: ['definitionContent','callToAction','grid','richContentBlock','banner','statusChecker','riddle','getToAnAnswer','spacer','button'] }] } }
  ]);
};

2) Entity mapping

// C#
private static readonly Dictionary<string, Type> ContentTypeMap = new()
{
    // ...
    { GetToAnAnswer.ContentType, typeof(GetToAnAnswer) }
};

3) Authoring flow - Create a GetToAnAnswer entry with Title and questionnaireSlug. - Embed it in a page rich text field as an embedded entry block. - Publish. The renderer injects BaseUrl and outputs the iframe.


Troubleshooting

  • Blank iframe: verify BaseUrl and CSP frame allowances.
  • 404 in iframe: check questionnaireSlug and host route shape.
  • No render in rich text: confirm content type ID and mapping; renderer SupportsContent covers embedded-entry-block.
  • CSP script issues: ensure nonces and the host script origin are permitted.