Skip to main content

Introducing Formengine - The New Formbuilder, try for FREE formengine.io.

Version: 10.0.0

How to create a plugin

In this tutorial, we'll take a step-by-step guide to creating plugins for the Workflow Server. In this example, we will develop a plugin to integrate with a Trello board. The principle of operation will be as follows: boards with schemes will be created in Trello, lists will display activities, and cards will show processes.

Step One - Creating a plugin​

Create a class that implements the IWorkflowPlugin interface named TrelloPlugin.cs.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using OptimaJet.Workflow.Core.Runtime;
using OptimaJet.Workflow.Plugins;

public class TrelloPlugin : IWorkflowPlugin
{
public void OnPluginAdd(WorkflowRuntime runtime, List<string> schemes = null)
{
throw new System.NotImplementedException();
}

public async Task OnRuntimeStartAsync(WorkflowRuntime runtime)
{
}

public string Name { get; } = "TrelloPlugin";
public bool Disabled { get; set; }
public Dictionary<string, string> PluginSettings { get; }
}

The interface members are assigned as follows:

  • Name - the plugin name.
  • PluginSettings - the dictionary of the plugin settings.
  • OnPluginAdd - called when the plugin is added to the runtime. This method is convenient for subscribing to the WorkflowRuntime events.
  • OnPluginStartAsync - called at the start of the runtime.

Step Two - Integration with Trello​

To communicate with Trello, we will use HTTP requests, for this we will need an HttpClient object. We will also describe classes for working with Trello entities.

Entities​

Models can be divided into different files.

public class Organization
{
public Organization()
{
Boards = new List<Board>();
}

public string Id { get; set; }
public string Name { get; set; }
public string DisplayName { get; set; }
public List<Board> Boards { get; }
}

public class Board
{
public Board()
{
Lists = new List<BoardList>();
}

public string Id { get; set; }
public string Name { get; set; }
public string IdOrganization { get; set; }
public List<BoardList> Lists { get; }
public Guid SchemeId { get; set; }
}

public class BoardList
{
public BoardList()
{
Cards = new List<Card>();
}

public string Id { get; set; }
public string Name { get; set; }
public string IdBoard { get; set; }
public List<Card> Cards { get; }
}

public class Card
{
public string Id { get; set; }
public string Name { get; set; }
public string Desc { get; set; }
public string IdList { get; set; }
public Guid ProcessId { get; set; }
}

And declare the necessary variables.

Variables​

Place this code in TrelloPlugin.cs:

public string Token { get; set; }
public string Key { get; set; }
private HttpClient _client;
private Organization _organization;
private WorkflowRuntime _runtime;
private List<string> _schemes;
private const string _url = "https://api.trello.com/1";

Step Three - Plugin logic​

This plugin will create one workspace and all further boards will be created in it.

Initial workspace​

Place this code in TrelloPlugin.cs:

private async Task<Organization> OrganizationInit(string name)
{
var response =
await _client.GetAsync(
$"{_url}/organizations/{name.ToLower()}?token={Token}&key={Key}");
if (response.StatusCode != HttpStatusCode.OK)
{
response = await _client.PostAsync(
$"{_url}/organizations?displayName={name}&token={Token}&key={Key}", null);
}

return JsonConvert.DeserializeObject<Organization>(await response.Content.ReadAsStringAsync());
}

Initial board​

Boards will be used to display the schemes.

  • The BoardInit method initializes the board.
  • The DeleteOldBoards method deletes already inactive boards.

Place this code in TrelloPlugin.cs:

