Skip to content

A .NET standard 2.0 wrapper around the official Typeform API. Currently a work-in-progress, see Supported Endpoints.

License

Notifications You must be signed in to change notification settings

kamranayub/typeform-dotnet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

97 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

typeform-dotnet

.NET Nuget Nuget

A .NET Standard 2.0 SDK wrapper built with Refit around Typeform's API.

Twitch Streams

This SDK is (mostly) built in public on Twitch. Watch the collection here:

https://www.twitch.tv/collections/Lm3OF4Q0xBZaSA

Supported Endpoints

Install

via Nuget

# Package Manager
Install-Package Typeform

# dotnet
dotnet add package Typeform

Usage

Create API client

To create an instance of the Refit ITypeformApi client:

using Typeform;

var client = TypeformClient.CreateApi();

.NET Service Injection

using Typeform;

// TODO: Not Implemented Yet
// services.AddTypeformApi();

// You can always do this
services.AddRefitClient<ITypeformApi>(TypeformClient.DefaultSettings);

Other DI Containers

using Typeform;

// Ninject
kernel.Bind<ITypeformApi>().ToMethod(ctx => TypeformClient.CreateApi()).InSingletonScope();

Customizing Refit Settings

TypeformClient.DefaultSettings contains the default Refit settings used for the API. You can create new derived settings to pass if you need to through the CreateApi static method.

TypeformClient.DefaultSystemTextJsonSerializerOptions contains the default System.Text.Json serializer options. This handles naming policy and JSON deserialization according to the requirements of the Typeform API.

Consuming the API

You will need to pass your Typeform OAuth or Personal Access Token. Currently, this is implemented as a string argument to the endpoint methods.

TODO: Obtaining an OAuth token is not implemented yet. But for server-side flows, usually a Personal Access Token is "good enough."

public class HomeController : Controller {
  private readonly IConfiguration _configuration;
  private readonly ITypeformApi _typeformApi;

  public Controller(IConfiguration configuration, ITypeformApi typeformApi) {
    _configuration = configuration;
    _typeformApi = typeformApi;
  }

  public async Task<ActionResult> Index() {
    var accessToken = _configuration["TypeformAccessToken"]
    var formId = "abc123";

    var responses = await _typeformApi.GetFormResponsesAsync(accessToken, formId);
  }
}

Responses API

Retrieve Responses

The Typeform Responses API returns form responses that include answers. Each answer can be a different type and to accomodate this, the SDK deserializes into different class implementations based on the answer type discriminator.

Retrieving an answer by type

If you know what type an answer is supposed to be, you can use the Answers.GetAnswer<T>(index) method to retrieve an answer at an index that is the expected type:

Answer By Index

var responses = await _typeformApi.GetFormResponsesAsync(accessToken, formId);

// Retrieve first response's answer as Text type (by index)
var answerText = responses.Items[0].Answers.GetAnswer<TypeformAnswerText>(0);

Answer By Field ID

var responses = await _typeformApi.GetFormResponsesAsync(accessToken, formId);

// Retrieve first response's answer as Text type (by field.id)
var answerText = responses.Items[0].Answers.GetAnswerById<TypeformAnswerText>("abc123");

Answer By Field Ref

var responses = await _typeformApi.GetFormResponsesAsync(accessToken, formId);

// Retrieve first response's answer as Text type (by field.ref)
var answerText = responses.Items[0].Answers.GetAnswerByRef<TypeformAnswerText>("my_custom_ref");

If you do not know what type an answer is supposed to be, you can inspect its type:

var responses = await _typeformApi.GetFormResponsesAsync(accessToken, formId);

// Retrieve first response's answer type
var firstAnswerType = responses.Items[0].Answers[0].Type;

if (firstAnswerType == TypeformAnswerType.Text) {
  var firstAnswer = responses.Items[0].Answers[0] as TypeformAnswerText;
}

Based on Typeform's response structure, it's not easily possible to get static typing of the answers without knowing the type in advance.

Answer type mapping

For reference, this is the mapping for each answer type used:

// Get the answer type enum value
var type = answer.Type;

// Determine the class type to map to
Type answerInstanceType = type switch
{
  TypeformAnswerType.Boolean => typeof(TypeformAnswerBoolean),
  TypeformAnswerType.Choice => typeof(TypeformAnswerChoice),
  TypeformAnswerType.Choices => typeof(TypeformAnswerChoices),
  TypeformAnswerType.Date => typeof(TypeformAnswerDate),
  TypeformAnswerType.Email => typeof(TypeformAnswerEmail),
  TypeformAnswerType.FileUrl => typeof(TypeformAnswerFileUrl),
  TypeformAnswerType.Number => typeof(TypeformAnswerNumber),
  TypeformAnswerType.Payment => typeof(TypeformAnswerPayment),
  TypeformAnswerType.Text => typeof(TypeformAnswerText),
  TypeformAnswerType.Url => typeof(TypeformAnswerUrl),
  _ => typeof(TypeformAnswer)
};

The SDK deserialization takes care of deserializing to the correct type so you can safely cast it.

Retrieving a variable's type

A form variables collection is similar to answers. You can use the same pattern to get variables by type.

Variables By Index

var responses = await _typeformApi.GetFormResponsesAsync(accessToken, formId);

// Retrieve first response's variable (by index)
var answerText = responses.Items[0].Variables.GetVariable<TypeformVariableText>(0);

Variables By Key

var responses = await _typeformApi.GetFormResponsesAsync(accessToken, formId);

// Retrieve first response's variable (by key)
var answerText = responses.Items[0].Variables.GetVariable<TypeformVariableText>("name");

Delete Responses

Use ITypeformApi.DeleteResponsesAsync() and pass a list of response IDs to delete.

Retrieve Response File

Uploaded files to a form can be downloaded via the REST API.

Retrieve file by file_url

The Typeform docs specify that you cannot rely on the values of file_url in Form Responses to have a consistent structure.

However, I have found that many file_url values do match the REST endpoint path value. To accommodate this, I've added an extension method GetFormResponseFileStreamFromUrlAsync which you can use to pass a FileUrl value directly and attempt to download a file.

ITypeformApi typeformApi = TypeformClient.CreateApi();

var responses = await typeformApi.GetFormResponsesAsync(accessToken, formId);
var uploadFileAnswer = responses.Items[0].Answers.GetAnswerByRef<TypeformAnswerFileUrl>("my_custom_upload_ref");

ApiResponse<Stream> fileResponse = await typeformApi.GetFormResponseFileStreamFromUrlAsync(
  accessToken,
  uploadFileAnswer.FileUrl
);

var contents = await fileResponse.ReadAllBytesAsync(/* chunkSize: <optional value in bytes> */);

await System.IO.File.WriteAllBytesAsync(filename, contents);

Retrieve file by parameters

You can also manually specify the form_id, response_id, field_id and filename to download using ITypeformApi.GetFormResponseFileStreamAsync().

The return value of this method is a Refit ApiResponse<Stream> and you can manipulate the Stream response any way you see fit. There is a ReadAllBytesAsync() extension method that will read the full bytes using a chunked buffer:

ITypeformApi typeformApi = TypeformClient.CreateApi();

ApiResponse<Stream> fileResponse = await typeformApi.GetFormResponseFileStreamAsync(
  accessToken,
  formId,
  responseId,
  fieldId,
  filename
);

var contents = await fileResponse.ReadAllBytesAsync(/* chunkSize: <optional value in bytes> */);

await System.IO.File.WriteAllBytesAsync(filename, contents);

If you need to download a file using a FileUrl value from the Form Responses API, you will need to construct your own HttpClient to download it like this example.

TODO