DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
World desk10 min

Replace Hardcoded Plan Checks with Feature Entitlements in C# (ASP.NET Core)

Scattered plan-name checks break whenever pricing changes. Learn how to model paid capabilities as entitlements enforced by ASP.NET Core policies, and where feature flags fit.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Replace scattered plan-name checks such as if (user.Plan == "Pro") with one entitlement decision. Model each paid capability as a named ID, resolve it from your authoritative subscription or account data, and enforce it on the server through an ASP.NET Core authorization policy and handler. Keep feature flags for a separate job: deciding whether a feature should be exposed, targeted, or rolled out. A flag does not prove that a customer has paid for something.

Why plan checks spread and why they break

Most applications begin with one comparison against a plan name. Within a year, the same comparison appears in controllers, Razor pages, background jobs, and view models. Each copy encodes a decision about what a plan includes. When marketing renames a tier, adds a mid-level plan, or grants a trial extension, every copy must change, and the ones you miss keep granting or denying access without any error.

As an Amazon Associate I earn from qualifying purchases.

The fix is not a cleaner if statement. It is separating three questions that plan checks currently answer at once:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Entitlement: Is this account allowed to use this capability?
  • Exposure: Should the interface show this capability to this user right now?
  • Rollout: Is the code path for this capability switched on for this environment, cohort, or percentage of users?

Entitlement is an access decision and belongs in authorization. Exposure and rollout are configuration-driven choices and belong in feature management. Mixing them is the root of most hardcoded plan checks.

Feature flags answer a different question

Microsoft’s .NET Feature Flag Management documentation describes the purpose plainly: “Feature flags provide a way for .NET and ASP.NET Core applications to turn features on or off dynamically.” That describes state controlled by configuration. It says nothing about whether a particular customer has purchased access.

A flag named ProExports that is turned on for every Pro account is therefore a copy of the plan check, moved into configuration. It still needs someone to write the mapping from plan to flag, and it cannot answer for an individual user whose trial or add-on differs from the plan name. Treat a flag named after a subscription tier as a red flag in code review.

The distinction in this article is an architectural inference drawn from Microsoft’s separate documentation of feature management and of ASP.NET Core authorization. Those documents do not state it as a rule, but they describe mechanisms built for different decisions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Step 1: Inventory the plan checks and name the capabilities

Search the codebase for plan identifiers and for the enums, strings, and boolean properties derived from them. A search for Plan, Tier, Subscription, and IsPaid usually finds most of them. For each hit, record three things:

  • The user-visible capability it gates, such as exporting a report or inviting a team member.
  • Whether the check protects a server-side operation or only changes what the interface shows.
  • Whether the check is temporary, such as a rollout guarded by a plan comparison that was never removed.

Only the first group is entitlement. Cosmetic differences, like a badge or a banner, can stay in views, and rollout gates should move to feature flags. Expect the inventory to reveal duplicate checks for the same capability written in different places with slightly different logic. Those discrepancies are the bugs this refactor fixes.

Step 2: Define stable capability identifiers

Give each entitlement a stable identifier in domain language, such as reports.export or team.members.invite. The identifier should describe what the customer can do, not which plan grants it. Plans change; capabilities rarely do. Microsoft’s documentation does not prescribe this naming scheme. It is a convention that keeps application code independent of pricing.

A minimal catalogue can be a static class so identifiers are checked at compile time:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
public static class Capabilities
{
    public const string ReportsExport = "reports.export";
    public const string TeamMembersInvite = "team.members.invite";
}

Step 3: Choose the authoritative entitlement source

The correct source depends on your billing and account model. Microsoft’s documentation does not prescribe a plan-to-feature mapping, so the schema and the source of truth are decisions you must make from your own data. The three common options are below.

Source How it works Main strength Main risk
Local entitlement projection A table keyed by account and capability, updated from billing events or admin actions Fast reads inside your own database; full control over the schema Projection can lag or drift from the billing system if events are missed
Identity claim Entitlements are added to the user’s token or cookie at sign-in No database call per request Claims persist until the token or cookie is reissued, so revoked access can outlive the change
External entitlement service Your code calls a billing or licensing API for each decision or per session Closest to the billing system of record Adds latency and a runtime dependency; failure behaviour must be designed explicitly

