Questionmark's Open Assessment Platform

Results API tutorials

The Results API follows the OData protocol, as found at odata.org/docs. The Results API can be accessed manually by typing URLs into a browser, but its real power comes from being programmatically accessible. Any tool or programming language that follows the OData protocol can get data from Questionmark's OData service as long as a valid username and password are supplied. What follows is a tutorial for connecting to Questionmark's OData service using Visual Studio 2012. These tutorials assume you are familiar with the programming language and tools appropriate for each. 

The following tutorials are available:

Connecting to the Results API

To connect to Questionmark's Results API:

  1. Open Visual Studio 12.
  2. Create a new console application project.
  3. Add a Service Reference pointing to the base Questionmark Results API URL for your tenant.
    • Right-click on the References node of Solution Explorer and choose Add Service Reference....
    • Enter the base Results API URL for Address.
    • Click Go to discover the services and operations available at th base Results API URL.
    • Select Entities in the Services pane.
    • Enter a proper namespace and click OK.
    • You will be prompted for credentials to connect to the Results API service. Provide the username and password for a Questionmark administrator with reporting permissions for the tenant you're attempting to access the OData service for. The credentials you provide will not be stored, they are only used to download a definition of the service to make local proxy classes.
  4. When Visual Studio finishes, you will see the new service reference listed in the Solution Explorer:

Click here to download sample tutorial code.

Retrieving data

Continuing from the Connecting to the Results API tutorial above, follow these steps to retrieve data from the OData service:

  1. Open Program.cs.
  2. Add the following code to the Main() method:
    static void Main(string[] args)
    {
      // creates an object to connect to the odata service
      var context = new Entities(new Uri("https://ondemand.questionmark.com/123456/odata/"));
    
      // supplies the credentials required to connect to the odata service
      // typically this would be stored in a config file in an encrypted section
      var credentials = new System.Net.NetworkCredential("SampleUser", "SampleUserPassword_00");
      context.Credentials = credentials;
    
      // the url submitted for context.Assessments will be 
      // "https://ondemand.questionmark.com/123456/odata/Assessments/"
      // this url is not actually called until we try to access the first Assessment 
      // object in context.Assessments
      foreach (var assessment in context.Assessments)
      {
        Console.WriteLine("Assessment Name: {0}", assessment.Name);
      }
    
      Console.WriteLine("");
      Console.WriteLine("press any key...");
      Console.ReadKey();
    }

Click here to download sample tutorial code.

Yes, it really is that simple. This sample accesses Questionmark's OData service and gets a list of assessments. It then iterates through the list and writes out the name of each assessment. The Results API returns a maximum of 500 rows of data at a time, so if there are more than 500 assessments, only the names of the first 500 are listed.

Using continuations

In the previous tutorial, Retrieving data, we showed how to get data from the OData service. In that example, though, we could only retrieve a maximum of 500 rows of data since that is all the server will return in one batch. To get more rows of data, you have to request it in batches. Of course, you want to request these batches in a specific order so that you do not get duplicate data.

Request batches is done using continuation links. In every batch of data, if there are more data to request for the current query, then there will be a continuation link near the end of the XML data. A continuation link looks like this:

<link rel="next" href="https://ondemand.questionmark.com/123456/odata/Assessments/?$skiptoken=99"/>

This continuation link allows us to request more assessment data past the assessment with an ID number of 99.

To use a continuation link, you first check if it exists and if it does, make another request to the OData service while passing in the continuation link. Here is an example of how to do that:

  1. Open Program.cs.
  2. Add the following code to the Main() method:
    static void Main(string[] args)
    {
      // creates an object to connect to the odata service
      var context = new Entities(new Uri("https://ondemand.questionmark.com/123456/odata/"));
    
      // supplies the credentials required to connect to the odata service
      // typically this would be stored in a config file in an encrypted section
      var credentials = new System.Net.NetworkCredential("SampleUser", "SampleUserPassword_00");
      context.Credentials = credentials;
    
      // the url submitted for context.Assessments will be 
      // "https://ondemand.questionmark.com/123456/odata/Assessments/"
      // this url is called when we try to access the first Assessment, which 
      // happens when we execute the query; we execute the query so that we can 
      // check for a continuation token
      var assessmentsResponse = context.Assessments.Execute() as QueryOperationResponse<Assessment>;
    
      // create a new empty list to hold all the rows, if there are any
      var assessments = new List<Assessment>();
    
      // did we get rows in the first batch?
      if (assessmentsResponse != null)
      {
        // if we did, then add them to our list
        assessments.AddRange(assessmentsResponse);
    
        // here's where we check for the continuation token, the 
        // <link rel="next"> tag
        var continuationToken = assessmentsResponse.GetContinuation();
    
        while (continuationToken != null)
        {
          // get the next batch of data identified by the continuation token
          assessmentsResponse = context.Execute(continuationToken);
    
          // add the next batch of data to our list
          assessments.AddRange(assessmentsResponse);
    
          // check for more data
          // IMPORTANT, if you forget this, you'll be in a never-ending loop
          continuationToken = assessmentsResponse.GetContinuation();
        }
      }
    
      // notice that this time we're looping through our list, not the
      // context object
      foreach (var assessment in assessments)
      {
        Console.WriteLine("Assessment Name: {0}", assessment.Name);
      }
    
      Console.WriteLine("");
      Console.WriteLine("press any key...");
      Console.ReadKey();
    }    

