Introducing Tuxboard

June 22nd, 2024

Today, I introduce Tuxboard, which is an open-source dashboard library for ASP.NET Core 6 and higher and provide a quick-start

What's Next?

Full examples located at Tuxboard.Examples.

One mantra developers should repeat often to themselves is DRY (Don't Repeat Yourself). Kind of ironic, isn't it?

When you start writing code, you begin writing similar code to what you did two years earlier. Now, you look at the code, refactor it, and continue to evolve it into something better.

After writing four dashboards throughout my career, it got to the point where I was taking legacy code and writing a new dashboard-esque module for some kind of new project.

This is where the story of Tuxboard picks up.

I turned the dashboard library into an open-source project.

Overview

Tuxboard is a lightweight, open-source ASP.NET Core dashboard library where you can create any type of custom dashboard for ASP.NET Core web applications.

It is meant to be as compact and flexible as possible.

When the project initially started, I wanted to keep three goals in mind moving forward:

  1. Easy Integration - Simple enhancement to applications
  2. Declarative first, Programmatic second - The concept of modifying Tuxboard using HTML, CSS, and JavaScript/TypeScript was more enticing than modifying code, then compiling. However, the option of writing code is available.
  3. Extensibility - Providing simple hooks at the programmatic and database level to speed up custom development requirements.

The idea of building on-top-of instead of modifying-base-code was also the intent. 

Features of Tuxboard

For ASP.NET 6 or higher

The latest version (1.6.5) uses .NET 8.0.

Available through NuGet

Currently using Azure DevOps which provides a fast way to compile and publish the library to NuGet.

Optimized Data-Access Using Entity Framework

Tuxboard has the ability to immediately create the schema and data for a sample database on installation using Migrations.

Custom Widgets

Tuxboard gives you the ability to create any number of custom widgets by adding a simple ViewComponent and a table entry to the Widgets table.

User-Specific Dashboards and Widgets

While you can create a simple static dashboard for everyone, user-specific dashboards provide a more customized look-and-feel for your users.

If you need a user-specific dashboard, there is an option to present only a list of widgets based on a user's role in the system (Role-based widgets). For example, if someone logs in and they're an administrator, they will see administrator-level widgets.

Along with specific widgets for users, I wanted to have the ability to have specific dashboards for role-based users as well. When someone logs into your system, users will be presented with a dashboard. Well, what kind of dashboard will they see as their default?

After everything is setup and a new user requests a dashboard, Tuxboard will create default dashboards with pre-filled widgets based on a user's role to kickstart the customization of their own dashboard.

CSS-Neutral

Since this is strictly a server-side library in C# and I'm not an expert in any of the modern front-end frameworks, you can create any type of HTML/JS Framework you choose.

TypeScript/Vanilla JS and Bootstrap will be used for the demos. If you would like to use a different CSS library (like Tailwind), I'll show you the hooks on how to update it in a future post.

Quick Start

Tuxboard's pre-requisites include:

For starters, let's create a static dashboard in a brand new project.

1. Create a new ASP.NET [Core] Web Application

For this demo, I've named the project, "MyProject."

2. Add the following NuGet packages to your project

3. Add a TuxConfig to your app.settings

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "TuxboardConfig": {
    "ConnectionString": "Data Source=localhost;Initial Catalog=MyDatabaseNameProject;Integrated Security=True;MultipleActiveResultSets=True",
    "Schema": "dbo"
 }
}

4. Add the Tuxboard DbContext to your Program.cs

Notes:

Program.cs

var builder = WebApplication.CreateBuilder(args);
 
// Tuxboard Configuration
var appConfig = new TuxboardConfig();
builder.Configuration
    .GetSection(nameof(TuxboardConfig))
    .Bind(appConfig);
 
builder.Services.Configure<TuxboardConfig>(
    builder.Configuration.GetSection(nameof(TuxboardConfig))
);
 
// Tuxboard DbContext
builder.Services.AddDbContext<TuxDbContext>(options =>
{
    options.UseSqlServer(appConfig.ConnectionString,
        x => x.MigrationsAssembly("MyProject")); // Your Assembly Name Here
});
 
// Add services to the container.
builder.Services.AddRazorPages();
 
// For Dependency Injection
builder.Services.AddTransient<IDashboardService, DashboardService>();
builder.Services.AddTransient<ITuxDbContext, TuxDbContext>();
 
var app = builder.Build();
 
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
 
app.UseHttpsRedirection();
app.UseStaticFiles();
 
app.UseRouting();
 
app.UseAuthorization();
 
app.MapRazorPages();
 
app.Run();

5. Create the Database using Migrations

  1. Select the Package Manager Console in Visual Studio.
  2. Type: Add-Migration Initial. You should see a Migrations folder after creating your migration.
  3. To update the database, type: Update-Database (this will create the database for you based on the connection string in the appsettings.json). This will create default data for your dashboard.