Many production systems combine a local projection for speed with periodic reconciliation against the billing system. Whichever option you choose, write down which system is authoritative when two sources disagree.

Step 4: Implement the policy, requirement, and handler

In ASP.NET Core, a policy is a named collection of requirements. A requirement describes the rule, and a handler decides whether the current user satisfies it. For entitlements, the requirement carries the capability ID, and the handler asks your entitlement source.

First, define the requirement and an entitlement lookup abstraction:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
using Microsoft.AspNetCore.Authorization;

public sealed class CapabilityRequirement(string capability) : IAuthorizationRequirement
{
    public string Capability { get; } = capability;
}

public interface IEntitlementStore
{
    Task<bool> HasCapabilityAsync(string accountId, string capability, CancellationToken ct = default);
}

Next, the handler. It reads the account from claims, asks the store, and calls Succeed only when the entitlement exists:

using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;

public sealed class CapabilityHandler(IEntitlementStore store)
    : AuthorizationHandler<CapabilityRequirement>
{
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        CapabilityRequirement requirement)
    {
        var accountId = context.User.FindFirstValue("account_id");
        if (string.IsNullOrEmpty(accountId))
        {
            return; // no Succeed call: requirement not met
        }

        if (await store.HasCapabilityAsync(accountId, requirement.Capability))
        {
            context.Succeed(requirement);
        }
    }
}

Register the policies and the handler in Program.cs:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("reports.export", policy =>
        policy.Requirements.Add(new CapabilityRequirement(Capabilities.ReportsExport)));

    options.AddPolicy("team.members.invite", policy =>
        policy.Requirements.Add(new CapabilityRequirement(Capabilities.TeamMembersInvite)));
});

builder.Services.AddScoped<IAuthorizationHandler, CapabilityHandler>();

Register the handler as scoped rather than singleton if IEntitlementStore depends on a scoped service such as an Entity Framework DbContext. A singleton handler that captures a scoped dependency fails at runtime when it is resolved. The claim name account_id is an example; use whatever claim your identity provider issues.

Protect endpoints with the policy name:

app.MapPost("/reports/{id}/export", [Authorize(Policy = "reports.export")] (int id) =>
{
    // export logic
    return Results.Accepted();
});

Policy-based authorization is the enforcement point for these endpoints. Everywhere a plan name was compared before, replace the comparison with the policy, or with an IAuthorizationService call where the policy cannot be declared on an endpoint.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Step 5: Use resource-based authorization when the target matters

Some entitlements depend on the object being acted on. An account may be entitled to export reports, but only reports belonging to a workspace it is subscribed to. A user-only policy cannot see the record, so the check must run after the resource loads. ASP.NET Core’s resource-based authorization handles this with a handler typed to the resource and an imperative call to IAuthorizationService.

public sealed class ReportExportHandler(IEntitlementStore store)
    : AuthorizationHandler<CapabilityRequirement, Report>
{
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        CapabilityRequirement requirement,
        Report report)
    {
        var accountId = context.User.FindFirstValue("account_id");
        if (accountId is null) return;

        if (report.WorkspaceAccountId == accountId
            && await store.HasCapabilityAsync(accountId, requirement.Capability))
        {
            context.Succeed(requirement);
        }
    }
}

// In a minimal API or controller action:
var report = await db.Reports.FindAsync(id);
if (report is null) return Results.NotFound();

var result = await authorization.AuthorizeAsync(user, report, Capabilities.ReportsExport);
if (!result.Succeeded) return Results.Forbid();

Load the resource first, then authorize against it. Returning NotFound before the authorization check, as shown, avoids confirming that a record exists to a caller who cannot see it, but choose the status code behaviour your API contract requires.

Step 6: Enforce on the server, then decide what the interface shows

Authorization must run where the protected operation runs. Client-side checks can hide a button, but a request can always be sent directly. A safe order of operations for each capability is:

  1. The server applies the entitlement policy to the endpoint or service method that performs the work.
  2. The server returns a forbidden or not-found response when the policy fails, and never performs the action.
  3. The view receives a capability set, such as IAuthorizationService results or a precomputed list of granted IDs, and renders the matching controls.
  4. Tests confirm that a direct request is rejected even when the button is hidden.