private async Task<Board> BoardInit(Organization organization, string name)
{
var organizationBoards = new List<Board>();
var response =
await _client.GetAsync(
$"{_url}/organization/{organization.Name}/boards?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
organizationBoards = JsonConvert.DeserializeObject<List<Board>>(await response.Content.ReadAsStringAsync());
}

var board = organizationBoards.FirstOrDefault(c =>
string.Equals(c.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (board != null) return board;
response = await _client.PostAsync(
$"{_url}/boards?name={name}&key={Key}&token={Token}&defaultLists=false&defaultLabels=false&idOrganization={organization.Id}",
null);
board = JsonConvert.DeserializeObject<Board>(await response.Content.ReadAsStringAsync());

return board;
}

private async Task DeleteOldBoards(Organization org)
{
var response =
await _client.GetAsync(
$"{_url}/organizations/{org.Id}/boards?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
var boards = JsonConvert.DeserializeObject<List<Board>>(await response.Content.ReadAsStringAsync());
foreach (var trelloBoard in boards)
{
if (org.Boards.Find(c => c.Id == trelloBoard.Id) == null)
{
await _client.DeleteAsync(
$"{_url}/boards/{trelloBoard.Id}?key={Key}&token={Token}");
}
}
}
}

Initial list​

The activity will present a list.

  • The ListInit method initializes the list.
  • The DeleteOldLists method deletes already inactive lists.
  • The Sortlists method sorts the lists in the order of activity in the diagram.

Place this code in TrelloPlugin.cs:

private async Task<BoardList> ListInit(Board board, string name)
{
List<BoardList> boardLists = new List<BoardList>();
var response =
await _client.GetAsync($"{_url}/boards/{board.Id}/lists?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
boardLists = JsonConvert.DeserializeObject<List<BoardList>>(await response.Content.ReadAsStringAsync());
}

var list = boardLists.FirstOrDefault(c =>
string.Equals(c.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (list == null)
{
response = await _client.PostAsync(
$"{_url}/lists?name={name}&idBoard={board.Id}&key={Key}&token={Token}", null);
list = JsonConvert.DeserializeObject<BoardList>(await response.Content.ReadAsStringAsync());
}

return list;
}

private async Task DeleteOldLists(Board board)
{
var response =
await _client.GetAsync($"{_url}/boards/{board.Id}/lists?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
var allLists = JsonConvert.DeserializeObject<List<BoardList>>(await response.Content.ReadAsStringAsync());
foreach (var list in allLists)
{
if (board.Lists.Find(c => c.Id == list.Id) == null)
{
await _client.PutAsync(
$"{_url}/lists/{list.Id}/closed?value=true&key={Key}&token={Token}", null);
}
}
}
}

private async Task SortLists(Board board)
{
int startPosition = 5;
var response =
await _client.GetAsync($"{_url}/boards/{board.Id}/lists?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
var lists = JsonConvert.DeserializeObject<List<BoardList>>(await response.Content.ReadAsStringAsync());
var pd = await _runtime.Builder.GetProcessSchemeAsync(board.SchemeId);
foreach (var activityDefinition in pd.Activities)
{
var list = lists.FirstOrDefault(c => c.Name.ToLower() == activityDefinition.Name.ToLower());
if (list != null)
{
await _client.PutAsync(
$"{_url}/lists/{list.Id}?pos={Math.Pow(2, startPosition)}&key={Key}&token={Token}",
null);
startPosition++;
}
}
}
}

Initial card​

The process instance will be a card.

  • The CardInit method initializes the card.
  • The DeleteOldCards method deletes all cards whose processes were deleted.
  • The MoveCard method Moves the card when the process moves through the activities.

Place this code in TrelloPlugin.cs:

private async Task<Card> CardInit(BoardList list, string name, string desc, Guid processId)
{
List<Card> cardList = new List<Card>();
var response =
await _client.GetAsync($"{_url}/lists/{list.Id}/cards?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
cardList = JsonConvert.DeserializeObject<List<Card>>(await response.Content.ReadAsStringAsync());
}

var card = cardList.FirstOrDefault(c => string.Equals(c.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (card == null)
{
response = await _client.PostAsync(
$"{_url}/cards?name={name}&desc={desc}&pos=top&idList={list.Id}&key={Key}&token={Token}",
null);
card = JsonConvert.DeserializeObject<Card>(await response.Content.ReadAsStringAsync());
}

card.ProcessId = processId;
return card;
}

private async Task DeleteOldCards(Board board)
{
foreach (var boardList in board.Lists)
{
var response =
await _client.GetAsync($"{_url}/lists/{boardList.Id}/cards?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
var CardsOnList = JsonConvert.DeserializeObject<List<Card>>(await response.Content.ReadAsStringAsync());
foreach (var card in CardsOnList)
{
if (boardList.Cards.Find(c => c.Id == card.Id) == null)
{
await _client.DeleteAsync($"{_url}/cards/{card.Id}?key={Key}&token={Token}");
}
}
}
}
}

private async Task MoveCard(Card card, BoardList oldList, BoardList newList)
{
var response = await _client.PutAsync(
$"{_url}/cards/{card.Id}?idList={newList.Id}&key={Key}&token={Token}", null);
if (response.StatusCode == HttpStatusCode.OK)
{
if (oldList != null)
{
oldList.Cards.Remove(oldList.Cards.Find(c => c.Id == card.Id));
}

var newCard = JsonConvert.DeserializeObject<Card>(await response.Content.ReadAsStringAsync());
newCard.ProcessId = card.ProcessId;
newList.Cards.Add(newCard);
}
}

Initial Trello​

And finally, initialization of the entire board, all the logic of initialization of the Trello board according to the selected schemes is collected here.

  • The TrelloInit method accomplishes this task.

Place this code in TrelloPlugin.cs:

private async Task<Organization> TrelloInit(WorkflowRuntime runtime, List<string> schemes = null)
{
_organization = await OrganizationInit("WorkflowServer");
foreach (var scheme in schemes)
{
var board = await BoardInit(_organization, scheme);
board.Lists.Clear();
_organization.Boards.Add(board);
var pd = await runtime.Builder.GetProcessSchemeAsync(scheme);
board.SchemeId = pd.Id;
foreach (var activity in pd.Activities)
{
var list = await ListInit(board, activity.Name);
board.Lists.Add(list);
}

await DeleteOldLists(board);
await SortLists(board);
}

await DeleteOldBoards(_organization);

var pi = await runtime.PersistenceProvider.GetProcessInstancesAsync(null, null);
var piGroup = pi.GroupBy(c => c.SchemeId).ToList();
foreach (var processInstanceItems in piGroup)
{
var board = _organization.Boards.FirstOrDefault(c => c.SchemeId == processInstanceItems.Key);
if (board != null)
{
foreach (var boardList in board.Lists)
{
boardList.Cards.Clear();
var piOnActivity = processInstanceItems.Where(c => c.ActivityName == boardList.Name).ToList();
foreach (var card in piOnActivity)
{
StringBuilder descriptionString = new StringBuilder();
descriptionString.AppendLine($"Process id: {card.Id}");
descriptionString.AppendLine(
$"Creation date: {card.CreationDate.ToLongDateString()} {card.CreationDate.ToLongTimeString()}");
var Card = await CardInit(boardList, card.Id.ToString(), descriptionString.ToString(), card.Id);
boardList.Cards.Add(Card);
}
}

await DeleteOldCards(board);
}
}

return _organization;
}

At the end, we implement the OnPluginAdd method, here all the actions of the plugin are performed when starting work.

OnPluginAdd​

Place this code in TrelloPlugin.cs:

public async void OnPluginAdd(WorkflowRuntime runtime, List<string> schemes = null)
{
_runtime = runtime;
_schemes = schemes;
if (schemes.IsNullOrEmpty())
{
_schemes = new List<string>();
var allSchemes = await runtime.GetSchemeCodesAsync();
_schemes.AddRange(allSchemes);
}

_client = new HttpClient();
_organization = await TrelloInit(runtime, _schemes);
runtime.OnSchemaWasChangedAsync += async (sender, args, cancellationToken) =>
{
await TrelloInit(_runtime, _schemes);
};

runtime.OnProcessActivityChanged += async (sender, args) =>
{
if (!_organization.Boards.Any(c =>
string.Equals(c.Name, args.SchemeCode, StringComparison.CurrentCultureIgnoreCase)))
{
_schemes.Add(args.SchemeCode);
await TrelloInit(runtime, _schemes);
}

var board = _organization.Boards.FirstOrDefault(c => c.Name == args.SchemeCode);
var oldList = args.PreviousActivityName == null
? null
: board.Lists.FirstOrDefault(x => x.Name.ToLower() == args.PreviousActivityName.ToLower());
var newList = board.Lists.FirstOrDefault(x => x.Name.ToLower() == args.CurrentActivityName.ToLower());
Card card;
if (oldList == null)
{
var descriptionBuilder = new StringBuilder();
descriptionBuilder.AppendLine($"Process id: {args.ProcessInstance.ProcessId}");
descriptionBuilder.AppendLine(
$"Creation date: {args.ProcessInstance.CreationDate.ToLongDateString()} {args.ProcessInstance.CreationDate.ToLongTimeString()}");
card = await CardInit(newList, args.ProcessId.ToString(), descriptionBuilder.ToString(),
args.ProcessId);
}
else
{
card = oldList.Cards.FirstOrDefault(c => c.ProcessId == args.ProcessId);
}

await MoveCard(card, oldList, newList);
};
}

Step Four - Initializing the plugin​

Add the plugin to the application in the Program.cs file:

var plugin = new TrelloPlugin
{
Token = "YOUR TRELLO TOKEN",
Key = "YOUR TRELLO KEY"
};
workflowServer.WorkflowRuntime.WithPlugin(plugin, new List<string>());

All plugin code​

TrelloPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using OptimaJet.Workflow.Core.Runtime;
using OptimaJet.Workflow.Plugins;
using Newtonsoft.Json;


namespace WorkflowServer.Plugins;

public class TrelloPlugin : IWorkflowPlugin
{
public string Name { get; } = "Trello Plugin";
public bool Disabled { get; set; }
public Dictionary<string, string> PluginSettings { get; }
public string Token { get; set; }
public string Key { get; set; }

private HttpClient _client;
private Organization _organization;
private WorkflowRuntime _runtime;
private List<string> _schemes;
private const string _url = "https://api.trello.com/1";

public async void OnPluginAdd(WorkflowRuntime runtime, List<string> schemes = null)
{
_runtime = runtime;
_schemes = schemes;
if (schemes.IsNullOrEmpty())
{
_schemes = new List<string>();
var allSchemes = await runtime.GetSchemeCodesAsync();
_schemes.AddRange(allSchemes);
}

_client = new HttpClient();
_organization = await TrelloInit(runtime, _schemes);
runtime.OnSchemaWasChangedAsync += async (sender, args, cancellationToken) =>
{
await TrelloInit(_runtime, _schemes);
};

runtime.OnProcessActivityChanged += async (sender, args) =>
{
if (!_organization.Boards.Any(c =>
string.Equals(c.Name, args.SchemeCode, StringComparison.CurrentCultureIgnoreCase)))
{
_schemes.Add(args.SchemeCode);
await TrelloInit(runtime, _schemes);
}

var board = _organization.Boards.FirstOrDefault(c => c.Name == args.SchemeCode);
var oldList = args.PreviousActivityName == null
? null
: board.Lists.FirstOrDefault(x => x.Name.ToLower() == args.PreviousActivityName.ToLower());
var newList = board.Lists.FirstOrDefault(x => x.Name.ToLower() == args.CurrentActivityName.ToLower());
Card card;
if (oldList == null)
{
var descriptionBuilder = new StringBuilder();
descriptionBuilder.AppendLine($"Process id: {args.ProcessInstance.ProcessId}");
descriptionBuilder.AppendLine(
$"Creation date: {args.ProcessInstance.CreationDate.ToLongDateString()} {args.ProcessInstance.CreationDate.ToLongTimeString()}");
card = await CardInit(newList, args.ProcessId.ToString(), descriptionBuilder.ToString(),
args.ProcessId);
}
else
{
card = oldList.Cards.FirstOrDefault(c => c.ProcessId == args.ProcessId);
}

await MoveCard(card, oldList, newList);
};
}

public async Task OnRuntimeStartAsync(WorkflowRuntime runtime)
{
}

private async Task<Organization> OrganizationInit(string name)
{
var response =
await _client.GetAsync(
$"{_url}/organizations/{name.ToLower()}?token={Token}&key={Key}");
if (response.StatusCode != HttpStatusCode.OK)
{
response = await _client.PostAsync(
$"{_url}/organizations?displayName={name}&token={Token}&key={Key}", null);
}

return JsonConvert.DeserializeObject<Organization>(await response.Content.ReadAsStringAsync());
}


private async Task<Board> BoardInit(Organization organization, string name)
{
var organizationBoards = new List<Board>();
var response =
await _client.GetAsync(
$"{_url}/organization/{organization.Name}/boards?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
organizationBoards = JsonConvert.DeserializeObject<List<Board>>(await response.Content.ReadAsStringAsync());
}

var board = organizationBoards.FirstOrDefault(c =>
string.Equals(c.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (board != null) return board;
response = await _client.PostAsync(
$"{_url}/boards?name={name}&key={Key}&token={Token}&defaultLists=false&defaultLabels=false&idOrganization={organization.Id}",
null);
board = JsonConvert.DeserializeObject<Board>(await response.Content.ReadAsStringAsync());

return board;
}

private async Task DeleteOldBoards(Organization org)
{
var response =
await _client.GetAsync(
$"{_url}/organizations/{org.Id}/boards?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
var boards = JsonConvert.DeserializeObject<List<Board>>(await response.Content.ReadAsStringAsync());
foreach (var trelloBoard in boards)
{
if (org.Boards.Find(c => c.Id == trelloBoard.Id) == null)
{
await _client.DeleteAsync(
$"{_url}/boards/{trelloBoard.Id}?key={Key}&token={Token}");
}
}
}
}

private async Task<BoardList> ListInit(Board board, string name)
{
List<BoardList> boardLists = new List<BoardList>();
var response =
await _client.GetAsync($"{_url}/boards/{board.Id}/lists?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
boardLists = JsonConvert.DeserializeObject<List<BoardList>>(await response.Content.ReadAsStringAsync());
}

var list = boardLists.FirstOrDefault(c =>
string.Equals(c.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (list == null)
{
response = await _client.PostAsync(
$"{_url}/lists?name={name}&idBoard={board.Id}&key={Key}&token={Token}", null);
list = JsonConvert.DeserializeObject<BoardList>(await response.Content.ReadAsStringAsync());
}

return list;
}

private async Task DeleteOldLists(Board board)
{
var response =
await _client.GetAsync($"{_url}/boards/{board.Id}/lists?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
var allLists = JsonConvert.DeserializeObject<List<BoardList>>(await response.Content.ReadAsStringAsync());
foreach (var list in allLists)
{
if (board.Lists.Find(c => c.Id == list.Id) == null)
{
await _client.PutAsync(
$"{_url}/lists/{list.Id}/closed?value=true&key={Key}&token={Token}", null);
}
}
}
}

private async Task SortLists(Board board)
{
int startPosition = 5;
var response =
await _client.GetAsync($"{_url}/boards/{board.Id}/lists?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
var lists = JsonConvert.DeserializeObject<List<BoardList>>(await response.Content.ReadAsStringAsync());
var pd = await _runtime.Builder.GetProcessSchemeAsync(board.SchemeId);
foreach (var activityDefinition in pd.Activities)
{
var list = lists.FirstOrDefault(c => c.Name.ToLower() == activityDefinition.Name.ToLower());
if (list != null)
{
await _client.PutAsync(
$"{_url}/lists/{list.Id}?pos={Math.Pow(2, startPosition)}&key={Key}&token={Token}",
null);
startPosition++;
}
}
}
}

private async Task<Card> CardInit(BoardList list, string name, string desc, Guid processId)
{
List<Card> cardList = new List<Card>();
var response =
await _client.GetAsync($"{_url}/lists/{list.Id}/cards?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
cardList = JsonConvert.DeserializeObject<List<Card>>(await response.Content.ReadAsStringAsync());
}

var card = cardList.FirstOrDefault(c => string.Equals(c.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (card == null)
{
response = await _client.PostAsync(
$"{_url}/cards?name={name}&desc={desc}&pos=top&idList={list.Id}&key={Key}&token={Token}",
null);
card = JsonConvert.DeserializeObject<Card>(await response.Content.ReadAsStringAsync());
}

card.ProcessId = processId;
return card;
}

private async Task DeleteOldCards(Board board)
{
foreach (var boardList in board.Lists)
{
var response =
await _client.GetAsync($"{_url}/lists/{boardList.Id}/cards?key={Key}&token={Token}");
if (response.StatusCode == HttpStatusCode.OK)
{
var CardsOnList = JsonConvert.DeserializeObject<List<Card>>(await response.Content.ReadAsStringAsync());
foreach (var card in CardsOnList)
{
if (boardList.Cards.Find(c => c.Id == card.Id) == null)
{
await _client.DeleteAsync($"{_url}/cards/{card.Id}?key={Key}&token={Token}");
}
}
}
}
}

private async Task MoveCard(Card card, BoardList oldList, BoardList newList)
{
var response = await _client.PutAsync(
$"{_url}/cards/{card.Id}?idList={newList.Id}&key={Key}&token={Token}", null);
if (response.StatusCode == HttpStatusCode.OK)
{
if (oldList != null)
{
oldList.Cards.Remove(oldList.Cards.Find(c => c.Id == card.Id));
}

var newCard = JsonConvert.DeserializeObject<Card>(await response.Content.ReadAsStringAsync());
newCard.ProcessId = card.ProcessId;
newList.Cards.Add(newCard);
}
}

private async Task<Organization> TrelloInit(WorkflowRuntime runtime, List<string> schemes = null)
{
_organization = await OrganizationInit("WorkflowServer");
foreach (var scheme in schemes)
{
var board = await BoardInit(_organization, scheme);
board.Lists.Clear();
_organization.Boards.Add(board);
var pd = await runtime.Builder.GetProcessSchemeAsync(scheme);
board.SchemeId = pd.Id;
foreach (var activity in pd.Activities)
{
var list = await ListInit(board, activity.Name);
board.Lists.Add(list);
}

await DeleteOldLists(board);
await SortLists(board);
}

await DeleteOldBoards(_organization);

var pi = await runtime.PersistenceProvider.GetProcessInstancesAsync(null, null);
var piGroup = pi.GroupBy(c => c.SchemeId).ToList();
foreach (var processInstanceItems in piGroup)
{
var board = _organization.Boards.FirstOrDefault(c => c.SchemeId == processInstanceItems.Key);
if (board != null)
{
foreach (var boardList in board.Lists)
{
boardList.Cards.Clear();
var piOnActivity = processInstanceItems.Where(c => c.ActivityName == boardList.Name).ToList();
foreach (var card in piOnActivity)
{
StringBuilder descriptionString = new StringBuilder();
descriptionString.AppendLine($"Process id: {card.Id}");
descriptionString.AppendLine(
$"Creation date: {card.CreationDate.ToLongDateString()} {card.CreationDate.ToLongTimeString()}");
var Card = await CardInit(boardList, card.Id.ToString(), descriptionString.ToString(), card.Id);
boardList.Cards.Add(Card);
}
}

await DeleteOldCards(board);
}
}

return _organization;
}
}

#region Models

public class Organization
{
public Organization()
{
Boards = new List<Board>();
}

public string Id { get; set; }
public string Name { get; set; }
public string DisplayName { get; set; }
public List<Board> Boards { get; }
}

public class Board
{
public Board()
{
Lists = new List<BoardList>();
}

public string Id { get; set; }
public string Name { get; set; }
public string IdOrganization { get; set; }
public List<BoardList> Lists { get; }
public Guid SchemeId { get; set; }
}

public class BoardList
{
public BoardList()
{
Cards = new List<Card>();
}

public string Id { get; set; }
public string Name { get; set; }
public string IdBoard { get; set; }
public List<Card> Cards { get; }
}

public class Card
{
public string Id { get; set; }
public string Name { get; set; }
public string Desc { get; set; }
public string IdList { get; set; }
public Guid ProcessId { get; set; }
}

#endregion