Mars Settlement in Blazor: Part I

Dan Gray

2020/03/28

Preamble

This is Part I of a undefined series of posts detailing the process of building a web-app based on the Blazor framework to explore the logistical pathways for colonizing Mars within the next decade.

Motivation

The primary motivation for this series is using a full-scale project to expand and practice building web-applications based on Blazor, and relying on Entity Framework (EF) as the “data-handling” interface.

This project is ambitious enough that is will likely see many iterations, alterations and consolidations thus requiring a consistent and increasingly deep knowledge of the tools, language and grammars required to produce an engaging and performant application.

Part 1: Establishing the Fundamental Data Model, Services and Views

Part 1 in this series describes the first steps is the app - initial model creation, data-modelling, deployment of services and basic UI development.

The process of building a generic Blazor app, with Entity Framework can be partitioned into the following tasks:

1. Define the Models Required

Add the Properties and Data Annotations

The initial model for this project is “Category” which defines the types of objects that are required for self-sufficient planetary existence.

A new Class file is added in the Data directory - the model properties can be set (using prop) and annotated as needed. Any property defined as Id will be automatically recognized by EF as a Primary Key column.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
Using System.Threading.Tasks

namespace Enceladus.Data
{
    public class Category
    {
        [Key]
        public int CategoryId { get; set; }

        [Required]
        public string Name { get; set; }
    }
}

2. Register the Models

Add Database Credentials (via appsettings.json)

When Blazor is initialized to use Local (User) Authentication the connections strings to your local development are setup. Typically they are something like:

"ConnectionStrings": {
  "DefaultConnection": "Server=(localdb)\\MYSQLLocalDB;Database=DbName
},.....

Add the Model to ApplicationDbContext.cs

Each model has to be added to the ApplicationDbContext.cs class and given a name (the suggestions are appropriate).

public DbSet<Category> Categories { get; set; }

Migrate and Update the Database

Use the Package Manager Console to initialise your model in the database.

Add-Migration "Description of Migration"
Update-Database

3. Design the Service

Define the Model Class

Create a new class file within the Services directory, such that it clearly identifies your model by name, i.e. CategoryService.cs in this case.

Add the ApplicationDbContext via Dependency Injection

