Skip to main content

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.2v10.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.1v10.0.2
AppUser (concrete class)IUserContext (interface) + internal UserContext
AddAppUser()AddUserContext()
TransientScoped

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 UserId property — typed access to the user ID without calling the extension method
  • HasScope now handles OIDC space-separated scopes — a "scope" claim with value "api.read api.write" now correctly matches both individual scopes
  • Constructor validates IHttpContextAccessor — throws ArgumentNullException instead of silently accepting null
  • Field renamed — internal User field renamed to _user (no external impact)
  • FindFirstValue extension removed — shadowed the built-in ClaimsPrincipal.FindFirstValue(string) method. Use the built-in directly.
  • GetUserId throws InvalidOperationException instead of ApplicationException — 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.