Validate UseWorkItemFilters names against registered tasks at worker build time#719
Merged
YunchuWang merged 6 commits intoMay 6, 2026
Conversation
…build time When customers call `UseWorkItemFilters(filters)` with explicit filters, any filter that references an orchestration, activity, or entity name not registered with the worker now throws `OptionsValidationException` at worker startup instead of silently waiting for work items the worker can never handle. This is wired up automatically through `IValidateOptions<DurableTaskWorkerWorkItemFilters>` so customers do not need to invoke validation themselves. Changes: - Add `DurableTaskWorkerWorkItemFiltersValidator` (`IValidateOptions<>`). - Register it from `UseWorkItemFilters(builder, filters)` when explicit, non-empty filters are supplied. Validator runs once after every PostConfigure callback so later overwrites are validated against the final state. - Filter-name lookups use `DurableTaskRegistry` dictionary keys (`TaskName`, `OrdinalIgnoreCase`) so case differences and entity-name normalization match the runtime behavior. - Failure message lists every unknown name grouped by Orchestrations / Activities / Entities for one-shot fix-up. - CHANGELOG note + XML doc updated to call out the new behavior. - Update two pre-existing tests that relied on configuring filters without registering the matching tasks; behavior they verify (filter propagation and explicit-overrides-auto-gen) is preserved with valid task registrations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds startup-time validation for explicitly supplied UseWorkItemFilters(filters) so mis-typed or unregistered orchestration/activity/entity names fail fast with an OptionsValidationException instead of silently preventing work from being processed.
Changes:
- Introduces
DurableTaskWorkerWorkItemFiltersValidator(IValidateOptions<DurableTaskWorkerWorkItemFilters>) to cross-check filter names against the worker’sDurableTaskRegistry. - Automatically wires the validator from
UseWorkItemFilters(builder, filters)when explicit, non-empty filters are provided, and updates XML docs + changelog. - Updates and adds unit tests to cover validation failures, message grouping, case-insensitive matching, and post-configure override scenarios.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/Worker/Grpc.Tests/DurableTaskWorkerWorkItemFiltersExtensionTests.cs | Updates test filter names to point at registered tasks. |
| test/Worker/Core.Tests/DependencyInjection/UseWorkItemFiltersTests.cs | Registers tasks where needed and adds validation-focused tests. |
| src/Worker/Core/DependencyInjection/DurableTaskWorkerWorkItemFiltersValidator.cs | New options validator that checks filter names against the task registry and formats failures. |
| src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs | Auto-registers the validator (explicit non-empty filters) and updates XML docs. |
| CHANGELOG.md | Notes the new fail-fast behavior for invalid explicit work-item filters. |
…me conversion, stronger DI registration test - DurableTaskWorkerBuilderExtensions: Make UseWorkItemFilters(filters) validator registration idempotent per builder name using a private marker class. Repeat calls with explicit non-empty filters no longer register multiple validator singletons (which would otherwise produce duplicated/order-dependent validation failures). - DurableTaskWorkerWorkItemFiltersValidator: Construct TaskName explicitly when probing the registry instead of relying on the implicit string->TaskName conversion at the call site. - UseWorkItemFiltersTests: Strengthen WorkItemFilters_NullExplicitFilters_DoNotRegisterValidator to actually inspect the IServiceCollection for the absence of IValidateOptions<DurableTaskWorkerWorkItemFilters> registrations. Add WorkItemFilters_RepeatedExplicitCalls_RegisterSingleValidatorPerWorker and WorkItemFilters_RepeatedExplicitInvalidCalls_ReportFailureExactlyOnce to lock in the idempotent-registration contract.
…arameter Replace the per-builder marker-based registration with TryAddEnumerable<IValidateOptions<...>, DurableTaskWorkerWorkItemFiltersValidator>(). The framework already passes the named-options name to IValidateOptions.Validate, so a single validator instance can dispatch by that parameter instead of being bound to a captured builderName. TryAddEnumerable de-duplicates by implementation type, eliminating the need for a separately tracked WorkItemFiltersValidatorRegistrationMarker class and the O(N) IServiceCollection scan that went with it. Removes ~100 lines of indirection (validator field, marker class, helper scan, conditional registration block) while preserving every observable behavior. The two now-redundant tests (NullExplicitFilters_DoNotRegisterValidator, RepeatedExplicitCalls_RegisterSingleValidatorPerWorker) are removed because they locked down internal registration mechanics that no longer apply; the user-observable invariants they encoded are still covered by RepeatedExplicitInvalidCalls_ReportFailureExactlyOnce and ValidationIsScopedToEachNamedWorker.
…me display 1. Restore the registration gate in UseWorkItemFilters(builder, filters): only call TryAddEnumerable when filters is non-null AND has at least one orchestration/activity/entity entry. Passing null (opt-out) or an empty filter set has nothing to validate, so registering the validator would unnecessarily pull IOptionsMonitor<DurableTaskRegistry> into the resolution chain and contradict the documented intent. 2. In DurableTaskWorkerWorkItemFiltersValidator, normalize the worker name shown in the failure message: when 'name' is null or string.Empty (the default-name worker), display '<default>' instead of an empty quoted name. The actual 'name' is still used to resolve the registry. Adds three lock-in tests: NullExplicitFilters_DoNotRegisterValidator, EmptyExplicitFilters_DoNotRegisterValidator, and DefaultNamedWorkerInvalidFilter_FailureMessageUsesDefaultPlaceholder.
bachuv
reviewed
May 5, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…FiltersValidator.Validate (review feedback).
bachuv
approved these changes
May 6, 2026
This was referenced May 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
What changed?
DurableTaskWorkerWorkItemFiltersValidator(IValidateOptions<DurableTaskWorkerWorkItemFilters>) that cross-checks every orchestration / activity / entity name in the configured filters against the worker'sDurableTaskRegistry.UseWorkItemFilters(builder, filters)now auto-registers this validator (only when explicit, non-empty filters are supplied) so customers do not have to invoke validation themselves. The validator runs once after everyPostConfigurecallback, so any later overwrites are still validated against the final state.TaskName,OrdinalIgnoreCase) so case differences and entity-name normalization match the runtime behavior.Why is this change needed?
When a customer calls
UseWorkItemFilters(...)with a name that is not registered with the worker (typo, removed task, copy/paste mistake, etc.), the worker would previously start successfully and then silently wait for work items it can never handle — orchestrations / activities / entities targeting those names would simply never run, with no error surfaced. Failing fast at worker startup with a precise, actionableOptionsValidationExceptionmakes this misconfiguration immediately diagnosable.Issues / work items
Project checklist
release_notes.md(entry added toCHANGELOG.mdunderUnreleased)UseWorkItemFilters(filters)now throwsOptionsValidationExceptionat worker startup. Existing callers whose filter names match registered tasks are unaffected.AI-assisted code disclosure (required)
Was an AI tool used? (select one)
If AI was used:
src/Worker/Core/DependencyInjection/DurableTaskWorkerWorkItemFiltersValidator.cssrc/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cstest/Worker/Core.Tests/DependencyInjection/UseWorkItemFiltersTests.cstest/Worker/Grpc.Tests/DurableTaskWorkerWorkItemFiltersExtensionTests.csCHANGELOG.mdAI verification (required if AI was used):
Testing
Automated tests
UseWorkItemFiltersTestsfor: unknown orchestration / activity / entity names producingOptionsValidationExceptionat startup, multi-category error grouping in the message, case-insensitive matching against registry keys, validator only registering for explicit non-empty filters, validator re-running after laterPostConfigureoverrides, and entity-name normalization matching the runtime path.Manual validation (only if runtime/behavior changed)
Notes for reviewers
UseWorkItemFilters(builder, filters)— auto-generated filters (the no-args overload) are intentionally not validated, since they are derived directly from the registry and cannot be inconsistent with it.DurableTaskRegistry's dictionary semantics so the validation is exactly aligned with what the worker would dispatch.