6. Add the Dashboard to the Index Page

Next, we need to update our IndexModel page with the proper code and HTML.

index.cshtml.cs

public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;
    private readonly IDashboardService _service;
    private readonly TuxboardConfig _config;

   public Dashboard Dashboard { get; set; }

    public IndexModel(
        ILogger<IndexModel> logger,
        IDashboardService service,
        IConfiguration config)
    {
        _logger = logger;
        _service = service;
        config
            .GetSection(nameof(TuxboardConfig))
            .Bind(_config);
    }

   public async Task OnGet()
    {
        Dashboard = await _service.GetDashboardAsync(_config);
    }
}

index.cshtml(Paste this under the existing DIV on the page)

<section class="dashboard" id="@Model.Dashboard.DashboardId">
    @foreach (var tab in Model.Dashboard.Tabs)
    {
        foreach (var layout in tab.Layouts)
        {
            <div class="dashboard-tab">

               @foreach (var row in layout.LayoutRows.OrderBy(e => e.RowIndex))
                {
                    <div class="layout-row">

                       @foreach (var col in row.GetColumnLayout())
                        {
                            <div class="column @col.ColumnClass">

                               @foreach (var wp in row.WidgetPlacements.Where(y => y.ColumnIndex == col.Index).OrderBy(e => e.WidgetIndex))
                                {
                                    <!-- Widgets -->
                                    <div class="card @(wp.Collapsed ? "collapsed" : string.Empty)"
                                         @(Html.Raw(wp.Widget.Moveable ? "draggable=\"true\"" : ""))
                                         data-id="@wp.WidgetPlacementId">

                                       <div class="card-header">
                                            @wp.GetSettingValueByName("widgettitle")
                                        </div>

                                       <div class="card-body">
                                            @await Component.InvokeAsync(wp.Widget.Name, Model)
                                        </div>

                                   </div>
                                }
                            </div>
                        }
                        <div class="clearfix"></div>
                    </div>
                }
                <div class="clearfix"></div>
            </div>
        }
    }
</section>

7. Add Project TagHelpers

Since Tuxboard uses TagHelpers, include the project name in the _ViewImports.cshtml file.

For example, for the 01-SimpleDashboard project at Tuxboard.Examples, the last line would be added (change in bold).

@using SimpleDashboard
@namespace SimpleDashboard.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, 01-SimpleDashboard

8. Add Your Widgets

By default, there are three widgets in the SQL Server dbo.Widgets table: General Info, HelloWorld, and Sample Table.

Since we already have widgets created in the table when we performed an update-database, we now need to define each widget in the Components folder for the project.

For now, we'll just create the simplest widget: HelloWorld.

\Pages\Shared\Components\HelloWorld\Default.cshtml

@model Tuxboard.Core.Infrastructure.Models.WidgetModel

<
h4>Hello World Widget</h4>

\Pages\Shared\Components\HelloWorld\HelloWorldViewComponent.cshtml.cs

[ViewComponent(Name="helloworld")]
public class HelloWorldViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(WidgetPlacement placement)
    {
        var widgetModel = new WidgetModel {Placement = placement};

       return View(widgetModel);
    }
}

You've just created your first widget for a dashboard.


9. Run the Project

When we run the project, you'll see the following screen.

Congratulations! You have created a static dashboard for everyone who visits this page.

FAQ

Here's some questions I've received about this library. I'll try to answer as many questions as I can.

1. Why not include MS Identity?

There are two reasons why I didn't include it in the library.

  1. Not everyone uses Identity and may have rolled their own Authorization/Authentication scheme (which I wouldn't recommend). I've seen User Id's as integers and GUIDs so modifications can be made to accommodate specific requirements.
  2. Users may not want a user-centric dashboard; Simply display a static dashboard with no interactive widgets or settings.

At the end of all of these posts, there is a final dashboard created to showcase the capabilities of Tuxboard using MS Identity. The example demonstrates the commonly-seen "Register/Login" ASP.NET Demo Project. You can register/login a user and they will be given a dashboard based on defaults ("you get a dashboard, You get a dashboard, and YOU get a dashboard!")

2. Why didn't you use <blah.blah> JavaScript framework?

I'll be honest. When I initially started this project, the dashboards were created using native JavaScript (ok, maybe a little bit of jQuery). I decided to move forward with native JS for five reasons:

  1. JavaScript is the common denominator for the web so it made sense to update my library. However, there is one exception I'm considering: TypeScript. At one point, I may move it over to TypeScript, but for now, I decided to keep it simple. I'm now using TypeScript for all of the examples.
  2. Native JavaScript is small when written well. With the web becoming bloated with all of these frameworks, I wanted to keep it lean.
  3. It uses most of the latest ES2016 features like fetch and arrow functions so extra libraries aren't required. (sorry IE).
  4. I found a Bootstrap library written in native JavaScript. ;-)
    Bootstrap 5 is coming and, by design, it uses vanilla JavaScript, no jQuery.

    Bootstrap 5 is already here and uses vanilla JavaScript! This is being used for the front-end demos in Tuxboard (specifically, only the Dropdown and Modal).
  5. With every single JS framework out there, I'm an expert in none of them. The resulting JavaScript should be small enough where a person could fork this repository and work on a Tuxboard.UI. version (i.e. Tuxboard.UI.Vue). Since there is JavaScript for only one page (the dashboard itself), I didn't feel there was a need to implement a huge JS framework for a dashboard.