We need access to the `ApplicationDbContext in our Service, this can be achieved by injecting it via Dependency Injection. A constructor is also needed to initialize the context.

In your model class add the following code:

private readonly ApplicationDbContext _db;

public CategoryService(ApplicationDbContext db)
{
    _db = db;
}

Add methods for CRUD operations

Common Create, Read, Update and Edit (CRUD) methods need to be built to allow interaction with our data. The following methods are placed inside the CategoryService.cs class.

public Category GetCategory(int catId)
{
    Category obj = new Category();
    return _db.Categories.FirstOrDefault(u => u.CategoryId == catId);
}


public List<Category> GetCategories()
{
    return _db.Categories.ToList();
}


public bool AddCategory(Category objCategory)
{
  _db.Categories.Add(objCategory);
  _db.SaveChanges();
  return true;
}


public bool UpdateCategory(Category objCategory)
{
  var ExistingCategroy = _db.Categories.FirstOrDefault(u => u.CategoryId == objCategory.CategoryId);
  
  if(ExistingCategory != null)
  {
      ExistingCategory.Name= objCategory.Name;
      _db.SaveChanges();
  }
  else 
  {
      return false;  
  }
  return true;
}


public bool DeleteCategory(Category objCategory)
{
  var ExistingCategory = _db.Categories.FirstOrDefault(u => u.CategoryId == objCategory.CategoryId);
  
  if(ExistingCategory != null)
  {
      _db.Categories.Remove(ExistingCategory);
      _db.SaveChanges();
  } 
  else 
  {
      return false;  
  }
  return true;
}

4. Register the Service

The Service becomes accessible in the app via a dedicated component (“page”). This components routing is defined in the Navigation Menu. Razor elements are used to build the UI.

Add Service to Startup.cs

Add the Service in the IConfiguration method - which is called by the runtime.

public IConfiguration Configuration { get; }

public void ConfigureServices(IServicCollection services)
{
    .......
    .........
    ............
    services.AddScoped<CategoryService>();  
}

Create the Component Page

Create the model Razor component, being careful to give it a unique name; in this example if it was simply called Category.razor it would be confused with the class of the same name. Razor components are thus typically designated with the suffix “Page” i.e. CategoryPage.razor.

Add Component to Navigation Menu

Add the component to the NavMenu.razor page so that it’s routing is established.

<li class="nav-item px-3">
    <NavLink class="nav-link" href="categories">
      <span class="oi oi-list-rich" aria-hidden="true"></span> Categories
    </NavLink>
</li> 

Reference Classes and Inherit the Service

Reference the required classes using the @ notation. The Service itself is injected using @inherits and OwningComponentBase. Comments can be added to Razor sections using the @\*…..\*@ notation.

@page /"categories"
@using Enceladus.Data
@using Enceladus.Services

@*inject service into component*@
@inherits OwningComponentBase<CategoryService>
  
....
........
............  

5. Create a Basic View

All Blazor components contain interchangeable elements of Razor and C# code which allows for a very flexible approach to developing each component. Some prefer to define the UI first, then flesh out the logic to bind the units together.

We can retrieve the data within OnInitialized using the registered Service and the GetCategories method.

Bootstrap is used to layout a basic table with header and body components. In this example its easy to see how common C# skills can be leveraged to create a row for every record, binding data (@obj.Name) and events.

Present the Data

@if (categories == null)
{
  <p><em>Loading or no categories exist</em></p>  
}
else
{
    <div class="container border p-3">
      <div class="row pt-4">
        <table class="table table-striped">
          <thead>
            <tr>
              <th>Category Name</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            @*for each row in the datasource we need a row in the table to display the data*@
              @foreach (var obj in categories)
                {
                <tr>
                  <td>@obj.Name</td>
                  <td><button class="btn btn-primary" style="width:150px">
                      Edit 
                      </button></td>
                </tr>
                }
          </tbody>
        </table>
      </div>
    </div>
}


@code {
  
  // create a List of type Category to hold the returned data
  List<Category> categories;
  
  protected ovverride void OnInitialized()
  {
      categories = Service.GetCategories()
    
  }
}

6. Expand the View to Encompass User Interaction

The page needs a few more elements to offer a dynamic user interface. Namely an ability to insert, edit and delete categories. We can take advantage of the Forms component built into Blazor to handle submission and validation of user input.

Create “Add Model” Dialog

First lets define the UI layout for popup windows, object initialization and form submission.

Add a simple button (“Add New Category”) to the main Bootstrap container.

......
........
        <div class="row">
            <div class="col-4">
                <button class="btn btn-info form-control" @onclick="AddNewCategory">
                    Add Category
                </button>
            </div>
        </div>
........  
......

In another Razor section define a popup window (a “modal-dialog”), which is plain Bootstrap. The modal body will hold the buttons, classes and bindings to the object.

This dialog is wrapped in an EditForm Blazor element. For this element we define the model used for interactions (objCategory) and well as the method to be called on valid submission. These methods will be placed in the @code section.

@if(ShowCategoryPopup== true)
{
<div class="modal" tabindex="-1" style="display:block" role="dialog">

    @*use built in Blazor Forms to submit and validate data input*@
    @*a model has to be supplied, as well as the method that should be called if valdiation is successful*@
    <EditForm Model="objCategory" OnValidSubmit="@ValidSubmission">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h3 class="text-info">Category</h3>
                    <button type="button" class="close" @onclick="CloseCategoryPopup">
                        <span aria-hidden="true">X</span>
                    </button>
                </div>
                <div class="modal-body">
                    <div class="row">
                        <div class="col-9">
                            <div class="row py-2">
                                <div class="col-4">
                                    Category Name
                                </div>
                                <div class="col-8">
                                    <input class="form-control" type="text"@bind="objCategory.Name" />
                                </div>
                            </div>
                            <div class="row py-2">
                                <div class="col-4">
                                </div>
                                <div class="col-8">
                                    <button class="btn btn-primary form-control" type="submit">
                                        Create Category
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </EditForm>
</div>
}

We need to initialise new properties and model objects, as well as create the methods for opening/closing the popup, and submitting the form.

  // set initial value of popup window
  bool ShowCategoryPopup = false;

  // initialize a new category object
  Category objCategory = new Category();


  // add a category
  void AddNewCategory()
  {
    objCategory.CategoryId = 0;
    ShowCategoryPopup = true;
  }
  
  
  // close the popup window
  void CloseCategoryPopup()
  {
    ShowCategoryPopup = false;  
  }


  // create category (via Service) based on user input 
  // the model is based on that define in EditForm i.e. objCategory
  void ValidSubmission()
  {
    var result = false;
    ShowCategoryPopup = false;
    
    // create new category and refresh categories list
    result = ServiceCreateCategory(objCategory);
    categories = Service.GetCategories();
  }

Use Form Validations

We can use the built in ASP.net validation tooling to check the input of our modal dialog. This is placed within the class “modal-body”.

If the input doesn’t match the type specified in the Data Annotations field of your model an error message is shown and valid submission is not possible.

......
........
  <div class="modal-body">
    <DataAnnotationsValidator/>
    <ValidationSummary/>
  <div class="modal-body">
........  
......

Create “Edit and Delete Model” Dialogs

As we will be using the same form for adding new and updating existing Categories we should slightly modify the ValidSubmission method. Pre-existing objects already have a CategoryId (>0) - therefore we can distinguish between the two methods provided via Service based on this difference.

The AddNewCategory method also requires a slight modification, so the CategoryName is not transferred over from Edit to Add steps. This is achieved by first initiating a fresh (empty) object.

......
........
    // create category (via Service) based on user input in form
    // the model is based on that defined in EditForm, i.e. objCategory
    // editing a category is also implemented in this method
    // the differentation is pre-existing categories already have a CategoryId 
    void ValidSubmission()
    {
        var result = false;
        ShowCategoryPopup = false;

        // using the CategoryId to distinguish between an Edit or New
        if (objCategory.CategoryId > 0)
        {
            result = Service.UpdateCategory(objCategory);
        }
        else
        {
            // create new catgegory
            result = Service.CreateCategory(objCategory);
        }

        // refresh list of categories
        categories = Service.GetCategories();
    }
........
......

......
........
    // add a category
    void AddNewCategory()
    {
        objCategory = new Category();

        objCategory.CategoryId = 0;
        ShowCategoryPopup = true;
    }    
........
......    

The onclick binding for the Edit button will bind the object selected to the function being called (EditCategory).

The header of the popup dialog window can be altered dependent on the CategoryId in a similar way to the ValidSubmission method - and using a ternary function is will either show “Update” (on Edit) or “Create” (on Add).

......
........    
  <tbody>
    @*for each row in the datasource we need a row in the table to display the data*@
    @foreach (var obj in categories)
    {
    <tr>
      <td>@obj.Name</td>
      @*pass a functin to onclick, binding the object selected*@
      <td>
        <button class="btn btn-primary" style="width:150px" @onclick="(()=> EditCategory(obj))">
          Edit
        </button>
      </td>
    </tr>
    }
  </tbody>
........
......     
    
......
........
      <div class="modal-header">
        <h3 class="text-info">@(objCategory.CategoryId !=0 ? "Update" : "Create") Category</h3>
          <button type="button" class="close" @onclick="CloseCategoryPopup">
            <span aria-hidden="true">X</span>
          </button>
      </div>
........
......      

To delete categories, we need a simple method and in the UI an appropriate @onlclick event. This button should only be shown if a category already exists. Thus a minor modification of the modal dialog is required.

......
........
    // delete a category :: clear and reload object holding previous categories
    void DeleteCategory()
    {
        ShowCategoryPopup = false;

        var result = Service.DeleteCategory(objCategory);
        categories.Clear();
        categories = Service.GetCategories();
    }
    
........  
......
  <div class="row py-2">
    <div class="offset-4 col">
      <button class="btn btn-primary form-control" type="submit">Submit</button>
    </div>
    @if(objCategory.CategoryId>0)
    { 
      <div class="col">
        <button class="btn btn-danger form-control" @onclick="DeleteCategory" type="submit">Delete</button>
      </div>
    }
  </div>
........
......  

In the next series, I will be adding a “Products” table, and sketching out a design for builidng packing configurations.