Action Filters no Dext permitem executar código antes e depois da execução de uma action de controller, de forma declarativa usando atributos. Inspirado no ASP.NET Core.
Action Filters são interceptadores que executam lógica em pontos específicos do pipeline de execução de uma action:
- OnActionExecuting: Antes da action executar
- OnActionExecuted: Depois da action executar (ou se houver exceção)
[LogAction] // ← Filter executado automaticamente
[DextGet('/users')]
function GetUsers: IResult;- ✅ Reutilização: Lógica comum em um só lugar
- ✅ Declarativo: Código limpo e legível
- ✅ Composição: Combine múltiplos filtros
- ✅ Testável: Filtros podem ser testados isoladamente
O Dext já vem com 5 filtros prontos para uso:
Loga automaticamente o tempo de execução e resultado da action.
[LogAction]
[DextGet('/api/users')]
function GetUsers: IResult;Output no console:
[ActionFilter] Executing: TUserController.GetUsers (GET /api/users)
[ActionFilter] Executed: TUserController.GetUsers - SUCCESS (took 45 ms)
Valida que um header específico está presente na requisição.
[RequireHeader('X-API-Key', 'API Key is required')]
[DextPost('/api/data')]
function PostData: IResult;Comportamento:
- Se o header estiver presente: continua normalmente
- Se o header estiver ausente: retorna
400 Bad Requestcom mensagem de erro
Adiciona headers de cache HTTP à resposta.
[ResponseCache(60, 'public')] // Cache por 60 segundos
[DextGet('/api/products')]
function GetProducts: IResult;Headers adicionados:
Cache-Control: public, max-age=60
Parâmetros:
Duration: Tempo em segundosLocation:'public','private', ou'no-cache'(padrão:'public')
Adiciona headers customizados à resposta.
[AddHeader('X-Custom-Header', 'MyValue')]
[DextGet('/api/info')]
function GetInfo: IResult;Placeholder para validação customizada (pode ser estendido).
[ValidateModel]
[DextPost('/api/users')]
function CreateUser([FromBody] User: TUserRequest): IResult;uses
Dext.Filters;
type
// Filtro que verifica se o usuário é admin
RequireAdminAttribute = class(ActionFilterAttribute)
public
procedure OnActionExecuting(AContext: IActionExecutingContext); override;
end;
implementation
procedure RequireAdminAttribute.OnActionExecuting(AContext: IActionExecutingContext);
begin
// Verificar se o usuário é admin
if (AContext.HttpContext.User = nil) or
(not AContext.HttpContext.User.IsInRole('Admin')) then
begin
// Short-circuit: retornar 403 Forbidden
AContext.Result := Results.StatusCode(403, '{"error":"Admin access required"}');
end;
end;[RequireAdmin]
[DextDelete('/api/users/{id}')]
function DeleteUser(Id: Integer): IResult;type
AuditAttribute = class(ActionFilterAttribute)
private
FAction: string;
public
constructor Create(const AAction: string);
procedure OnActionExecuted(AContext: IActionExecutedContext); override;
end;
constructor AuditAttribute.Create(const AAction: string);
begin
inherited Create;
FAction := AAction;
end;
procedure AuditAttribute.OnActionExecuted(AContext: IActionExecutedContext);
begin
if not Assigned(AContext.Exception) then
begin
// Logar ação bem-sucedida
var UserId := AContext.HttpContext.User.Identity.Name;
WriteLn(Format('[Audit] User %s performed %s at %s',
[UserId, FAction, DateTimeToStr(Now)]));
end;
end;
// Uso:
[Audit('DELETE_USER')]
[DextDelete('/api/users/{id}')]
function DeleteUser(Id: Integer): IResult;Quando você aplica múltiplos filtros, eles executam em ordem específica:
[FilterA]
[FilterB]
[FilterC]
[DextGet('/test')]
function Test: IResult;Ordem de execução:
FilterA.OnActionExecutingFilterB.OnActionExecutingFilterC.OnActionExecuting- Action executa
FilterC.OnActionExecuted(ordem reversa!)FilterB.OnActionExecutedFilterA.OnActionExecuted
Filtros podem ser aplicados tanto no controller quanto no método:
[LogAction] // ← Aplica a TODOS os métodos
[DextController('/api')]
TUserController = class
public
[ResponseCache(60)] // ← Aplica APENAS a este método
[DextGet('/users')]
function GetUsers: IResult;
end;Ordem:
- Filtros do Controller (OnActionExecuting)
- Filtros do Método (OnActionExecuting)
- Action
- Filtros do Método (OnActionExecuted - reverso)
- Filtros do Controller (OnActionExecuted - reverso)
Um filtro pode interromper a execução definindo um Result:
procedure OnActionExecuting(AContext: IActionExecutingContext);
begin
if not IsValid then
begin
AContext.Result := Results.BadRequest('{"error":"Invalid"}');
// Action NÃO será executada
// Filtros subsequentes NÃO serão executados
end;
end;type
UserRateLimitAttribute = class(ActionFilterAttribute)
private
FMaxRequests: Integer;
FWindowSeconds: Integer;
public
constructor Create(AMaxRequests, AWindowSeconds: Integer);
procedure OnActionExecuting(AContext: IActionExecutingContext); override;
end;
// Uso:
[UserRateLimit(10, 60)] // 10 req/min por usuário
[DextPost('/api/expensive-operation')]
function ExpensiveOperation: IResult;type
WrapResponseAttribute = class(ActionFilterAttribute)
public
procedure OnActionExecuted(AContext: IActionExecutedContext); override;
end;
procedure WrapResponseAttribute.OnActionExecuted(AContext: IActionExecutedContext);
begin
// Envolver resposta em um envelope padrão
// { "success": true, "data": {...} }
end;type
LogExceptionsAttribute = class(ActionFilterAttribute)
public
procedure OnActionExecuted(AContext: IActionExecutedContext); override;
end;
procedure LogExceptionsAttribute.OnActionExecuted(AContext: IActionExecutedContext);
begin
if Assigned(AContext.Exception) then
begin
// Logar exceção em sistema externo
LogToSentry(AContext.Exception);
// Marcar como handled para não propagar
AContext.ExceptionHandled := True;
// Retornar resposta customizada
AContext.Result := Results.StatusCode(500, '{"error":"Internal error"}');
end;
end;type
RequirePermissionAttribute = class(ActionFilterAttribute)
private
FPermission: string;
public
constructor Create(const APermission: string);
procedure OnActionExecuting(AContext: IActionExecutingContext); override;
end;
// Uso:
[RequirePermission('users.delete')]
[DextDelete('/api/users/{id}')]
function DeleteUser(Id: Integer): IResult;Contexto disponível ANTES da action executar.
property HttpContext: IHttpContext;
property ActionDescriptor: TActionDescriptor;
property Result: IResult; // Set para short-circuitContexto disponível DEPOIS da action executar.
property HttpContext: IHttpContext;
property ActionDescriptor: TActionDescriptor;
property Result: IResult; // Pode modificar o resultado
property Exception: Exception; // Se houver exceção
property ExceptionHandled: Boolean; // Set para marcar como handledInformações sobre a action.
ControllerName: string;
ActionName: string;
HttpMethod: string;
Route: string;Classe base para criar filtros.
procedure OnActionExecuting(AContext: IActionExecutingContext); virtual;
procedure OnActionExecuted(AContext: IActionExecutedContext); virtual;| Feature | Dext | ASP.NET Core |
|---|---|---|
| Action Filters | ✅ | ✅ |
| OnActionExecuting | ✅ | ✅ |
| OnActionExecuted | ✅ | ✅ |
| Short-circuit | ✅ | ✅ |
| Exception Handling | ✅ | ✅ |
| Controller-level Filters | ✅ | ✅ |
| Global Filters | ❌ (futuro) | ✅ |
| Async Filters | ❌ (limitação Delphi) | ✅ |
- Mantenha filtros simples: Um filtro deve fazer uma coisa bem feita
- Use short-circuit com cuidado: Apenas quando realmente necessário
- Evite lógica de negócio: Filtros são para cross-cutting concerns
- Teste isoladamente: Filtros devem ser testáveis sem controller
- Documente comportamento: Especialmente se modificar Result ou Exception
- Global Filters (aplicar a todos os controllers)
- Result Filters (executam após IResult.Execute)
- Exception Filters (especializados em tratar exceções)
- Resource Filters (executam antes do model binding)
Dext Framework - Modern Web Framework for Delphi