Add an API controller

Contents[Hide]

1. Overview

This article shows how to add an API controller to Dundas BI. This is done by creating an extension containing a class that extends System.Web.Http.ApiController.

Important
This sample will only work in version 6, or higher.

2. Getting started

The following prerequisites must be installed on your computer:

  • Visual Studio 2015, or higher
  • Microsoft .NET Framework 4.6.1
  • A deployed instance of Dundas BI version 6 or higher

2.1. Downloading sample solution

To download the custom API controller sample solution, click here.

2.2. Extracting sample to SDK folder

This sample is designed to automatically publish the extension to the instance. First, you must extract CustomApiController.zip to the SDK folder within the instance:

  1. Find the SDK folder for your instance. It is located at [instance root]\sdk.
  2. Extract CustomApiController.zip to the SDK folder.
  3. You should see the following result:

    Samples added to SDK folder
    Samples added to SDK folder

    With the following structure:

    • [instance root]
      • sdk
        • Samples
          • CustomApiController

Important
If the sample is not in this specific folder, it will not work correctly.

2.3. Opening solution

Find and open the solution located at:

[instance root]\sdk\samples\CustomApiController\CustomApiController\CustomApiController.sln

3. The project

The project is a class library.

  • CustomApiControllerPackage.cs - This class contains the package information about the extension package.
  • HelloWorldController.cs - Class that defines the API controller.
  • packages.config - A config file used to define the references for using Web API and JSON objects.
  • PublishExtension.targets - Used for auto publishing the extension after the build succeeds.

3.1. ExtensionPackageInfo class

        

/// <summary>
/// This class contains the package information about the extension package.
/// </summary>
public class CustomApiControllerPackage : ExtensionPackageInfo
{
	#region Public Properties

	/// <summary>
	/// Gets the name of the extension package author.
	/// </summary>
	public override string Author
	{
		get { return "Dundas Data Visualization Sample Author"; }
	}

	/// <summary>
	/// Gets the copyright text associated with the extension package.
	/// </summary>
	public override string Copyright
	{
		get { return "Dundas Data Visualization, Inc."; }
	}

	/// <summary>
	/// Gets the localized display name of the extension package.
	/// </summary>
	public override string DisplayName
	{
		get { return "Custom API Controller Sample Package"; }
	}

	/// <summary>
	/// Gets the unique identifier of the extension package.
	/// </summary>
	public override Guid Id
	{
		get { return new Guid("e57021c4-028b-4486-95cb-6df049040c08"); }
	}

	/// <summary>
	/// Gets the name of the extension package.
	/// </summary>
	public override string Name
	{
		get { return "Custom API Sample Package"; }
	}

	/// <summary>
	/// Gets the version of the extension package.
	/// </summary>
	public override Version Version
	{
		get { return new Version(0, 0, 1); }
	}

	#endregion Public Properties
}

3.2. Publish extension targets

This sample has a mechanism to automatically publish the extension. This mechanism is the PublishExtension.targets file, which overrides the AfterBuild target. This will create the following file after successfully compiling the solution:

[instance root]\www\BIWebsite\App_Data\Extensions\CustomApiController\bin\CustomApiController.dll

It will then touch the web.config to force the web application to reset.

3.3. Implementing the abstract ApiController class

In order to add an API controller to Dundas BI it is required to extend the System.Web.Http.ApiController . This class will automatically be picked up, and a route will be established for the one method defined below to:

POST https://{DundasBIUrl}/API/HelloWorld
    
public class HelloWorldController : ApiController
{

    /// <summary>
    /// Initializes a new instance of the <see cref="HelloWorldController"/> class.
    /// </summary>
    public HelloWorldController() { }



    /// <summary>
    /// <c>POST /API/HelloWorld/</c>
    /// Says hello.
    /// </summary>
    /// <param name="options">A JSON string containing the options for the transfer.</param>
    /// <param name="sessionId">The current session ID.</param>
    /// <returns>A <see cref="System.String"/> containing the hello world string.</returns>
    [HttpPost]
    [ActionName("Index")]
    public HttpResponseMessage Index([FromBody]dynamic options, Guid? sessionId = null)
    {
       // ...	
		
    }


}

Tip
For more information about WEB API routes, see Routing in ASP.NET Web API.

3.4. Creating a full hello world example

The following example will create a route for:

POST https://{DundasBIUrl}/API/HelloWorld

This takes a JSON argument in the body which is defined as a dynamic type named options in the method. The options contain a property called WithUsername and will reply with either Hello {username}, or Hello! based on that property:

    

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;
using System.Web.Http;

