Checking a customer's plan name is an easy way to decide whether they can use a paid feature. It also ties the application to the names in your pricing catalog. When a new plan includes an existing feature, the customer can have the right subscription and still fail the check.
This article walks through that failure in a C# export function. You'll move the access rule into a subscription catalog, make the function check the customer's access to CSV export, and verify that a new paid plan works without opening the feature to free customers.
We'll use Subscrio, the entitlement library we build. An entitlement describes what a customer can use under their subscription. Subscrio resolves that access; the application uses the answer to allow or deny the operation.
Before you get started
To run the example locally, you'll need:
- The .NET SDK to build and run the C# console application.
- SQL Server Express LocalDB for the sample database. Microsoft's guide includes installation options.
- Git for Windows to clone the sample repository using the commands below.
-
Subscrio, the entitlement library used in this walkthrough. The sample references the Subscrio.Core NuGet package;
dotnet restoreinstalls it.
The complete code is in the FeedbackDesk sample in the Subscrio samples repository. The clone and run commands are at the end of this article.
A paid customer who cannot export
FeedbackDesk is a fictional application where software teams collect customer feedback and prioritize feature requests. A product manager uses CSV export to download feedback and vote counts for a planning meeting. The Free plan supports collecting feedback; the Pro plan also includes exports.
FeedbackDesk introduces an Enterprise plan for larger teams. It includes CSV export too. But when an Enterprise customer's product manager clicks Export, the application denies the request. The export function still contains a condition written when Pro was the only plan with that feature:
bool CanExportUsingPlanName(string planKey) => planKey == "pro";
CanExportUsingPlanName is a function in our example application. Its name describes the existing approach: decide export access by comparing a plan name. Here is what it returns for the two paid plans:
| Customer's plan | Function returns | What happens | What should happen |
|---|---|---|---|
| Pro | true |
Export allowed | Export allowed |
| Enterprise | false |
Export denied | Export allowed |
The Enterprise denial is the bug. Nothing in this condition describes what Enterprise includes; it only recognizes the word "pro".
Adding || planKey == "enterprise" would fix this particular denial. That is a reasonable small change if the plans rarely change. As more features and offerings appear, however, each plan-name check becomes another place to keep synchronized with the catalog.
We'll give the export function a different question to ask: "Does this customer have CSV export?" To answer it, we first need to describe that feature and which plans include it.
Give CSV export a rule of its own
The rule we want is simple:
| Plan | CSV export |
|---|---|
| Free | Denied |
| Pro | Allowed |
| Enterprise | Allowed |
In Subscrio, the product groups the plans and features for FeedbackDesk. A feature represents a capability such as CSV export. A plan assigns a value to that feature. For an operation that is either included or excluded, we'll use a toggle with the values true and false.
The following sections are successive parts of one console program. They show the catalog setup, connect three example customers to their plans, and then replace the export check. The complete program's imports and database setup are linked in the run instructions below.
Connect to the sample database
Subscrio needs a database to store the catalog and subscriptions. The sample includes a LocalDatabase helper that creates a disposable SQL Server LocalDB database and supplies its connection string. That helper belongs to the sample; InstallSchemaAsync is the Subscrio call that creates its database tables.
await using var database = new LocalDatabase();
await database.CreateAsync();
using var app = new Subscrio.Core.Subscrio(
new SubscrioConfig
{
Database = new DatabaseConfig
{
ConnectionString = database.ConnectionString,
DatabaseType = DatabaseType.SqlServer
}
}
);
await app.InstallSchemaAsync();
The sample's LocalDatabase.cs contains the connection and cleanup code. In this program, app is the Subscrio client we'll use for the remaining calls.
Register FeedbackDesk and its export feature
Create a product identified by feedbackdesk. The key gives subsequent calls a consistent way to refer to this application.
await app.Products.CreateProductAsync(new("feedbackdesk", "FeedbackDesk"));
Next, define csv-export. Its default is false: a customer needs an applicable entitlement before the application will allow an export. Subscrio stores feature values as strings in this catalog API, so the toggle's default is written as "false".
await app.Features.CreateFeatureAsync(
new("csv-export", "CSV export", "toggle", "false")
);
Associate the feature with FeedbackDesk so its plans can assign export access:
await app.Products.AssociateFeatureAsync("feedbackdesk", "csv-export");
Record which plans include exports
Create the three plans under FeedbackDesk. The lowercase strings are their catalog keys.
foreach (var plan in new[] { "free", "pro", "enterprise" })
await app.Plans.CreatePlanAsync(new("feedbackdesk", plan, plan));
Now translate the access table into feature values. Both paid plans grant the same feature, while Free explicitly excludes it.
await app.Plans.SetFeatureValueAsync("free", "csv-export", "false");
await app.Plans.SetFeatureValueAsync("pro", "csv-export", "true");
await app.Plans.SetFeatureValueAsync("enterprise", "csv-export", "true");
We have now recorded why the Enterprise customer should be able to export. The next step is to connect a customer to that plan so Subscrio can resolve their access.
Connect each customer to a plan
FeedbackDesk sells subscriptions to teams. In this sample, each team is a Subscrio customer; the product manager is a user acting for that customer. We use three customers to check the full rule:
| Customer key | Plan | Expected export access |
|---|---|---|
startup-team |
Free | Denied |
growth-team |
Pro | Allowed |
enterprise-team |
Enterprise | Allowed |
A Subscrio subscription refers to a billing cycle belonging to a plan. Create one monthly cycle for each plan before assigning subscriptions. These records describe the subscription periods; creating them does not collect payment.
foreach (var plan in new[] { "free", "pro", "enterprise" })
await app.BillingCycles.CreateBillingCycleAsync(
new(plan, plan + "-monthly", "Monthly", "months", DurationValue: 1)
);
For example, enterprise-monthly belongs to the Enterprise plan. Now register the three customers:
foreach (var customer in new[] { "startup-team", "growth-team", "enterprise-team" })
await app.Customers.CreateCustomerAsync(new(customer));
Give each customer a subscription to the appropriate cycle. Each constructor below receives a subscription key, the customer key, and the billing-cycle key.
await app.Subscriptions.CreateSubscriptionAsync(
new("startup-team-plan", "startup-team", "free-monthly")
);
await app.Subscriptions.CreateSubscriptionAsync(
new("growth-team-plan", "growth-team", "pro-monthly")
);
await app.Subscriptions.CreateSubscriptionAsync(
new("enterprise-team-plan", "enterprise-team", "enterprise-monthly")
);
Subscrio can now follow enterprise-team's subscription to the Enterprise plan and find its csv-export value of "true".
Ask about export access when the customer exports
Return to the product manager's failed request. The application already knows which customer they act for. It can pass that customer key to the export function instead of passing a plan name.
In ExportFeedbackCsv, IsEnabledForCustomerAsync asks Subscrio whether csv-export is enabled for that customer within feedbackdesk. It returns a Boolean. The function allows the export when the answer is true and returns a denial marker when it is false.
async Task<string> ExportFeedbackCsv(string customerKey)
{
bool canExport = await app.FeatureChecker.IsEnabledForCustomerAsync(
customerKey,
"feedbackdesk",
"csv-export"
);
if (!canExport)
return "export_not_included";
return "feedback,votes\nDark mode,12";
}
ExportFeedbackCsv is our application code; IsEnabledForCustomerAsync is the Subscrio API. The CSV is a fixed example row: a request for dark mode with 12 votes. A real export would query the customer's feedback and write those rows after the access check. Likewise, the application would turn export_not_included into its chosen response or upgrade message.
The application remains responsible for authenticating the user and establishing which customer they may act for. A customer key supplied by an untrusted request is not proof of that relationship.
The export function now contains no plan names. Pro and Enterprise reach the same operation because both plans grant csv-export.
Check the customer's experience after the change
Call the export function for each of our three customers:
string freeResult = await ExportFeedbackCsv("startup-team");
string proResult = await ExportFeedbackCsv("growth-team");
string enterpriseResult = await ExportFeedbackCsv("enterprise-team");
The program checks that Free receives export_not_included and both paid customers receive the CSV. These are ordinary C# checks in the sample; a mismatch throws an error and fails the run.
string expectedCsv = "feedback,votes\nDark mode,12";
if (freeResult != "export_not_included")
throw new InvalidOperationException("Free must not include CSV export.");
if (proResult != expectedCsv)
throw new InvalidOperationException("Pro must receive the feedback CSV.");
if (enterpriseResult != expectedCsv)
throw new InvalidOperationException("Enterprise must receive the feedback CSV.");
After those checks pass, the program prints the following confirmation:
Free customer: export denied, as expected.
Pro customer: feedback CSV returned, as expected.
Enterprise customer: feedback CSV returned, as expected.
The Enterprise product manager can now download the feedback for their meeting. The Free customer is still denied, so fixing Enterprise has not accidentally made exports available to every customer.
Run the complete example
The FeedbackDesk sample includes Program.cs, database setup, and the checks above. Use Windows with the .NET SDK and SQL Server Express LocalDB installed.
git clone https://github.com/subscrio/samples.git
cd samples/examples/replace-hardcoded-plan-checks-csharp
sqllocaldb start MSSQLLocalDB
dotnet restore --locked-mode
dotnet run --no-restore
The program first prints the existing plan-name check's denial for Enterprise, then builds the catalog and verifies the corrected exports. It uses Windows integrated authentication, creates a uniquely named disposable database, and removes that database when it finishes.
If FeedbackDesk introduces another plan with CSV export, assign that plan's csv-export value in the catalog. The export function can keep asking the same customer-level question. Keep the Free, Pro, and Enterprise checks when extending the catalog so the access promised by each plan remains covered.
This article was prepared with AI assistance.
Top comments (1)
Hi. Your post, website, github all are great!
I wanna have meaningful conversation about collaboration with you.
I believe we can achieve something big by our collaborating.
How about discussing about collaboration via a meeting?