User Management Example
A complete example of using Orleans.Search for user management with searchable profiles.
Define the Grain Interface
IUserGrain.cs
public interface IUserGrain : IGrainWithStringKey
{
Task SetInfoAsync(string email, string displayName, bool isActive);
Task<string> GetEmailAsync();
Task<string> GetDisplayNameAsync();
Task<bool> IsActiveAsync();
Task DeactivateAsync();
}Define the Searchable State
UserState.cs
using TGHarker.Orleans.Search;
[Searchable(typeof(IUserGrain))]
[GenerateSerializer]
public class UserState
{
[Queryable]
[FullTextSearchable(Weight = 2.0)]
[Id(0)]
public string Email { get; set; } = string.Empty;
[Queryable]
[FullTextSearchable(Weight = 1.5)]
[Id(1)]
public string DisplayName { get; set; } = string.Empty;
[Queryable(Indexed = true)]
[Id(2)]
public bool IsActive { get; set; } = true;
[Id(3)]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[Id(4)]
public DateTime? LastLoginAt { get; set; }
}Implement the Grain
UserGrain.cs
public class UserGrain : Grain, IUserGrain
{
private readonly IPersistentState<UserState> _state;
public UserGrain(
[PersistentState("user", "UserStorage")] IPersistentState<UserState> state)
{
_state = state;
}
public async Task SetInfoAsync(string email, string displayName, bool isActive)
{
_state.State.Email = email;
_state.State.DisplayName = displayName;
_state.State.IsActive = isActive;
await _state.WriteStateAsync();
}
public Task<string> GetEmailAsync() => Task.FromResult(_state.State.Email);
public Task<string> GetDisplayNameAsync() => Task.FromResult(_state.State.DisplayName);
public Task<bool> IsActiveAsync() => Task.FromResult(_state.State.IsActive);
public async Task DeactivateAsync()
{
_state.State.IsActive = false;
await _state.WriteStateAsync();
}
}Configure the Silo
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.UseOrleans(siloBuilder =>
{
siloBuilder.UseLocalhostClustering();
siloBuilder.AddMemoryGrainStorage("UserStorage");
siloBuilder.AddSearchableGrainStorage("UserStorage");
});
builder.Services.AddOrleansSearch()
.UsePostgreSql("Host=localhost;Database=orleanssearch;Username=postgres;Password=postgres");
var app = builder.Build();User Service
UserService.cs
public class UserService
{
private readonly IClusterClient _client;
public UserService(IClusterClient client)
{
_client = client;
}
// Create a new user
public async Task<IUserGrain> CreateUserAsync(string userId, string email, string displayName)
{
var grain = _client.GetGrain<IUserGrain>(userId);
await grain.SetInfoAsync(email, displayName, true);
return grain;
}
// Find user by email
public async Task<IUserGrain?> FindByEmailAsync(string email)
{
return await _client.Search<IUserGrain>()
.Where(u => u.Email == email)
.FirstOrDefaultAsync();
}
// Search users by email domain
public async Task<List<IUserGrain>> FindByEmailDomainAsync(string domain)
{
return await _client.Search<IUserGrain>()
.Where(u => u.Email.Contains(domain))
.ToListAsync();
}
// Get all active users
public async Task<List<IUserGrain>> GetActiveUsersAsync()
{
return await _client.Search<IUserGrain>()
.Where(u => u.IsActive == true)
.ToListAsync();
}
// Get all inactive users
public async Task<List<IUserGrain>> GetInactiveUsersAsync()
{
return await _client.Search<IUserGrain>()
.Where(u => u.IsActive == false)
.ToListAsync();
}
// Count active users
public async Task<int> CountActiveUsersAsync()
{
return await _client.Search<IUserGrain>()
.Where(u => u.IsActive == true)
.CountAsync();
}
// Check if email is taken
public async Task<bool> IsEmailTakenAsync(string email)
{
return await _client.Search<IUserGrain>()
.Where(u => u.Email == email)
.AnyAsync();
}
// Search by display name
public async Task<List<IUserGrain>> SearchByNameAsync(string searchTerm)
{
return await _client.Search<IUserGrain>()
.Where(u => u.DisplayName.Contains(searchTerm))
.ToListAsync();
}
}API Endpoints
Program.cs (endpoints)
app.MapGet("/api/users/search", async (
string? email,
string? name,
bool? active,
UserService userService) =>
{
if (!string.IsNullOrEmpty(email))
{
var user = await userService.FindByEmailAsync(email);
return user != null
? Results.Ok(new { id = user.GetPrimaryKeyString(), email = await user.GetEmailAsync() })
: Results.NotFound();
}
if (!string.IsNullOrEmpty(name))
{
var users = await userService.SearchByNameAsync(name);
var results = new List<object>();
foreach (var user in users)
{
results.Add(new
{
id = user.GetPrimaryKeyString(),
email = await user.GetEmailAsync(),
displayName = await user.GetDisplayNameAsync()
});
}
return Results.Ok(results);
}
if (active.HasValue)
{
var users = active.Value
? await userService.GetActiveUsersAsync()
: await userService.GetInactiveUsersAsync();
return Results.Ok(users.Select(u => u.GetPrimaryKeyString()));
}
return Results.BadRequest("Provide email, name, or active parameter");
});
app.MapPost("/api/users", async (
CreateUserRequest request,
UserService userService) =>
{
if (await userService.IsEmailTakenAsync(request.Email))
{
return Results.Conflict("Email already taken");
}
var userId = Guid.NewGuid().ToString();
var user = await userService.CreateUserAsync(userId, request.Email, request.DisplayName);
return Results.Created($"/api/users/{userId}", new { id = userId });
});
record CreateUserRequest(string Email, string DisplayName);Usage Examples
// Create users
await userService.CreateUserAsync("user-1", "alice@example.com", "Alice Johnson");
await userService.CreateUserAsync("user-2", "bob@example.com", "Bob Smith");
await userService.CreateUserAsync("user-3", "charlie@other.com", "Charlie Brown");
// Find by exact email
var alice = await userService.FindByEmailAsync("alice@example.com");
// Find by email domain
var exampleUsers = await userService.FindByEmailDomainAsync("@example.com");
// Returns: alice, bob
// Search by name
var results = await userService.SearchByNameAsync("John");
// Returns: alice (Alice Johnson)
// Count active users
var activeCount = await userService.CountActiveUsersAsync();
// Check email availability
var isTaken = await userService.IsEmailTakenAsync("alice@example.com");
// Returns: trueNext Steps
- E-Commerce Products - Product catalog example
- Order Tracking - Order management example