Nextended.ResponseFilters
10.1.34
dotnet add package Nextended.ResponseFilters --version 10.1.34
NuGet\Install-Package Nextended.ResponseFilters -Version 10.1.34
<PackageReference Include="Nextended.ResponseFilters" Version="10.1.34" />
<PackageVersion Include="Nextended.ResponseFilters" Version="10.1.34" />
<PackageReference Include="Nextended.ResponseFilters" />
paket add Nextended.ResponseFilters --version 10.1.34
#r "nuget: Nextended.ResponseFilters, 10.1.34"
#:package Nextended.ResponseFilters@10.1.34
#addin nuget:?package=Nextended.ResponseFilters&version=10.1.34
#tool nuget:?package=Nextended.ResponseFilters&version=10.1.34
![]()
Nextended.ResponseFilters
Fluent, provider-agnostic pipeline that redacts, masks, rounds, truncates, hashes, prunes and restructures response DTOs before serialization โ per request, per user, per permission.
๐ Documentation: English ยท Deutsch
Fluent, attribute-aware response-filtering pipeline for redacting, masking, or transforming object graphs before serialization.
A ResponseFilter<T> looks like a FluentValidator<T> โ but instead of validating, it mutates the DTO right before it leaves your service: null out fields the user must not see, mask emails, replace internal flags, drop collection items conditionally.
Installation
dotnet add package Nextended.ResponseFilters
# ASP.NET Core integration:
dotnet add package Nextended.ResponseFilters.AspNetCore
Quick Start
public class OrderResponseFilter : ResponseFilter<OrderDto>
{
public OrderResponseFilter()
{
// Null out cost fields unless the user has the "Finance" role
Nullify(x => x.TotalCost, x => x.UnitCost)
.Unless(WhenInstance(_ => HasRole("Finance")));
// Mask credit card: 1234########5678
Mask(x => x.CreditCard).KeepFirst(4).KeepLast(4).When((_, ctx) => !ctx.IsAdmin());
// Pattern-replace email for unauthenticated callers
Mask(x => x.CustomerEmail).WithPattern("***@***.***")
.When((_, ctx) => !ctx.Services.GetRequiredService<ICurrentUser>().IsAuthenticated);
// Truncate notes after 200 chars with ellipsis
Truncate(x => x.Notes).After(200, "โฆ").Always();
// Reset multiple heterogeneous fields to their default values
SetToDefault(x => x.InternalScore, x => x.IsBookmarked, x => x.HiddenTags)
.When(NotInRole("Internal"));
// Hash a token (default: SHA-256 hex)
Hash(x => x.AuditToken).Always();
// Round prices for non-premium users
Round(x => x.Price).To(0).When(NotInRole("Premium"));
// Clear an internal-only collection
Clear(x => x.DebugTrace).When(NotInRole("Internal"));
// Strip hidden line items, then cap at 10
RemoveItems<LineDto>(x => x.Lines)
.Where(l => l.Hidden)
.Always();
Take<LineDto>(x => x.Lines).First(10).When(NotInRole("Premium"));
// Recurse into a collection โ each line item gets its own sub-filter
ForEach(x => x.Lines, line =>
{
line.Nullify(l => l.UnitCost).Unless(_ => HasRole("Finance"));
line.Truncate(l => l.Description).After(80).Always();
});
// Escape hatch for cross-property logic
Apply((order, _) =>
{
if (order.Status == "Cancelled") order.PaymentDetails = null;
}).Always();
}
private static bool HasRole(string role) => /* check current principal */ false;
private static SyncPredicate<OrderDto> NotInRole(string role) => (_, _) => !HasRole(role);
}
Then wire it up:
// Program.cs / Startup.cs
services.AddResponseFilters(new[] { typeof(OrderResponseFilter).Assembly });
// Manually run it (e.g. in a worker service)
var pipeline = sp.GetRequiredService<IResponseFilterPipeline>();
await pipeline.ProcessAsync(myOrderDto, new ResponseFilterContext(sp));
For ASP.NET Core: see Nextended.ResponseFilters.AspNetCore โ one extension call and every controller response is filtered automatically.
Concepts
| Concept | Purpose |
|---|---|
ResponseFilter<T> |
Abstract base class. Inherit, configure rules in the constructor. |
IResponseFilterContext |
Per-request bag: IServiceProvider, CancellationToken, Items for memoizing async work. |
IResponseFilterPipeline |
Walks the object graph depth-first and applies all matching filters. |
IResponseFilterRegistry |
Resolves filters per type from DI. |
Rule builders
Property mutators
| Builder | Purpose | Example |
|---|---|---|
Nullify(...) |
Set one or more nullable properties to null. |
Nullify(x => x.Cost, x => x.Notes).When(...) |
SetValue(...).To(...) |
Set a property to a constant or computed value. | SetValue(x => x.Status).To("hidden").When(...) |
SetToDefault(...) |
Reset properties to default(TProperty) โ handles nullable, non-nullable value types, and reference types in one call. |
SetToDefault(x => x.Cost, x => x.IsActive, x => x.Notes).When(...) |
Replace(...).With(...) |
Synonym for SetValue (reads better when there's an existing value). |
Replace(x => x.Email).With("***").When(...) |
Transform(...).Using(...) |
Map a property through a pure function. | Transform(x => x.Notes).Using(s => s?.ToUpper()).Always() |
Clear(...) |
Empty a property: string โ "", mutable list โ in-place .Clear(), array โ empty array, else โ null. |
Clear(x => x.Lines).When(...) |
String operations
| Builder | Purpose | Example |
|---|---|---|
Mask(...) |
String masking with KeepFirst(n), KeepLast(n), With(char), WithPattern(string). |
Mask(x => x.Card).KeepFirst(4).KeepLast(4).When(...) |
Truncate(...).After(n) |
Cut strings at N chars, optionally with suffix. | Truncate(x => x.Notes).After(200, "โฆ").Always() |
Hash(...) |
Replace string with a hash. Defaults to SHA-256 hex; .AsSha1(), .AsSha512(), .AsMd5(), or .Using(fn). |
Hash(x => x.Token).AsSha256().When(...) |
Numeric operations
| Builder | Purpose | Example |
|---|---|---|
Round(...).To(n) |
Round decimal/double/float. Choose midpoint rule with .To(n, mode). .ToInteger() for whole numbers. |
Round(x => x.Price).To(2).Always() |
Collection operations
| Builder | Purpose | Example |
|---|---|---|
ForEach(...) |
Recurse into a collection property; configure a sub-filter inline. | ForEach(x => x.Lines, line => line.Nullify(l => l.Cost).When(...)) |
RemoveItems(...).Where(pred) |
Remove items matching the predicate. Mutates IList<T> in place; rebuilds arrays. |
RemoveItems<Line>(x => x.Lines).Where(l => l.IsHidden).When(...) |
KeepOnly(...).Where(pred) |
Inverse of RemoveItems โ keep matching items, drop the rest. |
KeepOnly<Line>(x => x.Lines).Where(l => l.IsPublic).When(...) |
Take(...).First(n) / .Last(n) |
Limit a collection to the first/last N items. | Take<Line>(x => x.Lines).First(10).When(...) |
Structural operations (key-level)
A POCO can't drop a property or rename its JSON key at runtime, so these builders don't mutate the
instance โ they record an edit that is replayed against the serialized JSON tree. In ASP.NET Core
this happens automatically (the result filter swaps in the transformed JsonNode); when you run the
pipeline yourself, apply the edits with JsonStructuralTransformer.Transform(dto, ctx.StructuralEdits, jsonOptions).
| Builder | Purpose | Example |
|---|---|---|
Remove(...) |
Drop one or more properties from the output entirely (key disappears โ unlike Nullify, which keeps a null). |
Remove(x => x.Internal, x => x.Debug).When(...) |
Rename(...).To(name) |
Rename a property's serialized key to a fixed name. | Rename(x => x.Id).To("orderId").Always() |
TransformKey(...).Using(fn) |
Transform one property's serialized key through a function. | TransformKey(x => x.Id).Using(k => "x_" + k).Always() |
TransformKeys().Using(fn) |
Transform every property's serialized key (e.g. enforce a naming convention for one response). | TransformKeys().Using(k => k.ToUpperInvariant()).When(...) |
AddProperty(name).From(...) |
Inject an extra key that doesn't exist on the CLR type (computed per instance/context). | AddProperty("displayName").From(o => $"#{o.Id}").Always() |
The key transform receives the serialized key (after any JsonNamingPolicy / [JsonPropertyName]),
and edits resolve against the CLR member, so they keep working under any naming policy. Structural edits
inside ForEach sub-filters are applied per element. (Limitation: values reached only through dictionary
entries are not descended into for nested structural edits.)
Metadata-aware property selection
Two complementary ways to target properties by their PropertyInfo (e.g. an attribute) instead of, or
in addition to, naming them explicitly. Property metadata is static, so both are resolved at build time
โ zero runtime cost.
.WhenProperty(p => โฆ) โ a cross-cutting refinement available on every property-targeting builder.
It restricts the rule to the already-selected properties whose PropertyInfo matches, and composes with
When/Unless (and can be chained, logical AND):
// Only null the listed properties that carry [Secret]
Nullify(x => x.A, x => x.B, x => x.C)
.WhenProperty(p => p.GetCustomAttribute<SecretAttribute>() != null)
.When(NotInRole("Admin"));
Remove(x => x.Token).WhenProperty(p => p.PropertyType == typeof(string));
Properties(...) / PropertiesWhere(...) โ a transposed entry point: select the property set first,
then pick a type-agnostic operation (.Nullify(), .Remove(), .SetToDefault(), .TransformKey()).
PropertiesWhere scans the type and selects by metadata, so you don't have to enumerate the properties:
// Remove every [Secret] property from the response
PropertiesWhere(p => p.GetCustomAttribute<SecretAttribute>() != null).Remove().Always();
// Null a named set
Properties(x => x.Name, x => x.Id).Nullify().When(...);
This avoids a combinatorial NullifyWhere/RemoveWhere/โฆ explosion: one WhenProperty works for all
operations, and PropertiesWhere exposes the type-agnostic operations from a single builder. (WhenProperty
has no effect on Apply, which targets no property.)
Escape hatch
| Builder | Purpose | Example |
|---|---|---|
Apply(...) / ApplyAsync(...) |
Arbitrary Action/Func<โฆ, Task> on the instance for anything the structured builders don't cover. |
Apply((dto, ctx) => dto.Status = "redacted").When(...) |
Predicate vocabulary
All builders end with the same terminal vocabulary. Each terminal accepts predicates in every shape
(no-arg, context-only, instance-only, or both), in both sync and async (Task) variants. Pick the
overload that reads best at the call site โ the library adapts it to the canonical
AsyncPredicate<T> internally.
| Terminal | Fires when โฆ |
|---|---|
.When(predicate) |
predicate returns true |
.Unless(predicate) |
predicate returns false |
.Always() |
unconditional |
.WhenAll(p1, p2, โฆ) |
all AsyncPredicate<T> predicates true (short-circuits on first false) |
.WhenAny(p1, p2, โฆ) |
at least one AsyncPredicate<T> predicate true (short-circuits on first true) |
.WhenProperty(Func<PropertyInfo,bool>) is a refinement (not a terminal): it narrows the rule to the
target properties matching a metadata predicate at build time, then you still close with one of the terminals
above. See Metadata-aware property selection.
Supported predicate shapes (each on When and Unless)
| Shape | Use case |
|---|---|
Func<bool> |
Feature flag, constant. .When(() => Config.HideCost) |
Func<Task<bool>> |
Async no-arg signal. .When(async () => await CheckExternalAsync()) |
Func<IResponseFilterContext, bool> |
Context-only sync check. .When(ctx => ctx.Items["env"] == "prod") |
Func<IResponseFilterContext, Task<bool>> |
Context-only async, ideal for DI-resolved permission checks. .When(async ctx => !await ctx.Services.GetRequiredService<IPermissionChecker>().IsGrantedAsync("โฆ")) |
Func<T, bool> |
Pure instance check. .When(o => o.IsPublic) |
Func<T, Task<bool>> |
Instance check that touches IO. .When(async o => await IsAllowedAsync(o.Id)) |
SyncPredicate<T> (= Func<T, ctx, bool>) |
Canonical sync. .When((o, ctx) => โฆ) |
AsyncPredicate<T> (= Func<T, ctx, ValueTask<bool>>) |
Canonical async. The shape WhenAll/WhenAny consume directly. |
Extending the vocabulary
Every builder implements IRuleBuilder<T>, so consumer projects can plug in their own domain-specific
terminals as ordinary extension methods โ without having to wire them per builder type:
public static class PermissionRuleBuilderExtensions
{
public static ResponseFilter<T> WhenMissingPermission<T>(this IRuleBuilder<T> b, string policy)
where T : class
=> b.When(async (_, ctx) =>
{
var checker = ctx.Services.GetRequiredService<IPermissionChecker>();
return !await checker.IsGrantedAsync(policy).ConfigureAwait(false);
});
}
Then at the call site:
Nullify(x => x.TotalCost).WhenMissingPermission("Insights.ViewFinancial");
Why use this over attributes?
| Use case | Attribute | Fluent (ResponseFilter<T>) |
|---|---|---|
| Permission-based nulling | โ | โ |
| DTO from a 3rd-party library (no attribute access) | โ | โ |
| Masking instead of nulling | โ | โ |
| Conditional on another property | โ | โ |
| Tenant/user-context-aware | โ | โ |
| Unit-testable in isolation | โ ๏ธ | โ |
If your needs are simple (always-null-on-missing-permission), attributes are fine. Use this package when you need real conditional logic, transformation, or testability.
Performance
PropertyAccessoruses compiled Expression-Tree get/set delegates (cached perPropertyInfo) โ typically 10-50ร faster thanPropertyInfo.SetValue.TypeGraphInspectorcaches per-type metadata so the graph walker never reflects twice on the same type.TypeReachabilityCacheprecomputes per response root type whether any registered filter's target is reachable in the graph. When the answer is no, the pipeline is a one-cache-lookup no-op for that response โ no reflection, no walk, no allocation.- The walker also short-circuits per branch: if a nested property's static type can't reach a filtered type, that subtree is skipped entirely.
- Cycle detection via
ReferenceEqualityComparerprevents infinite recursion on back-references.
Configuration
AddResponseFilters / AddNextendedResponseFilters accept an optional Action<ResponseFilterOptions>:
builder.Services.AddNextendedResponseFilters(
assemblies: new[] { typeof(OrderResponseFilter).Assembly },
configure: opts =>
{
// Default: propagate exceptions thrown by filter rules to the host's exception handler.
// Switch to LogAndContinue if you'd rather absorb filter bugs at the cost of visibility.
opts.ExceptionBehavior = FilterExceptionBehavior.Rethrow;
// Default: skip the entire pipeline if no registered filter's target type is reachable
// in the response's type graph. Turn off only if you have run-time polymorphism that
// the static analyzer can't see (e.g. List<object> holding heterogeneous DTOs).
opts.SkipUnaffectedResponses = true;
// Custom opt-out predicate evaluated against the response root type.
opts.SkipResponseType = t => typeof(System.IO.Stream).IsAssignableFrom(t)
|| t.Namespace?.StartsWith("Volo.Abp") == true;
});
Exception handling
By default, exceptions thrown inside a filter rule propagate โ they reach the host's global exception handler unchanged. This is the right behaviour for almost every app: a filter throwing a BusinessException or UserFriendlyException is intentional and must be visible.
If you'd rather absorb filter failures (e.g. for a public CMS that must never 500), switch to FilterExceptionBehavior.LogAndContinue โ exceptions are caught, logged via ILogger<ResponseFilterPipeline>, and remaining filters keep running.
OperationCanceledException always propagates regardless of the chosen behaviour, so request aborts and host shutdown work correctly.
Supported frameworks
net8.0net9.0net10.0
Dependencies
The Nextended family
The other 17 packages in the suite:
Core libraries
- Nextended.Core โ Foundation library โ extension methods, custom types (Money, Date, BaseId, SuperType), class mapping, deep clone, encryption, hashing and the code-generation attributes.
- Nextended.Cache โ Expression-based caching โ automatic cache keys from method expressions, CacheProvider with condition-based invalidation, thread-safe AddOrGetExisting.
Data access
- Nextended.EF โ Entity Framework Core extensions โ graph loading (LoadGraphAsync, IncludeAll, MultiInclude), declarative include definitions, paging, dynamic sorting and bulk operations.
ASP.NET Core & web
- Nextended.Web โ ASP.NET Core utilities โ zero-config OData (AddODataAuto), composable IQueryable OData appliers, strongly typed controller URLs, streaming download helpers and a background executor that can replay a captured request.
- Nextended.ResponseFilters โ Fluent, provider-agnostic pipeline that redacts, masks, rounds, truncates, hashes, prunes and restructures response DTOs before serialization โ per request, per user, per permission. (this package)
- Nextended.ResponseFilters.AspNetCore โ ASP.NET Core adapter for Nextended.ResponseFilters โ registers the pipeline as a global IAsyncResultFilter and replays structural edits against the serialized JSON tree.
UI libraries
- Nextended.Blazor โ Blazor helpers โ IBrowserFile extensions (bytes, data URLs, downloads), a hierarchical model for browsing inside uploaded zip/tar/rar archives, MIME-type detection and component-parameter reflection.
- Nextended.UI โ WPF and Windows desktop helpers โ a global input-binding manager with hold/sequence matching, DirectInput and XInput gamepad readers, key-bind capture controls, converters, behaviours, markup extensions and runtime-defined PropertyGrid types.
Code generation & tooling
- Nextended.Imaging โ Image processing โ aspect-preserving resize, crop, colour replacement, brightness-based foreground picking, thumbnail generation, byte/data-URL conversion and MIME detection from magic bytes.
- Nextended.CodeGen โ Roslyn source generator โ DTOs and interfaces from your entities, strongly typed classes from JSON/XML, lookup tables from Excel, and documentation from source files.
.NET Aspire hosting
- Nextended.Aspire โ Conditional AppHost builder extensions โ WithReferenceIf / WaitForIf / WithExplicitStartIf, strongly typed environment variables from config objects, HTTPS dev-cert wiring, Docker guards, GitHub-source resources and npm app discovery.
- Nextended.Aspire.Hosting.Supabase โ The complete Supabase stack โ Postgres, Auth (GoTrue), REST, Realtime, Storage, Studio, Kong and Edge Functions โ as one composable Aspire resource.
- Nextended.Aspire.Hosting.N8n โ The n8n workflow-automation platform as an Aspire resource, with Postgres persistence, workflow import and a typed client for triggering workflows from .NET.
- Nextended.Aspire.Hosting.Grafana โ Grafana, Prometheus, Loki, Tempo, Promtail, cAdvisor, postgres_exporter and the OpenTelemetry Collector as composable resources with auto-provisioned datasources.
- Nextended.Aspire.Hosting.WebDataStudio โ WebDataStudio โ a browser database studio for PostgreSQL, MySQL, SQL Server, SQLite, Oracle, DuckDB, ClickHouse, MongoDB and Redis โ wired to the databases of your stack, with accounts and roles, an optional SQL assistant, and an MCP endpoint for AI agents.
- Nextended.Aspire.Hosting.AspireUI โ AspireUI โ the visual AppHost builder โ as a resource inside your own Aspire stack, with an optional pre-seeded admin user and a starter stack built from your project paths.
- Nextended.Aspire.Hosting.LocalAI โ Self-hosted, OpenAI-compatible multimodal AI โ image generation, text-to-speech, speech-to-text and video โ with gallery model management, GPU support and Open WebUI.
- Nextended.Aspire.Hosting.Php โ Run PHP endpoints inside your Aspire stack โ a docroot folder or a single router script served by PHP's built-in web server, with php.ini settings as fluent options.
Links
- ๐ฆ NuGet package
- ๐ Documentation โ English
- ๐ Dokumentation โ Deutsch
- ๐ Documentation portal
- ๐งโ๐ป Source code
- ๐ Report an issue
License
GPL-3.0-or-later โ see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Nextended.Core (>= 10.1.34)
-
net8.0
- Nextended.Core (>= 10.1.34)
-
net9.0
- Nextended.Core (>= 10.1.34)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Nextended.ResponseFilters:
| Package | Downloads |
|---|---|
|
Nextended.ResponseFilters.AspNetCore
ASP.NET Core adapter for Nextended.ResponseFilters. Wires the filter pipeline into the MVC pipeline as a global IAsyncResultFilter, mutating ObjectResult.Value before serialization. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.1.34 | 59 | 8/27/2026 |
| 10.1.33 | 107 | 8/24/2026 |
| 10.1.32 | 199 | 8/20/2026 |
| 10.1.31 | 106 | 8/19/2026 |
| 10.1.30 | 108 | 8/19/2026 |
| 10.1.21 | 129 | 7/30/2026 |
| 10.1.20 | 126 | 7/26/2026 |
| 10.1.19 | 121 | 7/23/2026 |
| 10.1.18 | 120 | 7/22/2026 |
| 10.1.17 | 121 | 7/21/2026 |
| 10.1.16 | 127 | 7/21/2026 |
| 10.1.15 | 129 | 7/21/2026 |
| 10.1.14 | 122 | 7/16/2026 |
| 10.1.13 | 133 | 7/12/2026 |
| 10.1.12 | 135 | 7/12/2026 |
| 10.1.11 | 138 | 7/6/2026 |
| 10.1.10 | 147 | 6/16/2026 |
| 10.1.9 | 135 | 5/29/2026 |
| 10.1.8 | 138 | 5/19/2026 |
| 10.1.7 | 213 | 5/16/2026 |