diff --git a/todos/netcore/.gitignore b/todos/netcore/.gitignore
new file mode 100644
index 0000000..09b57bc
--- /dev/null
+++ b/todos/netcore/.gitignore
@@ -0,0 +1,236 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+.vscode/
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+bin/
+Bin/
+obj/
+Obj/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+/wwwroot/dist/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Microsoft Azure ApplicationInsights config file
+ApplicationInsights.config
+
+# Windows Store app package directory
+AppPackages/
+BundleArtifacts/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+node_modules/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+
+# FAKE - F# Make
+.fake/
+
+# Jetbrains tools
+.idea/
diff --git a/todos/netcore/AspNet/AspNet.csproj b/todos/netcore/AspNet/AspNet.csproj
new file mode 100644
index 0000000..dbb0aa6
--- /dev/null
+++ b/todos/netcore/AspNet/AspNet.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netcoreapp2.2
+ InProcess
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/todos/netcore/AspNet/Program.cs b/todos/netcore/AspNet/Program.cs
new file mode 100644
index 0000000..351e53a
--- /dev/null
+++ b/todos/netcore/AspNet/Program.cs
@@ -0,0 +1,20 @@
+using System;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+
+namespace AspNet
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ Console.WriteLine(System.IO.Directory.GetCurrentDirectory());
+ }
+
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStartup()
+ .UseUrls("http://127.0.0.1:3000");
+ }
+}
diff --git a/todos/netcore/AspNet/Properties/launchSettings.json b/todos/netcore/AspNet/Properties/launchSettings.json
new file mode 100644
index 0000000..de2ca5b
--- /dev/null
+++ b/todos/netcore/AspNet/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:61031",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "AspNet": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:3000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/todos/netcore/AspNet/Startup.cs b/todos/netcore/AspNet/Startup.cs
new file mode 100644
index 0000000..8de52ee
--- /dev/null
+++ b/todos/netcore/AspNet/Startup.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Todos.Common;
+
+namespace AspNet
+{
+ public class Startup
+ {
+ // This method gets called by the runtime. Use this method to add services to the container.
+ // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton(new TodoList());
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.Run(async (context) =>
+ {
+ switch(context.Request.Method)
+ {
+ case "GET":
+ if(context.Request.Path.Value.Equals("/"))
+ {
+ await HandleIndex(context);
+ break;
+ }
+
+ if (context.Request.Path.Value.StartsWith("/static"))
+ {
+ await HandleStatic(context);
+ break;
+ }
+
+ Handle404(context);
+ break;
+
+ case "POST":
+ HandlePost(context);
+ RedirectToHome(context);
+ break;
+
+ default:
+ RedirectToHome(context);
+ break;
+ }
+ });
+ }
+
+ private void HandlePost(HttpContext context)
+ {
+ var todos = context.RequestServices.GetService();
+ var formData = context.Request.Form;
+ var item = formData["item"];
+ int.TryParse(item, out var id);
+
+ switch (context.Request.Path)
+ {
+ case "/done":
+ case "/not-done":
+ todos.Toggle(id);
+ break;
+
+ case "/delete":
+ todos.Remove(id);
+ break;
+
+ case "/":
+ todos.Add(item);
+ break;
+ }
+ }
+
+ private async Task HandleIndex(HttpContext context)
+ {
+ var todos = context.RequestServices.GetService();
+ var index = IndexTemplate.Render(todos.Get());
+ context.Response.ContentType = "text/html";
+ context.Response.StatusCode = 200;
+ await context.Response.Body.WriteAsync(index, 0, index.Length);
+ }
+
+ private async Task HandleStatic(HttpContext context)
+ {
+ var mimeTypes = new Dictionary() { { "css", "text/css" }, { "svg", "image/svg+xml" } };
+ var filePath = $"../{context.Request.Path}";
+ var extension = filePath.Split('.').Last();
+ try
+ {
+ using (var file = File.OpenRead(filePath))
+ {
+ context.Response.StatusCode = 200;
+ context.Response.ContentType = mimeTypes[extension];
+ await file.CopyToAsync(context.Response.Body);
+ }
+ }
+ catch
+ {
+ Handle404(context);
+ }
+ }
+
+ private void Handle404(HttpContext context)
+ {
+ context.Response.StatusCode = 404;
+ }
+
+ private void RedirectToHome(HttpContext context)
+ {
+ context.Response.StatusCode = 303;
+ context.Response.Headers.Add("Location", "/");
+ }
+ }
+}
diff --git a/todos/netcore/AspNet/appsettings.Development.json b/todos/netcore/AspNet/appsettings.Development.json
new file mode 100644
index 0000000..ba0f20b
--- /dev/null
+++ b/todos/netcore/AspNet/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ }
+}
diff --git a/todos/netcore/AspNet/appsettings.json b/todos/netcore/AspNet/appsettings.json
new file mode 100644
index 0000000..def9159
--- /dev/null
+++ b/todos/netcore/AspNet/appsettings.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/todos/netcore/Common/Common.csproj b/todos/netcore/Common/Common.csproj
new file mode 100644
index 0000000..c16c6d5
--- /dev/null
+++ b/todos/netcore/Common/Common.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netcoreapp2.2
+
+
+
diff --git a/todos/netcore/Common/IndexTemplate.cs b/todos/netcore/Common/IndexTemplate.cs
new file mode 100644
index 0000000..96c2bf0
--- /dev/null
+++ b/todos/netcore/Common/IndexTemplate.cs
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using System.Text;
+
+namespace Todos.Common
+{
+ public static class IndexTemplate
+ {
+ public static byte[] Render(IEnumerable todos)
+ {
+ var builder = new StringBuilder();
+
+ builder.Append(MainTemplateStart);
+ foreach(var todo in todos)
+ {
+ builder.Append(ToDoTemplate(todo));
+ }
+ builder.Append(MainTemplateEnd);
+ return Encoding.UTF8.GetBytes(builder.ToString());
+ }
+
+ private static string MainTemplateStart =>
+ @"
+
+
+ Todo MVP
+
+
+
+
+ Todo MVP
+
+
+ Todo list
+ ";
+
+ private static string MainTemplateEnd =>
+ @"
+
+
+ ";
+
+ private static string ToDoTemplate(Todo todo)
+ {
+ var toDoClass = todo.Done ? "todo done" : "todo";
+ var name = todo.Done ? $"{todo.Name}" : todo.Name;
+ var markAction = todo.Done ? "/not-done" : "/done";
+ var markClass = todo.Done ? "uncomplete" : "complete";
+ var markValue = todo.Done ? $"Mark not done \"{todo.Name}\"" : $"Mark done \"{todo.Name}\"";
+
+ return $@"
+
+ {name}
+
+
+
+ ";
+ }
+ }
+}
diff --git a/todos/netcore/Common/Todo.cs b/todos/netcore/Common/Todo.cs
new file mode 100644
index 0000000..db0ecb9
--- /dev/null
+++ b/todos/netcore/Common/Todo.cs
@@ -0,0 +1,11 @@
+namespace Todos.Common
+{
+ public struct Todo
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public bool Done { get; set; }
+ }
+}
diff --git a/todos/netcore/Common/TodoList.cs b/todos/netcore/Common/TodoList.cs
new file mode 100644
index 0000000..4233531
--- /dev/null
+++ b/todos/netcore/Common/TodoList.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Todos.Common
+{
+ public class TodoList
+ {
+ private readonly List _todos;
+ private int _nextId;
+
+ public TodoList()
+ {
+ _todos = new List();
+ }
+
+ public IEnumerable Get()
+ {
+ return _todos;
+ }
+
+ public void Add(string name)
+ {
+ _todos.Add(new Todo() { Id = Interlocked.Increment(ref _nextId), Name = name });
+ }
+
+ public void Toggle(int id)
+ {
+ for (var i = 0; i < _todos.Count; i++)
+ {
+ var todo = _todos[i];
+ if (todo.Id == id)
+ {
+ todo.Done = !todo.Done;
+ _todos[i] = todo;
+ break;
+ }
+ }
+ }
+
+ public void Remove(int id)
+ {
+ var index = -1;
+
+ for (var i = 0; i < _todos.Count; i++)
+ {
+ var todo = _todos[i];
+ if (todo.Id == id)
+ {
+ index = i;
+ break;
+ }
+ }
+
+ if (index >= 0)
+ {
+ _todos.RemoveAt(index);
+ }
+ }
+ }
+}
diff --git a/todos/netcore/HttpListener/HttpListener.csproj b/todos/netcore/HttpListener/HttpListener.csproj
new file mode 100644
index 0000000..358a29b
--- /dev/null
+++ b/todos/netcore/HttpListener/HttpListener.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ netcoreapp2.2
+
+
+
+
+
+
+
diff --git a/todos/netcore/HttpListener/HttpServer.cs b/todos/netcore/HttpListener/HttpServer.cs
new file mode 100644
index 0000000..f4b77d2
--- /dev/null
+++ b/todos/netcore/HttpListener/HttpServer.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using System.Web;
+using Todos.Common;
+
+namespace Todos.HttpListener
+{
+ public class HttpServer : IDisposable
+ {
+ private readonly System.Net.HttpListener _listener;
+ private readonly TodoList _todos;
+ private readonly string _prefix;
+
+ public HttpServer(string prefix)
+ {
+ try
+ {
+ _listener = new System.Net.HttpListener();
+ _prefix = prefix;
+ _todos = new TodoList();
+ }
+ catch (PlatformNotSupportedException)
+ {
+ throw;
+ }
+ }
+
+ public async void Start()
+ {
+ _listener.Prefixes.Add(_prefix);
+ _listener.Start();
+
+ Console.WriteLine($"Started listening on {_prefix}");
+
+ while (_listener.IsListening)
+ {
+ try
+ {
+ var context = await _listener.GetContextAsync();
+ var stopwatch = new Stopwatch();
+ Console.WriteLine($"Request starting HTTP/{context.Request.ProtocolVersion} {context.Request.HttpMethod} {context.Request.Url}");
+ stopwatch.Start();
+ await ProcessRequest(context);
+ stopwatch.Stop();
+ Console.WriteLine($"Request finished in {stopwatch.Elapsed.TotalMilliseconds:0.####}ms {context.Response.StatusCode} {context.Response.ContentType}");
+ }
+ catch(Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_listener != null && _listener.IsListening)
+ {
+ _listener.Stop();
+ }
+ }
+
+ private async Task ProcessRequest(HttpListenerContext context)
+ {
+ switch (context.Request.HttpMethod)
+ {
+ case "GET":
+ if (context.Request.RawUrl.Equals("/"))
+ {
+ await HandleIndex(context);
+ break;
+ }
+
+ if (context.Request.RawUrl.StartsWith("/static"))
+ {
+ await HandleStatic(context);
+ break;
+ }
+
+ Handle404(context);
+ break;
+
+ case "POST":
+ await HandlePost(context);
+ RedirectToHome(context);
+ break;
+
+ default:
+ RedirectToHome(context);
+ break;
+ }
+
+ context.Response.Close();
+ }
+
+ private async Task HandlePost(HttpListenerContext context)
+ {
+ using (var reader = new StreamReader(context.Request.InputStream))
+ {
+ var formData = HttpUtility.ParseQueryString(await reader.ReadToEndAsync());
+ var item = formData["item"];
+ int.TryParse(item, out var id);
+
+ switch (context.Request.RawUrl)
+ {
+ case "/done":
+ case "/not-done":
+ _todos.Toggle(id);
+ break;
+
+ case "/delete":
+ _todos.Remove(id);
+ break;
+
+ case "/":
+ _todos.Add(item);
+ break;
+ }
+ }
+ }
+
+ private async Task HandleStatic(HttpListenerContext context)
+ {
+ var mimeTypes = new Dictionary() { { "css", "text/css" }, { "svg", "image/svg+xml" } };
+ // When running in Visual studio current directory is set to bin of Debug or Release
+ // When running from dotnet command current directory is set tu folder you run command
+ //var filePath = $"../../../../{context.Request.RawUrl}";
+ var filePath = $".{context.Request.RawUrl}";
+ var extension = filePath.Split('.').Last();
+ try
+ {
+ using (var file = File.OpenRead(filePath))
+ {
+ context.Response.StatusCode = 200;
+ context.Response.ContentType = mimeTypes[extension];
+ await file.CopyToAsync(context.Response.OutputStream);
+ }
+ }
+ catch
+ {
+ Handle404(context);
+ }
+ }
+
+ private async Task HandleIndex(HttpListenerContext context)
+ {
+ var index = IndexTemplate.Render(_todos.Get());
+ context.Response.ContentType = "text/html";
+ context.Response.StatusCode = 200;
+ await context.Response.OutputStream.WriteAsync(index, 0, index.Length);
+ }
+
+ private void Handle404(HttpListenerContext context)
+ {
+ context.Response.StatusCode = 404;
+ }
+
+ private void RedirectToHome(HttpListenerContext context)
+ {
+ context.Response.StatusCode = 303;
+ context.Response.RedirectLocation = "/";
+ }
+ }
+}
diff --git a/todos/netcore/HttpListener/Program.cs b/todos/netcore/HttpListener/Program.cs
new file mode 100644
index 0000000..6c35a14
--- /dev/null
+++ b/todos/netcore/HttpListener/Program.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Todos.HttpListener
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ Console.WriteLine(System.IO.Directory.GetCurrentDirectory());
+ using (var server = new HttpServer("http://127.0.0.1:3000/"))
+ {
+ server.Start();
+ Console.WriteLine("Press any key to stop listening");
+ Console.ReadKey();
+ }
+ }
+ }
+}
diff --git a/todos/netcore/README.md b/todos/netcore/README.md
new file mode 100644
index 0000000..ddfa7ff
--- /dev/null
+++ b/todos/netcore/README.md
@@ -0,0 +1,37 @@
+# TODO-MVP in .NET CORE (C#)
+
+This implementation is written in [.NET Core] using two different HTTP servers that are available in the framework
+
+ - [ASP.NET Core] cross platform HTTP server ([Kestrel])
+ - [HTTP.sys] windows only HTTP server ([HttpListener])
+
+## Prerequisites
+ Install runtime or sdk from [.NET Core download] website
+
+## Run host
+
+From todo-mvp/todos/netcore folder run desired host
+
+#### HttpListener
+
+This will work on Windows
+
+ dotnet run --project HttpListener\HttpListener.csproj
+
+#### Kestrel
+
+This will work on Windows, Linux, macOS
+
+ dotnet run --project AspNet\AspNet.csproj
+
+## Go to website
+Access url [http://localhost:3000/](http://localhost:3000/)
+
+Enjoy
+
+[.NET Core]: https://docs.microsoft.com/en-us/dotnet/core/
+[.NET Core download]: https://dotnet.microsoft.com/download
+[ASP.NET Core]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/index?view=aspnetcore-2.2
+[HttpListener]: https://docs.microsoft.com/en-us/dotnet/framework/network-programming/httplistener
+[Kestrel]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-2.2
+[HTTP.sys]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/httpsys?view=aspnetcore-2.2
diff --git a/todos/netcore/Todos.sln b/todos/netcore/Todos.sln
new file mode 100644
index 0000000..d26f081
--- /dev/null
+++ b/todos/netcore/Todos.sln
@@ -0,0 +1,42 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28922.388
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpListener", "HttpListener\HttpListener.csproj", "{1FDB58F2-13C9-4F12-9E57-70D774572ACE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet", "AspNet\AspNet.csproj", "{66FD3C77-1098-48DE-9059-EB892B1809EB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{E770D3AA-C98B-4276-93E3-BD51907001A8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{69263B31-B30A-40B5-8384-E37B484B4748}"
+ ProjectSection(SolutionItems) = preProject
+ README.md = README.md
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1FDB58F2-13C9-4F12-9E57-70D774572ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1FDB58F2-13C9-4F12-9E57-70D774572ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1FDB58F2-13C9-4F12-9E57-70D774572ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1FDB58F2-13C9-4F12-9E57-70D774572ACE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {66FD3C77-1098-48DE-9059-EB892B1809EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {66FD3C77-1098-48DE-9059-EB892B1809EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {66FD3C77-1098-48DE-9059-EB892B1809EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {66FD3C77-1098-48DE-9059-EB892B1809EB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E770D3AA-C98B-4276-93E3-BD51907001A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E770D3AA-C98B-4276-93E3-BD51907001A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E770D3AA-C98B-4276-93E3-BD51907001A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E770D3AA-C98B-4276-93E3-BD51907001A8}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {37AB4976-9BF6-4DC0-A5CB-FDAFF6404762}
+ EndGlobalSection
+EndGlobal
diff --git a/todos/netcore/static/check.svg b/todos/netcore/static/check.svg
new file mode 100644
index 0000000..2df5dee
--- /dev/null
+++ b/todos/netcore/static/check.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/todos/netcore/static/plus.svg b/todos/netcore/static/plus.svg
new file mode 100644
index 0000000..23c27d8
--- /dev/null
+++ b/todos/netcore/static/plus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/todos/netcore/static/tick.png b/todos/netcore/static/tick.png
new file mode 100644
index 0000000..71fb4ad
Binary files /dev/null and b/todos/netcore/static/tick.png differ
diff --git a/todos/netcore/static/todo.css b/todos/netcore/static/todo.css
new file mode 100644
index 0000000..a4594b7
--- /dev/null
+++ b/todos/netcore/static/todo.css
@@ -0,0 +1,153 @@
+:root {
+ --text-color: rgb(20,20,20);
+ --border-color: black;
+ --title-color: rgb(60,60,60);
+ --done-color: rgb(200,200,200);
+ --background-color: white;
+ box-sizing: border-box;
+ padding: 0;
+ margin: 0;
+}
+
+body {
+ min-height: 100vh;
+ padding: 0;
+ align-content: center;
+ margin: 0;
+ font-family: sans-serif;
+ line-height: 1.3rem;
+ font-size: 24px;
+
+ background-color: var(--background-color);
+ color: var(--text-color);
+}
+
+label {
+ font-size: 0.8rem;
+}
+
+input {
+ box-sizing: border-box;
+ border: var(--border-color) solid 2px;
+ background-color: var(--background-color);
+}
+
+section {
+ padding: 1.5rem;
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 40rem;
+}
+
+.new-todo form {
+ display: grid;
+ grid-template-columns: auto 29px;
+ grid-template-rows: auto;
+ column-gap: 1rem;
+}
+
+.new-todo label[for="new-item"] {
+ grid-row: 1 / span 1;
+ grid-column: 1 / span 2;
+ margin: auto 1rem;
+}
+
+.new-todo input#new-item {
+ grid-row: 1 / span 1;
+ grid-column: 1 / span 1;
+ padding: 0.5rem;
+ font-size: 1rem;
+}
+
+.new-todo form input#add-new-item {
+ grid-row: 1 / span 1;
+ grid-column: 2 / span 1;
+ margin: auto;
+}
+
+.button-container {
+ float: right;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+ul {
+ margin: 0;
+ padding: 0;
+}
+
+ul:empty::after {
+ content: "You've not added any todos to the list. Use the box above to add a new todo.";
+ font-size: 1rem;
+ margin: 1rem;
+ font-style: italic;
+ border: 2px dotted black;
+ padding: 0.5rem;
+ display: block;
+ text-align: justify;
+ text-align-last: center;
+}
+
+li {
+ display: flex;
+ border-bottom: 1px dotted var(--border-color);
+}
+
+li:last-child {
+ border-bottom: none;
+}
+
+li .item-name {
+ flex: auto;
+ margin: auto;
+ padding: 0 1rem;
+}
+
+li form input,
+.new-todo form input#add-new-item {
+ width: 30px;
+ height: 30px;
+ padding: 0;
+
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: center;
+
+ /* hide text */
+ text-indent: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.todo form input {
+ margin: 1em;
+}
+
+.todo form input.delete {
+ background-image: url('trashcan.svg');
+ border: none;
+}
+input#add-new-item { background-image: url('plus.svg'); }
+.todo form input.complete { background-image: none; }
+.todo form input.uncomplete { background-image: url('check.svg'); }
+
+.done {
+ color: var(--done-color);
+}
+
+.todo form {
+ display: contents;
+}
+
+h1 {
+ font-weight: 200;
+ color: var(--title-color);
+ margin: 2rem;
+ display: none;
+}
+
+h2 {
+ border-bottom: 1px solid black;
+ padding: 0 0 1rem 1rem;
+}
diff --git a/todos/netcore/static/trashcan.svg b/todos/netcore/static/trashcan.svg
new file mode 100644
index 0000000..3d8c051
--- /dev/null
+++ b/todos/netcore/static/trashcan.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/todos/netcore/static/x.svg b/todos/netcore/static/x.svg
new file mode 100644
index 0000000..e377314
--- /dev/null
+++ b/todos/netcore/static/x.svg
@@ -0,0 +1 @@
+
\ No newline at end of file