
[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!
- Install the library and analyzers / code-fix providers from NuGet:
dotnet add package BlazingRouter
dotnet add package BlazingRouter.CodeFix
- 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
}
- Add the router to the services (in default Blazor template
Program.cs
):
builder.Services.AddBlazingRouter()
.Configure(ctx =>
{
})
.Build();
- 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>
- 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.
- 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.
Now that the basic setup is in place, we get to sew the rewards of our work.
- 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
- Actions or whole controllers can be protected with
[AuthrorizeExt]
:
@* About.razor *@
@attribute [AuthorizeExt(Roles.Admin)]
- 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"
}
- Now we can use prefabs in
[AuthorizeExt]
:
@* About.razor *@
@attribute [AuthorizeExt(MyRolePrefabs.AdminOrHigher)]
- 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();
- 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
- 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.
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.
This library is licensed under the MIT license. 💜