Creating Default Widgets using Roles
In today's post, we'll continue to use Identity to create widgets for specific roles
What's Next?
- Introducing Tuxboard
- Layout of a Tuxboard dashboard
- Dashboard Modularity using Tuxboard
- Moving Widgets in Tuxboard
- Creating a Tuxbar for Tuxboard
- Managing Layouts in Tuxboard: Simple Layout Dialog
- Managing Layouts in Tuxboard: Advanced Layout Dialog
- Adding Widgets with a Tuxboard Dialog
- Using Widget Toolbars (or Deleting Widgets)
- Creating User-Specific Dashboards
- Creating Default Dashboards using Roles
- Creating Default Widgets using Roles
- Creating Custom Tuxboard Widgets
Full examples located at Tuxboard.Examples.
In the last post, we created default dashboards for specific roles. When a new user is added, default dashboards make the onboarding easier for a user.
One feature of Tuxboard is the ability to deliver role-specific widgets. Role-based widgets can be administrative, standard, or simple informative.
In today's post, we'll demonstrate how to leverage those roles in delivering role-specific widgets to users. The widgets are filtered on the server and delivered to the user through the Add Widgets dialog. The good news for backend developers is no client-side code is necessary to update.
The process is similar to the previous post of creating default dashboards, but at a widget level.
Getting Started
The project we'll be working with is located at Tuxboard.Examples called 11-Default-Widgets.
Again, we need to setup the project so we can see how it runs and then follow along of how the project was built.
To get started with the finished project,
- Right-click on the
package.json
file and Restore Packages so the client-side JavaScript/TypeScript will work as expected. - Open the
appsettings.json
file and update the connection string. - Open the Package Manager Console (View > Other Windows... > Package Manager Console)
- Confirm you have the 11-Default-Widgets project selected in the Default Project Dropdown in the Package Manager Console.
- Since we already have a migration in place, type
update-database -Context TuxboardRoleDbContext
After updating the database, users and their roles are the only things missing to complete this demonstration.
As mentioned in the post before under the "Configuring the Database" section, register your users and assign each one a role before moving forward.
Creating the WidgetRole Entity
The WidgetRole entity is meant to be an associative (or junction) table. The entity is defined as follows:
public class WidgetRole { public virtual Guid WidgetId { get; set; } public virtual Guid RoleId { get; set; }
public virtual Widget Widget { get; set; } = default!; public virtual TuxboardRole Role { get; set; } = default!; }
Once we have the entity, we need to add our WidgetRole to the TuxboardRoleDbContext.
First, we need a WidgetRoleConfiguration
in our Data\Configuration
directory.
public class WidgetRoleConfiguration: IEntityTypeConfiguration<WidgetRole> { public void Configure(EntityTypeBuilder<WidgetRole> builder) { builder.ToTable(nameof(WidgetRole));
builder.HasKey(r => new { r.WidgetId, r.RoleId }); } }
Quick Tip
We define the table with a nameof(WidgetRole)
. The nameof() function creates a string version of our class. It produces the same as builder.ToTable("WidgetRole")
, but is more type-safe without "magic strings."
Next, we need to add a DbSet
to the TuxboardRoleDbContext
. First, through the interface (changes in bold).
public interface ITuxboardRoleDbContext: ITuxDbContext { DbSet<RoleDefaultDashboard> RoleDefaultDashboards { get; set; } DbSet<WidgetRole> WidgetRoles { get; set; } .
.
Then, we add the DbSet<WidgetRole>
to our concrete class TuxboardRoleDbContext
.
public class TuxboardRoleDbContext : TuxDbContext, ITuxboardRoleDbContext { public TuxboardRoleDbContext(DbContextOptions<TuxDbContext> options, IOptions<TuxboardConfig> config) : base(options, config) { }
public DbSet<RoleDefaultDashboard> RoleDefaultDashboards { get; set; }
public DbSet<WidgetRole> WidgetRoles { get; set; } .
.
In the OnModelCreating
, don't forget to add the WidgetRoleConfiguration()
.
modelBuilder.ApplyConfiguration(new WidgetRoleConfiguration());
Creating the Service
Since we have the WidgetRole table, our focus is now the service and how to pull the widgets based on a role.
The interface is meant to be as simple as the RoleDashboardService from before.
public interface IWidgetRoleService { Task<List<Widget>> GetWidgetsByRoleAsync(TuxboardUser user); Task<List<Widget>> GetDefaultWidgetsAsync(); }
The GetWidgetsByRoleAsync()
retrieves the widgets based on a user's role, but what about the GetDefaultWidgetsAsync()
? This concept is similar to how a user logs in and is given either a role-specific dashboard or a default dashboard. If they're a registered user, they should receive a dashboard.
The same concept applies to widgets. If they are a registered user, but don't have a role, they should receive a collection of widgets to add to their dashboard.
public class WidgetRoleService : IWidgetRoleService { private readonly ITuxboardRoleDbContext _context; private readonly UserManager<TuxboardUser> _userManager; private readonly RoleManager<TuxboardRole> _roleManager;
public WidgetRoleService(ITuxboardRoleDbContext context, UserManager<TuxboardUser> userManager, RoleManager<TuxboardRole> roleManager) { _context = context; _userManager = userManager; _roleManager = roleManager; }
public async Task<List<Widget>> GetWidgetsByRoleAsync(TuxboardUser user) { // Give them something at least. var result = await GetDefaultWidgetsAsync();
var roleName = await GetRoles(user); if (string.IsNullOrEmpty(roleName)) { return result; }
var role = await _roleManager.FindByNameAsync(roleName); if (role == null) return result;
return await _context.WidgetRoles .Include(e=> e.Widget) .Where(e => e.RoleId == role.Id) .Select(r=> r.Widget) .ToListAsync(); }
public async Task<List<Widget>> GetDefaultWidgetsAsync() => // Set up your own GroupName like "Standard" or something. await _context.Widgets .Where(e => e.GroupName == "Example") .ToListAsync();
private async Task<string> GetRoles(TuxboardUser user) { // *COULD* have more than one role; we just want the first one. var roles = await _userManager.GetRolesAsync(user); return (roles.Count == 1 ? roles.FirstOrDefault() : string.Empty)!; } }
The GetWidgetsByRoleAsync()
takes a TuxboardUser and immediately retrieves the default widgets for unregistered users or users without a role. In this case, widgets in the GroupName called "Example" are the default widgets presented to the user.
If they're a registered user and have a role, then the user is presented with a list of role-specific widgets in the dialog.
With the list of role-specific widgets, we can move up a level to the Add Widgets dialog in our Index page.
Dependency Injecting the WidgetRoleService
Before we head over to the Index page, we need to add our new service to our Middleware in the Program.cs
.
builder.Services.AddTransient<IWidgetRoleService, WidgetRoleService>();
Once we update our Program.cs
, we can move on to the Index page.
Updating the Add Widget Dialog
In the Index page, we need to inject our WidgetRoleService through the constructor (changes in bold).
public class IndexModel : PageModel { private readonly ILogger<IndexModel> _logger; private readonly IDashboardService _service; private readonly IRoleDashboardService _roleDashboardService; private readonly IWidgetRoleService _widgetRoleService; private readonly UserManager<TuxboardUser> _userManager; private readonly TuxboardConfig _config;
public Dashboard Dashboard { get; set; } = null!; public bool HasDashboard => Dashboard != null;
public IndexModel( ILogger<IndexModel> logger, IDashboardService service, IRoleDashboardService roleDashboardService, IWidgetRoleService widgetRoleService, UserManager<TuxboardUser> userManager, IOptions<TuxboardConfig> options) { _logger = logger; _service = service; _roleDashboardService = roleDashboardService; _widgetRoleService = widgetRoleService; _userManager = userManager; _config = options.Value; }
.
.
Once we're able to inject the service into the Index page, the OnPostAddWidgetsDialog()
method is easier to implement.
public async Task<IActionResult> OnPostAddWidgetsDialog() { List<WidgetDto> widgets = new();
var id = GetIdentity(); if (id != Guid.Empty) { var user = await GetTuxboardUser(id); widgets.AddRange( (await _widgetRoleService.GetWidgetsByRoleAsync(user)) .Select(r => r.ToDto()) .ToList() ); } else { widgets.AddRange( (await _widgetRoleService.GetDefaultWidgetsAsync()) .Select(r => r.ToDto()) .ToList() ); }
return ViewComponent("addwidgetdialog", new AddWidgetModel { Widgets = widgets }); }
Let's walk through the method.
We initialize the list of widgets to return to the Add Widgets Dialog as empty (for now).
The GetIdentity()
retrieves the current user logged in.
Since we're retrieving the user in multiple places throughout the code, it made sense to create a new method to grab a TuxboardUser.
private async Task<TuxboardUser> GetTuxboardUser(Guid id) => (await _userManager.FindByIdAsync(id.ToString()))!;
If the user is logged in, get the widgets by the user's role. If they aren't logged in, return a list of default widgets. Whether logged in or not, we receive a list of widgets and convert them into DTOs (Data Transfer Objects) for our Add Widget Dialog.
The good news is we simply modified the way a user retrieves widgets based on their roles. Once we have the widget DTOs, we pass them on to the AddWidgetDialogViewComponent
to render and send the view back to the client.
Updating the Database
The best way to present widgets to users is to take a hard look at the roles and identify which widgets are meant for privileged users and standard users.
If all users are meant to have any widget, insert all of the widgets and roles through SQL.
INSERT INTO WidgetRole SELECT w.WidgetId, tr.Id as RoleId FROM Widget w join TuxboardRole tr on 1=1
The SQL above will add all widgets to every role.
Once all WidgetRoles are entered into the table, double-check the table by using the following SQL statement.
SELECT tr.Name, w.Name, w.Title, w.GroupName FROM WidgetRole wr join Widget w on w.WidgetId=wr.WidgetId join TuxboardRole tr on tr.Id=wr.RoleId
Role | Name | Title | GroupName |
---|---|---|---|
Basic | helloworld | Hello World | Example |
Admin | table | Sample Table | General |
Admin | generalinfo | General Info | General |
Admin | helloworld | Hello World | Example |
The SQL results are meant to show an easy view of the roles and their associated widgets. In this example, basic users are only able to add a Hello World widget, but administrators are able to add all widgets.
Viewing The Results
When we run the application and log in as an administrator and use the Add Widget dialog, we can see the following results.
However, if we log in as a basic user and want to add a widget to the dashboard, our widget list is limited.
Providing specific widgets based on a user's role demonstrates Tuxboard's unique approach to dashboards.
Conclusion
While we focused on role-based widgets and default dashboards, the goal of these two past posts were meant to show the flexibility of Tuxboard and how to expand on it's ability to adapt to other concepts, but still keep the dashboard robust and maintainable.
The original concept was to introduce subscriber plans to Tuxboard for the initial design. The Plan and WidgetPlan table along with the DashboardDefault's PlanID field was created for inspiring developer/entrepreneurs to integrate a consumer-based dashboard into a product. They were originally intended for subscriber plans. This was touched on in the last post (under "Configuring the Database").
Based on these two posts, we were able to demonstrate how Tuxboard uses a role-based approach as easily as a subscriber plan approach.
What's Next?
- Introducing Tuxboard
- Layout of a Tuxboard dashboard
- Dashboard Modularity using Tuxboard
- Moving Widgets in Tuxboard
- Creating a Tuxbar for Tuxboard
- Managing Layouts in Tuxboard: Simple Layout Dialog
- Managing Layouts in Tuxboard: Advanced Layout Dialog
- Adding Widgets with a Tuxboard Dialog
- Using Widget Toolbars (or Deleting Widgets)
- Creating User-Specific Dashboards
- Creating Default Dashboards using Roles
- Creating Default Widgets using Roles
- Creating Custom Tuxboard Widgets
Full examples located at Tuxboard.Examples.