![]() |
VOOZH | about |
dotnet add package PrimusSaaS.Rbac.Dapper --version 1.0.1
NuGet\Install-Package PrimusSaaS.Rbac.Dapper -Version 1.0.1
<PackageReference Include="PrimusSaaS.Rbac.Dapper" Version="1.0.1" />
<PackageVersion Include="PrimusSaaS.Rbac.Dapper" Version="1.0.1" />Directory.Packages.props
<PackageReference Include="PrimusSaaS.Rbac.Dapper" />Project file
paket add PrimusSaaS.Rbac.Dapper --version 1.0.1
#r "nuget: PrimusSaaS.Rbac.Dapper, 1.0.1"
#:package PrimusSaaS.Rbac.Dapper@1.0.1
#addin nuget:?package=PrimusSaaS.Rbac.Dapper&version=1.0.1Install as a Cake Addin
#tool nuget:?package=PrimusSaaS.Rbac.Dapper&version=1.0.1Install as a Cake Tool
Dapper storage adapter for PrimusSaaS.Rbac. A lightweight, provider-agnostic alternative to the EF Core adapter that gives you full control over SQL. No DbContext, no migrations, no EF dependency.
/docs/modules/rbac/verified-dapper-quickstart/docs/modules/rbac/integration-guide/docs/modules/rbacexamples/RbacDapperQuickstart# Core RBAC + Dapper adapter
dotnet add package PrimusSaaS.Rbac
dotnet add package PrimusSaaS.Rbac.Dapper
# Pick your ADO.NET driver (the adapter has no opinion on provider)
dotnet add package Microsoft.Data.SqlClient # SQL Server
dotnet add package Npgsql # PostgreSQL
dotnet add package Microsoft.Data.Sqlite # SQLite
Follow steps 1-5 in order for the minimum runnable setup. Step 6 is optional programmatic seeding. Steps 1 and 2 (schema + config) must be done before the app starts because the adapter checks for seed data on startup.
If you are using this package for the first time, follow this order exactly:
ConnectionStrings:RbacIRbacDbConnectionFactoryAddPrimusRbacDapper(...)The main difference from the EF Core adapter is step 3: the schema must already exist before the store is first resolved.
// appsettings.json
{
"ConnectionStrings": {
"Rbac": "Server=localhost;Database=MyApp;Trusted_Connection=True;"
},
"Rbac": {
"SeedMode": "none"
}
}
SeedMode options: none (production default), minimal (one catch-all permission), sample (demo roles and groups for local dev).
Run the DDL once against your database before first launch. See the full DDL section below for the complete script.
IRbacDbConnectionFactoryThis is the one class you write — it tells the adapter how to get a connection:
using System.Data;
using Microsoft.Data.SqlClient;
using PrimusSaaS.Rbac.Dapper;
public class RbacConnectionFactory : IRbacDbConnectionFactory
{
private readonly string _connectionString;
public RbacConnectionFactory(IConfiguration config)
=> _connectionString = config.GetConnectionString("Rbac")!;
public IDbConnection CreateConnection() => new SqlConnection(_connectionString);
}
For PostgreSQL, swap SqlConnection for NpgsqlConnection. For SQLite, use SqliteConnection. The adapter works with any IDbConnection.
Program.csusing PrimusSaaS.Rbac;
using PrimusSaaS.Rbac.Dapper;
// Register the connection factory (singleton — connection pooling is handled by the driver)
builder.Services.AddSingleton<IRbacDbConnectionFactory, RbacConnectionFactory>();
// Register RBAC with Dapper store
builder.Services.AddPrimusRbacDapper(opts =>
builder.Configuration.GetSection("Rbac").Bind(opts));
// Optional: enable PostgreSQL session variable initializer for DB RLS policies
builder.Services.AddPrimusRbacPostgresRls();
What gets registered in DI:
| Service | Lifetime | Purpose |
|---|---|---|
IRbacService |
Scoped | Main entry point — evaluate access, manage roles/permissions |
IRbacStore → RbacDapperStore |
Scoped | Dapper-backed store |
IRbacAuditSink |
Singleton | Logs RBAC decisions (override to send elsewhere) |
IRbacDecisionCache |
Singleton | No-op by default (plug in Redis etc.) |
IRbacDbConnectionFactory |
Singleton | Your implementation |
Note:
RbacDapperStorealso implementsIRbacStoreAsync, whichRbacService.EvaluateAsync()discovers at runtime via cast — no separate DI registration is needed.
When using PostgreSQL Row-Level Security policies that reference
current_setting('app.rbac_actor'), app.rbac_roles, or app.rbac_tenant,
call AddPrimusRbacPostgresRls() so IRbacRlsInitializer resolves to
PostgresRbacRlsInitializer instead of the default no-op implementation.
Option A — attribute-based (AspNetCore package required):
dotnet add package PrimusSaaS.Rbac.AspNetCore
// On your endpoint or controller
[PrimusRbacAuthorize("orders", "read")]
public IActionResult GetOrders() { ... }
No extra AddPrimusRbacAspNetCore() registration method exists. Referencing the package is enough; the integration surface is the attribute and the .RequirePrimusRbac(...) endpoint filter.
app.MapGet("/orders", () => Results.Ok())
.RequireAuthorization()
.RequirePrimusRbac("orders", "read");
Option B — programmatic check via IRbacService:
public class OrdersController : ControllerBase
{
private readonly IRbacService _rbac;
public OrdersController(IRbacService rbac) => _rbac = rbac;
public async Task<IActionResult> GetOrders()
{
var decision = await _rbac.EvaluateAsync(new RbacAccessRequest
{
PrincipalId = User.FindFirstValue(ClaimTypes.NameIdentifier)!,
Resource = "orders",
Action = "read",
TenantId = User.FindFirstValue("tid")
});
if (!decision.Allowed)
return Forbid();
return Ok(await _orderService.GetAllAsync());
}
}
Use SeedPrimusRbac after app.Build() to create your initial data programmatically:
var app = builder.Build();
app.Services.SeedPrimusRbac(rbac =>
{
rbac.UpsertPermission(new RbacPermission(
Id: "perm:orders:read",
Action: "read",
Resource: "orders",
Effect: RbacEffect.Allow,
Scope: new RbacScope(ApplicationId: "my-app")));
rbac.UpsertRole(new RbacRole(
Id: "role:viewer",
Name: "Viewer",
Scope: new RbacScope(ApplicationId: "my-app"),
PermissionIds: new[] { "perm:orders:read" }));
});
Column names match the EF Core adapter so you can swap providers without migrating data. Run the script once before first launch.
CREATE TABLE RbacRoles (
Id NVARCHAR(120) NOT NULL PRIMARY KEY,
Name NVARCHAR(120) NOT NULL,
ApplicationId NVARCHAR(100) NULL,
TenantId NVARCHAR(100) NULL,
Environment NVARCHAR(50) NULL, -- maps to RbacScope.Qualifier
PermissionIdsJson NVARCHAR(MAX) NOT NULL DEFAULT '[]',
InheritsJson NVARCHAR(MAX) NULL,
Description NVARCHAR(MAX) NULL,
MetadataJson NVARCHAR(MAX) NULL
);
CREATE INDEX IX_RbacRoles_Name ON RbacRoles (Name);
CREATE INDEX IX_RbacRoles_Scope ON RbacRoles (ApplicationId, TenantId, Environment);
CREATE TABLE RbacPermissions (
Id NVARCHAR(120) NOT NULL PRIMARY KEY,
Action NVARCHAR(80) NOT NULL,
Resource NVARCHAR(200) NOT NULL,
Effect INT NOT NULL,
ApplicationId NVARCHAR(100) NULL,
TenantId NVARCHAR(100) NULL,
Environment NVARCHAR(50) NULL,
Description NVARCHAR(MAX) NULL,
ConditionsJson NVARCHAR(MAX) NULL,
ConditionGroupsJson NVARCHAR(MAX) NULL,
MetadataJson NVARCHAR(MAX) NULL
);
CREATE INDEX IX_RbacPermissions_ActionResource ON RbacPermissions (Action, Resource);
CREATE INDEX IX_RbacPermissions_Scope ON RbacPermissions (ApplicationId, TenantId, Environment, Action, Resource);
CREATE TABLE RbacAssignments (
Id NVARCHAR(140) NOT NULL PRIMARY KEY,
PrincipalId NVARCHAR(120) NOT NULL,
PrincipalType NVARCHAR(40) NOT NULL,
RoleId NVARCHAR(120) NOT NULL,
ApplicationId NVARCHAR(100) NULL,
TenantId NVARCHAR(100) NULL,
Environment NVARCHAR(50) NULL,
ExpiresAt DATETIME2 NULL,
Enabled BIT NOT NULL DEFAULT 1,
AttributesJson NVARCHAR(MAX) NULL
);
CREATE INDEX IX_RbacAssignments_PrincipalId ON RbacAssignments (PrincipalId);
CREATE INDEX IX_RbacAssignments_RoleId ON RbacAssignments (RoleId);
CREATE INDEX IX_RbacAssignments_PrincipalScope ON RbacAssignments (PrincipalId, ApplicationId, TenantId, Environment);
CREATE TABLE RbacGroups (
Id NVARCHAR(140) NOT NULL PRIMARY KEY,
Name NVARCHAR(120) NOT NULL,
Type NVARCHAR(80) NOT NULL,
ApplicationId NVARCHAR(100) NULL,
TenantId NVARCHAR(100) NULL,
Environment NVARCHAR(50) NULL,
ParentId NVARCHAR(140) NULL,
Description NVARCHAR(MAX) NULL,
MetadataJson NVARCHAR(MAX) NULL
);
CREATE INDEX IX_RbacGroups_ParentId ON RbacGroups (ParentId);
CREATE INDEX IX_RbacGroups_Type ON RbacGroups (Type);
CREATE INDEX IX_RbacGroups_Scope ON RbacGroups (ApplicationId, TenantId, Environment);
CREATE TABLE RbacMemberships (
Id NVARCHAR(160) NOT NULL PRIMARY KEY,
GroupId NVARCHAR(140) NOT NULL,
PrincipalId NVARCHAR(120) NOT NULL,
PrincipalType NVARCHAR(40) NOT NULL,
ApplicationId NVARCHAR(100) NULL,
TenantId NVARCHAR(100) NULL,
Environment NVARCHAR(50) NULL,
ExpiresAt DATETIME2 NULL,
Enabled BIT NOT NULL DEFAULT 1,
AttributesJson NVARCHAR(MAX) NULL
);
CREATE INDEX IX_RbacMemberships_GroupId ON RbacMemberships (GroupId);
CREATE INDEX IX_RbacMemberships_PrincipalId ON RbacMemberships (PrincipalId);
CREATE INDEX IX_RbacMemberships_GroupPrincipal ON RbacMemberships (GroupId, PrincipalId);
CREATE INDEX IX_RbacMemberships_Scope ON RbacMemberships (PrincipalId, ApplicationId, TenantId, Environment);
CREATE TABLE "RbacRoles" (
"Id" VARCHAR(120) NOT NULL PRIMARY KEY,
"Name" VARCHAR(120) NOT NULL,
"ApplicationId" VARCHAR(100) NULL,
"TenantId" VARCHAR(100) NULL,
"Environment" VARCHAR(50) NULL,
"PermissionIdsJson" TEXT NOT NULL DEFAULT '[]',
"InheritsJson" TEXT NULL,
"Description" TEXT NULL,
"MetadataJson" TEXT NULL
);
CREATE INDEX "IX_RbacRoles_Name" ON "RbacRoles" ("Name");
CREATE INDEX "IX_RbacRoles_Scope" ON "RbacRoles" ("ApplicationId", "TenantId", "Environment");
CREATE TABLE "RbacPermissions" (
"Id" VARCHAR(120) NOT NULL PRIMARY KEY,
"Action" VARCHAR(80) NOT NULL,
"Resource" VARCHAR(200) NOT NULL,
"Effect" INT NOT NULL,
"ApplicationId" VARCHAR(100) NULL,
"TenantId" VARCHAR(100) NULL,
"Environment" VARCHAR(50) NULL,
"Description" TEXT NULL,
"ConditionsJson" TEXT NULL,
"ConditionGroupsJson" TEXT NULL,
"MetadataJson" TEXT NULL
);
CREATE INDEX "IX_RbacPermissions_ActionResource" ON "RbacPermissions" ("Action", "Resource");
CREATE INDEX "IX_RbacPermissions_Scope" ON "RbacPermissions" ("ApplicationId", "TenantId", "Environment", "Action", "Resource");
CREATE TABLE "RbacAssignments" (
"Id" VARCHAR(140) NOT NULL PRIMARY KEY,
"PrincipalId" VARCHAR(120) NOT NULL,
"PrincipalType" VARCHAR(40) NOT NULL,
"RoleId" VARCHAR(120) NOT NULL,
"ApplicationId" VARCHAR(100) NULL,
"TenantId" VARCHAR(100) NULL,
"Environment" VARCHAR(50) NULL,
"ExpiresAt" TIMESTAMPTZ NULL,
"Enabled" BOOLEAN NOT NULL DEFAULT TRUE,
"AttributesJson" TEXT NULL
);
CREATE INDEX "IX_RbacAssignments_PrincipalId" ON "RbacAssignments" ("PrincipalId");
CREATE INDEX "IX_RbacAssignments_RoleId" ON "RbacAssignments" ("RoleId");
CREATE INDEX "IX_RbacAssignments_PrincipalScope" ON "RbacAssignments" ("PrincipalId", "ApplicationId", "TenantId", "Environment");
CREATE TABLE "RbacGroups" (
"Id" VARCHAR(140) NOT NULL PRIMARY KEY,
"Name" VARCHAR(120) NOT NULL,
"Type" VARCHAR(80) NOT NULL,
"ApplicationId" VARCHAR(100) NULL,
"TenantId" VARCHAR(100) NULL,
"Environment" VARCHAR(50) NULL,
"ParentId" VARCHAR(140) NULL,
"Description" TEXT NULL,
"MetadataJson" TEXT NULL
);
CREATE INDEX "IX_RbacGroups_ParentId" ON "RbacGroups" ("ParentId");
CREATE INDEX "IX_RbacGroups_Type" ON "RbacGroups" ("Type");
CREATE INDEX "IX_RbacGroups_Scope" ON "RbacGroups" ("ApplicationId", "TenantId", "Environment");
CREATE TABLE "RbacMemberships" (
"Id" VARCHAR(160) NOT NULL PRIMARY KEY,
"GroupId" VARCHAR(140) NOT NULL,
"PrincipalId" VARCHAR(120) NOT NULL,
"PrincipalType" VARCHAR(40) NOT NULL,
"ApplicationId" VARCHAR(100) NULL,
"TenantId" VARCHAR(100) NULL,
"Environment" VARCHAR(50) NULL,
"ExpiresAt" TIMESTAMPTZ NULL,
"Enabled" BOOLEAN NOT NULL DEFAULT TRUE,
"AttributesJson" TEXT NULL
);
CREATE INDEX "IX_RbacMemberships_GroupId" ON "RbacMemberships" ("GroupId");
CREATE INDEX "IX_RbacMemberships_PrincipalId" ON "RbacMemberships" ("PrincipalId");
CREATE INDEX "IX_RbacMemberships_GroupPrincipal" ON "RbacMemberships" ("GroupId", "PrincipalId");
CREATE INDEX "IX_RbacMemberships_Scope" ON "RbacMemberships" ("PrincipalId", "ApplicationId", "TenantId", "Environment");
SQLite: Use
TEXTfor all string/JSON/datetime columns andINTEGERinstead ofBOOLEAN/BIT. SQLite has noVARCHARlength enforcement but specifying it is harmless.
PrimusSaaS.Rbac.EFCoreThe two adapters share the same table names, column names, and JSON encoding. Swap is straightforward:
AddPrimusRbacEfCore<TContext>() and the EF Core package reference.IRbacDbConnectionFactory registration + AddPrimusRbacDapper().modelBuilder.AddPrimusRbac() in your DbContext.IRbacDbConnectionFactory.CreateConnection(). ADO.NET connection pooling handles the actual socket reuse.IRbacStoreAsync; uses DbConnection.OpenAsync when the provider exposes it.SeedMode option as EFCore (none / minimal / sample). Seeds only when all tables are empty.| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 1 NuGet packages that depend on PrimusSaaS.Rbac.Dapper:
| Package | Downloads |
|---|---|
|
PrimusSaaS.Rbac.Dapper.MySql
MySQL connection factory for PrimusSaaS.Rbac.Dapper. Drop-in replacement for the SQL Server factory. |
This package is not used by any popular GitHub repositories.