Step 3 of the view logic is where cosmetic and entitlement logic can drift apart again. Compute the interface’s view of capabilities from the same handler or the same store, not from a second copy of the plan rules.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Step 7: Add Microsoft.FeatureManagement only for rollout behaviour

Once entitlements live behind policies, feature flags can handle the questions they are designed for. The Microsoft.FeatureManagement library provides asynchronous checks through IFeatureManager.IsEnabledAsync, supports filters and variants, and integrates with .NET configuration and dependency injection. The API reference pages available at the time of writing report package version 4.3.0; confirm the current version on NuGet before you pin one in your project.

Install the package, register it, and bind flags from configuration:

// Program.cs
builder.Services.AddFeatureManagement();
{
  "FeatureManagement": {
    "NewExportLayout": false,
    "BulkInviteRollout": true
  }
}
public sealed class ExportController(IFeatureManager features) : ControllerBase
{
    [HttpPost("reports/{id}/export")]
    public async Task<IActionResult> Export(int id)
    {
        if (await features.IsEnabledAsync("NewExportLayout"))
        {
            // new rendering path, still behind the reports.export policy
        }
        return Accepted();
    }
}

The flag decides which path runs. The policy decides whether the caller may run it at all. Use AddFeatureManagement alongside, not instead of, the authorization configuration above. Azure App Configuration is one option for centrally managing these flags across environments; its documentation describes it as a configuration source for .NET feature flags, and it is a separate service with its own pricing and access requirements that you would need to evaluate on its own terms.

When a flag is the wrong tool

  • The question is “Has this customer paid?” Answer it with the entitlement source.
  • The rule must hold for every request regardless of rollout settings, such as a paid export limit. Keep it in authorization, where a misconfigured flag cannot disable it.
  • The flag would be named after a plan. Name it after the rollout instead, such as NewExportLayout.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Migrating without breaking access

Move one capability at a time. For each existing plan check:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Add the capability ID and its policy, backed by the new entitlement source.
  • Route the old plan comparison through the same evaluator during the transition, so both paths produce the same result for every account in your data.
  • Write tests for each plan, for accounts with add-ons or trial extensions, and for accounts whose plan changed recently.
  • Compare decisions in telemetry: log the old and new result, and alert on any mismatch before switching traffic fully.
  • Delete the plan comparison once parity holds across a representative period.

Expect mismatches in the first pass. They usually reveal accounts whose entitlements were never recorded in the new source. Fix the data before changing the code path.

Staleness and caching

An entitlement check is only as current as the data it reads. Decide, per capability, how quickly a subscription change must take effect. A cancelled export add-on may be acceptable to honour for a few minutes; a revoked admin role may not. The documentation reviewed does not establish a universal propagation interval for commercial entitlements, so this window is a product and security decision for your team.

Identity claims are the most common source of delay. Once a claim is issued, it remains valid until the token or cookie expires or is reissued. If you use claims, keep token lifetimes short or refresh claims when a subscription changes. If you cache entitlement lookups in memory or a distributed cache, define the expiry and invalidate the cache from billing events, not only on a timer.

Evidence and limits

The Microsoft documentation supporting this approach comes from three sources: the .NET Feature Flag Management documentation on Microsoft Learn, the IFeatureManager and IFeatureFilter API references for Microsoft.FeatureManagement, and the Microsoft Learn pages on policy-based and resource-based authorization in ASP.NET Core. They describe how each mechanism works. They do not prescribe a plan-to-feature schema, a naming convention, or a caching policy, and they contain no published figures on how entitlement migrations perform. The examples above are implementation patterns you should adapt and test in your own application.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check package versions and API signatures against the .NET version and Microsoft.FeatureManagement release your project uses before copying code.

Next, confirm that the authoritative entitlement data is complete for every account before removing the old checks. The refactor changes where the decision lives; it cannot make missing data correct.

Frequently Asked Questions

Can I put plan entitlements inside a feature flag provider?

You can, but it is usually the wrong layer. A flag provider controls whether functionality is turned on, often through configuration. It does not know which account paid for what. Keep the paid-capability decision in an entitlement source behind an authorization policy, and use flags only for rollout.

Do I need an ASP.NET Core policy if I already check a claim in the controller?

A controller-level claim check works for a single endpoint, but it spreads the rule across the code again. A named policy gives every endpoint, service, and view the same definition, and it lets resource-based checks run against loaded records through IAuthorizationService.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.