Click here to download sample tutorial code.

Creating charts

The previous tutorials have shown the basics of setting up a console application and retrieving all of the rows of data you are interested in. But that's not a realistic scenario; more likely, you've been asked to get the data and show it as some sort of chart, say, as a histogram, and make it available on the web so that the chart can be shown to decision-makers while they're on the road.

So let's build a one-page website with a chart on it that displays data retrieved from the Results API. We'll use Google Charts in our sample to simplify the actual drawing of the charts (click here to learn more about Google Charts). Let's follow these steps:

  1. Open Visual Studio 2012.
  2. Create a new empty ASP.NET MVC 4 web application with the ASPX view engine.
  3. Add a Service Reference pointing to the base OData URL for your tenant (see Connecting to OData for more information).
  4. Add a new Controller called HomeController, then change the Index action method's code to read as follows:
    public ActionResult Index()
    {
      return View();
    }

    Add a method called GetChartData() and change its code to read as follows:

    [AcceptVerbs(HttpVerbs.Get)]
    public string GetChartData()
    {
      // this code has all been shown in previous tutorials
      var context = new Entities(new Uri("https://ondemand.questionmark.com/123456/odata/"));
    
      var credentials = new System.Net. NetworkCredential("SampleUser", "SampleUserPassword_00");
      context.Credentials = credentials;
    
      var results = new List<Result>();
    
      // okay, this is new; we're filtering the results based on assessment key
      // that we looked up with a previous query, hardcoded here for convenience
      // YOU WILL NEED TO CHANGE THE HARDCODED KEY
      // we also order ascending by percentage score to make the chart cleaner
      // and we expand the Participant entity to use its Name property
      var resultsQuery = 
        context.Results
          .Expand(r => r.Participant)
          .OrderBy(r => r.ResultPercentageScore)
          .Where(r => r.AssessmentKey == 4003) as DataServiceQuery;
      // after we've put together a query as above, we then execute that query
      // this creates the URL "https://ondemand.questionmark.com/123456
      //   /odata/Results()?$orderby=ResultPercentageScore
      //   &$filter=AssessmentKey eq 4003&$expand=Participant"
      var resultsResponse = resultsQuery.Execute() as QueryOperationResponse<Result>;
    
      // this retrieves multiple batches of data if needed, as shown before
      if (resultsResponse != null)
      {
        results.AddRange(resultsResponse);
        var continuationToken = resultsResponse.GetContinuation();
        while (continuationToken != null)
        {
          resultsResponse = context.Execute(continuationToken);
          results.AddRange(resultsResponse);
          continuationToken = resultsResponse.GetContinuation();
        }
      }
    
      // now we put together a JSON string for the chart to use as a data source
      // the output looks like "[["John Doe",90],["Jane Eyre",98]]"
      var chartData = string.Empty;
    
      foreach (var result in results)
      {
        chartData += 
          string.Format(
            "[\"{0}\",{1}],", 
            result.Participant.ParticipantName, 
            result.ResultPercentageScore);
      }
      if (!string.IsNullOrWhiteSpace(chartData))
      {
        chartData = chartData.TrimEnd(new[] { ',' });
        chartData = string.Format("[{0}]", chartData);
      }
    
      return chartData;
    }
          
  5. Finally, add a new folder called Home under the View folder, then add a new View called Index under the Home folder.
    • Use the ASPX (C#) engine
    • Do not select a model class (we do not have one).
    • Deselect the Use a layout or master page checkbox (we do not have one).
  6. Change the view's code to read as follows:
    <%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>"%>
    
    <!DOCTYPE html>
    
    <html>
    
      <head runat="server">
        <meta name="viewport" content="width=device-width"/>
        <title>Index</title>
        <script type="text/javascript" src="https://www.google.com/jsapi"></script>
      </head>
    
      <body>
    
        <!-- this is the div where the chart will be drawn -->
        <div id="chart_div" style="width: 900px; height: 500px; border: 1px solid blue;"></div>
    
        <script type="text/javascript">
          // this loads the appropriate javascript
          google.load("visualization", "1", { packages: ["corechart"] });
          // causes the drawChart function to be called when the page has loaded
          google.setOnLoadCallback(drawChart);
    
          // this function takes output from GetChartData and creates a chart
          function drawChart() {
            // first, define the object to hold the data
            var data = new google.visualization.DataTable();
            data.addColumn("string", "");
            data.addColumn("number", "Score");
    
            // second, get the data
            data.addRows(<%: Html.Action("GetChartData") %>);
    
            // third, add any options you want for the chart
            var options={title: "Assessment Performance"};
    
            // fourth, tell the chart what div to draw itself in
            var chart = new google.visualization.ColumnChart(document.getElementById("chart_div"));
    
            // finally, fifth, draw the chart
            chart.draw(data, options);
          }
        </script>
    
      </body>
    
    </html>

This view draws out the actual chart using the data retrieved from GetChartData() and the Google Charts API.

Click here to download sample tutorial code.