Customizing Tenant Queries

By default, Bloqs provides a built-in GetTenantsQuery that determines which tenants are visible to the user. You can override this query in your API and provide your own logic to decide who can see which tenants.

Example: Overriding GetTenantsQuery

using Bloqs.Api.Engine.DataAccess;
using Bloqs.App.Engine;
using Bloqs.App.Engine.Queries;
using Bloqs.App.Engine.Queries.AccessControl;
using Bloqs.App.Engine.Queries.Query;
using Templates.SampleApp.Models.Global;

namespace Templates.SampleApp.Api.Queries.AccessControl;

public class GetTenants(IDataServiceCreator dataServiceCreator)
    : IQueryHandler<GetTenantsQuery, GetTenantsQueryResult>
{
    public async Task<GetTenantsQueryResult> HandleAsync(
        GetTenantsQuery query,
        CancellationToken cancellationToken = default
    )
    {
        var dataService = dataServiceCreator.GetDataService<Tenant>(
            DataServiceCreatorArgs.Create<Tenant>(query.AppName, Constants.Tenant.NoTenantName)
        );

        // Example: filter tenants by OrganizationId (could come from user claims)
        var currentUserOrgId = Guid.Parse("8a5a49b0-7f35-4f52-8e6b-d3a1cbead2b7");
        var tenants = await dataService.QueryAsync(t => t.OrganizationId == currentUserOrgId) ?? [];

        if (!tenants.Any(x => x.Name == Constants.Tenant.DefaultTenantName))
        {
            // Ensure the default tenant always exists
            tenants = tenants.Concat(
                [new Tenant() { Name = Constants.Tenant.DefaultTenantName }]
            );
        }

        return new GetTenantsQueryResult() { Tenants = tenants };
    }
}

Key Points

  • Registering the handler To make this query available, the class must be marked with the [QueryHandler] attribute. This tells Bloqs to register your handler and replace the default tenant query.

  • QueryAsync with filters The DataService exposes an overload of QueryAsync that accepts an expression filter. For example:

    var tenants = await dataService.QueryAsync(t => t.OrganizationId == currentUserOrgId);

    This ensures filtering is applied directly at the database level, so only relevant tenants are retrieved.


Custom Models

In this sample, we customize the tenant model to link tenants directly to an organization.

Tenant

using Bloqs.App.Engine.Attributes;

namespace Templates.SampleApp.Models.Global;

public class Tenant : Bloqs.App.Engine.AccessControl.Tenant
{
    [Title("Organization")]
    [Relation(typeof(Organization))]
    public Guid OrganizationId { get; set; }
}

Organization

using Bloqs.App.Engine.Entities;

namespace Templates.SampleApp.Models.Global;

public class Organization : Entity
{
    public required string Name { get; set; } = string.Empty;
}

Why Customize?

  • Control who can see what tenants (e.g., only tenants linked to a user’s organization).

  • Use QueryAsync with expressions to filter efficiently at the database level.

  • Ensure that business rules are enforced at the API level.

  • Extend the Bloqs model with domain-specific relationships, like linking tenants to organizations.

Last updated