<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged blazor</title>
<link>https://coderlegion.com/tag/blazor</link>
<description>Powered by Question2Answer</description>
<item>
<title>Let the Router Own the Next Paint</title>
<link>https://coderlegion.com/32251/let-the-router-own-the-next-paint</link>
<description>&lt;p&gt;Navigation bugs are often judged by their final state. Did the correct page appear? Did the authorization check pass? Did the URL settle on the expected address?&lt;/p&gt;
&lt;p&gt;Those questions matter, but they can miss the frame that users actually notice.&lt;/p&gt;
&lt;p&gt;In a recent component-based web fix, the destination page was correct. The address was correct. The authorization result was correct. Yet, between the address change and the destination render, the departing page painted once more. Because the framework also applied its scroll reset during that interval, users briefly saw stale content at the top of the viewport before the new page arrived.&lt;/p&gt;
&lt;p&gt;The repair produced a useful general lesson: after an address change, the router should own the next visible paint.&lt;/p&gt;
&lt;h3 id=&quot;a-correct-destination-can-still-have-a-wrong-journey&quot;&gt;A correct destination can still have a wrong journey&lt;/h3&gt;
&lt;p&gt;An end-state test sees a simple sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;start on page A;&lt;/li&gt;
&lt;li&gt;navigate to page B;&lt;/li&gt;
&lt;li&gt;assert that page B is visible.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The browser may have seen something richer:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;page A is visible at the user’s current scroll position;&lt;/li&gt;
&lt;li&gt;the address changes to B;&lt;/li&gt;
&lt;li&gt;page A renders again under B’s address;&lt;/li&gt;
&lt;li&gt;a framework scroll reset moves that stale page to the top;&lt;/li&gt;
&lt;li&gt;page B finally renders.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Steps three and four may last only one frame. They are still part of the product.&lt;/p&gt;
&lt;p&gt;This is why navigation correctness is not only about eventual state. It also includes the ordered render sequence between the user action and the destination becoming authoritative.&lt;/p&gt;
&lt;h3 id=&quot;the-hidden-effect-of-an-event-receiver&quot;&gt;The hidden effect of an event receiver&lt;/h3&gt;
&lt;p&gt;The source of the extra paint was subtle. An asynchronous navigation callback was registered with the component that hosted the route tree as its receiver.&lt;/p&gt;
&lt;p&gt;In many component frameworks, completing an event callback schedules a render of the receiving component. That behaviour is normally helpful: event handlers change state, so the framework redraws the owner automatically.&lt;/p&gt;
&lt;p&gt;Navigation is different. The callback prepared the transition, but the router—not the departing component—owned the destination. When the receiver-bound callback completed, the framework rendered the old route tree once more. The router rendered the new page immediately afterward, but it was no longer responsible for the first batch after the address changed.&lt;/p&gt;
&lt;p&gt;The fix did not add a delay, loading overlay, or animation. It removed the accidental receiver. The callback still ran, but its completion no longer implied that the departing route component needed to render. The router’s destination batch became the first visible paint after the address change.&lt;/p&gt;
&lt;h3 id=&quot;measure-the-interval-not-just-the-answer&quot;&gt;Measure the interval, not just the answer&lt;/h3&gt;
&lt;p&gt;The strongest part of the repair was the test shape.&lt;/p&gt;
&lt;p&gt;Instead of sleeping and hoping to catch a flicker, the test marked the instant the address changed. It then counted every component render until the destination page entered the render tree.&lt;/p&gt;
&lt;p&gt;Before the change, that interval contained twelve render notifications in the exercised path. After the change, it contained zero.&lt;/p&gt;
&lt;p&gt;The specific number is less important than the invariant:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Nothing else paints after the address changes and before the destination appears.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That assertion is deterministic. It does not depend on monitor refresh rate, screenshot timing, or a human noticing a flash. It also fails for the causal behaviour rather than a visual approximation of the symptom.&lt;/p&gt;
&lt;h3 id=&quot;keeping-the-old-page-has-a-boundary&quot;&gt;Keeping the old page has a boundary&lt;/h3&gt;
&lt;p&gt;There is a reasonable usability goal behind retaining the current page: an established screen is often a better placeholder than a blank shell while the destination prepares.&lt;/p&gt;
&lt;p&gt;But retained content needs a strict boundary. The old page belongs to the source route and its source authorization decision. It must not start making decisions for the new address, consume state prepared for the destination, or imply that access to the destination has been granted.&lt;/p&gt;
&lt;p&gt;Security invalidation is the important exception. If the user signs out or loses a role, the retained page must stop being authoritative immediately. Smoothness must never extend stale permission.&lt;/p&gt;
&lt;p&gt;That gives us two complementary rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;retain a certified source view only until its replacement is ready;&lt;/li&gt;
&lt;li&gt;invalidate it immediately when the identity or authorization basis changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result is continuity without authority leakage.&lt;/p&gt;
&lt;h3 id=&quot;test-the-negative-space&quot;&gt;Test the negative space&lt;/h3&gt;
&lt;p&gt;A good transition test suite should cover more than the successful move between two ordinary pages.&lt;/p&gt;
&lt;p&gt;Useful cases include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an allowed destination;&lt;/li&gt;
&lt;li&gt;a refused destination;&lt;/li&gt;
&lt;li&gt;a layout or shell change;&lt;/li&gt;
&lt;li&gt;sign-out during preparation;&lt;/li&gt;
&lt;li&gt;role loss during preparation;&lt;/li&gt;
&lt;li&gt;an older asynchronous result completing after a newer transition;&lt;/li&gt;
&lt;li&gt;a callback that finishes on a different scheduling context.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These cases reveal who truly owns the transition. If several components can repaint or decide access for the same interval, the design is probably carrying more than one authority.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-review-method&quot;&gt;A practical review method&lt;/h3&gt;
&lt;p&gt;When reviewing client-side navigation, draw a short timeline: user action, address change, preparation, first destination render, and interaction-ready state.&lt;/p&gt;
&lt;p&gt;For each gap, ask:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Which component may render?&lt;/li&gt;
&lt;li&gt;Which route does that render belong to?&lt;/li&gt;
&lt;li&gt;Which authorization decision supports it?&lt;/li&gt;
&lt;li&gt;Can an event callback schedule an incidental redraw?&lt;/li&gt;
&lt;li&gt;What happens if identity changes or an older task completes late?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then write at least one sequence assertion. Final-state tests remain valuable, but they should be paired with a guard over the transition when a single wrong frame matters.&lt;/p&gt;
&lt;h3 id=&quot;the-leadership-lesson&quot;&gt;The leadership lesson&lt;/h3&gt;
&lt;p&gt;Small visual defects often expose unclear ownership rather than missing polish. A loading spinner might hide the flash, but it would not explain why the departing component could repaint under the destination address.&lt;/p&gt;
&lt;p&gt;The more durable repair is to assign one owner to the critical interval and test that ownership directly.&lt;/p&gt;
&lt;p&gt;Users experience every frame, not only the last one. Our tests should be willing to do the same.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32251/let-the-router-own-the-next-paint</guid>
<pubDate>Sat, 24 Oct 2026 07:06:52 +0000</pubDate>
</item>
<item>
<title>Test the Owner, Not the Teardown</title>
<link>https://coderlegion.com/31880/test-the-owner-not-the-teardown</link>
<description>&lt;p&gt;Flaky lifecycle tests often produce a tempting diagnosis: the cleanup code must be unreliable. Sometimes that is true. Sometimes the test is asking a framework harness to prove a narrower ownership contract, and the harness is free to finish work on a different schedule.&lt;/p&gt;
&lt;p&gt;That distinction matters in interactive server-side UI systems. A component may subscribe to live updates, start a timer, register an observer, or attach a callback. The resource needs a clear owner and a reliable release path. But the test for that contract should not depend entirely on whether a whole rendered tree has completed teardown at one particular instant.&lt;/p&gt;
&lt;h3 id=&quot;the-shape-of-the-failure&quot;&gt;The shape of the failure&lt;/h3&gt;
&lt;p&gt;Consider a header component that subscribes to a process-local notification service. A child pop-up also owns a separate subscription. The component test renders the tree, publishes an update, observes the visual change, disposes the test context, and immediately expects the global subscription count to be zero.&lt;/p&gt;
&lt;p&gt;That sounds reasonable. It is also broader than the claim we really want to prove.&lt;/p&gt;
&lt;p&gt;Under a full test-suite load, the notification can overlap a render. The harness teardown call may return while the renderer still has a queued batch. At that instant the header might not yet have received its disposal callback. The non-zero count is therefore ambiguous: either the header leaked, or the harness has not disposed it yet.&lt;/p&gt;
&lt;p&gt;A test with two possible explanations is a weak regression guard.&lt;/p&gt;
&lt;h3 id=&quot;waiting-is-not-the-same-as-proving&quot;&gt;Waiting is not the same as proving&lt;/h3&gt;
&lt;p&gt;The first instinct is usually to add a retry window: wait for the subscription count to reach zero, perhaps for a few seconds.&lt;/p&gt;
&lt;p&gt;That is appropriate when the product contract is genuinely eventual. It is not a universal cure for lifecycle tests. If the harness has left the component alive, waiting merely spends more time observing the wrong boundary. The test remains dependent on incidental scheduling and can still fail without a cleanup defect.&lt;/p&gt;
&lt;p&gt;The key question is not, “How long should we wait?” It is, “Which object owns the resource, and what exact behaviour are we trying to prove?”&lt;/p&gt;
&lt;h3 id=&quot;put-the-assertion-at-the-ownership-boundary&quot;&gt;Put the assertion at the ownership boundary&lt;/h3&gt;
&lt;p&gt;The stronger test calls the owning component's disposal contract directly, on the correct renderer context, and checks only the resource that component owns.&lt;/p&gt;
&lt;p&gt;If the header owns one subscription and a still-rendered child owns another, disposing the header should reduce the count by one. Expecting zero would incorrectly make the header test responsible for the child's lifetime as well.&lt;/p&gt;
&lt;p&gt;That narrower assertion is not weaker. It is more precise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the owner releases its resource;&lt;/li&gt;
&lt;li&gt;an independently owned resource is not silently claimed by the parent test;&lt;/li&gt;
&lt;li&gt;the result does not depend on whole-tree teardown timing; and&lt;/li&gt;
&lt;li&gt;removing the owner's cleanup call makes the test fail consistently.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point is especially valuable. A small mutation check—temporarily disabling the cleanup call—shows whether the test detects the regression it claims to detect. If the test still passes, it is probably measuring something else.&lt;/p&gt;
&lt;h3 id=&quot;production-code-still-needs-lifecycle-discipline&quot;&gt;Production code still needs lifecycle discipline&lt;/h3&gt;
&lt;p&gt;A focused test does not excuse fragile implementation. Cleanup should normally be idempotent because framework callbacks, navigation, error handling, and explicit shutdown can converge on the same release path.&lt;/p&gt;
&lt;p&gt;The object should also refuse to acquire new resources after disposal. An asynchronous load may resume after the component has gone away. If its continuation subscribes again, a perfectly correct earlier disposal becomes irrelevant because nothing will release the new subscription.&lt;/p&gt;
&lt;p&gt;The same reasoning applies beyond subscriptions. Failed in-flight loads should close or abandon temporary buffers. Retained arrivals should have a sensible bound. Work dispatched to another recipient should not accidentally carry ambient request state from the publisher. These are all lifecycle questions: who owns the state, when does ownership end, and what context is allowed to cross the boundary?&lt;/p&gt;
&lt;h3 id=&quot;keep-two-layers-of-evidence&quot;&gt;Keep two layers of evidence&lt;/h3&gt;
&lt;p&gt;Direct ownership tests and integration tests answer different questions.&lt;/p&gt;
&lt;p&gt;An owner-level test asks: when this component is disposed, does it release what it owns, and can it remain released?&lt;/p&gt;
&lt;p&gt;An integration test asks: does navigation, circuit shutdown, or framework teardown actually invoke the right lifecycle path across the rendered tree?&lt;/p&gt;
&lt;p&gt;Both matter. The mistake is using only the second, broad test as proof of the first, narrow contract. When the broad test fails, you cannot tell whether ownership or orchestration is broken.&lt;/p&gt;
&lt;p&gt;Separating the layers improves diagnosis. A direct test failure points toward the owner. An integration-only failure points toward wiring, scheduling, or framework interaction. The test suite becomes a map of contracts instead of a collection of timing guesses.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-checklist&quot;&gt;A practical checklist&lt;/h3&gt;
&lt;p&gt;For each subscription, timer, observer, or callback:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Name the owner explicitly.&lt;/li&gt;
&lt;li&gt;Make release safe to call more than once.&lt;/li&gt;
&lt;li&gt;Prevent acquisition after the owner is disposed.&lt;/li&gt;
&lt;li&gt;Test the owner's release contract directly.&lt;/li&gt;
&lt;li&gt;Give child-owned resources separate coverage.&lt;/li&gt;
&lt;li&gt;Keep one integration test for the framework path.&lt;/li&gt;
&lt;li&gt;Prove the test's sensitivity by breaking cleanup locally and seeing it fail.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The trade-off is a few more focused tests and slightly more explicit lifecycle code. In return, failures become actionable. You know whether the owner leaked, the child still legitimately owns something, or the framework has not completed teardown.&lt;/p&gt;
&lt;p&gt;Reliable tests do not merely wait longer. They make ownership visible.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31880/test-the-owner-not-the-teardown</guid>
<pubDate>Tue, 20 Oct 2026 17:41:25 +0000</pubDate>
</item>
<item>
<title>Hand Off Intent, Not Authority</title>
<link>https://coderlegion.com/31588/hand-off-intent-not-authority</link>
<description>&lt;p&gt;Moving a user from one application host to another often looks harmless. A button opens a browser, the user continues, and the original client waits for them to return.&lt;/p&gt;
&lt;p&gt;Architecturally, that is not merely navigation. It is a trust-boundary decision.&lt;/p&gt;
&lt;p&gt;This becomes especially important when one user experience runs across several .NET hosts: perhaps an ASP.NET Core site, a Blazor WebAssembly client, and a .NET MAUI hybrid app. The shared components may look identical, but the hosts do not own the same secrets, sessions, callbacks, or security guarantees.&lt;/p&gt;
&lt;p&gt;The lesson is simple: when a workflow crosses hosts, hand off intent, not authority.&lt;/p&gt;
&lt;h3 id=&quot;the-tempting-shortcut&quot;&gt;The tempting shortcut&lt;/h3&gt;
&lt;p&gt;Suppose a client has already loaded an order-like resource and can display the next sensitive action. It is tempting to let every host render the same provider form or carry the current access proof into the browser.&lt;/p&gt;
&lt;p&gt;That preserves continuity, but it also widens the boundary. A client that only needed to display state may now receive signed fields, transaction details, callback assumptions, or a reusable proof. Putting any of that into a query string or fragment creates more problems: URLs are copied, logged, cached, placed in browser history, exposed to extensions, and sometimes sent as referrers.&lt;/p&gt;
&lt;p&gt;Shared UI does not imply shared authority. Reuse the presentation and state model where it helps, but make each host's capabilities explicit.&lt;/p&gt;
&lt;h3 id=&quot;a-narrower-handoff&quot;&gt;A narrower handoff&lt;/h3&gt;
&lt;p&gt;A safer design gives the less-trusted client one small capability: open a fixed public entry point on the authoritative host.&lt;/p&gt;
&lt;p&gt;The destination should be deliberately boring. It should not contain an order reference, email address, one-time code, access token, signed provider fields, or a claimed outcome. It should have no dynamic query string or fragment. It communicates only this intent: “the user wants to continue this workflow on the web.”&lt;/p&gt;
&lt;p&gt;The authoritative host then starts again from its own security context. It asks for the identifiers appropriate to the workflow, performs anti-forgery checks, verifies ownership through its normal mechanism, and only then exposes the sensitive action.&lt;/p&gt;
&lt;p&gt;That separation gives each statement a precise meaning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Opening the browser is navigation.&lt;/li&gt;
&lt;li&gt;Proving ownership grants access in the destination host.&lt;/li&gt;
&lt;li&gt;Starting the action is a separate command.&lt;/li&gt;
&lt;li&gt;A callback or authoritative read confirms the outcome.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of those statements is allowed to stand in for another.&lt;/p&gt;
&lt;h3 id=&quot;treat-public-configuration-as-an-api&quot;&gt;Treat public configuration as an API&lt;/h3&gt;
&lt;p&gt;Installable browser clients need runtime configuration, but a generated client configuration document is still a public API. Do not serialize a server configuration tree and hope that only safe values happen to be present.&lt;/p&gt;
&lt;p&gt;Build a small allowlist instead. For this kind of handoff, the browser might receive an enablement flag and a reviewed public origin. That is enough. Return the document with a no-store policy so a stale capability does not linger after an operational change.&lt;/p&gt;
&lt;p&gt;Validate the destination before the application starts. Require the exact shape you intend: HTTPS, a root origin, no embedded credentials, no path, no query, no fragment, and no unexpected local or alternate-port destination. Keep the feature disabled when configuration is absent. If someone explicitly enables it with an invalid value, fail startup with a value-free error rather than echoing the bad input into logs.&lt;/p&gt;
&lt;p&gt;Failing closed here is not pessimism. It turns configuration from ambient text into an enforceable security boundary.&lt;/p&gt;
&lt;h3 id=&quot;make-the-destination-defensive-too&quot;&gt;Make the destination defensive too&lt;/h3&gt;
&lt;p&gt;The source client should generate a clean URL, but the destination should not trust that promise.&lt;/p&gt;
&lt;p&gt;If someone arrives with a query string, canonicalize to the fixed entry before rendering. Apply no-store and a restrictive referrer policy. Do not pre-fill sensitive identifiers from URL input. Start with the ordinary ownership flow and its existing anti-forgery protection.&lt;/p&gt;
&lt;p&gt;This matters because links escape their intended callers. They are bookmarked, pasted into messages, opened by scanners, and reconstructed by users. A robust boundary remains safe even when the source is not your application.&lt;/p&gt;
&lt;h3 id=&quot;failure-must-remain-boring&quot;&gt;Failure must remain boring&lt;/h3&gt;
&lt;p&gt;A browser launch can fail for mundane reasons: platform policy, missing handlers, lifecycle changes, or an exception from the native shell. None of those failures should mutate domain state.&lt;/p&gt;
&lt;p&gt;The client should report only that the launch did not happen. It should not create the sensitive form, mark work as started, grant access, or imply success. Concurrent clicks can be collapsed while an open attempt is running, but a failed attempt should be retryable.&lt;/p&gt;
&lt;p&gt;This is a useful design test: if navigation failure requires compensation, the handoff probably carried too much authority.&lt;/p&gt;
&lt;h3 id=&quot;test-the-negative-space-1&quot;&gt;Test the negative space&lt;/h3&gt;
&lt;p&gt;Most happy-path tests prove that a button eventually opens the right page. Boundary tests need to prove what cannot happen.&lt;/p&gt;
&lt;p&gt;Useful tests include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the capability is off by default;&lt;/li&gt;
&lt;li&gt;malformed enablement and invalid destinations fail safely;&lt;/li&gt;
&lt;li&gt;the client opens only the fixed path;&lt;/li&gt;
&lt;li&gt;the URL contains no proof or private state;&lt;/li&gt;
&lt;li&gt;each host renders only its intended action;&lt;/li&gt;
&lt;li&gt;public configuration contains only allowlisted fields;&lt;/li&gt;
&lt;li&gt;the web entry removes unexpected query data before rendering;&lt;/li&gt;
&lt;li&gt;caching and referrer protections are present;&lt;/li&gt;
&lt;li&gt;ownership and anti-forgery checks happen in the destination;&lt;/li&gt;
&lt;li&gt;launch failure changes no workflow state; and&lt;/li&gt;
&lt;li&gt;repeated clicks do not create duplicate launches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are negative properties. They are easy to lose during refactoring unless tests name them directly.&lt;/p&gt;
&lt;h3 id=&quot;the-trade-off-is-honest-friction&quot;&gt;The trade-off is honest friction&lt;/h3&gt;
&lt;p&gt;Fresh ownership proof adds a step. A dedicated entry route, host-specific capability registration, and configuration validation add code. The boundary also needs maintenance as new hosts appear.&lt;/p&gt;
&lt;p&gt;That cost buys something valuable: the sensitive operation stays with the host designed to own it. The other clients remain simpler, public configuration stays small, and failures have a narrow blast radius.&lt;/p&gt;
&lt;p&gt;When reviewing any cross-host workflow, write down four things: the exact destination, the data allowed to cross, the authority that must be re-established, and the state a failed launch may change. If those answers are vague, the implementation probably is too.&lt;/p&gt;
&lt;p&gt;Intent can cross a boundary. Authority should have to earn its way back in.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31588/hand-off-intent-not-authority</guid>
<pubDate>Sun, 18 Oct 2026 08:36:51 +0000</pubDate>
</item>
<item>
<title>A Route Is Not a Feature Until Someone Can Reach It</title>
<link>https://coderlegion.com/30901/a-route-is-not-a-feature-until-someone-can-reach-it</link>
<description>&lt;p&gt;Modern applications are often assembled from modules. That is good for ownership, deployment boundaries, and keeping large codebases understandable. It also creates a subtle user-experience failure mode: a page can compile, carry a valid route, pass its local tests, and still be effectively absent because no one can discover it from a real entry point.&lt;/p&gt;
&lt;p&gt;This is not primarily a visual-design problem. It is an architecture-contract problem.&lt;/p&gt;
&lt;h3 id=&quot;routable-is-not-reachable&quot;&gt;Routable is not reachable&lt;/h3&gt;
&lt;p&gt;Imagine an administrative application with pages spread across two modules. The team already has a test that scans routed pages and checks that each one appears in a navigation catalogue. The test is green, so the navigation surface looks covered.&lt;/p&gt;
&lt;p&gt;Later, someone discovers that the scan only walks one module. The second route tree was outside its boundary. Pages there could be added indefinitely without the test knowing they existed.&lt;/p&gt;
&lt;p&gt;That is the first important lesson: a completeness check is only as complete as its discovery mechanism.&lt;/p&gt;
&lt;p&gt;When the scan is expanded, it finds an entry screen that has documentation and valid follow-on steps, but no link from the application itself. The workflow exists technically. Operationally, it begins with “type this URL.” That is not a usable feature contract.&lt;/p&gt;
&lt;h3 id=&quot;define-a-genuine-entry-point&quot;&gt;Define a genuine entry point&lt;/h3&gt;
&lt;p&gt;The tempting fix is to accept any reference to the route. Perhaps another page links to it. Perhaps it appears on a tab rail. Perhaps a help article mentions it.&lt;/p&gt;
&lt;p&gt;Those references are not equivalent.&lt;/p&gt;
&lt;p&gt;A help article explains a feature; it is not application navigation. A link on a neighbouring screen can be useful, but it may make discoverability depend on an accidental journey. A tab rail is particularly deceptive: it helps someone move within an area only after they have reached one of its pages. It cannot prove that the area itself has an entrance.&lt;/p&gt;
&lt;p&gt;The architectural rule should therefore name the user-facing property you actually want. For example:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Every static administrative workflow page must be represented by a discoverable entry in the primary hub available to its entitled audience.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That sentence is stricter than “the route is mentioned somewhere,” and that is precisely why it is useful.&lt;/p&gt;
&lt;h3 id=&quot;test-pages-not-strings&quot;&gt;Test pages, not strings&lt;/h3&gt;
&lt;p&gt;Route graphs have legitimate shapes that a simple string search will misclassify.&lt;/p&gt;
&lt;p&gt;A parameterised detail page is usually reached from a list or search result. Giving every detail route a top-level card would be noisy and misleading. The test can classify routes containing parameters as downstream pages rather than entry pages.&lt;/p&gt;
&lt;p&gt;The reverse case also matters. One page may answer on two route aliases for compatibility. Requiring two navigation cards would duplicate the same destination. The unit of the contract is the page, with at least one discoverable canonical route—not every literal route declaration.&lt;/p&gt;
&lt;p&gt;These distinctions keep the test strict without making it simplistic.&lt;/p&gt;
&lt;h3 id=&quot;make-the-scan-fail-honestly&quot;&gt;Make the scan fail honestly&lt;/h3&gt;
&lt;p&gt;Source-scanning tests have a dangerous failure mode: they can pass after discovering nothing. A changed directory, glob, parser, or build layout can turn the result set into an empty collection, after which every “all pages are covered” assertion becomes vacuously true.&lt;/p&gt;
&lt;p&gt;Add a coarse lower bound to the discovered page count. It does not need to encode the exact inventory. It only needs to make a broken scan conspicuous. Pair that with a useful failure message that lists uncovered entry pages and explains the intended remedy.&lt;/p&gt;
&lt;p&gt;Exceptions deserve the same discipline. Sometimes an exception is legitimate, but an exemption list easily becomes a hiding place. Store a reason beside each exception, keep the list narrow, and assert that every exception still corresponds to a real uncovered case. Once navigation is fixed, the stale exception should fail the next test run.&lt;/p&gt;
&lt;p&gt;That turns exceptions into temporary, reviewable decisions rather than permanent silence.&lt;/p&gt;
&lt;h3 id=&quot;align-visibility-with-authorization&quot;&gt;Align visibility with authorization&lt;/h3&gt;
&lt;p&gt;Navigation and authorization answer related but different questions.&lt;/p&gt;
&lt;p&gt;Navigation visibility asks, “Should this person be offered this destination?” Authorization asks, “May this request proceed?” A hidden card is not a security boundary. Someone can still follow a saved link or construct a request directly.&lt;/p&gt;
&lt;p&gt;The useful contract is alignment: the navigation entry should use the same entitlement expressed by the destination policy, while the page or endpoint continues to enforce that policy independently.&lt;/p&gt;
&lt;p&gt;This check often exposes ambiguity. If a destination has only broad authentication while its navigation card is restricted to a privileged audience, do not quietly declare the card to be protection. Flag the mismatch for an explicit security decision. Navigation work should reveal authorization uncertainty, not accidentally resolve it.&lt;/p&gt;
&lt;h3 id=&quot;the-trade-off-is-real&quot;&gt;The trade-off is real&lt;/h3&gt;
&lt;p&gt;A strict hub contract has costs. The catalogue grows. New pages require deliberate placement and wording. A hub can become crowded if every capability is presented at the same level.&lt;/p&gt;
&lt;p&gt;The alternative, however, is not free. A loose rule produces accidental journeys, invisible capabilities, and tests that certify the presence of links without certifying usable discovery.&lt;/p&gt;
&lt;p&gt;The practical balance is to keep the entry-point contract strict while allowing the interface to group, search, filter, and progressively disclose destinations. The architecture test should protect reachability; the visual design can protect clarity.&lt;/p&gt;
&lt;h3 id=&quot;a-small-checklist&quot;&gt;A small checklist&lt;/h3&gt;
&lt;p&gt;For a modular routed application:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Inventory every route tree, not only the first one the test was written for.&lt;/li&gt;
&lt;li&gt;Classify static entry pages, parameterised downstream pages, and route aliases.&lt;/li&gt;
&lt;li&gt;Identify the navigation roots users can actually reach.&lt;/li&gt;
&lt;li&gt;Require every entry page to appear from an appropriate root.&lt;/li&gt;
&lt;li&gt;Add a minimum discovery count to catch broken scans.&lt;/li&gt;
&lt;li&gt;Make exemptions explicit, reasoned, narrow, and self-cleaning.&lt;/li&gt;
&lt;li&gt;Align link visibility with destination authorization, while enforcing security at the destination.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The deeper lesson is that architecture includes the path a person can take through the system. If a feature has no honest entrance, its route is an implementation detail—not a delivered capability.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30901/a-route-is-not-a-feature-until-someone-can-reach-it</guid>
<pubDate>Tue, 13 Oct 2026 07:01:11 +0000</pubDate>
</item>
<item>
<title>The Async Result Belongs to the Snapshot That Started It</title>
<link>https://coderlegion.com/30557/the-async-result-belongs-to-the-snapshot-that-started-it</link>
<description>&lt;p&gt;Modern client components are long-lived, reactive, and full of asynchronous work. That combination creates a subtle class of defect: a response can be factually correct for the request that produced it, yet wrong for the screen that exists when it arrives.&lt;/p&gt;
&lt;p&gt;This is especially important when a component mixes route parameters with identity-scoped state. The user can navigate while a request is in flight. Authentication can resolve, refresh, or change. The component instance may survive both events. Unless the component tracks which context owns each result, an older task can update a newer view.&lt;/p&gt;
&lt;p&gt;The core rule is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;An awaited result belongs to the input snapshot that started it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That snapshot is more than a component reference. It can include the current route value, the current authentication-state operation, and a monotonically increasing generation number.&lt;/p&gt;
&lt;h3 id=&quot;how-the-race-forms&quot;&gt;How the race forms&lt;/h3&gt;
&lt;p&gt;Imagine a detail page that loads an item, then asks whether the current viewer has reacted to it. The page shows a highlighted action when the answer is yes.&lt;/p&gt;
&lt;p&gt;The component begins loading item A for identity A. Before the viewer-specific request returns, the user signs out and another identity becomes current—or the same component navigates to item B. The old request then completes with a truthful answer: identity A did react to item A.&lt;/p&gt;
&lt;p&gt;If the continuation writes directly to a shared &lt;code&gt;isActive&lt;/code&gt; field, that truth is now displayed in the wrong context. Item B may appear active, or identity B may see state derived from identity A.&lt;/p&gt;
&lt;p&gt;This is not merely a cosmetic race. Identity-derived state can influence which actions are enabled, what ownership badges appear, and which command a user can submit next.&lt;/p&gt;
&lt;h3 id=&quot;make-ownership-explicit&quot;&gt;Make ownership explicit&lt;/h3&gt;
&lt;p&gt;A practical pattern is to treat every meaningful input change as a new generation.&lt;/p&gt;
&lt;p&gt;When a route or authentication input changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Increment the generation.&lt;/li&gt;
&lt;li&gt;Capture the route and authentication input in local variables.&lt;/li&gt;
&lt;li&gt;Clear state derived from the previous identity.&lt;/li&gt;
&lt;li&gt;Disable identity-scoped actions while the new identity is unresolved.&lt;/li&gt;
&lt;li&gt;After each &lt;code&gt;await&lt;/code&gt;, confirm that the generation and captured inputs are still current.&lt;/li&gt;
&lt;li&gt;Apply results only when those checks pass.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The checks matter after every awaited boundary, not only at the end. A component may load the main item, related content, entitlement state, reaction state, and cart state in sequence. Any await is a point where navigation or identity can change.&lt;/p&gt;
&lt;p&gt;Cancellation tokens can reduce wasted work, and they are worth using when the underlying operation supports cancellation. But cancellation alone is not the ownership proof. Cancellation can race with completion, and some dependencies cannot cancel promptly. The continuation still needs to ask, “Does this result belong to the current snapshot?”&lt;/p&gt;
&lt;h3 id=&quot;fail-closed-while-identity-is-unknown&quot;&gt;Fail closed while identity is unknown&lt;/h3&gt;
&lt;p&gt;There is another important state between old identity and new identity: unresolved identity.&lt;/p&gt;
&lt;p&gt;Keeping the old viewer-specific UI visible during that interval is convenient, but unsafe. The component should clear ownership badges, active reactions, and similar derived state before awaiting the new authentication result. Any action that depends on identity should be disabled until the new snapshot is ready.&lt;/p&gt;
&lt;p&gt;That brief disabled state is honest. The application does not yet know which identity owns the next command.&lt;/p&gt;
&lt;p&gt;This principle also prevents an action from being sent using newly refreshed credentials while the visible UI still describes the previous viewer. Read state and write authority move together.&lt;/p&gt;
&lt;h3 id=&quot;test-the-interleaving-not-only-the-outcome&quot;&gt;Test the interleaving, not only the outcome&lt;/h3&gt;
&lt;p&gt;Ordinary happy-path tests rarely expose this defect because requests finish in the same order they started.&lt;/p&gt;
&lt;p&gt;A stronger component test controls the schedule:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start a viewer-specific read and hold its response.&lt;/li&gt;
&lt;li&gt;Change the route or authentication identity.&lt;/li&gt;
&lt;li&gt;Wait for the new screen to settle.&lt;/li&gt;
&lt;li&gt;Release the old response.&lt;/li&gt;
&lt;li&gt;Assert that the current screen does not adopt the old result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A separate test should hold authentication unresolved and verify that identity-scoped actions remain disabled. This proves that the UI fails closed during the transition.&lt;/p&gt;
&lt;p&gt;There is also a useful counter-test: rerender with exactly the same inputs while a valid request is pending. The component should not discard or duplicate work merely because the framework rendered again. The boundary is a meaningful input change, not every render cycle.&lt;/p&gt;
&lt;p&gt;These tests are valuable because they verify time and ownership, not just a final value. They turn a race that might appear once in a thousand interactions into a deterministic scenario.&lt;/p&gt;
&lt;h3 id=&quot;the-trade-off&quot;&gt;The trade-off&lt;/h3&gt;
&lt;p&gt;Generation markers, captured snapshots, and repeated currentness checks add code. Temporarily disabling an action can introduce a small pause. Overusing the pattern can also make a component difficult to read.&lt;/p&gt;
&lt;p&gt;The answer is not to attach a counter to every asynchronous line. Use the pattern where results are scoped to changing inputs: routes, selected records, authentication identity, workspace, locale, or another boundary that can change while the component remains mounted. Extract a small helper when the same ownership rule appears repeatedly.&lt;/p&gt;
&lt;p&gt;The payoff is larger than visual correctness. Explicit async ownership reduces stale-route updates, prevents state from crossing identity boundaries, and stops commands from being enabled under an unresolved context.&lt;/p&gt;
&lt;p&gt;The humble takeaway is this: asynchronous correctness is not only about receiving the right answer. It is also about proving that the answer still belongs here.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30557/the-async-result-belongs-to-the-snapshot-that-started-it</guid>
<pubDate>Sat, 10 Oct 2026 06:01:28 +0000</pubDate>
</item>
<item>
<title>Design the Cursor, Not Just the Query</title>
<link>https://coderlegion.com/28877/design-the-cursor-not-just-the-query</link>
<description>&lt;p&gt;Pagination is often introduced as a performance improvement: fetch fewer rows, render less markup, and keep response sizes predictable. That is only half the job.&lt;/p&gt;
&lt;p&gt;For an operational queue that changes while people work, pagination also defines what it means to continue reading. If that contract is weak, the database can answer every query correctly while the person using the screen still sees duplicates or misses work.&lt;/p&gt;
&lt;p&gt;The important design question is not merely, “How many rows should this query return?” It is, “What does the next request mean after the underlying set has changed?”&lt;/p&gt;
&lt;h3 id=&quot;why-offset-paging-drifts&quot;&gt;Why offset paging drifts&lt;/h3&gt;
&lt;p&gt;Consider a queue ordered newest first. A reviewer asks for the first twenty-five rows. The next request uses an offset of twenty-five.&lt;/p&gt;
&lt;p&gt;Between those requests, a new row arrives at the top. Every earlier row has moved down one position. The second query now begins with the final row from page one, so the reviewer sees a duplicate. More importantly, one unseen row has been pushed beyond the requested range.&lt;/p&gt;
&lt;p&gt;This is not an exotic race. Append-mostly queues invite it because adding work is the ordinary operation. Offset paging treats positions as stable even though insertion changes every position below it.&lt;/p&gt;
&lt;p&gt;Mutable sorting creates a similar problem. If the queue is ordered by status or last-updated time, an operator’s decision can move a row while the walk is in progress. A continuation based on that value inherits the instability of the workflow.&lt;/p&gt;
&lt;h3 id=&quot;continue-from-an-immutable-boundary&quot;&gt;Continue from an immutable boundary&lt;/h3&gt;
&lt;p&gt;A stronger contract says: continue after the boundary returned by the previous read.&lt;/p&gt;
&lt;p&gt;For a newest-first queue, that boundary can be an immutable creation or submission timestamp. New rows arriving above the boundary are not part of the current walk. They will appear when the reviewer starts again from the top, but they do not shift the meaning of “next.”&lt;/p&gt;
&lt;p&gt;Equal timestamps need deliberate handling. Adding an identifier as a tie-breaker becomes fragile when data providers sort that type differently.&lt;/p&gt;
&lt;p&gt;Another approach is to carry the boundary timestamp plus the number of rows already consumed at that exact timestamp. The database remains responsible for its native ordering within the tie, while the continuation records how far through that tie the walk has progressed. The detail matters less than the invariant: ties must neither duplicate nor hide rows.&lt;/p&gt;
&lt;h3 id=&quot;make-the-continuation-part-of-the-contract&quot;&gt;Make the continuation part of the contract&lt;/h3&gt;
&lt;p&gt;An opaque cursor should not be a loosely encoded page number. It is protocol state, so treat it that way.&lt;/p&gt;
&lt;p&gt;Useful safeguards include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a version, so the format can evolve deliberately;&lt;/li&gt;
&lt;li&gt;a strict length limit and parse validation;&lt;/li&gt;
&lt;li&gt;the immutable boundary and tie position;&lt;/li&gt;
&lt;li&gt;the active filter identity; and&lt;/li&gt;
&lt;li&gt;sensible bounds on any stored count.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Binding the cursor to its filter is especially important. A position in “all open items” does not mean the same thing in “items for one category.” Reusing the cursor after changing filters can silently skip work. Rejecting that request is clearer than pretending the cursor is still meaningful.&lt;/p&gt;
&lt;p&gt;Likewise, a malformed continuation should fail explicitly. Silently dropping it and returning the first page converts a bad request into a plausible-looking duplicate page. Operational software benefits from visible invalid states.&lt;/p&gt;
&lt;h3 id=&quot;keep-every-downstream-read-bounded&quot;&gt;Keep every downstream read bounded&lt;/h3&gt;
&lt;p&gt;Paging the primary table is not enough if enrichment still loads data for the entire result set.&lt;/p&gt;
&lt;p&gt;A common pattern reads the page, extracts its identifiers, and then fetches related summaries in a second query. That identifier list must come from the bounded page. Otherwise the visible query is paged while a hidden IN clause continues growing with the queue.&lt;/p&gt;
&lt;p&gt;Fetching one extra primary row is a simple way to determine whether a continuation is needed. Return the requested page size, and issue a next cursor only when the extra row exists. The presence of that cursor—not whether the current page happens to be full—becomes the authoritative “has more” signal.&lt;/p&gt;
&lt;p&gt;This also avoids an exact count on every read. For many live queues, “Page 3” plus Previous and Next is the more honest interface.&lt;/p&gt;
&lt;h3 id=&quot;carry-the-contract-into-the-blazor-ui&quot;&gt;Carry the contract into the Blazor UI&lt;/h3&gt;
&lt;p&gt;Server-side continuation paging should not be wrapped in a client component that first downloads the entire queue. The UI has to follow the same contract.&lt;/p&gt;
&lt;p&gt;A practical Blazor page can keep a small trail containing the cursor used to open each visited page. Next stores the returned cursor. Previous reuses the earlier cursor to re-read that logical page. After an operator records a decision, refresh the current page rather than throwing them back to the beginning.&lt;/p&gt;
&lt;p&gt;Page changes should also clear any pending confirmation tied to a row that is no longer visible. That is a small state-management detail with a large clarity benefit: the user should never be asked to confirm an action on an item they cannot see.&lt;/p&gt;
&lt;h3 id=&quot;test-the-movement-not-only-the-shape&quot;&gt;Test the movement, not only the shape&lt;/h3&gt;
&lt;p&gt;Happy-path tests that assert “twenty-five rows returned” do not prove a paging contract.&lt;/p&gt;
&lt;p&gt;The valuable cases exercise movement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;insert a newer row after reading page one, then verify page two has no gap or duplicate;&lt;/li&gt;
&lt;li&gt;create several rows with the same boundary timestamp;&lt;/li&gt;
&lt;li&gt;reject zero, negative, and excessive page sizes;&lt;/li&gt;
&lt;li&gt;reject malformed, oversized, and filter-mismatched cursors;&lt;/li&gt;
&lt;li&gt;verify related-data queries receive only current-page identifiers;&lt;/li&gt;
&lt;li&gt;navigate Next and Previous in the component; and&lt;/li&gt;
&lt;li&gt;perform an action and confirm the current page is refreshed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These tests describe the promise a reviewer experiences, not merely an implementation detail.&lt;/p&gt;
&lt;h3 id=&quot;the-honest-trade-off&quot;&gt;The honest trade-off&lt;/h3&gt;
&lt;p&gt;Continuation paging is more work than offset paging. It needs a stable sort, a cursor format, versioning, filter binding, careful tie semantics, and focused tests. It also makes random access and exact page counts less convenient.&lt;/p&gt;
&lt;p&gt;That cost is justified when the list is both live and operationally important. Bounded queries protect the system under bursty load; stable continuation protects the human workflow under concurrent change.&lt;/p&gt;
&lt;p&gt;Takeaway: choose pagination from the mutation pattern, not from the table widget. If ordinary inserts can change what “next” means, the continuation itself deserves first-class design.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28877/design-the-cursor-not-just-the-query</guid>
<pubDate>Mon, 28 Sep 2026 06:23:05 +0000</pubDate>
</item>
<item>
<title>A Page Is Not Shipped Until People Can Find It</title>
<link>https://coderlegion.com/28578/a-page-is-not-shipped-until-people-can-find-it</link>
<description>&lt;p&gt;A routed page can compile, pass its authorization checks, have documentation, and still be functionally absent. If the people entitled to use it cannot discover a normal way in, the product behaves as though the capability was never built.&lt;/p&gt;
&lt;p&gt;That is easy to miss in a modular Blazor application. One team adds pages in one project, another module owns a second page tree, and a central hub or navigation catalogue tries to present the whole product coherently. Each local change can look correct while the experience between modules quietly breaks.&lt;/p&gt;
&lt;p&gt;The remedy is not another release checklist. It is an executable discoverability contract.&lt;/p&gt;
&lt;h3 id=&quot;define-the-invariant-at-the-page-boundary&quot;&gt;Define the invariant at the page boundary&lt;/h3&gt;
&lt;p&gt;A useful rule is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Every static operational page must have one intentional entry point whose visibility matches the page's access rule.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“Static” matters. A detail page with an identifier in its route is normally reached from a list, search result, or parent screen. It does not need a global menu item. A screen that answers on two route aliases is still one page; adding two cards would create duplication rather than discoverability.&lt;/p&gt;
&lt;p&gt;The rule should cover pages, not merely strings that resemble URLs. That keeps the test aligned with the product concept people actually use.&lt;/p&gt;
&lt;p&gt;It should also say “intentional entry point,” not “mentioned somewhere.” A link buried inside another page may technically make a route reachable, but it does not necessarily make the capability findable. Likewise, a help article that names a screen is documentation, not navigation. Requiring a deliberate card or menu entry forces the information architecture to be explicit.&lt;/p&gt;
&lt;h3 id=&quot;scan-every-module-not-only-the-original-tree&quot;&gt;Scan every module, not only the original tree&lt;/h3&gt;
&lt;p&gt;Architecture tests often begin with a useful local assumption: all administrative pages live under one directory, namespace, or assembly. The test passes for years. Then a new module introduces another tree and the guard continues to report green because it never sees the new pages.&lt;/p&gt;
&lt;p&gt;This is a particularly awkward failure because both the feature and the guard are “working as designed.” The design boundary is simply stale.&lt;/p&gt;
&lt;p&gt;The test therefore needs a completeness check as well as a matching check. Enumerate every known page tree. Parse the routed components. Exclude only clearly contextual routes. Then compare the resulting pages with the navigation catalogue.&lt;/p&gt;
&lt;p&gt;Add a sanity assertion too. If a mature tree suddenly produces zero—or far fewer—pages, the parser or directory assumption probably broke. Without that guard, a failed scan can look like perfect compliance.&lt;/p&gt;
&lt;p&gt;When a new module owns pages, extending the scan is part of integrating that module. This turns architectural coverage into an explicit responsibility rather than inherited folklore.&lt;/p&gt;
&lt;h3 id=&quot;keep-navigation-and-authorization-separate&quot;&gt;Keep navigation and authorization separate&lt;/h3&gt;
&lt;p&gt;The navigation entry should use the same access decision as the destination page. If the card is broader, people see an invitation that ends in refusal. If it is narrower, people who are entitled to use the page cannot find it.&lt;/p&gt;
&lt;p&gt;It is tempting to reuse a nearby permission because it is convenient. That can be wrong in both directions. When no existing decision expresses the page's real audience, adding a focused visibility flag is usually clearer than bending an unrelated one.&lt;/p&gt;
&lt;p&gt;But matching visibility does not turn navigation into security. A hidden card only changes what the interface presents. A direct request, a bookmarked address, or another client can still reach the endpoint. The page or server operation must continue to enforce authorization independently.&lt;/p&gt;
&lt;p&gt;This separation produces a healthy pair of invariants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;navigation makes entitled capabilities discoverable;&lt;/li&gt;
&lt;li&gt;authorization rejects requests that are not entitled.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Testing both is stronger than asking either mechanism to do two jobs.&lt;/p&gt;
&lt;h3 id=&quot;prefer-a-precise-contract-over-linked-somewhere-&quot;&gt;Prefer a precise contract over “linked somewhere”&lt;/h3&gt;
&lt;p&gt;A broad reachability scan can be a good first diagnostic. Search the codebase for links to each route and identify pages that have no inbound reference. That quickly reveals obvious orphans.&lt;/p&gt;
&lt;p&gt;As a permanent contract, however, “linked somewhere” is weak. A tab rail is visible only after someone has already reached a page in that tab set. A contextual link may depend on data or state. A help page may mention the route without providing product navigation. Source text can prove that a string exists; it cannot prove that the intended audience has a clear entry point.&lt;/p&gt;
&lt;p&gt;Tightening the rule to require the chosen navigation catalogue makes the standard less ambiguous. It also creates useful pressure: when a page fits nowhere, the team must decide whether the navigation model is incomplete, the page is contextual, or the feature should not be exposed yet.&lt;/p&gt;
&lt;h3 id=&quot;accept-the-maintenance-cost-deliberately&quot;&gt;Accept the maintenance cost deliberately&lt;/h3&gt;
&lt;p&gt;The stricter contract adds work. New pages need catalogue entries, accurate visibility mapping, and suitable grouping. Route conventions must be understood well enough for the test to distinguish static pages, detail pages, and aliases. Large navigation surfaces may expose sections that no longer fit their historical categories.&lt;/p&gt;
&lt;p&gt;Those are not test problems to hide with a long exemption list. They are product and architecture decisions the test has made visible.&lt;/p&gt;
&lt;p&gt;Keep exemptions small, named, and justified by a navigation model rather than by inconvenience. Fail when an exemption becomes stale. If a page genuinely belongs behind a list or workflow, encode that reason. If it is an operational destination, give it an intentional entry point.&lt;/p&gt;
&lt;p&gt;The payoff is modest but durable: completed capabilities do not disappear between modules, permission drift becomes easier to see, and a new page cannot quietly land outside the product's navigation contract.&lt;/p&gt;
&lt;p&gt;A page is not shipped merely because its route exists. It is shipped when the right person can find it—and the wrong person still cannot execute it.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28578/a-page-is-not-shipped-until-people-can-find-it</guid>
<pubDate>Fri, 25 Sep 2026 06:06:10 +0000</pubDate>
</item>
<item>
<title>When a Green Test Protects the Wrong Authorization Rule</title>
<link>https://coderlegion.com/28115/when-a-green-test-protects-the-wrong-authorization-rule</link>
<description>&lt;p&gt;An automated test is useful only when its assertion represents the rule we intend to preserve. That sounds obvious until a test passes for the exact condition that should make a reviewer uneasy.&lt;/p&gt;
&lt;p&gt;A recent Blazor change offered a concrete reminder. Two routed administrative pages required a signed-in user but did not assert a permission policy at the page. The links that led to them were filtered by permission. That made the interface look appropriately restricted, while a signed-in person could still enter the URL directly. Two existing tests expected the permissive page attributes, so the green suite helped the gap survive.&lt;/p&gt;
&lt;p&gt;The engineering lesson is broader than those pages: when a test blesses an authorization omission, fix the assertion and add an invariant that catches the next omission. Keep the invariant honest about what it can and cannot prove.&lt;/p&gt;
&lt;h3 id=&quot;navigation-visibility-is-a-different-boundary&quot;&gt;Navigation visibility is a different boundary&lt;/h3&gt;
&lt;p&gt;Consider an administrative interface with a configuration card. The card appears only when the current viewer has a configuration permission. The destination page, however, asks only whether the viewer is signed in. Hiding the card changes what the viewer sees; it does not change what the route accepts.&lt;/p&gt;
&lt;p&gt;The direct URL is the simplest counterexample. If the user knows or guesses it, the navigation filter is bypassed. A page-level policy is needed to decide whether that route may render for the viewer. The underlying API or command must make its own authorization decision as well. These checks serve different entry points, so one cannot stand in for the other.&lt;/p&gt;
&lt;p&gt;In the inspected change, the page policies were added. One data endpoint already had a stricter authorization rule and kept it. The lesson is to review each boundary on its own terms: visible link, routed page, and operation.&lt;/p&gt;
&lt;h3 id=&quot;read-assertions-as-product-decisions&quot;&gt;Read assertions as product decisions&lt;/h3&gt;
&lt;p&gt;The surprising part was not that a page attribute was too broad. It was that tests explicitly expected that breadth. One assertion accepted the absence of a policy; another looked for a bare sign-in attribute. Those statements had made an implementation detail into a contract.&lt;/p&gt;
&lt;p&gt;When correcting a test like this, start by naming the intended behaviour in plain language. For example: “This routed configuration page requires the configuration policy.” Then inspect the compiled authorization attribute and assert that policy. This makes the test follow the behaviour the framework will see, rather than a particular text fragment in a Razor file.&lt;/p&gt;
&lt;p&gt;That specific test matters even if a broader scan exists. A scan can report that a policy is present, but a policy for an unrelated administrative task could still pass it. The page-specific assertion checks the narrower decision about &lt;em&gt;which&lt;/em&gt; permission belongs there.&lt;/p&gt;
&lt;h3 id=&quot;turn-the-omission-into-an-invariant&quot;&gt;Turn the omission into an invariant&lt;/h3&gt;
&lt;p&gt;The change also added a structural test over routed administrative pages in two application areas. It checks whether each page asserts a permission policy. The test has a nonempty coverage guard, because a scan that accidentally finds zero pages is worse than a failing scan: it can look reassuring while doing nothing.&lt;/p&gt;
&lt;p&gt;This is a useful pattern for requirements that should apply across a family of files. A single page test protects a known case. A route scan makes it harder for a new page to repeat the same omission. Neither test needs to simulate every user and role to answer the structural question, “Did this route declare a permission policy?”&lt;/p&gt;
&lt;p&gt;The boundary of that answer is important. The scan does not prove that every declared policy is correct. It does not prove that an API rejects unauthorized calls. It does not prove that authorization is complete across the product. Those claims require different tests and review.&lt;/p&gt;
&lt;h3 id=&quot;make-exceptions-accountable&quot;&gt;Make exceptions accountable&lt;/h3&gt;
&lt;p&gt;Some landing pages can legitimately be available to any signed-in viewer because their contents filter themselves by the viewer's permissions. A blanket rule that rejects every such page would create noise and encourage people to weaken the test. The committed scan therefore allows explicit exemptions with reasons.&lt;/p&gt;
&lt;p&gt;An exemption is still a maintenance obligation. If a page is deleted, renamed, or later gains a policy, its old exemption should disappear. A second test checks for those stale entries. It keeps the exception list from becoming a historical catalogue that a future developer might mistake for permission to add more exceptions.&lt;/p&gt;
&lt;p&gt;The two corrected pages are pinned separately. That matters because someone could otherwise make the broad scan green by putting a vulnerable page on the exemption list. The pinned tests say those particular pages must keep their policies and cannot be exempted.&lt;/p&gt;
&lt;p&gt;The same change initially documented a separate unresolved permission decision as a known gap. A follow-up commit assigned that page a policy after an owner made the decision. Recording the gap first kept the review honest while the right policy was decided rather than guessed from a neighbouring page.&lt;/p&gt;
&lt;h3 id=&quot;the-practical-review-sequence&quot;&gt;The practical review sequence&lt;/h3&gt;
&lt;p&gt;When a green test has protected the wrong rule, I use this sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reproduce the bypass at the relevant entry point in a controlled test or review, such as direct route access rather than clicking the navigation card.&lt;/li&gt;
&lt;li&gt;State the intended permission and correct the local assertion so it checks the framework-visible attribute.&lt;/li&gt;
&lt;li&gt;Add a structural invariant if the same omission can recur across many pages.&lt;/li&gt;
&lt;li&gt;Give every real exception a reason, and make stale exceptions fail.&lt;/li&gt;
&lt;li&gt;Keep page and operation authorization under separate review; presence of a page policy is only one piece of evidence.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach costs some test and exception-list maintenance. The benefit is a much earlier signal when another route omits a policy, while the specific assertions preserve the decisions that matter most. A green suite should mean that our intended boundary is holding, not merely that yesterday's implementation has stayed the same.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28115/when-a-green-test-protects-the-wrong-authorization-rule</guid>
<pubDate>Tue, 22 Sep 2026 07:08:12 +0000</pubDate>
</item>
<item>
<title>Pagination Is a Consistency Contract</title>
<link>https://coderlegion.com/27813/pagination-is-a-consistency-contract</link>
<description>&lt;p&gt;Page numbers feel harmless. They are familiar, easy to render, and easy to explain. But on a mutable work queue, they can lie.&lt;/p&gt;
&lt;p&gt;Imagine a reviewer reading a newest-first queue. They load the first 25 items. Before they request the next page, a new item arrives at the top. An offset such as &lt;code&gt;skip 25&lt;/code&gt; now starts one row later than it would have a moment ago. The last row from page one may appear again, and the row displaced by the insert may never appear in that walk.&lt;/p&gt;
&lt;p&gt;Nothing crashed. No error was logged. The interface simply presented an inconsistent view of the work.&lt;/p&gt;
&lt;p&gt;The engineering lesson is that pagination over changing data is a consistency contract, not a decorative control beneath a table.&lt;/p&gt;
&lt;h3 id=&quot;begin-with-the-mutation-model&quot;&gt;Begin with the mutation model&lt;/h3&gt;
&lt;p&gt;The right pagination strategy depends on how the collection changes.&lt;/p&gt;
&lt;p&gt;For an append-mostly queue ordered newest first, new work normally arrives above the operator's current position. Other fields, such as workflow status, may change while the operator is reviewing an item. Those two facts suggest a useful boundary: continue from something fixed when the item entered the queue, not from a mutable field and not from the current numeric position.&lt;/p&gt;
&lt;p&gt;A submission timestamp can provide that boundary if it is frozen when the item is created. Status usually cannot; ordering by status lets a decision move rows between pages while someone is walking the list.&lt;/p&gt;
&lt;p&gt;The principle is broader than timestamps: choose an ordering key whose meaning remains stable for the duration of a walk.&lt;/p&gt;
&lt;h3 id=&quot;make-the-server-own-the-continuation&quot;&gt;Make the server own the continuation&lt;/h3&gt;
&lt;p&gt;A client should not reconstruct position from a page number, a visible row, or a guessed offset. The server should return an opaque continuation and accept it unchanged on the next request.&lt;/p&gt;
&lt;p&gt;For a newest-first queue, a continuation can describe the last timestamp boundary and how many rows at that exact instant have already been emitted. The next query includes rows at or below the boundary, skips only the already-seen ties at that boundary, and takes one extra row to determine whether another page exists.&lt;/p&gt;
&lt;p&gt;Why count ties instead of comparing an identifier? Identifier ordering is not always portable across database providers. Let the query provider apply its established ordering, and record only how far through the boundary group the walk has progressed.&lt;/p&gt;
&lt;p&gt;Opaque does not mean unchecked. Reject blank, malformed, oversized, or unsupported continuations. A bad continuation should not silently restart at the top, because a restart looks like valid data with unexplained duplicates.&lt;/p&gt;
&lt;h3 id=&quot;bind-position-to-query-context&quot;&gt;Bind position to query context&lt;/h3&gt;
&lt;p&gt;A continuation names a position in one specific ordered result set. Change the filter and that position may no longer mean anything.&lt;/p&gt;
&lt;p&gt;Suppose page one was filtered to one workflow cycle, then the same continuation was replayed with the filter removed. Honouring it could skip rows that were never part of the original walk. The continuation should therefore carry enough context to prove it belongs to the active query. If the context differs, refuse the request and let the client restart explicitly.&lt;/p&gt;
&lt;p&gt;The same discipline applies to page size. Put a small upper bound in the contract and reject values outside it. Quietly shrinking an oversized request hides caller mistakes; accepting it defeats the point of paging.&lt;/p&gt;
&lt;p&gt;Bounding the first query is only half the work. If each row needs evidence, comments, or another related record, fetch those details only for identifiers on the current page. Otherwise an apparently paged endpoint can still perform an unbounded secondary read.&lt;/p&gt;
&lt;h3 id=&quot;preserve-the-operator-s-place&quot;&gt;Preserve the operator's place&lt;/h3&gt;
&lt;p&gt;Continuation cursors naturally move forward. A usable review screen also needs Back and refresh.&lt;/p&gt;
&lt;p&gt;The client can keep a short trail of the server-provided continuations used to open each visited page. Previous then replays the cursor that originally opened the prior page. The client is not interpreting the cursor; it is remembering the path.&lt;/p&gt;
&lt;p&gt;After a reviewer changes an item, refresh the page they are currently viewing. Returning to page one after every decision is technically simple and operationally punishing. It also encourages people to work around the interface rather than trust it.&lt;/p&gt;
&lt;p&gt;There is one awkward edge case: a later page may become empty before it is refreshed. Do not render a global empty-state message that implies the entire queue has vanished. Reset to the first page and show the current truth from a valid starting position.&lt;/p&gt;
&lt;h3 id=&quot;test-the-walk-not-only-the-query&quot;&gt;Test the walk, not only the query&lt;/h3&gt;
&lt;p&gt;Happy-path tests that assert “25 rows returned” are not enough. The important properties live between requests.&lt;/p&gt;
&lt;p&gt;Useful executable checks include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a new item arriving above the boundary neither duplicates nor hides an existing row;&lt;/li&gt;
&lt;li&gt;a workflow status change does not alter page membership;&lt;/li&gt;
&lt;li&gt;several items sharing one timestamp are neither repeated nor lost;&lt;/li&gt;
&lt;li&gt;a continuation cannot be replayed with a different filter;&lt;/li&gt;
&lt;li&gt;malformed continuations and invalid page sizes are refused;&lt;/li&gt;
&lt;li&gt;forward and backward navigation use the exact server continuations;&lt;/li&gt;
&lt;li&gt;a write refreshes the active page; and&lt;/li&gt;
&lt;li&gt;an empty later page recovers without claiming the whole queue is empty.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These tests describe the contract more clearly than the word “paged” ever could.&lt;/p&gt;
&lt;h3 id=&quot;the-trade-off-is-explicit-complexity&quot;&gt;The trade-off is explicit complexity&lt;/h3&gt;
&lt;p&gt;Cursor paging asks more of both sides. The server must define and validate a stable continuation. The client must retain a small history. Ties and disappearing pages need deliberate behaviour. Troubleshooting is less intuitive than looking at &lt;code&gt;page=3&lt;/code&gt; in a URL.&lt;/p&gt;
&lt;p&gt;In return, database work stays bounded and a reviewer can walk a changing queue without silent duplicates or gaps. That is a worthwhile trade whenever missing one item matters more than keeping the paging code superficially simple.&lt;/p&gt;
&lt;p&gt;Before choosing offsets for a mutable list, simulate one insert and one update between consecutive requests. If the same walk no longer produces the same set of rows, the pager needs a stronger contract.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27813/pagination-is-a-consistency-contract</guid>
<pubDate>Sun, 20 Sep 2026 07:52:36 +0000</pubDate>
</item>
<item>
<title>Realtime Is a Hint, Not Your Source of Truth</title>
<link>https://coderlegion.com/27482/realtime-is-a-hint-not-your-source-of-truth</link>
<description>&lt;p&gt;A notification badge looks like a small feature. In a distributed application, it can expose a large architectural assumption.&lt;/p&gt;
&lt;p&gt;I recently investigated a badge that sometimes stayed stale in an ASP.NET Core and Blazor application running across multiple server instances. The notification itself had been created successfully. Durable state was correct. A separate push path could also complete. Yet the visible count did not always change until the user left the screen and returned.&lt;/p&gt;
&lt;p&gt;The bug was not really about arithmetic. It was about assigning too much authority to a realtime transport.&lt;/p&gt;
&lt;h3 id=&quot;the-hidden-assumption&quot;&gt;The hidden assumption&lt;/h3&gt;
&lt;p&gt;The UI initially loaded its count from the server and then relied on live events to remain current. That feels reasonable while developing against one server with a stable connection. In production, several ordinary conditions weaken the assumption:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The client can disconnect briefly.&lt;/li&gt;
&lt;li&gt;A load balancer can place the sender and receiver on different instances.&lt;/li&gt;
&lt;li&gt;A workflow can create the same durable state without passing through the page that happens to broadcast.&lt;/li&gt;
&lt;li&gt;A transport or hub can fail after the database operation has already succeeded.&lt;/li&gt;
&lt;li&gt;An idempotent retry can revisit work that should not increment the UI again.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these conditions makes realtime technology a poor choice. They simply mean that a realtime message should usually be treated as a hint that fresh state is available, not as the state itself.&lt;/p&gt;
&lt;h3 id=&quot;a-three-layer-reliability-pattern&quot;&gt;A three-layer reliability pattern&lt;/h3&gt;
&lt;p&gt;The repair became much clearer once the responsibilities were separated into three layers.&lt;/p&gt;
&lt;h4 id=&quot;first-persist-the-truth&quot;&gt;First, persist the truth&lt;/h4&gt;
&lt;p&gt;The durable record must be committed independently of whether any connected client is listening. If the user closes the app, changes networks, or connects to another instance, the correct unread state must still be recoverable later.&lt;/p&gt;
&lt;p&gt;This ordering matters. Broadcasting first can produce a convincing UI update for data that subsequently fails to commit. Persisting first means a missed broadcast can be healed; an invented broadcast cannot be trusted.&lt;/p&gt;
&lt;h4 id=&quot;second-broadcast-from-the-business-chokepoint&quot;&gt;Second, broadcast from the business chokepoint&lt;/h4&gt;
&lt;p&gt;The live update originally depended on a narrower UI workflow. Other creation paths could produce the same durable result without emitting the event. Moving the broadcast to the shared dispatch path made the behavior consistent across interactive and background work.&lt;/p&gt;
&lt;p&gt;The broadcast was also tied to the outcome of the durable write. A genuinely new record emits a hint. An idempotent replay that finds the work already completed does not. That prevents duplicate increments and makes the event reflect a real state transition rather than merely an attempted command.&lt;/p&gt;
&lt;h4 id=&quot;third-reconcile-periodically&quot;&gt;Third, reconcile periodically&lt;/h4&gt;
&lt;p&gt;Even a correctly placed broadcast can be missed. The client therefore refreshes the authoritative count on a bounded interval. The loop is cancelled when the component is disposed, and a transient refresh failure does not kill future attempts.&lt;/p&gt;
&lt;p&gt;This is not polling as the primary experience. Realtime still gives the fast path. Polling is the repair path.&lt;/p&gt;
&lt;h3 id=&quot;the-trade-off-1&quot;&gt;The trade-off&lt;/h3&gt;
&lt;p&gt;Hybrid delivery is not free.&lt;/p&gt;
&lt;p&gt;Periodic reconciliation adds read traffic. The interval also defines a maximum period of visible staleness when a realtime event is missed. A very short interval increases load and can waste battery or bandwidth on mobile clients. A long interval lowers cost but leaves stale UI visible for longer.&lt;/p&gt;
&lt;p&gt;That makes the interval a product and operational decision, not a magic constant. Ask how damaging stale information is, how expensive the read is, how many active clients exist, and whether the endpoint can use a cheap aggregate or cache.&lt;/p&gt;
&lt;p&gt;For a badge, bounded staleness may be acceptable. For a payment confirmation or permission change, the interaction may require an immediate authoritative read instead. The pattern stays the same, but the recovery budget changes.&lt;/p&gt;
&lt;p&gt;Failure boundaries matter too. Once durable work has succeeded, a hub failure should not turn the whole operation into a failure. Otherwise a temporary transport problem can encourage retries of already-completed business work. Log the broadcast failure, observe it, and let reconciliation repair the presentation.&lt;/p&gt;
&lt;h3 id=&quot;test-the-contract-not-the-transport&quot;&gt;Test the contract, not the transport&lt;/h3&gt;
&lt;p&gt;The most useful tests were not attempts to prove that a network never fails. They pinned the business boundary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New durable state produces one realtime hint.&lt;/li&gt;
&lt;li&gt;Previously completed or deduplicated work produces no new hint.&lt;/li&gt;
&lt;li&gt;A failing realtime transport does not invalidate successful durable work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Client-side tests can add the complementary guarantees: an initial authoritative load occurs, the reconciliation loop refreshes state, transient errors do not stop later ticks, and disposal cancels the loop.&lt;/p&gt;
&lt;p&gt;These tests document the intended hierarchy. The database-backed read is authoritative. Realtime reduces latency. Reconciliation restores convergence.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-review-checklist&quot;&gt;A practical review checklist&lt;/h3&gt;
&lt;p&gt;When reviewing a live-update feature, I now ask five questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What is the source of truth?&lt;/li&gt;
&lt;li&gt;Where is the single shared point that knows a real state transition occurred?&lt;/li&gt;
&lt;li&gt;Can the live event be safely missed or duplicated?&lt;/li&gt;
&lt;li&gt;How and how quickly does the client converge again?&lt;/li&gt;
&lt;li&gt;What happens if the transport fails after the business operation succeeds?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If those answers are vague, the feature may look realtime in a demo while remaining fragile in production.&lt;/p&gt;
&lt;p&gt;The broader lesson is modest: reliability often comes from combining mechanisms with explicit roles. Durable storage provides truth. Realtime provides speed. Periodic reconciliation provides healing. None needs to pretend it can do the others’ job.&lt;/p&gt;
&lt;p&gt;That separation made a small badge easier to trust, and it is a pattern I would reuse for dashboards, counters, presence indicators, job progress, and other derived UI state.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27482/realtime-is-a-hint-not-your-source-of-truth</guid>
<pubDate>Thu, 17 Sep 2026 08:20:15 +0000</pubDate>
</item>
<item>
<title>Blazor Latest Features</title>
<link>https://coderlegion.com/18293/blazor-latest-features</link>
<description>&lt;h2 id=&quot;blazor-latest-features&quot;&gt;Blazor Latest Features&lt;/h2&gt;
&lt;p&gt;Blazor, Microsoft’s modern web UI framework, has evolved significantly in recent releases (.NET 8 and .NET 9). It now provides a powerful mix of &lt;strong&gt;full-stack capabilities, flexible rendering, and high performance&lt;/strong&gt;, making it a strong alternative to JavaScript-based frameworks.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-unified-full-stack-blazor&quot;&gt;1. Unified Full-Stack Blazor&lt;/h3&gt;
&lt;p&gt;With &lt;strong&gt;.NET 8&lt;/strong&gt;, Blazor became a &lt;strong&gt;full-stack framework&lt;/strong&gt; that unifies server and client rendering within a single project.&lt;/p&gt;
&lt;p&gt;Supported rendering models:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Static Server-Side Rendering (SSR)&lt;/li&gt;
&lt;li&gt;Interactive Server&lt;/li&gt;
&lt;li&gt;Interactive WebAssembly&lt;/li&gt;
&lt;li&gt;Interactive Auto (hybrid)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Developers can now choose the best rendering strategy &lt;strong&gt;per component&lt;/strong&gt;, improving both performance and flexibility.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-2-advanced-rendering-modes&quot;&gt;⚙️ 2. Advanced Rendering Modes&lt;/h3&gt;
&lt;p&gt;Blazor introduces powerful rendering options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Static SSR&lt;/strong&gt; → Fast HTML, SEO-friendly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive Server&lt;/strong&gt; → Real-time updates via SignalR&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive WebAssembly&lt;/strong&gt; → Runs fully in the browser&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto Mode&lt;/strong&gt; → Switches dynamically between server and client&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ This enables hybrid applications where static content and interactive UI coexist seamlessly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-3-streaming-rendering&quot;&gt;⚡ 3. Streaming Rendering&lt;/h3&gt;
&lt;p&gt;Blazor supports &lt;strong&gt;streaming SSR&lt;/strong&gt;, allowing content to be delivered progressively while data loads.&lt;/p&gt;
&lt;h4 id=&quot;benefits-&quot;&gt;Benefits:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Faster perceived load time&lt;/li&gt;
&lt;li&gt;Better user experience for data-heavy apps&lt;/li&gt;
&lt;li&gt;Improved responsiveness&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-performance-improvements&quot;&gt;4. Performance Improvements&lt;/h3&gt;
&lt;p&gt;Recent versions include major performance enhancements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Faster startup times (WebAssembly optimizations)&lt;/li&gt;
&lt;li&gt;Smaller application bundles&lt;/li&gt;
&lt;li&gt;Improved rendering efficiency&lt;/li&gt;
&lt;li&gt;Optimized static file delivery&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Result: &lt;strong&gt;faster apps and better scalability&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-new-blazor-web-app-template&quot;&gt;5. New Blazor Web App Template&lt;/h3&gt;
&lt;p&gt;The new unified template simplifies development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single project structure&lt;/li&gt;
&lt;li&gt;Built-in rendering configuration&lt;/li&gt;
&lt;li&gt;Reduced setup complexity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ Ideal for modern full-stack applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-better-developer-experience&quot;&gt;6. Better Developer Experience&lt;/h3&gt;
&lt;p&gt;Blazor now includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Constructor-based dependency injection&lt;/li&gt;
&lt;li&gt;Improved debugging and diagnostics&lt;/li&gt;
&lt;li&gt;Simplified authentication handling&lt;/li&gt;
&lt;li&gt;Cleaner component architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Makes development faster and more maintainable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-improved-interactivity&quot;&gt;7. Improved Interactivity&lt;/h3&gt;
&lt;p&gt;Enhancements in .NET 9:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Better reconnection handling&lt;/li&gt;
&lt;li&gt;Improved routing and navigation&lt;/li&gt;
&lt;li&gt;Enhanced state management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ Applications are more stable and responsive.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-cross-platform-amp-hybrid-apps&quot;&gt;8. Cross-Platform &amp;amp; Hybrid Apps&lt;/h3&gt;
&lt;p&gt;Blazor integrates with &lt;strong&gt;.NET MAUI&lt;/strong&gt;, allowing apps to run on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Web&lt;/li&gt;
&lt;li&gt;Desktop&lt;/li&gt;
&lt;li&gt;Mobile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; One codebase across multiple platforms.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;9-blazor-ui-component-libraries&quot;&gt;9. Blazor UI Component Libraries&lt;/h2&gt;
&lt;p&gt;Blazor’s ecosystem includes powerful UI libraries that accelerate development.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mudblazor-open-source-&quot;&gt;MudBlazor (Open Source)&lt;/h3&gt;
&lt;p&gt;A popular Material Design component library.&lt;/p&gt;
&lt;h4 id=&quot;features-&quot;&gt;Features:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Modern UI components (DataGrid, Dialogs, Forms)&lt;/li&gt;
&lt;li&gt;Pure C# (minimal JavaScript)&lt;/li&gt;
&lt;li&gt;Easy setup&lt;/li&gt;
&lt;li&gt;Open-source&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ Best for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dashboards&lt;/li&gt;
&lt;li&gt;Admin panels&lt;/li&gt;
&lt;li&gt;Rapid development&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;syncfusion-blazor&quot;&gt;Syncfusion Blazor&lt;/h3&gt;
&lt;p&gt;Enterprise-grade UI component suite.&lt;/p&gt;
&lt;h4 id=&quot;features--1&quot;&gt;Features:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;80+ components (Grid, Charts, Scheduler, etc.)&lt;/li&gt;
&lt;li&gt;High-performance data handling&lt;/li&gt;
&lt;li&gt;Built-in AI-enhanced components&lt;/li&gt;
&lt;li&gt;Excellent documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ Best for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enterprise apps&lt;/li&gt;
&lt;li&gt;Data-intensive dashboards&lt;/li&gt;
&lt;li&gt;Reporting systems&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;devexpress-blazor&quot;&gt;DevExpress Blazor&lt;/h3&gt;
&lt;p&gt;Professional enterprise UI library.&lt;/p&gt;
&lt;h4 id=&quot;features--2&quot;&gt;Features:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Advanced DataGrid and TreeList&lt;/li&gt;
&lt;li&gt;Reporting tools&lt;/li&gt;
&lt;li&gt;High performance&lt;/li&gt;
&lt;li&gt;Accessibility improvements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ Best for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Large-scale business applications&lt;/li&gt;
&lt;li&gt;Complex data visualization&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-conclusion&quot;&gt;✅ Conclusion&lt;/h2&gt;
&lt;p&gt;Blazor has evolved into a &lt;strong&gt;modern, enterprise-ready full-stack framework&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;key-strengths-&quot;&gt;Key strengths:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;✅ Flexible rendering (SSR + WASM)&lt;/li&gt;
&lt;li&gt;✅ Strong performance improvements&lt;/li&gt;
&lt;li&gt;✅ Simplified developer experience&lt;/li&gt;
&lt;li&gt;✅ Rich UI component ecosystem&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With libraries like &lt;strong&gt;MudBlazor, Syncfusion, and DevExpress&lt;/strong&gt;, developers can build powerful applications faster than ever.&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18293/blazor-latest-features</guid>
<pubDate>Tue, 19 May 2026 08:11:04 +0000</pubDate>
</item>
<item>
<title>Building a Clean Master-Detail App with Blazor Server, MudBlazor, and EF Core</title>
<link>https://coderlegion.com/13513/building-a-clean-master-detail-app-with-blazor-server-mudblazor-and-ef-core</link>
<description>&lt;h3 id=&quot;building-a-clean-master-detail-app-with-blazor-server-mudblazor-and-ef-core&quot;&gt;Building a Clean Master-Detail App with Blazor Server, MudBlazor, and EF Core&lt;/h3&gt;
&lt;p&gt;When building business applications, one of the most common requirements is a master-detail workflow. Orders and order items are a perfect example. &lt;/p&gt;
&lt;p&gt;You need a clean list view, a reliable detail page, inline editing, validation, filtering, and a structure that can grow without becoming messy.&lt;/p&gt;
&lt;p&gt;That is exactly what &lt;strong&gt;BlazorOrderApp&lt;/strong&gt; is designed to demonstrate.&lt;/p&gt;
&lt;p&gt;This application is a production-ready &lt;strong&gt;Blazor Server&lt;/strong&gt; solution built with &lt;strong&gt;MudBlazor&lt;/strong&gt; for the UI, &lt;strong&gt;EF Core with SQL Server&lt;/strong&gt; for persistence, and a clean architecture that separates concerns across &lt;strong&gt;Domain&lt;/strong&gt;, &lt;strong&gt;Application&lt;/strong&gt;, &lt;strong&gt;Infrastructure&lt;/strong&gt;, and &lt;strong&gt;Web&lt;/strong&gt; layers. &lt;/p&gt;
&lt;p&gt;Instead of placing everything in pages or code-behind files, the app keeps business rules, data access, validation, and UI responsibilities clearly divided.&lt;/p&gt;
&lt;p&gt;At the core of the app is a classic &lt;strong&gt;master-detail pattern&lt;/strong&gt;. The &lt;code&gt;Orders.razor&lt;/code&gt; page acts as the master list, giving users filtering, search, pagination, and a structured overview of all orders. The &lt;code&gt;OrderDetail.razor&lt;/code&gt; page handles the detail side, where users can inspect customer information, manage order items, and update order status in a focused way.&lt;/p&gt;
&lt;p&gt;A big strength of this project is its use of the &lt;strong&gt;Repository&lt;/strong&gt; and &lt;strong&gt;Specification&lt;/strong&gt; patterns. The repository layer keeps EF Core logic centralized and testable, while specifications allow queries to be defined as reusable objects instead of scattered LINQ expressions. This makes filtering, paging, and retrieving complex data much easier to maintain over time.&lt;/p&gt;
&lt;p&gt;Validation is handled with &lt;strong&gt;FluentValidation&lt;/strong&gt;, which keeps rules explicit and easy to evolve as business needs grow. Combined with MudBlazor dialogs for create, edit, and delete operations, the result is a workflow that feels polished and user-friendly without sacrificing structure behind the scenes.&lt;/p&gt;
&lt;p&gt;Overall, &lt;strong&gt;BlazorOrderApp&lt;/strong&gt; is a strong example of how to build a modern internal business application in Blazor. It shows that you can create a clean, scalable CRUD system with a professional UI, solid architecture, and maintainable backend patterns, all within the .NET ecosystem.&lt;/p&gt;
&lt;p&gt;Source code : &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorOrderApp&quot;&gt;https://github.com/stevsharp/BlazorOrderApp&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/13513/building-a-clean-master-detail-app-with-blazor-server-mudblazor-and-ef-core</guid>
<pubDate>Thu, 26 Mar 2026 11:08:58 +0000</pubDate>
</item>
<item>
<title>Building a Blazor Form with the Command Pattern and Undo</title>
<link>https://coderlegion.com/13248/building-a-blazor-form-with-the-command-pattern-and-undo</link>
<description>&lt;p&gt;Building a Blazor Form with the Command Pattern and Undo&lt;/p&gt;
&lt;p&gt;In a typical Blazor form, inputs update the model directly. That is simple and works fine for basic CRUD screens, but it starts to feel limiting when you want more control over how changes happen, especially if you want undo support.&lt;/p&gt;
&lt;p&gt;That is where the Command Pattern becomes useful.&lt;/p&gt;
&lt;p&gt;Instead of letting the form change the model directly, each meaningful edit is wrapped inside a command object. That command is responsible for two things: applying the change and reversing it later if the user clicks Undo.&lt;/p&gt;
&lt;p&gt;In other words, the form is no longer just changing values. It is recording actions.&lt;/p&gt;
&lt;p&gt;The basic idea&lt;/p&gt;
&lt;p&gt;In this approach, every field change becomes its own small object.&lt;/p&gt;
&lt;p&gt;So if the user changes FirstName, that is one command.&lt;br&gt;
If they change Email, that is another command.&lt;br&gt;
If they toggle IsActive, that is a third command.&lt;/p&gt;
&lt;p&gt;Those commands are stored in a stack, and Undo simply reverses the most recent one.&lt;/p&gt;
&lt;p&gt;The core interface is very small:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public interface IUndoableCommand
{
    string Name { get; }
    void Execute();
    void Undo();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps the pattern easy to understand. Every command knows what it does, and how to undo it.&lt;/p&gt;
&lt;p&gt;The undo manager&lt;/p&gt;
&lt;p&gt;To store the history of changes, we use a simple UndoManager:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public sealed class UndoManager
{
    private readonly Stack&amp;lt;IUndoableCommand&amp;gt; _undoStack = new();

    public bool CanUndo =&amp;gt; _undoStack.Count &amp;gt; 0;

    public void Execute(IUndoableCommand command)
    {
        command.Execute();
        _undoStack.Push(command);
    }

    public void Undo()
    {
        if (_undoStack.Count == 0)
            return;

        var command = _undoStack.Pop();
        command.Undo();
    }

    public void Clear()
    {
        _undoStack.Clear();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The idea is straightforward. When a command runs, it is pushed onto the stack. When the user clicks Undo, the last command is popped and reversed.&lt;/p&gt;
&lt;p&gt;A reusable command for field changes&lt;/p&gt;
&lt;p&gt;For a form, a generic SetPropertyCommand&amp;lt;TModel, TValue&amp;gt; is often enough to get started.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public sealed class SetPropertyCommand&amp;lt;TModel, TValue&amp;gt; : IUndoableCommand
{
    private readonly TModel _model;
    private readonly Func&amp;lt;TModel, TValue&amp;gt; _getter;
    private readonly Action&amp;lt;TModel, TValue&amp;gt; _setter;
    private readonly TValue _newValue;

    private TValue? _oldValue;
    private bool _captured;

    public string Name { get; }

    public SetPropertyCommand(
        string name,
        TModel model,
        Func&amp;lt;TModel, TValue&amp;gt; getter,
        Action&amp;lt;TModel, TValue&amp;gt; setter,
        TValue newValue)
    {
        Name = name;
        _model = model;
        _getter = getter;
        _setter = setter;
        _newValue = newValue;
    }

    public void Execute()
    {
        if (!_captured)
        {
            _oldValue = _getter(_model);
            _captured = true;
        }

        _setter(_model, _newValue);
    }

    public void Undo()
    {
        if (!_captured)
            return;

        _setter(_model, _oldValue!);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What makes this useful is that it does not permanently track the whole form. It only represents one change at a time.&lt;/p&gt;
&lt;p&gt;That is an important distinction.&lt;/p&gt;
&lt;p&gt;SetPropertyCommand is not a background tracker watching every field. It is more like a history entry. One command instance represents one action, like:&lt;/p&gt;
&lt;p&gt;change FirstName from &quot;Maria&quot; to &quot;Anna&quot;&lt;/p&gt;
&lt;p&gt;change Email from &quot;&lt;em&gt;Emails are not allowed&lt;/em&gt;&quot; to &quot;&lt;em&gt;Emails are not allowed&lt;/em&gt;&quot;&lt;/p&gt;
&lt;p&gt;change IsActive from false to true&lt;/p&gt;
&lt;p&gt;Example from the form&lt;/p&gt;
&lt;p&gt;In the Blazor form, a field change is turned into a command like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ExecutePropertyCommand(
    &quot;Change First Name&quot;,
    m =&amp;gt; m.FirstName,
    (m, v) =&amp;gt; m.FirstName = v,
    value ?? string.Empty,
    nameof(CustomerEditModel.FirstName));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a nice example because it shows exactly what the command needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;how to read the property&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how to set the property&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;what the new value is&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;which field changed&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then inside ExecutePropertyCommand, the actual command is created:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var command = new SetPropertyCommand&amp;lt;CustomerEditModel, TValue&amp;gt;(
    name,
    _model,
    getter,
    setter,
    newValue);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then executed through the undo manager:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_undoManager.Execute(command);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why this feels cleaner&lt;/p&gt;
&lt;p&gt;This approach separates form editing from raw property assignment.&lt;/p&gt;
&lt;p&gt;Without the pattern, a form usually does this:&lt;/p&gt;
&lt;p&gt;_model.FirstName = value;&lt;/p&gt;
&lt;p&gt;That works, but the change is gone the moment it happens. There is no history, no reversible action, and no good place to add undo logic later.&lt;/p&gt;
&lt;p&gt;With the Command Pattern, the change becomes a first-class action. That makes the form behavior more predictable, easier to test, and easier to extend.&lt;/p&gt;
&lt;p&gt;Why Save should stay separate&lt;/p&gt;
&lt;p&gt;One important design choice is keeping Save outside the command stack.&lt;/p&gt;
&lt;p&gt;Undo should reverse form edits, not database persistence.&lt;/p&gt;
&lt;p&gt;That is why Save is better as a separate action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private Task SaveAsync()
{
    ValidateModel();

    if (!_editContext.Validate())
        return Task.CompletedTask;

    // persist here

    _undoManager.Clear();
    RefreshPreview();

    return Task.CompletedTask;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps responsibilities clear:&lt;/p&gt;
&lt;p&gt;commands handle reversible UI changes&lt;/p&gt;
&lt;p&gt;Save persists the current state&lt;/p&gt;
&lt;p&gt;Trying to make Save itself a command usually creates confusion, because undoing a database write is a completely different problem.&lt;/p&gt;
&lt;p&gt;A good fit for Blazor forms&lt;/p&gt;
&lt;p&gt;This pattern is especially useful when a form has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;multiple editable fields&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;toggles or switches&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;add/remove actions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sections that can be reset&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a real requirement for undo&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Command Pattern gives you a clean way to implement it without scattering logic all over the component.&lt;/p&gt;
&lt;p&gt;Final thought&lt;/p&gt;
&lt;p&gt;The most helpful way to think about this is not that the command is “tracking fields.” It is not.&lt;/p&gt;
&lt;p&gt;What it is really doing is turning every meaningful edit into a small object that remembers what changed, so the form can step backward when needed.&lt;/p&gt;
&lt;p&gt;That is what makes undo possible, and that is what makes the pattern useful in a Blazor form.&lt;/p&gt;
&lt;p&gt;Source Code : &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorCommandLab&quot;&gt;https://github.com/stevsharp/BlazorCommandLab&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13248/building-a-blazor-form-with-the-command-pattern-and-undo</guid>
<pubDate>Fri, 20 Mar 2026 08:35:01 +0000</pubDate>
</item>
<item>
<title>When IDbContextFactory enters the chat, and what it actually fixes.</title>
<link>https://coderlegion.com/11754/when-idbcontextfactory-enters-the-chat-and-what-it-actually-fixes</link>
<description>&lt;h3 id=&quot;when-idbcontextfactory-enters-the-chat-and-what-it-actually-fixes&quot;&gt;When &lt;code&gt;IDbContextFactory&lt;/code&gt; enters the chat, and what it actually fixes&lt;/h3&gt;
&lt;p&gt;Most EF Core examples use a scoped &lt;code&gt;DbContext&lt;/code&gt;. In a normal Web API request, that’s fine, the request ends, the context is disposed.&lt;/p&gt;
&lt;p&gt;Then you build Blazor Server.&lt;/p&gt;
&lt;p&gt;A Blazor Server circuit can stay alive for a long time, minutes or hours. If your &lt;code&gt;DbContext&lt;/code&gt; is scoped, it can also stay alive that long. That’s when things start to break in annoying ways.&lt;/p&gt;
&lt;h4 id=&quot;what-problems-show-up&quot;&gt;What problems show up&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1) “A second operation was started on this context…”&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;DbContext&lt;/code&gt; is not thread-safe. In Blazor Server it’s easy to trigger overlapping work (clicks, timers, loading states). If two queries happen at the same time on the same context, you get this error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2) Tracking bloat&lt;/strong&gt;&lt;br&gt;
A long-lived context keeps tracking entities. Over time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;memory grows&lt;/li&gt;
&lt;li&gt;queries can feel slower&lt;/li&gt;
&lt;li&gt;SaveChanges can get heavy&lt;/li&gt;
&lt;li&gt;you hit “already being tracked” errors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3) Stale or confusing data&lt;/strong&gt;&lt;br&gt;
Because the context tracks objects, you can end up reusing old instances without realizing it, the UI feels out of sync.&lt;/p&gt;
&lt;h4 id=&quot;what-idbcontextfactory-changes&quot;&gt;What &lt;code&gt;IDbContextFactory&lt;/code&gt; changes&lt;/h4&gt;
&lt;p&gt;It gives you a fresh &lt;code&gt;DbContext&lt;/code&gt; per operation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create context&lt;/li&gt;
&lt;li&gt;run the query or command&lt;/li&gt;
&lt;li&gt;dispose the context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So each click, each load, each action gets its own clean context. No shared state, no concurrency fights, no growing tracker.&lt;/p&gt;
&lt;h4 id=&quot;when-to-use-it&quot;&gt;When to use it&lt;/h4&gt;
&lt;p&gt;Use &lt;code&gt;IDbContextFactory&lt;/code&gt; when you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blazor Server&lt;/li&gt;
&lt;li&gt;background jobs or hosted services&lt;/li&gt;
&lt;li&gt;timers, polling, signalR style event flows&lt;/li&gt;
&lt;li&gt;any parallel DB work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In simple controller-per-request apps, scoped &lt;code&gt;DbContext&lt;/code&gt; is still perfectly fine.&lt;/p&gt;
&lt;h4 id=&quot;the-takeaway&quot;&gt;The takeaway&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;IDbContextFactory&lt;/code&gt; is not a “nice to have”, it’s the safer default when your app is long-lived and event-driven. It prevents a whole category of EF Core bugs before they happen.&lt;/p&gt;
&lt;p&gt;Source Code :&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorFactoryDemo&quot;&gt;https://github.com/stevsharp/BlazorFactoryDemo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;More to read  : &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.idbcontextfactory-1?view=efcore-10.0&quot;&gt;https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.idbcontextfactory-1?view=efcore-10.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11754/when-idbcontextfactory-enters-the-chat-and-what-it-actually-fixes</guid>
<pubDate>Mon, 16 Feb 2026 09:47:23 +0000</pubDate>
</item>
<item>
<title>Creating a Syncfusion MultiSelect Component with Two-Way Binding in Blazor</title>
<link>https://coderlegion.com/4311/creating-a-syncfusion-multiselect-component-with-two-way-binding-in-blazor</link>
<description>&lt;p&gt;In this example, we build a reusable Blazor component using Syncfusion’s SfMultiSelect that supports:&lt;/p&gt;
&lt;p&gt;Dynamic option filtering based on a Picklist type (e.g., Color, Unit)&lt;/p&gt;
&lt;p&gt;Two-way data binding with a parent component&lt;/p&gt;
&lt;p&gt;Internal state management to avoid unnecessary updates&lt;/p&gt;
&lt;p&gt; Why This Pattern?&lt;/p&gt;
&lt;p&gt;Often in enterprise forms, you need a dynamic dropdown that updates based on a type and preselects values passed from a parent. This setup ensures:&lt;/p&gt;
&lt;p&gt;Clean encapsulation&lt;/p&gt;
&lt;p&gt;Correct @bind support&lt;/p&gt;
&lt;p&gt;Lightweight rendering&lt;/p&gt;
&lt;p&gt;✅ Component Markup&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;SfMultiSelect TValue=&quot;List&amp;lt;PicklistSetDto&amp;gt;&quot;
               TItem=&quot;PicklistSetDto&quot;
               Placeholder=&quot;@Label&quot;
               DataSource=&quot;@Options&quot;
               @bind-Value=&quot;InternalSelectedValues&quot;
               Mode=&quot;VisualMode.Box&quot;
               AllowFiltering=&quot;true&quot;
               FilterType=&quot;FilterType.Contains&quot;
               PopupHeight=&quot;300px&quot;&amp;gt;
    &amp;lt;MultiSelectFieldSettings Text=&quot;Text&quot; Value=&quot;Value&quot; /&amp;gt;
&amp;lt;/SfMultiSelect&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Component Logic&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@code {
    [Parameter]
    public string Label { get; set; } = &quot;Select Options&quot;;

    [Parameter]
    public Picklist Name { get; set; }

    [Parameter]
    public IEnumerable&amp;lt;PicklistSetDto&amp;gt; SelectedValues { get; set; } = new List&amp;lt;PicklistSetDto&amp;gt;();

    [Parameter]
    public EventCallback&amp;lt;IEnumerable&amp;lt;PicklistSetDto&amp;gt;&amp;gt; SelectedValuesChanged { get; set; }

    protected List&amp;lt;PicklistSetDto&amp;gt; Options = new();

    private List&amp;lt;PicklistSetDto&amp;gt; _internalSelectedValues = new();

    private List&amp;lt;PicklistSetDto&amp;gt; InternalSelectedValues
    {
        get =&amp;gt; _internalSelectedValues;
        set
        {
            if (!_internalSelectedValues.SequenceEqual(value ?? new()))
            {
                _internalSelectedValues = value ?? new();
                SelectedValuesChanged.InvokeAsync(_internalSelectedValues);
            }
        }
    }

    protected override async Task OnInitializedAsync()
    {
        Options = await Task.FromResult(PicklistService.DataSource
            .Where(x =&amp;gt; x.Name == Name)
            .ToList());
    }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        await base.SetParametersAsync(parameters);

        InternalSelectedValues = Options
            .Where(x =&amp;gt; SelectedValues.Any(s =&amp;gt; s.Value == x.Value))
            .ToList();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2217441687434629520&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt; Example Usage in Parent Component&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;PicklistMultiSelect Label=&quot;Select Colors&quot;
                     Name=&quot;Picklist.Color&quot;
                     SelectedValues=&quot;@colorData&quot;
                     SelectedValuesChanged=&quot;OnColorChanged&quot; /&amp;gt;

private List&amp;lt;PicklistSetDto&amp;gt; colorData = new();
private void OnColorChanged(IEnumerable&amp;lt;PicklistSetDto&amp;gt; values)
{
    colorData = values.ToList();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To efficiently handle large datasets in Syncfusion’s SfMultiSelect in Blazor, you should avoid loading the entire dataset into memory and instead use server-side filtering and paging. Here's how to implement it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;SfMultiSelect TValue=&quot;List&amp;lt;PicklistSetDto&amp;gt;&quot;
               TItem=&quot;PicklistSetDto&quot;
               @ref=&quot;multiObj&quot;
               Placeholder=&quot;@Label&quot;
               @bind-Value=&quot;InternalSelectedValues&quot;
               AllowFiltering=&quot;true&quot;
               EnableVirtualization=&quot;true&quot;
               PopupHeight=&quot;300px&quot;
               Filtering=&quot;OnFilter&quot;&amp;gt;
    &amp;lt;MultiSelectFieldSettings Text=&quot;Text&quot; Value=&quot;Value&quot; /&amp;gt;
&amp;lt;/SfMultiSelect&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚠ EnableVirtualization=&quot;true&quot; improves rendering performance but requires paging logic in OnFilter.&lt;/p&gt;
&lt;p&gt;✅ 2. Implement OnFilter with Server-Side Querying&lt;br&gt;
Replace your in-memory filter with actual server-side filtering:&lt;/p&gt;
&lt;p&gt;✅ 1. Enable Virtualization&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private async Task OnFilter(FilteringEventArgs args)
{
    args.PreventDefaultAction = true;

    // Server-side call (adjust as needed)
    var filtered = await PicklistService.GetFilteredAsync(Name, args.Text);

    await multiObj.FilterAsync(filtered, new Query());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ 3. Modify Your Service to Support Filtering and Paging&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public async Task&amp;lt;List&amp;lt;PicklistSetDto&amp;gt;&amp;gt; GetFilteredAsync(Picklist name, string? filter)
{
    using var context = _dbContextFactory.CreateDbContext();

    var query = context.PicklistSets
        .Where(p =&amp;gt; p.Name == name);

    if (!string.IsNullOrWhiteSpace(filter))
        query = query.Where(p =&amp;gt; p.Text.Contains(filter));

    return await query
        .OrderBy(p =&amp;gt; p.Text)
        .Take(100) // Limit for performance
        .Select(p =&amp;gt; new PicklistSetDto
        {
            Value = p.Value,
            Text = p.Text,
            Name = p.Name
        })
        .ToListAsync();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ 4. Optional – Add Debounce to Reduce API Calls&lt;/p&gt;
&lt;p&gt;This delays filtering execution until the user stops typing, avoiding too many API hits.&lt;/p&gt;
&lt;p&gt;DebounceDelay=&quot;300&quot;&lt;/p&gt;
&lt;p&gt; Why Use Syncfusion in Blazor?&lt;br&gt;
Syncfusion’s Blazor UI components offer a wide range of benefits for modern web apps. Here’s why it’s a great choice for developers:&lt;/p&gt;
&lt;p&gt;✅ 1. Rich Component Library&lt;br&gt;
Syncfusion provides 80+ production-ready components, including:&lt;/p&gt;
&lt;p&gt;SfGrid, SfMultiSelect, SfDatePicker, SfDialog, SfChart, and more&lt;/p&gt;
&lt;p&gt;Covers everything from data entry to dashboards&lt;/p&gt;
&lt;p&gt;⚡ 2. Performance Optimized&lt;br&gt;
Built-in virtualization and lazy loading&lt;/p&gt;
&lt;p&gt;Handles large datasets smoothly (e.g., thousands of records in a Grid)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Consistent UI/UX&lt;br&gt;
Beautiful, modern design out of the box&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Theme support: Fluent, Bootstrap5, Material, Tailwind, or your custom styles&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Developer Productivity&lt;br&gt;
Intuitive APIs that follow Blazor standards (@bind-Value, DataSource, events)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Extensive documentation and real-world samples&lt;/p&gt;
&lt;p&gt;Native Blazor — not just JS interop wrappers&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enterprise-Ready Features&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Built-in support for localization, accessibility (WCAG 2.0), and RTL&lt;/p&gt;
&lt;p&gt;Works seamlessly with validation frameworks like FluentValidation&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Excellent Integration with Modern Blazor Patterns&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Compatible with @inject, CascadingParameter, and EventCallback&lt;/p&gt;
&lt;p&gt;MVVM-friendly (e.g., CommunityToolkit.MVVM)&lt;/p&gt;
&lt;p&gt;Supports reactive forms and dynamic configuration&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Strong Commercial Support&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Regular updates and new feature rollouts&lt;/p&gt;
&lt;p&gt;Dedicated support via forums and ticket system&lt;/p&gt;
&lt;p&gt;Source code access included in paid plans&lt;/p&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4311/creating-a-syncfusion-multiselect-component-with-two-way-binding-in-blazor</guid>
<pubDate>Tue, 29 Jul 2025 08:55:27 +0000</pubDate>
</item>
<item>
<title>State Management Made Easy with Fluxor in Blazor</title>
<link>https://coderlegion.com/2807/state-management-made-easy-with-fluxor-in-blazor</link>
<description>&lt;p&gt;&lt;strong&gt;Exploring Actions, Effects, and the Benefits of Fluxor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Modern web applications are becoming increasingly complex, and with complexity comes the need for structured state management. In the world of Blazor a popular .NET-based web UI framework .&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fluxor&lt;/strong&gt; offers a powerful yet easy-to-understand state container that brings predictable state flow to your applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fluxor&lt;/strong&gt; is a zero-boilerplate Flux/Redux library for Microsoft .NET.&lt;/p&gt;
&lt;p&gt;It brings the same clean architecture and unidirectional data flow popularized by Redux in the JavaScript ecosystem—adapted for C# developers building with Blazor.&lt;/p&gt;
&lt;p&gt;We’ll break down the core concepts of Fluxor and show how you can use Actions, Reducers, and Effects to create a clean, maintainable architecture.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorAndFluxorStateMachine&quot;&gt; Explore the GitHub project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;️ What is Fluxor?&lt;/p&gt;
&lt;p&gt;Fluxor provides a structured and predictable way to manage state in your Blazor application. Inspired by Redux, it embraces:&lt;/p&gt;
&lt;p&gt; Unidirectional data flow — State changes follow a clear path: Action ➝ Reducer ➝ State ➝ UI.&lt;/p&gt;
&lt;p&gt; Single source of truth — All application state lives in a centralized, immutable store.&lt;/p&gt;
&lt;p&gt; Middleware &amp;amp; Effects — Separate side-effects like API calls from your UI and business logic.&lt;/p&gt;
&lt;p&gt;Unlike some Redux implementations, Fluxor handles a lot of the boilerplate for you with C#-friendly conventions and strong typing.&lt;/p&gt;
&lt;p&gt; Actions – Declaring Intent&lt;/p&gt;
&lt;p&gt;In both Redux and Fluxor, Actions are plain messages that describe what happened. They're just &lt;br&gt;
signals , they don’t contain logic.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public record FetchItemsAction;
public record FetchItemsSuccessAction(List&amp;lt;Item&amp;gt; Items);
public record FetchItemsFailedAction(string ErrorMessage);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚙️ Effects – Handling Side Effects&lt;/p&gt;
&lt;p&gt;Since Reducers must be pure, Effects in Fluxor allow you to perform async operations like API calls without cluttering your UI.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ItemEffects
{
    [EffectMethod]
    public async Task HandleFetchItems(FetchItemsAction action, IDispatcher dispatcher)
    {
        try
        {
            var items = await itemService.GetItemsAsync();
            dispatcher.Dispatch(new FetchItemsSuccessAction(items));
        }
        catch (Exception ex)
        {
            dispatcher.Dispatch(new FetchItemsFailedAction(ex.Message));
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This Effect reacts to FetchItemsAction and handles fetching data from an external service.&lt;/p&gt;
&lt;p&gt; State and Reducers – Updating the Store&lt;/p&gt;
&lt;p&gt;Reducers are responsible for updating state in response to actions. They are pure functions and return a new state based on the action received.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static class ItemReducers
{
    [ReducerMethod]
    public static ItemState OnFetchSuccess(ItemState state, FetchItemsSuccessAction action) =&amp;gt;
        state with { Items = action.Items, IsLoading = false };

    [ReducerMethod]
    public static ItemState OnFetchFailed(ItemState state, FetchItemsFailedAction action) =&amp;gt;
        state with { ErrorMessage = action.ErrorMessage, IsLoading = false };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reducers never mutate state—they return a new state object.&lt;/p&gt;
&lt;p&gt; Try It Yourself&lt;/p&gt;
&lt;p&gt;Check out the working example project to see all these concepts in action:&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorAndFluxorStateMachine&quot;&gt;https://github.com/stevsharp/BlazorAndFluxorStateMachine&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It demonstrates:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Dispatching Actions from UI&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handling async logic with Effects&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clean Reducer logic&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blazor component integration&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ Why Use Fluxor?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;No boilerplate overload – clean C# syntax&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clear, testable state transitions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Great for scaling large Blazor apps&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Familiar to those who’ve used Redux in React or Angular&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keeps business logic and UI concerns separate&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; References&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mrpmorris/Fluxor&quot;&gt;Fluxor GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://redux.js.org/&quot;&gt;Redux Official Site&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://angular-redux.js.org/&quot;&gt;Angular Redux&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorAndFluxorStateMachine&quot;&gt;Blazor Documentation – Microsoft&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;BlazorAndFluxorStateMachine – GitHub Sample&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List item&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/2807/state-management-made-easy-with-fluxor-in-blazor</guid>
<pubDate>Fri, 25 Apr 2025 12:59:35 +0000</pubDate>
</item>
<item>
<title>How to Bind a MudSelect from an External Source in Blazor Using MudBlazor</title>
<link>https://coderlegion.com/1832/how-to-bind-a-mudselect-from-an-external-source-in-blazor-using-mudblazor</link>
<description>&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor&quot;&gt;Blazor&lt;/a&gt; is a modern framework from Microsoft that allows you to build interactive web applications using C# and .NET, both on the server and the client, without needing JavaScript. It brings the full power of .NET to the browser via WebAssembly or server-side rendering.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://mudblazor.com/&quot;&gt;MudBlazor&lt;/a&gt; is a popular UI component library built specifically for Blazor. It offers clean, responsive components following Google's Material Design system, making it incredibly easy to create modern, beautiful, and interactive web UIs entirely in C#.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Use Case&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s say you need to:&lt;/p&gt;
&lt;p&gt;Display a multi-select dropdown&lt;/p&gt;
&lt;p&gt;Load the options from an external service&lt;/p&gt;
&lt;p&gt;Sync selected values with the parent component&lt;/p&gt;
&lt;p&gt;Reuse this logic throughout your application&lt;/p&gt;
&lt;p&gt;We'll walk through a real-world pattern using MudSelect, bound to a list of brands (dtoBrands) provided by a service.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A Blazor project with MudBlazor installed&lt;/p&gt;
&lt;p&gt;A service that returns the brand data&lt;br&gt;
A DTO like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class BrandDto
{
    public string Text { get; set; }
    public string Value { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Sample Service Interface&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public interface IBrandService
{
    Task&amp;lt;IEnumerable&amp;lt;BrandDto&amp;gt;&amp;gt; GetBrandsAsync();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; The MudSelect Markup&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;MudSelect T=&quot;BrandDto&quot;
           Label=&quot;@Label&quot;
           MultiSelection=&quot;true&quot;
           Clearable=&quot;true&quot;
           @bind-SelectedValues=&quot;InternalSelectedValues&quot;
           ToStringFunc=&quot;item =&amp;gt; item.Text&quot;&amp;gt;
    @if (dtoBrands.Any())
    {
        @foreach (var item in dtoBrands)
        {
            &amp;lt;MudSelectItem T=&quot;BrandDto&quot; Value=&quot;item&quot;&amp;gt;
                @item.Text
            &amp;lt;/MudSelectItem&amp;gt;
        }
    }
&amp;lt;/MudSelect&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚙️ Component Logic&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@inject IBrandService BrandService

@code {
    [Parameter]
    public string Label { get; set; } = &quot;Select Brands&quot;;

    [Parameter]
    public IEnumerable&amp;lt;BrandDto&amp;gt; SelectedValues { get; set; } = new HashSet&amp;lt;BrandDto&amp;gt;();

    [Parameter]
    public EventCallback&amp;lt;IEnumerable&amp;lt;BrandDto&amp;gt;&amp;gt; SelectedValuesChanged { get; set; }

    protected IEnumerable&amp;lt;BrandDto&amp;gt; dtoBrands = new List&amp;lt;BrandDto&amp;gt;();

    private IEnumerable&amp;lt;BrandDto&amp;gt; _internalSelectedValues = new HashSet&amp;lt;BrandDto&amp;gt;();

    private IEnumerable&amp;lt;BrandDto&amp;gt; InternalSelectedValues
    {
        get =&amp;gt; _internalSelectedValues;
        set
        {
            if (_internalSelectedValues != value)
            {
                _internalSelectedValues = value;
                SelectedValuesChanged.InvokeAsync(_internalSelectedValues);
            }
        }
    }

    protected override async Task OnInitializedAsync()
    {
        dtoBrands = await BrandService.GetBrandsAsync();

        _internalSelectedValues = new HashSet&amp;lt;BrandDto&amp;gt;(
            dtoBrands.Where(x =&amp;gt; SelectedValues.Any(s =&amp;gt; s.Value == x.Value))
        );
    }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        await base.SetParametersAsync(parameters);

        _internalSelectedValues = new HashSet&amp;lt;BrandDto&amp;gt;(
            dtoBrands.Where(x =&amp;gt; SelectedValues.Any(s =&amp;gt; s.Value == x.Value))
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; How It Works&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1170082142962582969&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;♻️ Example Usage&lt;/p&gt;
&lt;p&gt;Imagine turning this into a reusable component, like BrandSelector.razor. You’d use it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;BrandSelector Label=&quot;Choose Brands&quot;
               SelectedValues=&quot;@selectedBrands&quot;
               SelectedValuesChanged=&quot;@OnSelectedBrandsChanged&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Why This Pattern Rocks&lt;/p&gt;
&lt;p&gt;By combining Blazor's powerful component model with MudBlazor's modern UI toolkit, you get:&lt;/p&gt;
&lt;p&gt;✅ Fully data-driven dropdowns&lt;/p&gt;
&lt;p&gt;✅ Type-safe C# components&lt;/p&gt;
&lt;p&gt;✅ No need for JavaScript&lt;/p&gt;
&lt;p&gt;✅ A consistent look and feel&lt;/p&gt;
&lt;p&gt;And all of it is maintainable and testable across large-scale apps.&lt;/p&gt;
&lt;p&gt;✅ Final Thoughts&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Blazor&lt;/strong&gt; and &lt;strong&gt;MudBlazor&lt;/strong&gt; are a dream combo for building modern web apps in C#. By binding a MudSelect to an external service, you're creating a highly flexible and reusable pattern that fits beautifully in a clean architecture.&lt;/p&gt;
&lt;p&gt; Useful Links&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-9.0&quot;&gt;Blazor documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://mudblazor.com/&quot;&gt;MudBlazor official site&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://mudblazor.com/api/mudselect#pages&quot;&gt;MudSelect documentation&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/1832/how-to-bind-a-mudselect-from-an-external-source-in-blazor-using-mudblazor</guid>
<pubDate>Thu, 10 Apr 2025 09:15:42 +0000</pubDate>
</item>
<item>
<title>A Comprehensive Guide to Building Web Applications with Blazor</title>
<link>https://coderlegion.com/1482/a-comprehensive-guide-to-building-web-applications-with-blazor</link>
<description>&lt;p&gt;Blazor is a powerful framework from Microsoft that allows you to build interactive web applications using C# and .NET. It leverages WebAssembly to run .NET code directly in the browser, offering a rich development experience similar to frameworks like Angular or React. This guide will take you through the process of building a Blazor application, step by step, with examples of API consumption and component interactions.&lt;/p&gt;
&lt;p&gt;Table of Contents&lt;br&gt;
Introduction to Blazor&lt;br&gt;
Setting Up Your Blazor Project&lt;br&gt;
Creating Components in Blazor&lt;br&gt;
Data Binding and Event Handling&lt;br&gt;
Consuming APIs in Blazor&lt;br&gt;
Component Interactions&lt;br&gt;
Differences with ReactJS and NextJS&lt;br&gt;
Deploying Your Blazor Application&lt;br&gt;
Conclusion&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Introduction to Blazor&lt;br&gt;
Blazor comes in two flavors:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Blazor WebAssembly (WASM): Runs in the browser via WebAssembly.&lt;br&gt;
Blazor Server: Runs on the server and uses SignalR to update the client.&lt;br&gt;
In this guide, we’ll focus on Blazor WebAssembly, but many concepts apply to Blazor Server as well.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Setting Up Your Blazor Project&lt;br&gt;
First, ensure you have the .NET SDK installed. You can download it from Microsoft’s official site.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a new Blazor WebAssembly project using the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet new blazorwasm -o BlazorApp
cd BlazorApp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, open the project in your preferred IDE (Visual Studio, Visual Studio Code, etc.).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creating Components in Blazor&lt;br&gt;
Components are the building blocks of a Blazor application. They are .razor files and can include HTML, C#, and Razor syntax.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a new component named Counter.razor:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@page &quot;/counter&quot;
@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

&amp;lt;h3&amp;gt;Counter&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;Current count: @currentCount&amp;lt;/p&amp;gt;

&amp;lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;IncrementCount&quot;&amp;gt;Click me&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This simple component includes a button that increments a counter.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Data Binding and Event Handling&lt;br&gt;
Blazor supports both one-way and two-way data binding. Here’s an example of a component with two-way data binding:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a new component named DataBindingExample.razor:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@page &quot;/databinding&quot;
@code {
    private string name = &quot;&quot;;

    private void HandleSubmit()
    {
        Console.WriteLine($&quot;Hello, {name}!&quot;);
    }
}

&amp;lt;h3&amp;gt;Data Binding Example&amp;lt;/h3&amp;gt;

&amp;lt;input @bind=&quot;name&quot; placeholder=&quot;Enter your name&quot; /&amp;gt;
&amp;lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;HandleSubmit&quot;&amp;gt;Submit&amp;lt;/button&amp;gt;

&amp;lt;p&amp;gt;You entered: @name&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Consuming APIs in Blazor&lt;br&gt;
Blazor can consume APIs using HttpClient. First, configure HttpClient in Program.cs:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;   builder.Services.AddScoped(sp =&amp;gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a service to fetch data. Create a new folder Services and add a file WeatherService.cs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    using System.Net.Http;
    using System.Net.Http.Json;
    using System.Threading.Tasks;
    
    public class WeatherService
    {
        private readonly HttpClient _httpClient;
    
        public WeatherService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }
    
        public async Task&amp;lt;WeatherForecast[]&amp;gt; GetWeatherForecasts()
        {
            return await _httpClient.GetFromJsonAsync&amp;lt;WeatherForecast[]&amp;gt;(&quot;sample-data/weather.json&quot;);
        }
    }

Register the service in Program.cs:

    builder.Services.AddScoped&amp;lt;WeatherService&amp;gt;();
    Create a WeatherForecast.razor component to display the data:
    
    @page &quot;/weather&quot;
    @inject WeatherService WeatherService
    
    @code {
        private WeatherForecast[] forecasts;
    
        protected override async Task OnInitializedAsync()
        {
            forecasts = await WeatherService.GetWeatherForecasts();
        }
    }
    
    &amp;lt;h3&amp;gt;Weather forecast&amp;lt;/h3&amp;gt;
    
    @if (forecasts == null)
    {
        &amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Loading...&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
    }
    else
    {
        &amp;lt;table class=&quot;table&quot;&amp;gt;
            &amp;lt;thead&amp;gt;
                &amp;lt;tr&amp;gt;
                    &amp;lt;th&amp;gt;Date&amp;lt;/th&amp;gt;
                    &amp;lt;th&amp;gt;Temperature (C)&amp;lt;/th&amp;gt;
                    &amp;lt;th&amp;gt;Summary&amp;lt;/th&amp;gt;
                &amp;lt;/tr&amp;gt;
            &amp;lt;/thead&amp;gt;
            &amp;lt;tbody&amp;gt;
                @foreach (var forecast in forecasts)
                {
                    &amp;lt;tr&amp;gt;
                        &amp;lt;td&amp;gt;@forecast.Date.ToShortDateString()&amp;lt;/td&amp;gt;
                        &amp;lt;td&amp;gt;@forecast.TemperatureC&amp;lt;/td&amp;gt;
                        &amp;lt;td&amp;gt;@forecast.Summary&amp;lt;/td&amp;gt;
                    &amp;lt;/tr&amp;gt;
                }
            &amp;lt;/tbody&amp;gt;
        &amp;lt;/table&amp;gt;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Component Interactions&lt;br&gt;
Components can interact with each other using parameters and cascading values.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a parent component ParentComponent.razor:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@page &quot;/parent&quot;
@code {
    private string parentMessage = &quot;Message from Parent&quot;;
}

&amp;lt;h3&amp;gt;Parent Component&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;Parent's message: @parentMessage&amp;lt;/p&amp;gt;

&amp;lt;ChildComponent MessageFromParent=&quot;@parentMessage&quot; /&amp;gt;
Create a child component ChildComponent.razor:

@code {
    [Parameter]
    public string MessageFromParent { get; set; }
}

&amp;lt;h3&amp;gt;Child Component&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;Received message: @MessageFromParent&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Differences with ReactJS and NextJS&lt;br&gt;
Blazor, ReactJS, and NextJS are all popular frameworks for building modern web applications, but they have distinct differences. This section delves deeper into their core aspects to help you understand which one might be the best fit for your needs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Language and Ecosystem&lt;br&gt;
Blazor&lt;/p&gt;
&lt;p&gt;Language: Blazor uses C# for both client-side and server-side logic. This is beneficial for developers who are already familiar with the .NET ecosystem.&lt;br&gt;
Ecosystem: It integrates seamlessly with the .NET ecosystem, including libraries and tools like Entity Framework, ASP.NET Core, and Azure services. The .NET ecosystem is mature and provides robust support for enterprise-level applications.&lt;br&gt;
Advantages: Uniform language for both front-end and back-end, strong typing with C#, rich tooling support in Visual Studio.&lt;br&gt;
ReactJS&lt;/p&gt;
&lt;p&gt;Language: React uses JavaScript, the most popular language for web development. TypeScript, a superset of JavaScript, is also commonly used to provide static typing.&lt;br&gt;
Ecosystem: React has a vast ecosystem with countless libraries for state management (Redux, MobX), routing (React Router), and UI components (Material-UI, Ant Design). The React community is very active, providing extensive resources and third-party libraries.&lt;br&gt;
Advantages: Extensive library support, large community, flexibility in choosing tools and libraries, high performance due to the virtual DOM.&lt;br&gt;
NextJS&lt;/p&gt;
&lt;p&gt;Language: Next.js, built on React, also uses JavaScript or TypeScript.&lt;br&gt;
Ecosystem: Next.js enhances React with features like server-side rendering (SSR) and static site generation (SSG). It simplifies deployment and scaling through its close integration with Vercel, the platform that maintains Next.js.&lt;br&gt;
Advantages: Built-in SSR and SSG, API routes for serverless functions, zero-config deployment, improved SEO and performance.&lt;br&gt;
Performance&lt;br&gt;
Blazor WebAssembly&lt;/p&gt;
&lt;p&gt;Performance: Runs .NET code directly in the browser via WebAssembly, providing a near-native performance once the application is loaded. The initial load time can be higher due to the size of the .NET runtime that needs to be downloaded.&lt;br&gt;
Optimization: Techniques like ahead-of-time (AOT) compilation and tree shaking are used to optimize performance.&lt;br&gt;
ReactJS&lt;/p&gt;
&lt;p&gt;Performance: React’s virtual DOM diffing algorithm efficiently updates the UI. The performance is generally high but can vary based on how the application is implemented.&lt;br&gt;
Optimization: Code splitting, lazy loading, and memoization are commonly used to enhance performance in React applications.&lt;br&gt;
NextJS&lt;/p&gt;
&lt;p&gt;Performance: Next.js excels in performance with its built-in features for SSR and SSG, which ensure faster initial page loads and better SEO. It also supports incremental static regeneration, allowing pages to be updated after the build process.&lt;br&gt;
Optimization: Automatic code splitting, image optimization, and caching strategies contribute to Next.js’s performance.&lt;br&gt;
Development Experience&lt;br&gt;
Blazor&lt;/p&gt;
&lt;p&gt;Development Experience: Offers a familiar development experience for .NET developers with features like dependency injection, a component-based architecture, and comprehensive tooling in Visual Studio.&lt;br&gt;
Tooling: Strong integration with Visual Studio, including debugging, IntelliSense, and project templates.&lt;br&gt;
Learning Curve: Easier for developers already familiar with C# and .NET.&lt;br&gt;
ReactJS&lt;/p&gt;
&lt;p&gt;Development Experience: Highly flexible and modular. Developers can choose from a wide range of libraries and tools to set up their development environment.&lt;br&gt;
Tooling: Supported by tools like Create React App for project scaffolding, React DevTools for debugging, and extensive support in VS Code.&lt;br&gt;
Learning Curve: Requires learning JavaScript/TypeScript and React concepts like JSX, hooks, and state management.&lt;br&gt;
NextJS&lt;/p&gt;
&lt;p&gt;Development Experience: Streamlined development with out-of-the-box support for SSR, SSG, and API routes. Provides a simplified setup and configuration process.&lt;br&gt;
Tooling: Excellent integration with Vercel for deployment and scalability, and built-in development server with hot module replacement.&lt;br&gt;
Learning Curve: Easier for developers familiar with React, but requires understanding of SSR/SSG concepts.&lt;br&gt;
Deployment&lt;br&gt;
Blazor&lt;/p&gt;
&lt;p&gt;Deployment: Can be deployed as static files to any static web host, such as GitHub Pages, Azure Static Web Apps, or AWS S3. Blazor Server apps can be deployed to any server that supports .NET.&lt;br&gt;
CI/CD: Integrates with Azure DevOps, GitHub Actions, and other CI/CD tools for automated deployments.&lt;br&gt;
ReactJS&lt;/p&gt;
&lt;p&gt;Deployment: Typically deployed as static files to services like Vercel, Netlify, or traditional web servers. Can also be integrated with server-side frameworks like Express.js for more complex deployments.&lt;br&gt;
CI/CD: Supported by various CI/CD platforms, including GitHub Actions, Travis CI, and CircleCI.&lt;br&gt;
NextJS&lt;/p&gt;
&lt;p&gt;Deployment: Optimized for deployment on Vercel, which supports both static and dynamic content seamlessly. Can also be deployed to other platforms like Netlify, AWS, or traditional servers.&lt;br&gt;
CI/CD: Vercel provides seamless CI/CD integration, automatically deploying updates when code is pushed to the repository.&lt;br&gt;
Community and Support&lt;br&gt;
Blazor&lt;/p&gt;
&lt;p&gt;Community: Growing community with strong backing from Microsoft. Resources include official documentation, Microsoft Learn, and community forums.&lt;br&gt;
Support: Enterprise-level support available from Microsoft, along with various third-party support options.&lt;br&gt;
ReactJS&lt;/p&gt;
&lt;p&gt;Community: Massive and active community with numerous resources, tutorials, courses, and third-party libraries. Strong backing from Facebook.&lt;br&gt;
Support: Community-driven support with extensive online resources, plus commercial support available from various vendors.&lt;br&gt;
NextJS&lt;/p&gt;
&lt;p&gt;Community: Rapidly growing community with strong backing from Vercel. Extensive documentation and tutorials are available.&lt;br&gt;
Support: Commercial support from Vercel, along with community-driven support and resources.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Deploying Your Blazor Application&lt;br&gt;
To deploy your Blazor WebAssembly application, publish it using the following command:&lt;/p&gt;
&lt;p&gt; dotnet publish -c Release&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This command creates a dist folder in your project, which you can deploy to any static file host such as GitHub Pages, Azure Static Web Apps, or AWS S3.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Conclusion&lt;br&gt;
Choosing between Blazor, ReactJS, and NextJS depends on your specific requirements, existing skill set, and the ecosystem you’re comfortable with. Blazor is an excellent choice for .NET developers looking to leverage their existing skills in web development. ReactJS offers unparalleled flexibility and a vast ecosystem, making it suitable for a wide range of applications. NextJS builds on ReactJS to provide powerful features for server-side rendering and static site generation, making it ideal for high-performance and SEO-friendly applications.&lt;/li&gt;
&lt;/ol&gt;
</description>
<category>Razor Pages</category>
<guid isPermaLink="true">https://coderlegion.com/1482/a-comprehensive-guide-to-building-web-applications-with-blazor</guid>
<pubDate>Sat, 15 Mar 2025 01:05:51 +0000</pubDate>
</item>
</channel>
</rss>