{"id":23448485,"url":"https://github.com/juryrigcoder/bl-web-blazor-todo","last_synced_at":"2026-01-22T12:06:47.099Z","repository":{"id":187830741,"uuid":"615303247","full_name":"juryrigcoder/BL-Web-Blazor-Todo","owner":"juryrigcoder","description":"Demo of Blazor on the server","archived":false,"fork":false,"pushed_at":"2023-04-02T12:08:57.000Z","size":37,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-10T02:49:10.707Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juryrigcoder.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-03-17T11:52:29.000Z","updated_at":"2025-01-19T09:33:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"aa6b7550-3721-4062-9bc2-5139865bf511","html_url":"https://github.com/juryrigcoder/BL-Web-Blazor-Todo","commit_stats":null,"previous_names":["juryrigcoder/bl-web-blazor-todo"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/juryrigcoder/BL-Web-Blazor-Todo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juryrigcoder%2FBL-Web-Blazor-Todo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juryrigcoder%2FBL-Web-Blazor-Todo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juryrigcoder%2FBL-Web-Blazor-Todo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juryrigcoder%2FBL-Web-Blazor-Todo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juryrigcoder","download_url":"https://codeload.github.com/juryrigcoder/BL-Web-Blazor-Todo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juryrigcoder%2FBL-Web-Blazor-Todo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28662831,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T01:17:37.254Z","status":"online","status_checked_at":"2026-01-22T02:00:07.137Z","response_time":144,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-23T22:16:38.997Z","updated_at":"2026-01-22T12:06:47.080Z","avatar_url":"https://github.com/juryrigcoder.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BL-Web-Blazor-Todo\n\nThe solution is in three parts: 1. A class library to hold the Todo model 2. An set of API endpoint to CRUD Todo objects 3. A frontend to view and create Todo items.\n\n## Introduction\n\nTo demonstrate the dotnet CLI is better to see it in use that it is to use it without knowing it. VS code lets us interact at the commandline level where as VS lets us use the CLI through a more abstracted means.\n\n## Project sctructure\n\nA root folder is needed to start with, this is the location for three more folders to hold: 1. a class library project, 2. API project and 3. Frontend project. The root folder is equivalent to a VS solution and it is the resource container that holds all the elements needed for the project, however the compiled result of all three projects can be distributed and run seperatley.\n\nUsing the folder structure:\n\nBL-Web-Blazor-Todo -\u003e\n  BL-Web-Blazor-Todo\n  Blazor-Demo\n  Library-Demo\n  \n## Library project\n\nIn VS Code open the folder BL-Web-Blazor-Todo and open a terminal at this location, the type:\n\n     dotnet new classlib Library-Demo\n\nThis will create a new folder 'Library-Demo', rename the CS place to TodoItem.cs and add the following code:\n```\n    using System.Text.Json.Serialization;\n\n    namespace Library;\n    public class TodoItem\n    {\n        [JsonPropertyName(\"id\")]\n        public int? Id { get; set; }\n        [JsonPropertyName(\"text\")]\n        public string? Text { get; set; }\n        [JsonPropertyName(\"isComplete\")]\n        public bool IsComplete { get; set; }\n    }\n```\n\nThis is the class object that describes out Todo items. Next, open a terminal at the library projects location and use the following command to build the project:\n\n    dotnet build\n\n## API project   \n\nIn VS Code open the folder BL-Web-Blazor-Todo and open a terminal at this location, the type:\n\n     dotnet new webapi -o API-Demo --use-program-main --use-minimal-apis\n\nThis will create a minimal web API with a main method insertion point. If you intend on using HTTPS then you'll need a dev cert:\n\n    dotnet dev-certs https --trust\n\nTo use the class library model in the API project we need to open a terminal in the API-Demo folder and tell dotnet to reference the Library csproj file:\n\n    dotnet add API-Demo/API-Demo.csproj reference ../Library-Demo/Library-Demo.csproj\n\nOr add the following to the API-Demo.csproj file:\n\n```\n  \u003cItemGroup\u003e\n    \u003cProjectReference Include=\"..\\Library-Demo\\Library-Demo.csproj\" /\u003e\n  \u003c/ItemGroup\u003e\n```\n\nTo use the API and Library we could simple add a use statement to reference the Library DLL at the head of the program.cs:\n\n```\nusing Library;\n```\n\nThen include a GET method to creating an enumerable of TodoItems and return them as JSON:\n\n```\n        app.MapGet(\"/todo\", () =\u003e\n        {\n            List\u003cTodoItem\u003e list = new List\u003cTodoItem\u003e() {\n                new TodoItem() { Text = \"This is a list of text.\", IsComplete = true } ,\n                new TodoItem() { Text = \"New todo item.\", IsComplete = false } ,\n                new TodoItem() { Text = \"Make toast.\", IsComplete = false } ,\n                new TodoItem() { Text = \"Collect stuff.\", IsComplete = true } ,\n                new TodoItem() { Text = \"Manage things.\", IsComplete = true }\n                };\n            return list == null ? Results.NotFound() : Results.Ok(list);\n        })\n        .WithName(\"GetTodo\")\n        .WithOpenApi();\n```\nHowever, if a static set of objects isn't enough then we need to add a way to create and store new TodoItem objects. One of the fastest ways to do this is to use a single\nDLL doctument store like Litedb. So, whilst we are in API-Demo folder open a terminal and register the dependancy:\n\n    dotnet add package LiteDB\n\nAnd then add the following the the program.cs in the Main method:    \n\n```\n        var connectionString = \"temp.db\";\n\n        //add as a singleton - it's a single file with a single access point\n        builder.Services.AddSingleton\u003cILiteDatabase, LiteDatabase\u003e(\n        x =\u003e new LiteDatabase(connectionString));\n```        \n\nAnd to add some TodoItem objects to Litedb we can create: \n\n```\n        app.MapGet(\"/items\", (ILiteDatabase db) =\u003e\n        {\n            //create some items\n            List\u003cTodoItem\u003e list = new List\u003cTodoItem\u003e() {\n                new TodoItem() { Text = \"This is a list of text.\", IsComplete = true } ,\n                new TodoItem() { Text = \"New todo item.\", IsComplete = false } ,\n                new TodoItem() { Text = \"Make toast.\", IsComplete = false } ,\n                new TodoItem() { Text = \"Collect stuff.\", IsComplete = true } ,\n                new TodoItem() { Text = \"Manage things.\", IsComplete = true }\n            };\n            //group insert into database\n            db.GetCollection\u003cTodoItem\u003e(\"TodoItems\").InsertBulk(list);\n            \n            //find all items in db\n            var items = db.GetCollection\u003cTodoItem\u003e(\"TodoItems\").FindAll().ToList();\n            //get results it items is set and not null\n            return items == null ? Results.NotFound() : Results.Ok(items);\n        })\n        .WithName(\"GetItems\")\n        .WithOpenApi();\n```\n\nFinally, to insert a single TodoItem object into the database we need:\n\n```       \n        app.MapPost(\"/insert\",([Microsoft.AspNetCore.Mvc.FromBody] TodoItem item, ILiteDatabase db) =\u003e{\n            var items = db.GetCollection\u003cTodoItem\u003e(\"TodoItems\").Insert(item);\n            return Results.Created($\"/insert/{item._id}\", item);\n        })\n        .WithName(\"PostItems\")\n        .WithOpenApi();\n```\n\n## Blazor project \n\nIn VS Code open the folder BL-Web-Blazor-Todo then open a terminal at this location and type:\n\n     dotnet new blazorserver -o Blazor-Demo\n\nThis is the front end project, it is the most important part of this whole solution and it based on server side page rendering using the Razor templating framework and\nall html requests are server side rendered and delivered to the clients browser. Blazor is the over arching framework and is similar to React, Angular and other HTML\nrendering frameworks.\n\nAs with all C# console applications the entry point is the Program.cs file and it is here were services, dependacies and initial endpoint are defined.\n\nLets add a Razor page to the Pages folder and use it to:\n\n- initially populate the page with data from an API call\n- create a new item, add it to the existing todo items and post it back to the API where it can be save into a database\n\nPages -\u003e Items.razor\n\nDirectives are identified at the top of a razor file using the @ symbol\n\n```\n    @page \"/items\" //page route\n    @using Library //using external DLL reference\n    @inject IHttpClientFactory ClientFactory // Dependency Injection of a HTTP client\n```\n\nNext comes the body of the html and uses standard HTML tags mixed with C# expressions denoted by the @ :\n\n```\n    \u003cdiv class=\"flex flex-col p-20 bg-blue-100\"\u003e\n    \u003cdiv class=\"flex flex-col space-y-4 max-w-xl\"\u003e\n        \u003cinput class=\"rounded-lg\" placeholder=\"Something todo\" @bind-value=\"newTodo\" /\u003e\n        \u003cbutton class=\"rounded-lg text-xl bg-blue-500 text-blue-100 hover:bg-blue-600 duration-300\" @onclick=\"AddTodo\"\u003eAdd item\u003c/button\u003e\n    \u003c/div\u003e\n```\n\nFinally, we have a code block:\n\n```\n    @code {\n        private IEnumerable\u003cTodoItem\u003e? TodoItemList = Array.Empty\u003cTodoItem\u003e();\n        public List\u003cTodoItem\u003e? displayTodo = new List\u003cTodoItem\u003e(); \n        private bool getTodoItemsError;\n        private bool shouldRender;\n        protected override bool ShouldRender() =\u003e shouldRender;\n        public string? newTodo;\n        public async Task AddTodo()\n        {\n            if (!string.IsNullOrWhiteSpace(newTodo))\n            {\n                TodoItem ins = new TodoItem { Text = newTodo, IsComplete = false };\n                displayTodo?.Add(ins);\n                newTodo = string.Empty;\n                await InsertItem(ins);\n            }\n        }\n    }\n```\n\nTo initialise a page with data we use the OnInitializedAsync() method:\n\n```\n    protected override async Task OnInitializedAsync()\n    {\n        var request = new HttpRequestMessage(HttpMethod.Get, \"http://localhost:5154/items\");\n        var client = ClientFactory.CreateClient();\n        var response = await client.SendAsync(request);\n\n        if (response.IsSuccessStatusCode)\n        {\n            using var responseStream = await response.Content.ReadAsStreamAsync();\n            TodoItemList = await System.Text.Json.JsonSerializer.DeserializeAsync\u003cIEnumerable\u003cTodoItem\u003e\u003e(responseStream);\n        }\n        else\n        {\n            getTodoItemsError = true;\n        }\n        displayTodo = TodoItemList?.ToList\u003cTodoItem\u003e() ?? new List\u003cTodoItem\u003e();\n        shouldRender = true;\n    }\n```\n\nTo add an new item we create methods to update and insert:\n\n```\n    public async Task AddTodo()\n    {\n        if (!string.IsNullOrWhiteSpace(newTodo))\n        {\n            TodoItem ins = new TodoItem { Text = newTodo, IsComplete = false };\n            displayTodo?.Add(ins);\n            newTodo = string.Empty;\n            await InsertItem(ins);\n        }\n    }\n\n    public async Task InsertItem(TodoItem postBody){\n        var client = ClientFactory.CreateClient();\n        using var response = await client.PostAsJsonAsync(\"http://localhost:5154/insert\", postBody);\n        shouldRender = true;\n    }\n```\n\n## Tailwind CSS\n\nTailwind is a Node.js project that standardised CSS. As this is a demo focused on Blazor we will simple add the Tailwind CLI to our environment path\nusing the dotnet CLI:\n\n```\ndotnet tool install --global tailwindcss.cli\n```\nAfter installing Tailwind we need a resource folder to hold config files, this tutorial discribes the process best https://gist.github.com/arkada38/74d2787b2fa092a723ba6892cc3b2ed1\n\nThen we can update the HTML to include Tailwind classes:\n\n```\n    \u003cdiv class=\"flex flex-col p-20 bg-blue-100\"\u003e\n    \u003cdiv class=\"flex flex-col space-y-4 max-w-xl\"\u003e\n        \u003cinput class=\"rounded-lg\" placeholder=\"Something todo\" @bind-value=\"newTodo\" /\u003e\n        \u003cbutton class=\"rounded-lg text-xl bg-blue-500 text-blue-100 hover:bg-blue-600 duration-300\" @onclick=\"AddTodo\"\u003eAdd item\u003c/button\u003e\n    \u003c/div\u003e\n    \u003cbr/\u003e\n        \u003cdiv class=\"flex flex-col space-y-4\"\u003e\n            @foreach (var Item in displayTodo ?? Enumerable.Empty\u003cTodoItem\u003e())\n            {\n                \u003cdiv class=\"bg-white p-6 rounded-lg shadow-lg max-w-xl\"\u003e\n                    \u003cp class=\"text-2xl font-bold mb-2 text-gray-800 break-words\"\u003e@Item.Text\u003c/p\u003e\n                    \u003cp class=\"text-gray-700\"\u003e@Item.IsComplete\u003c/p\u003e\n                    \u003cp class=\"text-gray-500\"\u003e@Item._id\u003c/p\u003e\n                \u003c/div\u003e\n            }\n        \u003c/div\u003e\n    \u003c/div\u003e\n```\n\nOpen a terminal in the StaticAssets folder and start a tailwind cli watch session\n\n```\ntailwindcss --watch\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuryrigcoder%2Fbl-web-blazor-todo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuryrigcoder%2Fbl-web-blazor-todo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuryrigcoder%2Fbl-web-blazor-todo/lists"}