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.

2. Getting started

The following prerequisites must be installed on your computer:

  • Visual Studio 2017 or higher
  • Microsoft .NET Framework 4.7.2
  • A deployed instance of Dundas BI - this article details extensions for version 7 and higher

2.1. Downloading sample solution

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

(A solution is also available for Dundas BI version 6.)

2.2. Opening solution

Extract CustomApiController.zip to a folder and open the solution in Microsoft Visual Studio to build the extension.

To use the option to publish directly to your Dundas BI instance, run Visual Studio as an administrator before opening the solution. For example, you can right-click Visual Studio in the start menu and find the Run as administrator option.

The solution file is located at:
[Extracted folder]\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.
  • PublishExtensionTemplate.props - Used for auto publishing the extension after the build succeeds, and defines extension properties, and files.

3.1. ExtensionPackageInfo class

        

/// <summary>
/// This class contains the package information about the extension package.
/// </summary>
public class CustomApiControllerPackage : ExtensionPackageInfo2
{
	/// <summary>Initializes a new instance of the 
        /// <see cref="CustomApiControllerPackage"/> 
        /// class.</summary>
        /// <param name="extensionManifest">The extension manifest.</param>
        public CustomApiControllerPackage(ExtensionManifest extensionManifest)
                    : base(extensionManifest)
        {
        }
}

3.2. Publish extension template

This sample has a mechanism to automatically publish the extension when building, which is the Dundas.BI.PublishExtension NuGet package. When this package is added to the project, it creates a PublishExtensionTemplate.props file containing MSBuild property and item groups, which define how to create and publish the extension.

When the DtFilePath property is set to the file path of the dt tool of a Dundas BI instance, it will then publish the extension directly to that instance when you build the solution. It will also touch the web.config file to force the web application to reset.

If the DtFilePath property is not set, it will create a .zip file you can add to your Dundas BI instance using the Extensions screen in the administration UI. After building the solution, this .zip file can be found within the bin subfolder of your solution.

For more details on using this package to automate publishing extensions, see Using the Dundas.BI.PublishExtension NuGet package.

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 || 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:
Phone: 9am-6pm, ET, Mon-Fri
Email: 7am-6pm, ET, Mon-Fri