using Dundas.BI;
using Dundas.BI.AccountServices;
using Dundas.BI.WebApi.Controllers;
using Dundas.BI.WebApi.Models;

using Newtonsoft.Json;

   // ...

/// <summary>
/// 
/// </summary>
/// <seealso cref="System.Web.Http.ApiController" />
public class HelloWorldController : ApiController
{

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="HelloWorldController"/> class.
    /// </summary>
    public HelloWorldController() { }

	#endregion Constructors

	#region Private Methods

	private Guid? GetSessionIdFromCookieOrProvided(Guid? sessionIdFromQueryString)
	{
		// Check if it's provided.
		if (sessionIdFromQueryString.HasValue
			&& sessionIdFromQueryString.Value != Guid.Empty)
		{
			return sessionIdFromQueryString;
		}

		// Try to get it from the cookie.
		string cookieName = "dundas_webapp_sessionid";
		CookieHeaderValue cookie = this.Request.Headers.GetCookies(cookieName).FirstOrDefault();
		if (cookie != null)
		{
			Guid result;
			if (Guid.TryParse(cookie[cookieName].Value, out result)
				&& result != Guid.Empty)
			{
				return result;
			}
		}

		return null;
	}

	private CallerContext Session(Guid? sessionId)
	{
		ICallerContextService callerContextService = Engine.Current.GetService<ICallerContextService>();

		// If an empty GUID is passed, there is no session, so we need to pass null when creating the context.
		Guid? sessIdForContext = this.GetSessionIdFromCookieOrProvided(sessionId);

		// Store this session ID in case we need it for exception handling (it may not be the current session).
		this.StoreSessionIdInHttpContext(sessIdForContext);

		// Return the context.
		return callerContextService.CreateAndSetCurrentContext(sessIdForContext);
	}

	private void StoreSessionIdInHttpContext(Guid? sessionId)
	{
		// Used to associate exceptions with a session.

		if (System.Web.HttpContext.Current != null)
		{
			System.Web.HttpContext.Current.Items["sessionId"] = sessionId;
		}
	}

	#endregion Private Methods

	#region Public Methods

	/// <summary>
	/// <c>POST /API/HelloWorld/</c>
	/// Says hello.
	/// </summary>
	/// <param name="options">A JSON string containing the options for the transfer.</param>
	/// <param name="sessionId">The current session ID.</param>
	/// <returns>A <see cref="System.String"/> containing the hello world string.</returns>
	[HttpPost]
	[ActionName("Index")]
	public HttpResponseMessage Index([FromBody]dynamic options, Guid? sessionId = null)
	{
		// Breakpoint for testing for correct mapping.
		// System.Diagnostics.Debugger.Launch();

		if (!this.ModelState.IsValid || sessionId == Guid.Empty || options == null)
		{
			return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState);
		}

		using (this.Session(sessionId))
		{
			try
			{

				Session session = Engine.Current.Session;


				var response = this.Request.CreateResponse(HttpStatusCode.OK);

				if (options.WithUsername == true)
				{
					response.Content =
						new StringContent(
							string.Format(
								CultureInfo.CurrentCulture,
								"Hello {0}!",
								session.AccountDisplayName
								)
							);
				}
				else
				{

					response.Content =
						new StringContent(
								"Hello!"
							);

				}
				return response;
			}
			catch (ArgumentException ex)
			{
				return this.Request.CreateResponse(HttpStatusCode.BadRequest, ex);
			}
			catch (InvalidOperationException ex)
			{
				return this.Request.CreateResponse(HttpStatusCode.MethodNotAllowed, ex);
			}
			catch (NoPrivilegeException ex)
			{
				return this.Request.CreateResponse(HttpStatusCode.Forbidden, ex);
			}
			catch (InvalidSessionException ex)
			{
				return this.Request.CreateErrorResponse(
					(HttpStatusCode)Dundas.BI.WebApi.WebApiConstants.InvalidSessionHttpStatusCode, 
					ex
				);
			}
		}
	}

	#endregion Public Methods
}

4. Making the route available

It may be necessary to stop the Dundas BI web application and delete the temporary internet files if you receive a message like No HTTP resource was found that matches the request URI ... . The files are located in a sub folder here: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files. After deleting the appropriate folder restart the Dundas BI web application. This will cause the application to create the routes again when it starts.

5. See also

Dundas Data Visualization, Inc.
500-250 Ferrand Drive
Toronto, ON, Canada
M3C 3G8

North America: 1.800.463.1492
International: 1.416.467.5100

Dundas Support Hours: 7am-6pm, ET, Mon-Fri