Previously the SkiaSharp resizer ignored ImageResizeArgs.Mode entirely
and always stretched, so callers that never specified a mode were
implicitly relying on stretch output. Map None and Default to Stretch
to preserve that observable behavior while still honoring explicit
Max/Min/Crop/Pad/BoxPad values.
- SkiaSharp resizer now honors ImageResizeArgs.Mode (Stretch, Max, Min,
Crop, Pad, BoxPad) instead of stretching to the target dimensions
regardless of mode. None and Default normalize to Crop to match the
ImageSharp contributor.
- BoxPad now Max-fits the source into the target box first when the
source exceeds it, instead of cropping with negative offsets.
- SkiaSharp compressor caches the bitmap stream length before
SKBitmap.Decode takes ownership of it, so the post-encode size check
no longer accesses a disposed stream and tolerates non-seekable
inputs reaching the contributor directly.
- Add resizer tests for every mode (exact target size for fixed modes,
bounded size for Max/Min, pixel-level transparency check for BoxPad
with a source larger than the target), and a compressor test that
feeds a non-seekable stream directly to the contributor.
Existing tests only checked Result.Length which doesn't depend on
Position, so a contributor that forgot to seek the result stream back
to 0 would still pass. Add an explicit Position == 0 assertion plus a
CopyToAsync round-trip that compares the copied length against the
result stream length, so the same regression cannot slip in again.
Match the ImageSharp and Magick.NET resizer contributors which return
the result stream positioned at the start, so downstream consumers
read the bytes instead of getting an empty read at end-of-stream.
- Add SkiaSharpImageCompressorContributor and SkiaSharpCompressOptions so
Volo.Abp.Imaging.SkiaSharp ships a compressor alongside its existing
resizer.
- Bump Magick.NET-Q16-AnyCPU 14.9.1 -> 14.13.0 to clear the
NU1901/NU1902/NU1903 advisories.
- Mirror _models with Dictionary<TState, ...> populated in AddCheckModels
- GetModelOrNull goes through the dict, matching IsEnabledAsync's first-wins
- Pin first-wins via a regression test
- Add state-aware overload to ISimpleStateCheckerSerializer/Contributor
- Features and Permissions contributors recognise their batch checker and emit a per-state record
- PermissionDefinitionSerializer threads the owning permission through
- Pin equality semantics to match the batch runtime (default comparer)
When a state has multiple batch checkers (e.g. RequirePermissions plus
RequireFeatures), the per-state result was overwritten on every checker
pass, so a later 'true' silently masked an earlier 'false'. AND-combine
the results to require all checkers to pass.
- Soften IsSandboxed XML doc as a best-effort marker
- Replace #XXXXX placeholders with #25399
- Set Scriban MemberFilter to allowlist public properties only,
blocking method/field access and reflection escape paths
- Update Razor and Scriban safe-runtime docs to match
- Add reflection-escape, method-invocation and nested-property
tests for Scriban
ITemplateRenderingEngine exposes a new IsSandboxed property so callers can
decide whether editing a template requires elevated trust.
- TemplateRenderingEngineBase provides a virtual default of false (secure-by-default)
- RazorTemplateRenderingEngine declares IsSandboxed=false (compiles to .NET assembly via Roslyn)
- ScribanTemplateRenderingEngine declares IsSandboxed=true (DSL without .NET interop)
- Razor integration docs and TextTemplateManagement docs document the implications
- Migration guide for ABP 10.4 documents the new abstraction member
InMemoryDynamicBackgroundWorker indirectly implements ISingletonDependency
via IBackgroundWorker, so conventional registration tries to register it
as a service. Its constructor takes a string workerName parameter that
the DI container cannot resolve, which crashes any host that runs
ServiceCollection validation (e.g. ASP.NET Core in Development, where
WebApplicationBuilder.Build() enables ValidateOnBuild). The dynamic worker
is created on demand by DefaultDynamicBackgroundWorkerManager and must
not be auto-registered, so mark it with [DisableConventionalRegistration].
- Look up the existing aria-describedby attribute with OrdinalIgnoreCase to match the casing rules used by HTML and TagHelperAttributeList
- Tokenize the existing value on all ASCII whitespace (space, tab, newline, carriage return, form feed) instead of just the literal space
- Cover the whitespace-separated case with a new test
Cover the new aria-describedby behaviour for <abp-input>:
- form-text rendered as <div>
- aria-describedby reaches the final HTML
- no-id case skips the InfoText id and aria-describedby
- caller-supplied aria-describedby is preserved (append + dedupe)
- [InputInfoText] attribute path produces a single aria-describedby
- Add TagHelperOutputExtensions.AppendAriaDescribedby helper that preserves caller-supplied tokens (space-separated id list) and dedupes
- Replace SetAttribute calls in AddInfoTextId/GetInfoAsHtml of abp-input/abp-select with the helper
- Cover the consumer-provided aria-describedby case with a new test
- Stop using the localized text as the aria-describedby value in AddInfoTextId; reference the actual id directly
- Skip rendering the InfoText id and aria-describedby when the input/select has no id (or an empty one) so the form never renders a non-unique "InfoText" id
- Cover the no-id case with a new test
- Move inputTag.Render after GetInfoAsHtml so aria-describedby reaches the final HTML
- Replace Attributes.Add with Attributes.SetAttribute for aria-describedby to avoid duplicates when [InputInfoText] and info="..." are both present
- Apply the same fixes to AbpInputTagHelperService for consistency
- Cover the [InputInfoText] attribute path with an additional test
- Replace <small> with <div> to match abp-input and the Bootstrap 5 form-text guidance
- Add aria-describedby on the select so screen readers announce the description
Resolve `WebAssemblyStyleFiles` and `WebAssemblyScriptFiles` through `IComponentBundleUrlBuilder` so they receive the same `PathBase` prefixing as the prerender bundle files.
- Extract provider name mapping into EfCoreDatabaseProviderHelper
- Match provider names by Contains instead of exact switch, so newer assembly names (e.g. MySql.EntityFrameworkCore) are recognized without code changes
- Add unit tests covering real provider assemblies and string fallbacks
Add an optional ExclusionFilters parameter (default: ExclusionFilters.Sensitive) to ReplaceEmbeddedByPhysical and forward it to the PhysicalFileProvider constructor when creating PhysicalVirtualFileSetInfo. This lets callers control which files are excluded when replacing embedded virtual file sets with physical directories.
Add an optional ExclusionFilters parameter (default: ExclusionFilters.Sensitive) to ReplaceEmbeddedByPhysical and forward it to the PhysicalFileProvider constructor when creating PhysicalVirtualFileSetInfo. This lets callers control which files are excluded when replacing embedded virtual file sets with physical directories.
Replace linear scans with Dictionary/HashSet lookups in both
RequirePermissionsSimpleBatchStateChecker and
RequireFeaturesSimpleBatchStateChecker for consistency.
When IsEnabledAsync(TState[]) evaluates non-batch checkers, the original
implementation called InternalIsEnabledAsync for each state, which created
a new DI scope every time. In real-world scenarios with thousands of
permissions (e.g. 4050 permissions each with RequireFeaturesSimpleStateChecker),
this caused N scope creations and N redundant Redis queries, resulting in
~3.5s latency.
This change shares a single DI scope across all non-batch checker evaluations
in the batch path by extracting EvaluateCheckersAsync and calling it directly
with the shared scope. Each state still gets an isolated ITransientCachedServiceProvider
to prevent transient service leakage across states, while scoped services
(e.g. IFeatureChecker) are naturally shared within the scope, enabling cache reuse.
The single-state path (IsEnabledAsync(TState)) remains completely unchanged.