ARCH-001 Phase 27: consolidate SearchController picker (Category/Collection/Brand) - #828
Merged
KrzysztofPajak merged 1 commit intoSep 11, 2026
Conversation
…/Brand picker
Admin/Store/Vendor's SearchController each duplicate 3 Kendo-autocomplete picker
methods (Category/Collection/Brand). Admin's Index (full admin command/menu
search) and CustomerGroup/Stores/Vendor pickers have no Store/Vendor equivalent
and stay in Grand.Web.Admin's own controller untouched - same
"consolidate-a-sub-resource" shape as TaxCategory (Phase 22), but the first
3-way (Admin+Store+Vendor) instance of it.
Design note - deliberately does NOT reuse IAdminDataScope<Category>/<Collection>/
<Brand>: those entities' routed scopes (RoutedCategoryDataScope etc.) fail closed
for the "Vendor" area, because Category/Collection/Brand have no Vendor CRUD
screen. This picker sub-resource DOES run under Vendor (Vendor's own
SearchController already exposed it), so resolving one of those scopes here
would throw InvalidOperationException on every Vendor picker call. Investigated
each host's original code instead: Admin and Vendor both hardcoded
storeId: "" (no store filter); only Store scoped by
WorkContext.CurrentCustomer.StaffStoreId. Confirmed in CategoryService.
GetAllCategories (and the same shape in Collection/Brand's services) that
storeId: "" and storeId: null are handled identically
(!string.IsNullOrEmpty(storeId) gates the filter both ways), so a new
`protected virtual string PickerStoreId => "";` on BaseSearchController
(Admin/Vendor's original behavior, no override needed) overridden only by
Store's concrete subclass exactly replicates all 3 hosts' original behavior.
No behavior change.
New BaseSearchController in Grand.Web.AdminShared holds Category/Collection/
Brand. Admin/Store/Vendor's SearchController are now thin subclasses restating
their host's [Authorize*]/[Area]/[AuthorizeMenu]/[AutoValidateAntiforgeryToken]
attribute set (BaseSearchController can't inherit any single host's base
controller - same reason as every other Base*Controller in this initiative).
No prior test coverage existed for any of the 3 originals. Added:
- BaseSearchControllerTests: Category/Collection/Brand x {default "" storeId,
overridden storeId} against the shared base directly.
- SearchControllerAttributeTests (Admin + Store) - subclass/attribute wiring,
Store's includes one behavior test proving PickerStoreId actually reflects
WorkContext.CurrentCustomer.StaffStoreId end-to-end.
- SearchControllerSurfaceTests (Vendor) - same attribute-wiring shape, matching
Grand.Web.Vendor.Tests' existing *SurfaceTests naming convention
(VendorReviewControllerSurfaceTests).
Verified: dotnet build GrandNode.sln 0 errors. Per-project test runs (parallel
full-solution runs are known-flaky): Grand.Web.Admin.Tests 1439/1439,
Grand.Web.Store.Tests 142/142, Grand.Web.Vendor.Tests 26/26.
No live smoke test run this phase - deferred, not blocking (read-only picker
endpoints, no cross-tenant/access-control surface changed, same justification
class as Phase 24's deferral).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013y3MqvZq7y1Uc5p4JZad2i
KrzysztofPajak
deleted the
arch001/phase27-search-picker-consolidation
branch
September 11, 2026 14:00
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.
ARCH-001 Phase 27: SearchController picker consolidation (Category/Collection/Brand)
Fresh-survey candidate (found alongside PictureController/Phase 25). Admin's,
Store's, and Vendor's
SearchControllereach duplicate 3 Kendo-autocompletepicker actions:
Category,Collection,Brand. Admin'sIndex(the fulladmin command/menu search box) and its
CustomerGroup/Stores/Vendorpickers have no Store/Vendor equivalent and stay duplicated in Admin's own
controller, untouched — same "consolidate only a sub-resource" shape as
TaxCategory (Phase 22), but the first 3-way (Admin+Store+Vendor) instance
of it in this initiative.
Design finding: why this does NOT reuse
IAdminDataScope<TEntity>The obvious first instinct — inject
IAdminDataScope<Category>/<Collection>/<Brand>, already registered for all 3 hosts from the Category/Collection/Brandphases — turns out to be a trap here. Those entities' routed scopes
(
RoutedCategoryDataScopeetc.) fail closed for the "Vendor" area, becauseCategory/Collection/Brand have no Vendor CRUD screen at all:
This picker sub-resource does run under Vendor (Vendor's own
SearchControlleralready exposed it pre-consolidation) — reusing those scopes would have thrown
on every single Vendor picker call.
Investigated each host's actual original code instead of assuming: Admin and
Vendor both hardcoded
storeId: ""(no store filter at all); only Store scopedby
WorkContext.CurrentCustomer.StaffStoreId. Confirmed inCategoryService.GetAllCategories(and the identical shape in Collection/Brand'sservices) that
storeId: ""andstoreId: nullare handled identically —!string.IsNullOrEmpty(storeId)gates the store filter, true for both. So aprotected virtual string PickerStoreId => "";on the newBaseSearchController(matching Admin/Vendor's original behavior, no override needed for either) that
only Store's concrete subclass overrides exactly replicates all 3 hosts'
original behavior. No behavior change.
Change
New
BaseSearchControllerinGrand.Web.AdminSharedholdsCategory/Collection/Brand. Admin/Store/Vendor'sSearchControllerare now thinsubclasses restating their host's
[Authorize*]/[Area]/[AuthorizeMenu]/[AutoValidateAntiforgeryToken]attribute set —BaseSearchControllercan'tinherit any single host's base controller, same reason as every other
Base*Controllerin this initiative.Tests
No prior test coverage existed for any of the 3 originals. Added:
BaseSearchControllerTests—Category/Collection/Brand× {default""storeId, overridden storeId} exercised directly against the shared base.
SearchControllerAttributeTests(Admin + Store) — subclass/attribute wiring;Store's includes one behavior test proving
PickerStoreIdactually reflectsWorkContext.CurrentCustomer.StaffStoreIdend-to-end, not just that theoverride exists.
SearchControllerSurfaceTests(Vendor) — same attribute-wiring shape, matchingGrand.Web.Vendor.Tests' existing*SurfaceTestsnaming convention(
VendorReviewControllerSurfaceTests).Verification
dotnet build GrandNode.sln— 0 errors. Per-project test runs (parallelfull-solution runs are known-flaky, per repo history):
Grand.Web.Admin.Tests: 1439/1439Grand.Web.Store.Tests: 142/142Grand.Web.Vendor.Tests: 26/26No live smoke test run this phase — deferred, not blocking. These are
read-only picker endpoints (Kendo autocomplete data sources); no cross-tenant
write surface or access-control boundary changed, same deferral justification
class as Phase 24 (Currency/Language cleanup).