Skip to content

Strongly-typed Router for Blazor / .NET Core, source-generated, AOT ready.

License

Notifications You must be signed in to change notification settings

lofcz/BlazingRouter

Repository files navigation

BlazingRouter BlazingRouter.CodeFix

BlazingRouter

Te Reo Icon Strongly typed router, focused on performance and covering even the most complex routing needs. Recall the default router in .NET: [Authorize(Roles = "admin,developer")]. Was it really admin? Maybe administrator? Maybe you've introduced a constant somewhere, like static class Roles { public const string Admin = "admin"; }. Is there something enforcing usage of it? Maybe you've derived your own attribute [AuthorizeRole(Roles role)]. BlazingRouter offers another approach - ditch the string-based underlying structure entirely!



Getting Started

  1. Install the library and analyzers / code-fix providers from NuGet:
dotnet add package BlazingRouter
dotnet add package BlazingRouter.CodeFix
  1. Add a new file Roles.cs (name doesn't matter). Define your roles enum inside and decorate it with [AuthRoleEnum]:
namespace YourProject;

[AuthRoleEnum] 
public enum MyRoles
{
    User,
    Admin,
    Developer
}
  1. Add the router to the services (in default Blazor template Program.cs):
builder.Services.AddBlazingRouter()
  .Configure(ctx =>
  {

  })
  .Build();
  1. Replace <Router> with <RouterExt>:
<RouterExt AppAssembly="@typeof(App).Assembly">
  <Found Context="routeData">
      <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
          <NotAuthorized></NotAuthorized>
          <Authorizing></Authorizing>
      </AuthorizeRouteView>
  </Found>
  <NotFound></NotFound>
</RouterExt>
  1. Add folder Pages and place your .razor views in, using MVC conventions (Controller/Action). For example:
|- Program.cs
|- Pages
   |- Home
      |- Index.razor
      |- About.razor

There's no need to add @page "" directives in the .razor files, the routing will work automatically. However, @page can still be used to define extra routes.

  1. Make sure routing is set up so that <RouterExt> can act on any request. One way is to use _Host.cshtml fallback:
WebApplication app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseMvcWithDefaultRoute();

app.UseEndpoints(x =>
{
    x.MapBlazorHub();
    x.MapFallbackToPage("/_Host");
});

app.Run();

From Host.cshtml we load App component which loads <RouterExt> in the demo setup.

Reaping The Benefits

Now that the basic setup is in place, we get to sew the rewards of our work.

  1. MVC routing works out of the box. Navigate to:
/           // resolves to /Home/Index
/Home       // this too
/Home/Index // this one as well
/Home/About // gets us to /Home/About.razor
  1. Actions or whole controllers can be protected with [AuthrorizeExt]:
@* About.razor *@
@attribute [AuthorizeExt(Roles.Admin)]
  1. To grant access to the at least one of roles pattern, we can introduce another enum in Roles.cs:
[AuthRolePrefabsEnum]
public enum MyRolePrefabs
{
  /// <inheritdoc cref="MyRolePrefabsDocs.UserOrHigher"/> <-- ✨ magic documentation rendering roles to which the prefab is resolved!
  [RolePrefab([MyRoles.User], [AdminOrHigher])]
  UserOrHigher, // grant access to "user" or any role granted access by "AdminOrHigher" prefab

  /// <inheritdoc cref="MyRolePrefabsDocs.AdminOrHigher"/>
  [RolePrefab(MyRoles.Admin, MyRoles.Developer)]
  AdminOrHigher // grant access to "admin" or "developer"
}
  1. Now we can use prefabs in [AuthorizeExt]:
@* About.razor *@
@attribute [AuthorizeExt(MyRolePrefabs.AdminOrHigher)]
  1. With the implementation as above, all checks for roles silently fail and users are denied access. To fix this, we need to extend our configuration:
builder.Services.AddBlazingRouter()
  .Configure(ctx =>
  {
     ctx.HasRole = (principal, role) =>
     {
         // use ClaimsPrincipal to check for the role, "role" is strongly typed as "MyRoles"!
         return false;   
     }
  })
  .Build();
  1. The configuration can be further extended to implement:
ctx.OnSetupAllowedUnauthorizedRoles = () => {} // which resources are available to unauthenticated users (by default none!)
ctx.OnRedirectUnauthorized = (user, route) => {} // where do we redirect the user if the resource requested is inaccessible
ctx.OnPageScanned = (type) => {} // enables associating extra routes with Pages, apart from the one picked by conventions. Great for route localization!
ctx.OnTypeDiscovered = (type) => {} // by default, only certain types are considered as Pages. Using this callback, extra types may be promoted to Pages
  1. We can add routes at runtime:
RouteManager.AddRoute("/blog/{year:int}/{month:int}/{slug}", typeof(MyPage)); 

Route syntax supports most of the features implemented by the default router, see the docs for syntax.

Benchmark

The library was measured to perform > 200 000 op/s with 1 000 registered non-trivial routes on a i7 8th gen CPU. See the benchmark.

License

This library is licensed under the MIT license. 💜

About

Strongly-typed Router for Blazor / .NET Core, source-generated, AOT ready.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published