diff --git a/examples/csharp/csharp-courses_steps_csv-01_base/csharp-courses_steps_csv-01_base.sln b/examples/csharp/csharp-courses_steps_csv-01_base/csharp-courses_steps_csv-01_base.sln
new file mode 100644
index 00000000..56a83fec
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-01_base/csharp-courses_steps_csv-01_base.sln
@@ -0,0 +1,56 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.6.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8B7D4918-2C37-4F16-B355-8EF19B1E23F8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoursesStepsCsv", "src\CoursesStepsCsv\CoursesStepsCsv.csproj", "{C6E9F888-08F3-4D4B-9AEB-919F615C988F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C8A13F5C-212F-4163-885A-01C093B26450}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoursesStepsCsv.Tests", "test\CoursesStepsCsv.Tests\CoursesStepsCsv.Tests.csproj", "{C6D93799-5C86-410F-A957-1A531A757DAE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x64.Build.0 = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x86.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x64.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F} = {8B7D4918-2C37-4F16-B355-8EF19B1E23F8}
+ {C6D93799-5C86-410F-A957-1A531A757DAE} = {C8A13F5C-212F-4163-885A-01C093B26450}
+ EndGlobalSection
+EndGlobal
diff --git a/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/CourseStepsGetController.cs b/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/CourseStepsGetController.cs
new file mode 100644
index 00000000..982f73b2
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/CourseStepsGetController.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using Microsoft.VisualBasic.FileIO;
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class CourseStepsGetController
+ {
+ private readonly Platform platform;
+
+ public CourseStepsGetController(Platform platform)
+ {
+ this.platform = platform;
+ }
+
+ public string Get(string courseId)
+ {
+ var csv = platform.FindCourseSteps(courseId);
+
+ var results = "[";
+
+ var lines = csv.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ var row = lines[i].Split(',');
+
+ var type = row[1];
+ var duration = 0.0;
+ var points = 0.0;
+
+ if (type == "video")
+ {
+ duration = int.Parse(row[3]) * 1.1;
+ }
+
+ if (type == "quiz")
+ {
+ duration = int.Parse(row[2]) * 0.5; // 0.5 = time in minutes per question
+ }
+
+ if (type != "video" && type != "quiz")
+ {
+ continue;
+ }
+
+ if (type == "video")
+ {
+ points = int.Parse(row[3]) * 1.1 * 100;
+ }
+
+ if (type == "quiz")
+ {
+ points = int.Parse(row[2]) * 0.5 * 10;
+ }
+
+ var step = new Step
+ {
+ Id = row[0],
+ Type = row[1],
+ Duration = duration,
+ Points = points
+ };
+
+ results += JsonSerializer.Serialize(step, new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ });
+
+ if (i != lines.Length - 1)
+ {
+ results += ",";
+ }
+ }
+ results += "]";
+
+ return results;
+ }
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/CoursesStepsCsv.csproj b/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/CoursesStepsCsv.csproj
new file mode 100644
index 00000000..f208d303
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/CoursesStepsCsv.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net5.0
+
+
+
diff --git a/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/Platform.cs b/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/Platform.cs
new file mode 100644
index 00000000..e9ef3120
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/Platform.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public interface Platform
+ {
+ string FindCourseSteps(string courseId);
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/Step.cs b/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/Step.cs
new file mode 100644
index 00000000..77a4b962
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-01_base/src/CoursesStepsCsv/Step.cs
@@ -0,0 +1,11 @@
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class Step
+ {
+ public string Id { get; set; }
+ public string Type { get; set; }
+ public double Duration { get; set; }
+ public double Points { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/csharp-courses_steps_csv-01_base/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs b/examples/csharp/csharp-courses_steps_csv-01_base/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs
new file mode 100644
index 00000000..2354645b
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-01_base/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs
@@ -0,0 +1,63 @@
+using System;
+using Xunit;
+using Moq;
+
+namespace CodelyTv.CoursesStepsCsv.Tests
+{
+ public class CourseStepsGetControllerShould
+ {
+ private readonly Mock platform;
+ private readonly CourseStepsGetController courseStepsGetController;
+ public CourseStepsGetControllerShould()
+ {
+ platform = new Mock();
+ courseStepsGetController = new CourseStepsGetController(platform.Object);
+ }
+
+ [Fact]
+ public void ReturnEmptyStepList()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var emptyCsv = "";
+
+ GivenPlatformReturnsCourseStepCsv(courseId, emptyCsv);
+
+ var actualCourseSteps = courseStepsGetController.Get(courseId);
+
+ Assert.Equal("[]", actualCourseSteps);
+ }
+
+ [Fact]
+ public void ReturnExistingCourseSteps()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var csv = String.Join(
+ Environment.NewLine,
+ "1,video,,13",
+ "2,quiz,5,");
+ GivenPlatformReturnsCourseStepCsv(courseId, csv);
+
+ var results = courseStepsGetController.Get(courseId);
+
+ var expected = "[{\"id\":\"1\",\"type\":\"video\",\"duration\":14.3,\"points\":1430},{\"id\":\"2\",\"type\":\"quiz\",\"duration\":2.5,\"points\":25}]";
+ Assert.Equal(expected, results);
+ }
+
+ [Fact]
+ public void IgnoreStepsWithInvalidType()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var csv = "1,survey,,13";
+ GivenPlatformReturnsCourseStepCsv(courseId, csv);
+
+ var results = courseStepsGetController.Get(courseId);
+
+ Assert.Equal("[]", results);
+ }
+
+ private void GivenPlatformReturnsCourseStepCsv(string courseId, string csv)
+ {
+ platform.Setup(x => x.FindCourseSteps(courseId)).Returns(csv);
+ }
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-01_base/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj b/examples/csharp/csharp-courses_steps_csv-01_base/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj
new file mode 100644
index 00000000..528458bd
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-01_base/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net5.0
+ CodelyTv.CoursesStepsCsv.Tests
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/examples/csharp/csharp-courses_steps_csv-02_semantics/csharp-courses_steps_csv-02_semantics.sln b/examples/csharp/csharp-courses_steps_csv-02_semantics/csharp-courses_steps_csv-02_semantics.sln
new file mode 100644
index 00000000..56a83fec
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-02_semantics/csharp-courses_steps_csv-02_semantics.sln
@@ -0,0 +1,56 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.6.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8B7D4918-2C37-4F16-B355-8EF19B1E23F8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoursesStepsCsv", "src\CoursesStepsCsv\CoursesStepsCsv.csproj", "{C6E9F888-08F3-4D4B-9AEB-919F615C988F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C8A13F5C-212F-4163-885A-01C093B26450}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoursesStepsCsv.Tests", "test\CoursesStepsCsv.Tests\CoursesStepsCsv.Tests.csproj", "{C6D93799-5C86-410F-A957-1A531A757DAE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x64.Build.0 = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x86.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x64.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F} = {8B7D4918-2C37-4F16-B355-8EF19B1E23F8}
+ {C6D93799-5C86-410F-A957-1A531A757DAE} = {C8A13F5C-212F-4163-885A-01C093B26450}
+ EndGlobalSection
+EndGlobal
diff --git a/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/CourseStepsGetController.cs b/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/CourseStepsGetController.cs
new file mode 100644
index 00000000..a6f222a5
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/CourseStepsGetController.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using Microsoft.VisualBasic.FileIO;
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class CourseStepsGetController
+ {
+ private const double VIDEO_DURATION_PAUSES_MULTIPLIER = 1.1;
+ private const double QUIZ_TIME_PER_QUESTION_MULTIPLIER = 0.5;
+ private const int QUIZ_POINTS_PER_MINUTE = 10;
+ private const string STEP_TYPE_QUIZ = "quiz";
+ private const string STEP_TYPE_VIDEO = "video";
+ private const int VIDEO_POINTS_PER_MINUTE = 100;
+ private readonly Platform platform;
+
+ public CourseStepsGetController(Platform platform)
+ {
+ this.platform = platform;
+ }
+
+ public string Get(string courseId)
+ {
+ var csv = platform.FindCourseSteps(courseId);
+
+ var results = "[";
+
+ var lines = csv.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ var row = lines[i].Split(',');
+
+ var id = row[0];
+ var type = row[1];
+ int? quizTotalQuestions = string.IsNullOrEmpty(row[2]) ? null : int.Parse(row[2]);
+ int? videoDuration = string.IsNullOrEmpty(row[3]) ? null : int.Parse(row[3]);
+
+ var stepDurationInMinutes = 0.0;
+ var points = 0.0;
+
+ if (type == STEP_TYPE_VIDEO)
+ {
+ stepDurationInMinutes = videoDuration.Value * VIDEO_DURATION_PAUSES_MULTIPLIER;
+ }
+
+ if (type == STEP_TYPE_QUIZ)
+ {
+ stepDurationInMinutes = quizTotalQuestions.Value * QUIZ_TIME_PER_QUESTION_MULTIPLIER;
+ }
+
+ if (type != STEP_TYPE_VIDEO && type != STEP_TYPE_QUIZ)
+ {
+ continue;
+ }
+
+ if (type == STEP_TYPE_VIDEO)
+ {
+ points = stepDurationInMinutes * VIDEO_POINTS_PER_MINUTE;
+ }
+
+ if (type == STEP_TYPE_QUIZ)
+ {
+ points = stepDurationInMinutes * QUIZ_POINTS_PER_MINUTE;
+ }
+
+ var step = new Step
+ {
+ Id = id,
+ Type = type,
+ Duration = stepDurationInMinutes,
+ Points = points
+ };
+
+ results += JsonSerializer.Serialize(step, new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ });
+
+ if (i != lines.Length - 1)
+ {
+ results += ",";
+ }
+ }
+ results += "]";
+
+ return results;
+ }
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/CoursesStepsCsv.csproj b/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/CoursesStepsCsv.csproj
new file mode 100644
index 00000000..f208d303
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/CoursesStepsCsv.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net5.0
+
+
+
diff --git a/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/Platform.cs b/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/Platform.cs
new file mode 100644
index 00000000..e9ef3120
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/Platform.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public interface Platform
+ {
+ string FindCourseSteps(string courseId);
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/Step.cs b/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/Step.cs
new file mode 100644
index 00000000..77a4b962
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-02_semantics/src/CoursesStepsCsv/Step.cs
@@ -0,0 +1,11 @@
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class Step
+ {
+ public string Id { get; set; }
+ public string Type { get; set; }
+ public double Duration { get; set; }
+ public double Points { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/csharp-courses_steps_csv-02_semantics/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs b/examples/csharp/csharp-courses_steps_csv-02_semantics/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs
new file mode 100644
index 00000000..2354645b
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-02_semantics/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs
@@ -0,0 +1,63 @@
+using System;
+using Xunit;
+using Moq;
+
+namespace CodelyTv.CoursesStepsCsv.Tests
+{
+ public class CourseStepsGetControllerShould
+ {
+ private readonly Mock platform;
+ private readonly CourseStepsGetController courseStepsGetController;
+ public CourseStepsGetControllerShould()
+ {
+ platform = new Mock();
+ courseStepsGetController = new CourseStepsGetController(platform.Object);
+ }
+
+ [Fact]
+ public void ReturnEmptyStepList()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var emptyCsv = "";
+
+ GivenPlatformReturnsCourseStepCsv(courseId, emptyCsv);
+
+ var actualCourseSteps = courseStepsGetController.Get(courseId);
+
+ Assert.Equal("[]", actualCourseSteps);
+ }
+
+ [Fact]
+ public void ReturnExistingCourseSteps()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var csv = String.Join(
+ Environment.NewLine,
+ "1,video,,13",
+ "2,quiz,5,");
+ GivenPlatformReturnsCourseStepCsv(courseId, csv);
+
+ var results = courseStepsGetController.Get(courseId);
+
+ var expected = "[{\"id\":\"1\",\"type\":\"video\",\"duration\":14.3,\"points\":1430},{\"id\":\"2\",\"type\":\"quiz\",\"duration\":2.5,\"points\":25}]";
+ Assert.Equal(expected, results);
+ }
+
+ [Fact]
+ public void IgnoreStepsWithInvalidType()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var csv = "1,survey,,13";
+ GivenPlatformReturnsCourseStepCsv(courseId, csv);
+
+ var results = courseStepsGetController.Get(courseId);
+
+ Assert.Equal("[]", results);
+ }
+
+ private void GivenPlatformReturnsCourseStepCsv(string courseId, string csv)
+ {
+ platform.Setup(x => x.FindCourseSteps(courseId)).Returns(csv);
+ }
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-02_semantics/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj b/examples/csharp/csharp-courses_steps_csv-02_semantics/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj
new file mode 100644
index 00000000..528458bd
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-02_semantics/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net5.0
+ CodelyTv.CoursesStepsCsv.Tests
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/csharp-courses_steps_csv-03_split_parsing_phase.sln b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/csharp-courses_steps_csv-03_split_parsing_phase.sln
new file mode 100644
index 00000000..56a83fec
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/csharp-courses_steps_csv-03_split_parsing_phase.sln
@@ -0,0 +1,56 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.6.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8B7D4918-2C37-4F16-B355-8EF19B1E23F8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoursesStepsCsv", "src\CoursesStepsCsv\CoursesStepsCsv.csproj", "{C6E9F888-08F3-4D4B-9AEB-919F615C988F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C8A13F5C-212F-4163-885A-01C093B26450}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoursesStepsCsv.Tests", "test\CoursesStepsCsv.Tests\CoursesStepsCsv.Tests.csproj", "{C6D93799-5C86-410F-A957-1A531A757DAE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x64.Build.0 = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x86.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x64.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F} = {8B7D4918-2C37-4F16-B355-8EF19B1E23F8}
+ {C6D93799-5C86-410F-A957-1A531A757DAE} = {C8A13F5C-212F-4163-885A-01C093B26450}
+ EndGlobalSection
+EndGlobal
diff --git a/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/CourseStepsGetController.cs b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/CourseStepsGetController.cs
new file mode 100644
index 00000000..0c0afc40
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/CourseStepsGetController.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using Microsoft.VisualBasic.FileIO;
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class CourseStepsGetController
+ {
+ private const double VIDEO_DURATION_PAUSES_MULTIPLIER = 1.1;
+ private const double QUIZ_TIME_PER_QUESTION_MULTIPLIER = 0.5;
+ private const int QUIZ_POINTS_PER_MINUTE = 10;
+ private const string STEP_TYPE_QUIZ = "quiz";
+ private const string STEP_TYPE_VIDEO = "video";
+ private const int VIDEO_POINTS_PER_MINUTE = 100;
+ private readonly Platform platform;
+
+ public CourseStepsGetController(Platform platform)
+ {
+ this.platform = platform;
+ }
+
+ public string Get(string courseId)
+ {
+ var csv = platform.FindCourseSteps(courseId);
+ List csvSteps = ParseCsv(csv);
+
+ var results = "[";
+
+ for (int i = 0; i < csvSteps.Count; i++)
+ {
+ var csvStep = csvSteps.ElementAt(i);
+
+ var id = csvStep.StepId;
+ var type = csvStep.Type;
+ var quizTotalQuestions = csvStep.QuizTotalQuestions;
+ var videoDuration = csvStep.VideoDuration;
+
+ var stepDurationInMinutes = 0.0;
+ var points = 0.0;
+
+ if (type == STEP_TYPE_VIDEO)
+ {
+ stepDurationInMinutes = videoDuration.Value * VIDEO_DURATION_PAUSES_MULTIPLIER;
+ }
+
+ if (type == STEP_TYPE_QUIZ)
+ {
+ stepDurationInMinutes = quizTotalQuestions.Value * QUIZ_TIME_PER_QUESTION_MULTIPLIER;
+ }
+
+ if (type == STEP_TYPE_VIDEO)
+ {
+ points = stepDurationInMinutes * VIDEO_POINTS_PER_MINUTE;
+ }
+
+ if (type == STEP_TYPE_QUIZ)
+ {
+ points = stepDurationInMinutes * QUIZ_POINTS_PER_MINUTE;
+ }
+
+ var step = new Step(id, type, stepDurationInMinutes, points);
+
+ results += JsonSerializer.Serialize(step, new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ });
+
+ if (i != csvSteps.Count - 1)
+ {
+ results += ",";
+ }
+ }
+ results += "]";
+
+ return results;
+ }
+
+ private List ParseCsv(string csv)
+ {
+ var csvSteps = new List();
+
+ var lines = csv.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ var row = lines[i].Split(',');
+
+ var id = row[0];
+ var type = row[1];
+ int? quizTotalQuestions = string.IsNullOrEmpty(row[2]) ? null : int.Parse(row[2]);
+ int? videoDuration = string.IsNullOrEmpty(row[3]) ? null : int.Parse(row[3]);
+
+ if (type != STEP_TYPE_VIDEO && type != STEP_TYPE_QUIZ)
+ {
+ continue;
+ }
+
+ var csvStep = new CsvStep(id, type, quizTotalQuestions, videoDuration);
+
+ csvSteps.Add(csvStep);
+ }
+
+ return csvSteps;
+ }
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/CoursesStepsCsv.csproj b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/CoursesStepsCsv.csproj
new file mode 100644
index 00000000..f208d303
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/CoursesStepsCsv.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net5.0
+
+
+
diff --git a/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/CsvStep.cs b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/CsvStep.cs
new file mode 100644
index 00000000..57d508ee
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/CsvStep.cs
@@ -0,0 +1,19 @@
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class CsvStep
+ {
+ public string StepId { get; }
+ public string Type { get; }
+ public double? QuizTotalQuestions { get; }
+ public double? VideoDuration { get; }
+
+ public CsvStep(string stepId, string type, double? quizTotalQuestions, double? videoDuration)
+ {
+ StepId = stepId;
+ Type = type;
+ QuizTotalQuestions = quizTotalQuestions;
+ VideoDuration = videoDuration;
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/Platform.cs b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/Platform.cs
new file mode 100644
index 00000000..e9ef3120
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/Platform.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public interface Platform
+ {
+ string FindCourseSteps(string courseId);
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/Step.cs b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/Step.cs
new file mode 100644
index 00000000..6026404c
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/src/CoursesStepsCsv/Step.cs
@@ -0,0 +1,19 @@
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class Step
+ {
+ public string Id { get; }
+ public string Type { get; }
+ public double Duration { get; }
+ public double Points { get; }
+
+ public Step(string id, string type, double duration, double points)
+ {
+ Id = id;
+ Type = type;
+ Duration = duration;
+ Points = points;
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs
new file mode 100644
index 00000000..2354645b
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs
@@ -0,0 +1,63 @@
+using System;
+using Xunit;
+using Moq;
+
+namespace CodelyTv.CoursesStepsCsv.Tests
+{
+ public class CourseStepsGetControllerShould
+ {
+ private readonly Mock platform;
+ private readonly CourseStepsGetController courseStepsGetController;
+ public CourseStepsGetControllerShould()
+ {
+ platform = new Mock();
+ courseStepsGetController = new CourseStepsGetController(platform.Object);
+ }
+
+ [Fact]
+ public void ReturnEmptyStepList()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var emptyCsv = "";
+
+ GivenPlatformReturnsCourseStepCsv(courseId, emptyCsv);
+
+ var actualCourseSteps = courseStepsGetController.Get(courseId);
+
+ Assert.Equal("[]", actualCourseSteps);
+ }
+
+ [Fact]
+ public void ReturnExistingCourseSteps()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var csv = String.Join(
+ Environment.NewLine,
+ "1,video,,13",
+ "2,quiz,5,");
+ GivenPlatformReturnsCourseStepCsv(courseId, csv);
+
+ var results = courseStepsGetController.Get(courseId);
+
+ var expected = "[{\"id\":\"1\",\"type\":\"video\",\"duration\":14.3,\"points\":1430},{\"id\":\"2\",\"type\":\"quiz\",\"duration\":2.5,\"points\":25}]";
+ Assert.Equal(expected, results);
+ }
+
+ [Fact]
+ public void IgnoreStepsWithInvalidType()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var csv = "1,survey,,13";
+ GivenPlatformReturnsCourseStepCsv(courseId, csv);
+
+ var results = courseStepsGetController.Get(courseId);
+
+ Assert.Equal("[]", results);
+ }
+
+ private void GivenPlatformReturnsCourseStepCsv(string courseId, string csv)
+ {
+ platform.Setup(x => x.FindCourseSteps(courseId)).Returns(csv);
+ }
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj
new file mode 100644
index 00000000..528458bd
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-03_split_parsing_phase/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net5.0
+ CodelyTv.CoursesStepsCsv.Tests
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/csharp-courses_steps_csv-04_split_serialization_phase.sln b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/csharp-courses_steps_csv-04_split_serialization_phase.sln
new file mode 100644
index 00000000..56a83fec
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/csharp-courses_steps_csv-04_split_serialization_phase.sln
@@ -0,0 +1,56 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.6.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8B7D4918-2C37-4F16-B355-8EF19B1E23F8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoursesStepsCsv", "src\CoursesStepsCsv\CoursesStepsCsv.csproj", "{C6E9F888-08F3-4D4B-9AEB-919F615C988F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C8A13F5C-212F-4163-885A-01C093B26450}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoursesStepsCsv.Tests", "test\CoursesStepsCsv.Tests\CoursesStepsCsv.Tests.csproj", "{C6D93799-5C86-410F-A957-1A531A757DAE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x64.Build.0 = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F}.Release|x86.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x64.Build.0 = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6D93799-5C86-410F-A957-1A531A757DAE}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C6E9F888-08F3-4D4B-9AEB-919F615C988F} = {8B7D4918-2C37-4F16-B355-8EF19B1E23F8}
+ {C6D93799-5C86-410F-A957-1A531A757DAE} = {C8A13F5C-212F-4163-885A-01C093B26450}
+ EndGlobalSection
+EndGlobal
diff --git a/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/CourseStepsGetController.cs b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/CourseStepsGetController.cs
new file mode 100644
index 00000000..9f6ee88e
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/CourseStepsGetController.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using Microsoft.VisualBasic.FileIO;
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class CourseStepsGetController
+ {
+ private const double VIDEO_DURATION_PAUSES_MULTIPLIER = 1.1;
+ private const double QUIZ_TIME_PER_QUESTION_MULTIPLIER = 0.5;
+ private const int QUIZ_POINTS_PER_MINUTE = 10;
+ private const string STEP_TYPE_QUIZ = "quiz";
+ private const string STEP_TYPE_VIDEO = "video";
+ private const int VIDEO_POINTS_PER_MINUTE = 100;
+ private readonly Platform platform;
+
+ public CourseStepsGetController(Platform platform)
+ {
+ this.platform = platform;
+ }
+
+ public string Get(string courseId)
+ {
+ var csv = platform.FindCourseSteps(courseId);
+ List csvSteps = ParseCsv(csv);
+ List steps = CreateStepsFromPrimitives(csvSteps);
+ return ToJson(steps);
+ }
+
+ private List ParseCsv(string csv)
+ {
+ var csvSteps = new List();
+
+ var lines = csv.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ var row = lines[i].Split(',');
+
+ var id = row[0];
+ var type = row[1];
+ int? quizTotalQuestions = string.IsNullOrEmpty(row[2]) ? null : int.Parse(row[2]);
+ int? videoDuration = string.IsNullOrEmpty(row[3]) ? null : int.Parse(row[3]);
+
+ if (type != STEP_TYPE_VIDEO && type != STEP_TYPE_QUIZ)
+ {
+ continue;
+ }
+
+ var csvStep = new CsvStep(id, type, quizTotalQuestions, videoDuration);
+
+ csvSteps.Add(csvStep);
+ }
+
+ return csvSteps;
+ }
+
+ private static List CreateStepsFromPrimitives(List csvSteps)
+ {
+ var steps = new List();
+
+ csvSteps.ForEach(csvStep =>
+ {
+ var id = csvStep.StepId;
+ var type = csvStep.Type;
+ var quizTotalQuestions = csvStep.QuizTotalQuestions;
+ var videoDuration = csvStep.VideoDuration;
+
+ var stepDurationInMinutes = 0.0;
+ var points = 0.0;
+
+ if (type == STEP_TYPE_VIDEO)
+ {
+ stepDurationInMinutes = videoDuration.Value * VIDEO_DURATION_PAUSES_MULTIPLIER;
+ }
+
+ if (type == STEP_TYPE_QUIZ)
+ {
+ stepDurationInMinutes = quizTotalQuestions.Value * QUIZ_TIME_PER_QUESTION_MULTIPLIER;
+ }
+
+ if (type == STEP_TYPE_VIDEO)
+ {
+ points = stepDurationInMinutes * VIDEO_POINTS_PER_MINUTE;
+ }
+
+ if (type == STEP_TYPE_QUIZ)
+ {
+ points = stepDurationInMinutes * QUIZ_POINTS_PER_MINUTE;
+ }
+
+ var step = new Step(id, type, stepDurationInMinutes, points);
+
+ steps.Add(step);
+ });
+ return steps;
+ }
+
+ private static string ToJson(List steps)
+ {
+ return JsonSerializer.Serialize(steps, new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ });
+ }
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/CoursesStepsCsv.csproj b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/CoursesStepsCsv.csproj
new file mode 100644
index 00000000..f208d303
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/CoursesStepsCsv.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net5.0
+
+
+
diff --git a/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/CsvStep.cs b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/CsvStep.cs
new file mode 100644
index 00000000..57d508ee
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/CsvStep.cs
@@ -0,0 +1,19 @@
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class CsvStep
+ {
+ public string StepId { get; }
+ public string Type { get; }
+ public double? QuizTotalQuestions { get; }
+ public double? VideoDuration { get; }
+
+ public CsvStep(string stepId, string type, double? quizTotalQuestions, double? videoDuration)
+ {
+ StepId = stepId;
+ Type = type;
+ QuizTotalQuestions = quizTotalQuestions;
+ VideoDuration = videoDuration;
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/Platform.cs b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/Platform.cs
new file mode 100644
index 00000000..e9ef3120
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/Platform.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public interface Platform
+ {
+ string FindCourseSteps(string courseId);
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/Step.cs b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/Step.cs
new file mode 100644
index 00000000..6026404c
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/src/CoursesStepsCsv/Step.cs
@@ -0,0 +1,19 @@
+
+namespace CodelyTv.CoursesStepsCsv
+{
+ public sealed class Step
+ {
+ public string Id { get; }
+ public string Type { get; }
+ public double Duration { get; }
+ public double Points { get; }
+
+ public Step(string id, string type, double duration, double points)
+ {
+ Id = id;
+ Type = type;
+ Duration = duration;
+ Points = points;
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs
new file mode 100644
index 00000000..2354645b
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/test/CoursesStepsCsv.Tests/CourseStepsGetControllerShould.cs
@@ -0,0 +1,63 @@
+using System;
+using Xunit;
+using Moq;
+
+namespace CodelyTv.CoursesStepsCsv.Tests
+{
+ public class CourseStepsGetControllerShould
+ {
+ private readonly Mock platform;
+ private readonly CourseStepsGetController courseStepsGetController;
+ public CourseStepsGetControllerShould()
+ {
+ platform = new Mock();
+ courseStepsGetController = new CourseStepsGetController(platform.Object);
+ }
+
+ [Fact]
+ public void ReturnEmptyStepList()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var emptyCsv = "";
+
+ GivenPlatformReturnsCourseStepCsv(courseId, emptyCsv);
+
+ var actualCourseSteps = courseStepsGetController.Get(courseId);
+
+ Assert.Equal("[]", actualCourseSteps);
+ }
+
+ [Fact]
+ public void ReturnExistingCourseSteps()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var csv = String.Join(
+ Environment.NewLine,
+ "1,video,,13",
+ "2,quiz,5,");
+ GivenPlatformReturnsCourseStepCsv(courseId, csv);
+
+ var results = courseStepsGetController.Get(courseId);
+
+ var expected = "[{\"id\":\"1\",\"type\":\"video\",\"duration\":14.3,\"points\":1430},{\"id\":\"2\",\"type\":\"quiz\",\"duration\":2.5,\"points\":25}]";
+ Assert.Equal(expected, results);
+ }
+
+ [Fact]
+ public void IgnoreStepsWithInvalidType()
+ {
+ var courseId = "8fe17ce6-1d33-4b6b-a27c-4e0d1f870a19";
+ var csv = "1,survey,,13";
+ GivenPlatformReturnsCourseStepCsv(courseId, csv);
+
+ var results = courseStepsGetController.Get(courseId);
+
+ Assert.Equal("[]", results);
+ }
+
+ private void GivenPlatformReturnsCourseStepCsv(string courseId, string csv)
+ {
+ platform.Setup(x => x.FindCourseSteps(courseId)).Returns(csv);
+ }
+ }
+}
diff --git a/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj
new file mode 100644
index 00000000..528458bd
--- /dev/null
+++ b/examples/csharp/csharp-courses_steps_csv-04_split_serialization_phase/test/CoursesStepsCsv.Tests/CoursesStepsCsv.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net5.0
+ CodelyTv.CoursesStepsCsv.Tests
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+