Overview
Silky's RPC filter pipeline is modeled after ASP.NET Core MVC's filter system, driving different filter types through a state machine in a fixed execution order. The framework maintains separate filter pipelines on the server side (LocalInvoker) and the client side (RemoteInvoker).
Filter Type Hierarchy
Server-Side Filters
| Interface | Execution Timing | Description |
|---|---|---|
IServerAuthorizationFilter / IAsyncServerAuthorizationFilter | Earliest — before Action execution | Authorization filters; set Result to short-circuit the pipeline |
IServerExceptionFilter / IAsyncServerExceptionFilter | After exceptions occur | Catch and handle exceptions from Action or Result filters |
IServerFilter / IAsyncServerFilter | Before and after Action execution | General Action filters; can short-circuit by not calling next() |
IServerResultFilter / IAsyncServerResultFilter | Before and after result is written | Can modify or replace the execution result |
IAlwaysRunServerResultFilter / IAsyncAlwaysRunServerResultFilter | Always runs regardless of short-circuiting | For cleanup operations that must execute |
Client-Side Filters
Symmetric to server-side, with Client prefix:
| Interface | Description |
|---|---|
IClientFilter / IAsyncClientFilter | Client-side Action filter |
IClientExceptionFilter / IAsyncClientExceptionFilter | Client-side exception filter |
IClientResultFilter / IAsyncClientResultFilter | Client-side result filter |
IAlwaysRunClientResultFilter | Always-run client-side result filter |
Creating Custom Filters
Extend ServerFilterAttribute (or ClientFilterAttribute) and override the needed methods:
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method,
AllowMultiple = true, Inherited = true)]
public class TimingServerFilter : ServerFilterAttribute
{
private Stopwatch _sw;
public override void OnActionExecuting(ServerInvokeExecutingContext context)
{
_sw = Stopwatch.StartNew();
}
public override void OnActionExecuted(ServerInvokeExecutedContext context)
{
_sw.Stop();
var serviceEntryId = context.ServiceEntry.Id;
Console.WriteLine($"[{serviceEntryId}] Elapsed: {_sw.ElapsedMilliseconds}ms");
}
}
// Apply to interface or method:
[ServiceRoute]
[TimingServerFilter]
public interface IOrderAppService
{
Task<OrderDto> GetByIdAsync(long id);
}
Filter Order
IOrderedFilter.Order (lower value = executes first):
public class TimingServerFilter : ServerFilterAttribute
{
public override int Order => -100; // runs before default filters
}
Filter Discovery & Resolution
At Service Entry Construction Time
The framework collects all IFilterMetadata attributes from the interface and method:
- Interface-level filters:
FilterScope.Global(applied to all methods) - Method-level filters:
FilterScope.Action - Ordered by
IOrderedFilter.Orderwithin each type
At Runtime (DefaultFilterProvider)
DefaultFilterProvider instantiates FilterDescriptor into executable FilterItem objects for each call. Filter instances are either:
- Attribute-based: Created fresh per call (Transient)
- Service-based: Resolved from the DI container (respects registered lifetime)
State Machine Execution
The LocalInvoker drives execution through states:
State: ActionBegin
│
├── Run all Authorization filters (IServerAuthorizationFilter)
│ If any sets Result → skip to ResultBegin
│
▼
State: AuthorizationBegin
│
├── Run Action filter OnActionExecuting() for each filter
│ If any short-circuits (sets Result without calling next) → skip to ResultBegin
│
▼
State: ActionInside
│
├── ObjectMethodExecutor invokes the business method
│ Exception → ExceptionFilter handling
│
▼
State: ResultBegin
│
├── Run Result filter OnResultExecuting() for each filter
├── Write result
└── Run Result filter OnResultExecuted() for each filter
│
▼
State: InvokeEnd
│ Result available in LocalInvoker.Result
Short-Circuiting Rules
- Authorization filter: Setting
context.Resultshort-circuits directly toResultBegin(skips all Action filters and the business method) - Action filter (OnActionExecuting): Not calling
next()short-circuits toResultBegin - Exception filter: Setting
context.ExceptionHandled = trueprevents the exception from propagating - AlwaysRun filters: Execute regardless of any short-circuiting — suitable for guaranteed cleanup
Built-in Filters
| Filter | Description |
|---|---|
AuthorizationServerFilter | Validates [Authorize] attribute and JWT claims from RpcContext |
CachingInterceptFilter | Implements cache read/update/remove interception (delegates to CachingInterceptor) |
ValidationFilter | Validates method parameters using IValidator (FluentValidation or DataAnnotations) |
MaxConcurrentHandlingFilter | Server-side: enforces MaxConcurrentHandlingCount concurrency limit |
