Skip to main content

Queryable attribute and OData querying

QueryableAttribute is a server action filter. It reads the data queries issued by Lagoon components, then applies the requested filters, sorts, pages, calculations, distinct queries, and exports to the result.

LgGridView uses this mechanism when it loads remote data via ControllerUri. See also the LgGridView page.

Principle​

The client posts a request to the controller URI suffixed with $qd. The server middleware detects this suffix, routes the request to the actual GET action, and adds the isQuery header.

ElementValue
Data query suffix$qd
Detection headerisQuery
Suffix constantIDataQueryRequest.QueryIdentifier
Header constantIDataQueryRequest.HeaderQueryIdentifier

QueryableAttribute then loads the request body, executes the action, retrieves the returned value, and applies the transformation via ResponseFactory<T>.

Controller-side contract​

Apply [Queryable] to an action that returns a queryable source. The action can receive a QueryArg<T> to retrieve the business argument sent by the grid.

FirmsController.cs
[Authorize]
[Route("api/[controller]")]
public class FirmsController : LgControllerBase
{
private readonly ApplicationDbContext _db;

public FirmsController(ApplicationDbContext db)
{
_db = db;
}

[Queryable]
[HttpGet("GetFirmsByCountry")]
public IQueryable<VmCountryFirm> Get(QueryArg<int?> data)
{
// Retourne une requête EF Core : Lagoon appliquera filtres, tris et pagination après l'action.
return _db.Firms.Where(c => c.CountryId == data.Value).Select(x => new VmCountryFirm
{
Id = x.Id,
FirmName = x.Name,
});
}
}
tip
LgControllerBase

Lagoon API controllers inherit from LgControllerBase to benefit from standardized error handling with Problem(ex) and the framework's server-side helpers.

Querying from LgGridView​

On the client side, LgGridView configures remote loading with ControllerUri. The filters, sorts, pagination, and exports configured by the user are sent automatically.

FirmsPage.razor
@inherits LgPage

<LgGridView TItem="VmCountryFirm"
StateId="firms-by-country"
ControllerUri="api/Firms/GetFirmsByCountry"
ControllerQueryArg="_countryId"
Title="#FirmsTitle">
<Columns Context="firm">
<LgGridTextColumn @bind-Value="firm.FirmName" Title="#FirmName" />
</Columns>
</LgGridView>
FirmsPage.razor.cs
public partial class FirmsPage : LgPage
{
// Argument métier transmis au contrôleur dans QueryArg<int?>.
private int? _countryId = 1;
}

What the attribute applies​

Client requestServer effect
FiltersApplies the column filters.
SortsApplies the sort order.
CurrentPage / PageSizeReturns only the requested page.
TotalCountComputes the total row count if requested.
DistinctPropertyReturns the distinct values of a property.
CalculationsReturns the requested calculations.
ExportReturns a file using the requested export provider.
ExportProvidersExposes the export providers declared on the action.

Points of attention​

PointRule
Return typePrefer IQueryable<T> to let EF Core compose the SQL query.
ProjectionProject to a ViewModel before returning to avoid exposing the full EF Core entity.
Early executionAvoid ToList() before returning: filters and sorts would be applied in memory.
ErrorsFor actions with business logic, use try / catch and Problem(ex) via LgControllerBase.
ExportExports are handled in the filter when Export is present in the request.

Example with error handling​

FirmsController.cs
[Queryable]
[HttpGet("GetFirmsByCountry")]
public IActionResult GetFirmsByCountry(QueryArg<int?> data)
{
try
{
// Conserve IQueryable jusqu'au retour pour permettre le requêtage serveur.
IQueryable<VmCountryFirm> firms = _db.Firms.Where(c => c.CountryId == data.Value).Select(x => new VmCountryFirm
{
Id = x.Id,
FirmName = x.Name,
});
return Ok(firms);
}
catch (Exception ex)
{
return Problem(ex);
}
}