Migration Guides
v10.0.2 → v10.0.3
Breaking Change: AddUserContext() no longer registers IHttpContextAccessor
AddUserContext() now registers the IUserContext interface (backed by the internal UserContext class) and no longer registers IHttpContextAccessor as a side effect.
Register IHttpContextAccessor explicitly with builder.Services.AddHttpContextAccessor().
What changed
| v10.0.2 | v10.0.3 |
|---|---|
AddScoped<UserContext>() — only the internal concrete class was registered, so injecting IUserContext failed with InvalidOperationException: No service for type 'RA.Utilities.Authorization.IUserContext' has been registered. | AddScoped<IUserContext, UserContext>() — IUserContext resolves |
Also called AddHttpContextAccessor() | Does not register IHttpContextAccessor — register it yourself |
Why this change?
The UserContext implementation reads the current request's ClaimsPrincipal through IHttpContextAccessor. On v10.0.2 the service collection contained only the internal UserContext class, so the documented usage — inject IUserContext — failed at runtime. v10.0.3 provides the interface mapping and keeps the package's registrations limited to the service it owns, leaving IHttpContextAccessor registration to the application.
Migration Steps
1. Register IHttpContextAccessor
var builder = WebApplication.CreateBuilder(args);
// ✅ v10.0.3 — required by the IUserContext implementation
builder.Services.AddHttpContextAccessor();
builder.Services.AddUserContext();
If your application already registers IHttpContextAccessor (for example, for logging enrichment), no action is needed — the additional call is harmless.
2. Injecting IUserContext now works
On v10.0.2, injecting IUserContext failed because only the internal UserContext class was registered.
On v10.0.3, AddUserContext() maps IUserContext → UserContext, so injection sites keep working with no changes.
3. Extension classes refactored to C# 14 extension members (no action)
ClaimsPrincipalExtensions and DependencyInjectionExtensions were re-implemented with C# 14 extension members.
Class names, namespaces, method names, and signatures are unchanged — call sites need no changes.
Verify the upgrade
Run an endpoint that injects IUserContext (e.g., the /me sample from the package docs). If the app fails with Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor', step 1 was missed.
v10.0.1 → v10.0.2
Breaking Change: AppUser replaced by IUserContext
The concrete AppUser class has been removed.
The same functionality is now exposed through the IUserContext interface, implemented internally by UserContext.
The registration method was renamed accordingly, and the service lifetime changed from transient to scoped.
What changed
| v10.0.1 | v10.0.2 |
|---|---|
AppUser (concrete class) | IUserContext (interface) + internal UserContext |
AddAppUser() | AddUserContext() |
| Transient | Scoped |
Why this change?
Programming against an interface decouples your application code from the implementation,
follows the Dependency Inversion Principle, and simplifies unit testing — you can mock IUserContext directly instead of constructing a concrete class with a mocked IHttpContextAccessor. The scoped lifetime aligns the service with the request lifetime, which is the natural fit for request-bound state.
Migration Steps
1. Update service registration
// ❌ v10.0.1
builder.Services.AddAppUser();
// ✅ v10.0.2
builder.Services.AddUserContext();
2. Update injection sites
// ❌ v10.0.1
public OrdersController(AppUser user) => _user = user;
// ✅ v10.0.2
public OrdersController(IUserContext user) => _user = user;
3. Update unit tests
// ❌ v10.0.1 — construct with a mocked IHttpContextAccessor
var mockAccessor = new Mock<IHttpContextAccessor>();
mockAccessor.Setup(a => a.HttpContext)
.Returns(new DefaultHttpContext { User = claimsPrincipal });
var user = new AppUser(mockAccessor.Object);
// ✅ v10.0.2 — mock the interface directly
var mockUser = new Mock<IUserContext>();
mockUser.SetupGet(u => u.UserId).Returns(Guid.NewGuid());
var user = mockUser.Object;
4. Scoped lifetime note
No action is needed for typical request-scoped usage. If you previously captured an AppUser beyond a request's scope (e.g., in fire-and-forget tasks),
resolve IUserContext within the operation's own scope instead.
Verify the upgrade
Build your solution — every remaining reference to AppUser or AddAppUser() fails to compile,
so the compiler walks you through all call sites.
v10.0.0 → v10.0.1
Breaking Change: HasClaim signature changed
HasClaim(string claimValue) → HasClaim(string claimType, string claimValue)
The old signature accepted only a claim value and hardcoded the claim type to the literal string "claim",
which never matched real claims from any standard auth provider. The new signature requires both a claim type and value.
Why this change?
The old HasClaim(string claimValue) was functionally broken — it checked c.Type == "claim",
but no JWT issuer, OIDC provider, or cookie auth middleware emits a claim type literally named "claim". The method would always return false.
The fix adds a claimType parameter. The new signature HasClaim(string claimType, string claimValue) matches the standard ClaimsPrincipal.HasClaim(string type, string value) pattern.
Migration Steps
1. Update AppUser.HasClaim calls
// ❌ v10.0.0
_user.HasClaim("CanDeleteUsers");
// ✅ v10.0.1
_user.HasClaim("permission", "CanDeleteUsers");
2. Update ClaimsPrincipalExtensions.HasClaim calls
// ❌ v10.0.0
principal.HasClaim("SomeValue");
// ✅ v10.0.1
principal.HasClaim("my_claim_type", "SomeValue");
Other Changes (non-breaking)
- New
Guid UserIdproperty — typed access to the user ID without calling the extension method HasScopenow handles OIDC space-separated scopes — a"scope"claim with value"api.read api.write"now correctly matches both individual scopes- Constructor validates
IHttpContextAccessor— throwsArgumentNullExceptioninstead of silently accepting null - Field renamed — internal
Userfield renamed to_user(no external impact) FindFirstValueextension removed — shadowed the built-inClaimsPrincipal.FindFirstValue(string)method. Use the built-in directly.GetUserIdthrowsInvalidOperationExceptioninstead ofApplicationException— more specific exception type
Verify the upgrade
// If you use HasClaim, ensure both claimType and claimValue are provided
_user.HasClaim("permission", "CanDeleteUsers"); // ✅
// If you previously called FindFirstValue as an extension:
// ❌ principal.FindFirstValue(ClaimTypes.Email) via our extension
// ✅ principal.FindFirstValue(ClaimTypes.Email) via the built-in — just works
Full Changelog
See the package release notes for a complete version history.