3. Why Bootstrap?

Bootstrap has been around for a long time and has become a standard for developers for the ease of use and JavaScript library of UI components (Tailwind is coming up from the rear though and is becoming JUST as popular, but I find it too wordy).

As I mentioned before, one of my goals for this dashboard library was to make Tuxboard declarative, then programmatic. This means if a change was required in the HTML, CSS, or JavaScript, it could be modified without compiling C# code. I wanted NO HTML, CSS, OR JAVASCRIPT IN C# CODE (already touched on this point before).

With that said, I wanted Tuxboard to be an easy way to swap out a CSS library and apply those styles to Widgets and Layouts based on the library and not rely on any C# code.

Most of the views are represented in each core ViewComponent and Widget layouts.

4. What's the difference between a Widget and a WidgetPlacement?

A Widget is a common component used on a dashboard and merely acts as a template.

A WidgetPlacement is a reference to a widget used on a dashboard with personalized settings attached to it. So essentially, it's a reference to a Widget. The WidgetPlacement is what appears on a dashboard where the user can move, add, delete, or adjust a certain widget's settings

Let's break this down with an example.

One example of a widget could be an RSS Feed Widget.

An RSS Feed widget would allow users to enter a favorite RSS URL feed and always see their updated news on the dashboard. Using that widget, a user can add multiple RSS Widgets to their dashboard. Every time they add a new RSS Widget, we are using the Widget template to create a WidgetPlacement on the dashboard. Each item on a dashboard is considered a WidgetPlacement referring to a Widget along with it's default WidgetSettings.

In programming terms, a Widget would be considered the abstract class with a WidgetPlacement as the instance of a Widget.

5. Do you have any sample projects?

Recently, I've removed some previous ("messy") examples and created a whole new repository of new examples called "Tuxboard.Examples". Of course, this is FAR from done based on what I have, but this "gets the ball rolling."

Each project has an associated post on building them.

Now that I have an isolated repository for these examples, I'll be adding more as I go.

6. Why create a static dashboard?

Over the years, most users aren't looking for the flashy, glitzy features. They want something that works and can be expanded on later.

When I say "static dashboard," this translates to "public dashboard" which can't be modified and is set in stone. If you want a more personalized dashboard, you can add the user-specific features later.

Some companies like static dashboards with default widgets for everyone who visits. (shrug)

7. Why, oh why...did you call it Tuxboard?

For developers, what's the one thing we have problems with? You guessed it...naming things.

The idea behind the name was to create an entire library of dashboard lego pieces so it could stand out from any other dashboard library (if there was such a thing).

Kind of like someone wearing a tuxedo at a casual party.

So I took the first part of tuxedo and the last part of a dashboard.

That's how I came up with Tuxboard.

8. Why build a dashboard library?

There are a couple of reasons actually.

  1. As mentioned earlier, I didn't want to continue creating a new dashboard every single time. I wanted a flexible, single-page dashboard full-stack solution.
  2. I've seen a number of freelance platforms (freelancer.com, upwork.com, etc) ask up to $1,300 for someone to create a dashboard for their application (yeah, I know...wow!)
  3. While I've been writing code for a loooong time, I've never released anything as open-source. I thought this would be a great library to share with the .NET community.

9. Why perform posts when you can easily use an API?

While I do love me a good ole API, the ability to render a ViewComponent on-the-fly and return back an "island of HTML" is a more attractive approach for a modular dashboard design. The API would require loading a ViewComponent, pass in a model (our variable called dashboard, in this case), rendering it, and returning it back through a JSON data object of some kind. Seems like too much work for something we can simply call and move on.

I'll be working on making this into a better implementation.

Where To Next?

With this first post, I hope this gives you a good understanding of how to build your own dashboard.

I've used Tuxboard to create:

I'll be writing about each of these topics in future posts since this is now in the general public.

Stay tuned for more on the Tuxboard page.

What's Next?

Full examples located at Tuxboard.Examples.