4. UI for Organizations (Tenants)

4. UI for Organizations (Tenants)

Oct 16, 2023ยท

5 min read

If we recall from the previous post, one of the requirements is to incorporate a multi-tenancy feature into our project. In this article, I will focus on the presentation level for tenants.

UI First or Functionality First?

Personally, I prefer to begin by visualizing how my tenants will appear on a page and then progressively delve into a more detailed level of implementation. This deeper level would encompass low-level components like database tables, middle-level service classes, and so on. This approach is known as UI-First Software Development. On the other hand, there is another approach known as Functionality-First Software Development.

Let's briefly compare both approaches.

As users may not know how to manage tenants, they might want to "try" to visualize the tenant management process first and then "try" to formulate possible actions for tenant management. By adopting the UI-First approach, it allows for the early collection of user feedback, which reduces the risk of miscommunication and unnecessary back-end work. Therefore, I have decided to opt for the UI-First approach, and let's begin by creating our first tenant management page.

Please follow these steps within Visual Studio:

  1. Create a folder named Tenants under Pages folder.

  2. Add a new Razor Page named Index.cshtml.

  3. Replace the content of Index.cshtml.cs as shown below:

     @page
     @model IndexModel
     @{
         ViewData["Title"] = "Tenants";
     }
    
     <table class="table">
         <thead>
             <tr>
                 <th scope="col">Name</th>
                 <th scope="col">Action</th>
             </tr>
         </thead>
         <tbody>
             <tr>
                 <td>Tenant 1</td>
                 <td>
                     <a class="btn btn-outline-secondary">
                         Edit
                     </a>
                     <a class="btn btn-outline-info">
                         Delete
                     </a>
                 </td>
             </tr>
             <tr>
                 <td>Tenant 2</td>
                 <td>
                     <a class="btn btn-outline-secondary">
                         Edit
                     </a>
                     <a class="btn btn-outline-info">
                         Delete
                     </a>
                 </td>
             </tr>
             <tr>
                 <td>Tenant 3</td>
                 <td>
                     <a class="btn btn-outline-secondary">
                         Edit
                     </a>
                     <a class="btn btn-outline-info">
                         Delete
                     </a>
                 </td>
             </tr>
         </tbody>
     </table>
    
  4. Next, we need to go \Pages\Shared\_Layout.cshtml to modify the link to Tenants on the side menu.

Press F5 to run the project, you will see the output as shown below:

The page above displays a table with a list of tenants featuring two columns: Name and Action.

Up to this point, the rows in the table are static, meaning they are hard-coded in HTML.

Now, let's work on making the table load data dynamically.

View Model

If you remember from the previous post, we already have a folder named Models.

Within the folder, there exists a model class called User, designed to represent a user for display on Razor Page Login.cshtml.

However, I am now inclined to rename this class as UserViewModel. This new name better reflects its role as a model specifically intended for a view. This adjustment serves the purpose of avoiding potential conflicts with classes of the same name in different C# libraries or projects.

You can learn more about it here.

Let's rename the class and file name.

After changing to UserViewModel, press ctrl + . and choose Rename 'User' to 'UserViewModel' so that we can propagate the change to the rest of the project.

Change the file name from User.cs to UserViewModel.cs.

With that preceding code, let's create a view model class named OrganizationViewModel with the following code:

namespace Portal.Models;

public class OrganizationViewModel
{
    public int Id { get; set; }
    public Guid Identifier { get; set; }
    public string Name { get; set; }
}

Go back to /Pages/Tenants/Index.cshtml.cs file.

Let's add a field named Organizations as a placeholder for all organizations.

public List<OrganizationViewModel> Organizations { get; set; } = new();

We also wish to include mock data in the placeholder to demonstrate that dynamic data is being loaded.

With the addition of code to insert mock data, the final code for Index.cshtml.cs appears as follows:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Portal.Models;

namespace Portal.Pages.Tenants;

public class IndexModel : PageModel
{
    public List<OrganizationViewModel> Organizations { get; set; } = new();

    public void OnGet()
    {
        Organizations = new List<OrganizationViewModel>
        {
            new OrganizationViewModel
            {
                Id = 1,
                Identifier = Guid.NewGuid(),
                Name = "Acme"
            },
            new OrganizationViewModel
            {
                Id = 2,
                Identifier = Guid.NewGuid(),
                Name = "Contoso"
            }
        };
    }
}

Now, let's modify the code on Index.cshtml file to load data dynamically from the placeholder Organizations that we just created.

@page
@model IndexModel
@{
    ViewData["Title"] = "Tenants";
}

<table class="table">
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Action</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var organization in Model.Organizations)
        {
            <tr>
                <td>@organization.Name</td>
                <td>
                    <a class="btn btn-outline-secondary">
                        Edit
                    </a>
                    <a class="btn btn-outline-info">
                        Delete
                    </a>
                </td>
            </tr>
        }
    </tbody>
</table>

Press F5 to run the project.

We are now able to load data dynamically for the table.

If you observe closely, you'll notice that one action is missing here. Just as we have actions for editing and deleting, we should also include an action for adding. Additionally, it would be a good idea to provide a title for the page.

Let's add the following code above <table> HTML element in Index.cshtml as shown below:

@page
@model IndexModel
@{
    ViewData["Title"] = "Tenants";
}

<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
    <h1 class="h2">@ViewData["Title"]</h1>
    <div class="btn-toolbar mb-2 mb-md-0">
        <div class="btn-group me-2">
            <button type="button" class="btn btn-sm btn-outline-secondary">Add</button>
        </div>
    </div>
</div>

<table class="table">
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">Action</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var organization in Model.Organizations)
        {
            <tr>
                <td>@organization.Name</td>
                <td>
                    <a class="btn btn-outline-secondary">
                        Edit
                    </a>
                    <a class="btn btn-outline-info">
                        Delete
                    </a>
                </td>
            </tr>
        }
    </tbody>
</table>

Press F5 to run the project.

That concludes the discussion on the tenant user interface. Next, I will delve into the implementation of multi-tenancy for our portal.

Download source code

Github repository

Did you find this article valuable?

Support Han Chee by becoming a sponsor. Any amount is appreciated!

ย