How-To Tutorials
C#
Console Task App
this tutorial provides a step by step walkthrough on how to build a task list app with a standard c# console app start project overview /#setting up /#configuring commands /#reviewing programcs setting up to set up your project for ditto /#creating a new project /#configuring commands /#reviewing programcs creating a new project open visual studio for windows or for mac create a new project and select console application when prompted to select a target framework, select net 6 0 select a name for your project; for example, "tasks " adding ditto to the project we've deployed ditto for c# on the standard nuget package repository we will need to add ditto to this project right click the project's dependencies folder, and click manage nuget dependencies search for "ditto" in the search bar and add the package called "ditto" by "ditto" ensure not to mistake it for another package if you prefer a different way of installation, feel free to take a look at the alternative ways to install or https //www nuget org/packages/ditto/ install package ditto dotnet add package ditto \<packagereference include="ditto" version="2 " /> in your program cs file, add using dittosdk and using system collections generic to the top of the file like so program cs using system; using dittosdk; using system collections generic; now we'll need to hold a reference to our ditto instance as a static variable and also add a static dittolivequery and static dittosubscription variable these variables must be static because the console program's main function is also static instantiate the ditto static variable by constructing it with a development identity with an appid "live ditto tasks" if you want to sync with other tutorial app types like ios or android, you'll need to match the appid to enable sync program cs namespace tasks { class program { // 4 static ditto ditto; static dittolivequery livequery; static dittosubscription subscription; public static void main(params string\[] args) { // 5 ditto = new ditto(identity dittoidentity onlineplayground("replace me with your app id", "replace me with your playground token"), path); try { ditto startsync(); } catch (dittoexception ex) { console writeline("there was an error starting ditto "); console writeline("here's the following error"); console writeline(ex tostring()); console writeline("ditto cannot start sync but don't worry "); console writeline("ditto will still work as a local database "); } console writeline("welcome to ditto's task app"); } } } creating a task cs file ditto documents have a flexible structure oftentimes, in strongly typed languages like c#, we will create a data structure give more definition to the app create a new c# file named "task cs" in your project in the new file, you'll need to reference system , system collections generic and dittosdk add the matching variables string id , string body , and bool iscompleted to the struct we will use this to match the document values to to the struct add an constructor to task that takes in a dittodocument in the constructor, parse out the document's keys with ditto's type safe value accessors this will safely map all the document's values to the struct's variables that we created in step 2 in addition we will add a couple of other constructor overloads for easier creation of data override the tostring() method of the struct we will later use this to easily print out a more readable string that we can use in console writeline back in the main program cs add a function to the struct called todictionary which will serialize the values into a dictionary\<string, object> this will assist us later when we need to insert a new document into ditto task cs // 1 using system; using system collections generic; using dittosdk; namespace tasks { public struct task { string id; string body; bool iscompleted; // 3 public task(string id, string body, bool iscompleted) { this id = id; this body = body; this iscompleted = iscompleted; } public task(string body, bool iscompleted) { this id = guid newguid() tostring(); this body = body; this iscompleted = iscompleted; } public task(dittodocument document) { this id = document\[" id"] stringvalue; this body = document\["body"] stringvalue; this iscompleted = document\["iscompleted"] booleanvalue; } // 4 public override string tostring() { return $"task id { id}, body {body}, iscompleted {iscompleted}"; } // 5 public dictionary\<string, object> todictionary() { return new dictionary\<string, object> { { " id", id }, { "body", body }, { "iscompleted", iscompleted }, }; } // 5 } } https //legacydocs ditto live/csharp/tutorial/console/setup#13 create a new file called taskcs configuring commands unlike many ui applications, console or command line applications have limitations to user interactions for example, console applications typically read text commands during a while loop as a standard design pattern this section will outline the command line design and set up the loop to allow the user to give continual commands /#designing the commands /#observing the tasks with a live query /#setting up thew hile loop designing the commands our tasks console app will have five commands that map to ditto and general console commands we will need \ insert to allow the user to insert a new document into the ditto collection('tasks") collection toggle to allow the user to update a new document's bool iscompleted property by its id delete to allow the user to remove a new document by its id list will print every current task that we have in the collection in addition, we will always call this method before every item exit will quit the console app as a best practice, long running console applications should continuously print the primary set of commands as long as they are succinct we'll create a utility method called listcommands() which, will console writeline each of the commands program cs using system; using dittosdk; using system collections generic; namespace tasks { class program { public static void main(params string\[] args) { ditto = new ditto(dittoidentity onlineplayground("replace me with your app id", "replace me with your playground token"), path); // omitted for brevity // see `setup` } public static void listcommands() { console writeline(" commands "); console writeline(" insert my new task"); console writeline(" inserts a task"); console writeline(" example \\" insert get milk\\""); console writeline(" toggle mytasktd"); console writeline(" toggles the iscomplete property to the opposite value"); console writeline(" example \\" toggle 1234abc\\""); console writeline(" delete mytasktd"); console writeline(" deletes a task"); console writeline(" example \\" delete 1234abc\\""); console writeline(" list"); console writeline(" list the current tasks"); console writeline(" exit"); console writeline(" exits the program"); console writeline(" commands "); } } } observing the tasks with a live query as we insert, update, and delete our tasks, we will update the tasks collection to sync changes coming from other devices, we will need create a live query by calling observe remember ditto will only sync with devices by calling observe on queries the observe method will return a dittolivequery object as long as the dittolivequery object stays in scope and is not garbage collected, the live query will fire for any changes to the tasks collection remember, the observe callback will fire for both local changes and remote changes program cs dittolivequery livequery = ditto store\["tasks"] find("!isdeleted") observelocal((docs, event) => { // this will fire for all changes synchronized to the store }); in the context of our console application, we need to store a list\<task> as a static variable so that we can print it upon command observe all the document in the tasks collection take care to store the dittolivequery as a static variable as well in the observe callback, convert all the list\<dittodocument> docs into list\<task> and assign them to both variables detailed in step 1 and 2 program cs using system; using dittosdk; using system collections generic; namespace tasks { class program { static ditto ditto; // 1 static list\<task> tasks = new list\<task>(); // 2 static dittolivequery livequery; static dittosubscription subscription; public static void main(params string\[] args) { ditto = new ditto(dittoidentity onlineplayground("replace me with your app id", "replace me with your playground token"), path); try { ditto startsync(); } catch (dittoexception ex) { console writeline("there was an error starting ditto "); console writeline("here's the following error"); console writeline(ex tostring()); console writeline("ditto cannot start sync but don't worry "); console writeline("ditto will still work as a local database "); } console writeline("welcome to ditto's task app"); // 3 subscription = ditto store\["tasks"] find("!isdeleted") subscribe() livequery = ditto store\["tasks"] find("!isdeleted") observelocal((docs, event) => { tasks = docs convertall(document => new task(document)); }); } public static void listcommands() { // omitted for brevity } } } we have all the basic building blocks for syncing tasks to a locally stored list\<task> variable in the following section, we will go over how to map the user commands to actual ditto live query, insert, update and delete methods setting up the w hile loop to determine whether or not the while loop should run, we need an addition static bool isaskedtoexit = false if the user turns this to true via the exit command, the while loop will stop and the application will exit in each iteration of the while loop, we will need read the command from the user in c#, we can use the console readline api, which will prompt the user for a string entry we can store this into string command we can add a switch statement which will parse the correct command and react to the command if the user types in insert , we will parse out the string without the insert command we assume this string is the body for a new document so we will call the upsert command with ditto via program cs string taskbody = s replace(" insert ", ""); ditto store\["tasks"] upsert(new task(taskbody, false) todictionary()); check for a switch case for toggle we will parse out the string without toggle and assume the user's input is a task document's id we can then find the document by its id and call update check for a switch case for delete we will parse out the string without delete and assume the user's input is a task document's id we can then find the document by its id and call update finally we will add a command to look for list , which will print out all the tasks that we've synced program cs using system; using dittosdk; using system collections generic; namespace tasks { class program { static ditto ditto; // 1 static bool isaskedtoexit = false; static list\<task> tasks = new list\<task>(); static dittolivequery livequery; static dittosubscription subscription; public static void main(params string\[] args) { ditto = new ditto(dittoidentity onlineplayground("replace me with your app id", "replace me with your playground token"), path); / omitted for brevity / subscription = ditto store\["tasks"] find("!isdeleted") subscribe() livequery = ditto store\["tasks"] find("!isdeleted") observelocal((docs, event) => { tasks = docs convertall(d => new task(d)); }); ditto store\["tasks"] find("isdeleted == true") evict(); listcommands(); while (!isaskedtoexit) { // 2 console write("\nyour command "); string command = console readline(); switch (command) { // 3 4 case string s when command startswith(" insert") string taskbody = s replace(" insert ", ""); ditto store\["tasks"] upsert(new task(taskbody, false) todictionary()); break; // 5 case string s when command startswith(" toggle") string idtotoggle = s replace(" toggle ", ""); ditto store\["tasks"] findbyid(new dittodocumentid( idtotoggle)) update((mutabledoc) => { if (mutabledoc == null) return; mutabledoc\["iscompleted"] set(!mutabledoc\["iscompleted"] booleanvalue); }); break; // 6 case string s when command startswith(" delete") string idtodelete = s replace(" delete ", ""); ditto store\["tasks"] findbyid(new dittodocumentid( idtodelete)) update((mutabledoc) => { if (mutabledoc == null) return; mutabledoc\["isdeleted"] set(true); }); break; case { } when command startswith(" list") tasks foreach(task => { console writeline(task tostring()); }); break; // 1 case { } when command startswith(" exit") console writeline("good bye!"); isaskedtoexit = true; break; default console writeline("unknown command"); listcommands(); break; } } } } } reviewing program cs our application is complete! our program cs file should look like the following now you can run the example in your terminal, command line or right within the run command in visual studio program cs using system; using dittosdk; using system collections generic; namespace tasks { class program { static ditto ditto; static bool isaskedtoexit = false; static list\<task> tasks = new list\<task>(); static dittolivequery livequery; static dittosubscription subscription; public static void main(params string\[] args) { ditto = new ditto(dittoidentity onlineplayground("replace me with your app id", "replace me with your playground token"), path); try { dittotransportconfig transportconfig = new dittotransportconfig(); // enable local area network connections transportconfig enableallpeertopeer(); // listen for incoming connections on port 4000 transportconfig listen tcp enabled = true; transportconfig listen tcp interfaceip = "0 0 0 0"; transportconfig listen tcp port = 4000; // connect explicitly to a remote device on transportconfig connect tcpservers add("135 1 5 5 12345"); // you can add as many tcpservers as you would like transportconfig connect tcpservers add("185 1 5 5 12345"); ditto transportconfig = transportconfig; ditto startsync(); } catch (dittoexception ex) { console writeline("there was an error starting ditto "); console writeline("here's the following error"); console writeline(ex tostring()); console writeline("ditto cannot start sync but don't worry "); console writeline("ditto will still work as a local database "); } console writeline("welcome to ditto's task app"); subscription = ditto store\["tasks"] find("!isdeleted") subscribe(); livequery = ditto store\["tasks"] find("!isdeleted") observelocal((docs, event) => { tasks = docs convertall(document => new task(document)); }); ditto store\["tasks"] find("isdeleted == true") evict(); listcommands(); while (!isaskedtoexit) { console write("\nyour command "); string command = console readline(); switch (command) { case string s when command startswith(" insert") string taskbody = s replace(" insert ", ""); ditto store\["tasks"] upsert(new task(taskbody, false) todictionary()); break; case string s when command startswith(" toggle") string idtotoggle = s replace(" toggle ", ""); ditto store\["tasks"] findbyid(new dittodocumentid( idtotoggle)) update((mutabledoc) => { if (mutabledoc == null) return; mutabledoc\["iscompleted"] set(!mutabledoc\["iscompleted"] booleanvalue); }); break; case string s when command startswith(" delete") string idtodelete = s replace(" delete ", ""); ditto store\["tasks"] findbyid(new dittodocumentid( idtodelete)) update((mutabledoc) => { if (mutabledoc == null) return; mutabledoc\["isdeleted"] set(true); }); break; case { } when command startswith(" list") tasks foreach(task => { console writeline(task tostring()); }); break; case { } when command startswith(" exit") console writeline("good bye!"); isaskedtoexit = true; break; default console writeline("unknown command"); listcommands(); break; } } } public static void listcommands() { console writeline(" commands "); console writeline(" insert my new task"); console writeline(" inserts a task"); console writeline(" example \\" insert get milk\\""); console writeline(" toggle mytasktd"); console writeline(" toggles the iscomplete property to the opposite value"); console writeline(" example \\" toggle 1234abc\\""); console writeline(" delete mytasktd"); console writeline(" deletes a task"); console writeline(" example \\" delete 1234abc\\""); console writeline(" list"); console writeline(" list the current tasks"); console writeline(" exit"); console writeline(" exits the program"); console writeline(" commands "); } } }