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
- 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
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.
Use JsonSerializer for non-primitive types in AbpLoggerExtensions.LogData
to output meaningful JSON instead of type names like List`1[Dictionary`2[...]]
- Replace FormattedStringValueExtracter.Extract with LastIndexOf in
PermissionGrantCacheItem and ResourcePermissionGrantCacheItem to
eliminate repeated string tokenization and object allocations on
every cache key parse (~12,000 calls per request with 4000+ permissions)
- Add fast-path in SimpleStateCheckerManager.InternalIsEnabledAsync to
skip DI scope creation when both StateCheckers and GlobalStateCheckers
are empty, avoiding thousands of unnecessary scope allocations
- Optimize PermissionChecker.IsGrantedAsync(string[]) and
ResourcePermissionChecker.IsGrantedAsync(string[], resourceName, resourceKey)
to load all permission definitions once via GetPermissionsAsync /
GetResourcePermissionsAsync instead of N individual GetOrNullAsync calls,
and use batch StateCheckerManager.IsEnabledAsync for state checking
- Optimize AbpApplicationConfigurationAppService.GetAuthConfigAsync to
pre-load all permission names into a HashSet for O(1) lookup instead
of N async GetOrNullAsync calls inside the loop
- Fix GetResourcePermissionsAsync to deduplicate by (ResourceName, Name)
instead of Name only, matching the actual uniqueness constraint of
resource permissions defined in PermissionDefinitionContext
Production impact (customer with 4000+ permissions): 10s+ -> ~682ms
When a type's assembly contains an ABP module but is not part of the
module dependency chain, Autofac's property injection is silently
skipped. This causes LazyServiceProvider and other injected properties
to remain null, leading to NullReferenceException at runtime.
This change detects the misconfiguration at startup and logs a warning
with the affected assembly name, module type, and a fix suggestion.
- Upgrade Autofac from 8.4.0 to 9.1.0
- Upgrade Autofac.Extensions.DependencyInjection from 10.0.0 to 11.0.0
- Upgrade Microsoft.Bcl.AsyncInterfaces from 10.0.2 to 10.0.4
- Remove AnyKeyRegistrationSource (now native in Autofac 9.1.0)
- Add MSDI KeyedService.AnyKey to Autofac KeyedService.AnyKey translation
- Use Parameters.KeyedServiceKey<object>() for keyed factory key retrieval
- Add ExternallyOwned() to instance registrations
- Add unit tests for keyed services and AnyKey support
* Fix stdout/stderr deadlock in CmdHelper.RunCmdAndGetOutput
Read stdout and stderr concurrently to prevent pipe buffer deadlock.
The sequential ReadToEnd() calls caused a hang when child processes
(e.g. dotnet build) produced enough stderr output to fill the OS
pipe buffer (~4KB on Windows), since the parent blocked on stdout
while the child blocked on stderr.
Made-with: Cursor
* Simplify Task.WhenAll result handling per review
Made-with: Cursor
* Add test for CmdHelper to prevent stdout/stderr deadlock
* Reduce timeout in deadlock test for CmdHelper to 10 seconds
---------
Co-authored-by: maliming <malimings@gmail.com>