Repository Interfaces¶
EzSCIM uses a two-level interface hierarchy. Choose the level that fits your implementation.
Interface hierarchy¶
IUserDataRepository<TUser>
└── IUserGroupDataRepository<TUser, TGroup>
IScimUserOnlyRepository<TUser>
└── IScimUserGroupRepository<TUser, TGroup>
└── IScimRepository (concrete: IScimUserGroupRepository<ScimUser, ScimGroup>)
Data layer interfaces (your implementation)¶
These interfaces connect your storage to EzSCIM. You implement them.
IUserDataRepository<TUser>¶
Namespace: EzSCIM.DataRepositories
Use when you support users only.
public interface IUserDataRepository<TUser> where TUser : class
{
Task<TUser?> GetUserAsync(string id);
/// <summary>
/// Returns a queryable source. EzSCIM applies SCIM filters as LINQ expressions.
/// Must return IQueryable backed by a real query provider (EF Core, etc.)
/// or an in-memory source for simple cases.
/// </summary>
IQueryable<TUser> QueryUsers();
Task<TUser> CreateUserAsync(TUser user);
Task<TUser?> UpdateUserAsync(string id, TUser user);
Task<bool> DeleteUserAsync(string id);
}
IUserGroupDataRepository<TUser, TGroup>¶
Namespace: EzSCIM.DataRepositories
Use when you support users and groups. Inherits all user methods.
public interface IUserGroupDataRepository<TUser, TGroup>
: IUserDataRepository<TUser>
where TUser : class
where TGroup : class
{
Task<TGroup?> GetGroupAsync(string id);
IQueryable<TGroup> QueryGroups();
Task<TGroup> CreateGroupAsync(TGroup group);
Task<TGroup?> UpdateGroupAsync(string id, TGroup group);
Task<bool> DeleteGroupAsync(string id);
}
SCIM layer interfaces (registered in DI)¶
These interfaces are used by EzSCIM controllers. You register them in DI — the adapters or your custom implementation bridges your data layer to these.
IScimUserOnlyRepository<TUser>¶
Namespace: EzSCIM.Repositories
public interface IScimUserOnlyRepository<TUser> where TUser : ScimUser
{
Task<TUser?> GetUserAsync(string id);
Task<TUser?> GetUserByUserNameAsync(string userName);
Task<ScimListResponse<TUser>> GetUsersAsync(FilterExpression? filter, int startIndex, int count);
Task<TUser> CreateUserAsync(TUser user);
Task<TUser?> UpdateUserAsync(string id, TUser user);
Task<TUser?> PatchUserAsync(string id, ScimPatchRequest patchRequest);
Task<bool> DeleteUserAsync(string id);
}
IScimRepository¶
Namespace: EzSCIM.Repositories
The main interface — combines users and groups with concrete ScimUser / ScimGroup types.
Register this in DI and the controllers resolve it automatically.
DI registration patterns¶
Pattern B is recommended for most applications
builder.Services.AddScoped<IUserDataRepository<MyUser>, MyUserRepository>();
builder.Services.AddScoped<IScimFilterTranslator<MyUser>, GenericScimFilterTranslator<MyUser>>();
builder.Services.AddScoped<IScimUserOnlyRepository<ScimUser>>(sp =>
new ScimUserRepositoryAdapter<MyUser>(
sp.GetRequiredService<IUserDataRepository<MyUser>>(),
sp.GetRequiredService<IScimFilterTranslator<MyUser>>()));
builder.Services.AddScoped<IUserGroupDataRepository<MyUser, MyGroup>, MyRepository>();
builder.Services.AddScoped<IScimFilterTranslator<MyUser>, GenericScimFilterTranslator<MyUser>>();
builder.Services.AddScoped<IScimFilterTranslator<MyGroup>, GenericScimFilterTranslator<MyGroup>>();
builder.Services.AddScoped<IScimRepository, MyScimRepository>();
public class MyScimRepository : IScimRepository
{
public Task<ScimUser?> GetUserAsync(string id) { ... }
public Task<ScimListResponse<ScimUser>> GetUsersAsync(FilterExpression? filter, int startIndex, int count) { ... }
// ... all other methods
}
builder.Services.AddScoped<IScimRepository, MyScimRepository>();
IScimFilterTranslator<T>¶
Namespace: EzSCIM.Filtering
Translates a SCIM FilterExpression AST into an IQueryable<T> with LINQ .Where().
The built-in GenericScimFilterTranslator<T> uses [ScimProperty] annotations on your
entity class to resolve attribute name → C# property mappings automatically.
// Works automatically when TUser properties are annotated with [ScimProperty]
builder.Services.AddScoped<IScimFilterTranslator<MyUser>, GenericScimFilterTranslator<MyUser>>();
Custom filter translator (advanced)
Notes on IQueryable<T>¶
Use a real IQueryable source
- The
QueryUsers()/QueryGroups()methods must return a realIQueryable<T>source (EF CoreDbSet.AsQueryable(), CosmosContainer.GetItemLinqQueryable<T>(), etc.) - EzSCIM calls
.Where(filterExpression)→.Skip()→.Take()→.ToListAsync()on it - In-memory lists (
list.AsQueryable()) work for simple cases but load all rows first - If your data source does not support
IQueryable, implementIScimRepositorydirectly and apply filters manually