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.
| Element | Value |
|---|---|
| Data query suffix | $qd |
| Detection header | isQuery |
| Suffix constant | IDataQueryRequest.QueryIdentifier |
| Header constant | IDataQueryRequest.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.
[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,
});
}
}
LgControllerBaseLagoon 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.
@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>
public partial class FirmsPage : LgPage
{
// Argument métier transmis au contrôleur dans QueryArg<int?>.
private int? _countryId = 1;
}
What the attribute applies
| Client request | Server effect |
|---|---|
Filters | Applies the column filters. |
Sorts | Applies the sort order. |
CurrentPage / PageSize | Returns only the requested page. |
TotalCount | Computes the total row count if requested. |
DistinctProperty | Returns the distinct values of a property. |
Calculations | Returns the requested calculations. |
Export | Returns a file using the requested export provider. |
ExportProviders | Exposes the export providers declared on the action. |
Points of attention
| Point | Rule |
|---|---|
| Return type | Prefer IQueryable<T> to let EF Core compose the SQL query. |
| Projection | Project to a ViewModel before returning to avoid exposing the full EF Core entity. |
| Early execution | Avoid ToList() before returning: filters and sorts would be applied in memory. |
| Errors | For actions with business logic, use try / catch and Problem(ex) via LgControllerBase. |
| Export | Exports are handled in the filter when Export is present in the request. |
Example with error handling
[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);
}
}