Using PostHog in your .NET applications
PostHog helps you build better products. It tracks what users do. It controls features in production. And now it works with .NET!
I joined PostHog at the beginning of the year as a Product Engineer on the Feature Flags team. Feature flags are just one of the many tools PostHog offers to help product engineers build better products.
Much of my job will consist of writing Python and React with TypeScript. But when I started, I noticed they didn’t have a .NET SDK. It turns out, I know a thing or two about .NET!
So if you’ve been wanting to use PostHog in your ASP.NET Core applications, yesterday is your lucky day! The 1.0 version of the PostHog .NET SDK for ASP.NET Core is available on NuGet.
dotnet add package PostHog.AspNetCore
You can find documentation for the library on the PostHog docs site, but I’ll cover some of the basics here. I’ll also cover non-ASP.NET Core usage later in this post.
Configuration
To configure the client SDK, you’ll need:
- Project API Key - from the PostHog dashboard
- Personal API Key - for local evaluation (Optional, but recommended)
Note
For better performance, enable local feature flag evaluation by adding a personal API key (found in Settings). This avoids making API calls for each flag check.
By default, the PostHog client looks for settings in the PostHog
section of the configuration system such as in the appSettings.json
file:
{
"PostHog": {
"ProjectApiKey": "phc_..."
}
}
Treat your personal API key as a secret by using a secrets manager to store it. For example, for local development, use the dotnet user-secrets
command to store your personal API key:
dotnet user-secrets init
dotnet user-secrets set "PostHog:PersonalApiKey" "phx_..."
In production, you might use Azure Key Vault or a similar service to provide the personal API key.
Register the client
Once you set up configuration, register the client with the dependency injection container.
In your Program.cs
file, call the AddPostHog
extension method on the WebApplicationBuilder
instance. It’ll look something like this:
using PostHog;
var builder = WebApplication.CreateBuilder(args);
builder.AddPostHog();
Calling builder.AddPostHog()
adds a singleton implementation of IPostHogClient
to the dependency injection container. Inject it into your controllers or pages like so:
public class MyController(IPostHogClient posthog) : Controller
{
}
public class MyPage(IPostHogClient posthog) : PageModel
{
}
Usage
Use the IPostHogClient
service to identify users, capture analytics, and evaluate feature flags.
Use the IdentifyAsync
method to identify users:
// This stores information about the user in PostHog.
await posthog.IdentifyAsync(
distinctId,
user.Email,
user.UserName,
// Properties to set on the person. If they're already
// set, they will be overwritten.
personPropertiesToSet: new()
{
["phone"] = user.PhoneNumber ?? "unknown",
["email_confirmed"] = user.EmailConfirmed,
},
// Properties to set once. If they're already set
// on the person, they won't be overwritten.
personPropertiesToSetOnce: new()
{
["joined"] = DateTime.UtcNow
});
Some things to note about the IdentifyAsync
method:
- The
distinctId
is the identifier for the user. This could be an email, a username, or some other identifier such as the database Id. The important thing is that it’s a consistent and unique identifier for the user. If you use PostHog on the client, use the samedistinctId
here as you do on the client. - The
personPropertiesToSet
andpersonPropertiesToSetOnce
are optional. You can use them to set properties about the user. - If you choose a
distinctId
that can change (such as username or email), you can use theAliasAsync
method to alias the olddistinctId
with the new one so that the user can be tracked across differentdistinctIds
.
To capture an event, call the Capture
method:
posthog.Capture("some-distinct-id", "my-event");
This will capture an event with the distinct id, the event name, and the current timestamp. You can also include properties:
posthog.Capture(
"some-distinct-id",
"user signed up",
new() { ["plan"] = "pro" });
The Capture
method is synchronous and returns immediately. The actual batching and sending of events is done in the background.
Feature flags
To evaluate a feature flag, call the IsFeatureEnabledAsync
method:
if (await posthog.IsFeatureEnabledAsync(
"new_user_feature",
"some-distinct-id")) {
// The feature flag is enabled.
}
This will evaluate the feature flag and return true
if the feature flag is enabled. If the feature flag is not enabled or not found, it will return false
.
Feature Flags can contain filter conditions that might depend on properties of the user. For example, you might have a feature flag that is enabled for users on the pro plan.
If you’ve previously identified the user and are NOT using local evaluation, the feature flag is evaluated on the server against the user properties set on the person via the IdentifyAsync
method.
But if you’re using local evaluation, the feature flag is evaluated on the client, so you have to pass in the properties of the user:
await posthog.IsFeatureEnabledAsync(
featureKey: "person-flag",
distinctId: "some-distinct-id",
personProperties: new() { ["plan"] = "pro" });
This will evaluate the feature flag and return true
if the feature flag is enabled and the user’s plan is “pro”.
.NET Feature Management
.NET Feature Management is an abstraction over feature flags that is supported by ASP.NET Core. With it enabled, you can use the <feature />
tag helper to conditionally render UI based on the state of a feature flag.
<feature name="my-feature">
<p>This is a feature flag.</p>
</feature>
You can also use the FeatureGateAttribute
in your controllers and pages to conditionally execute code based on the state of a feature flag.
[FeatureGate("my-feature")]
public class MyController : Controller
{
}
If your app already uses .NET Feature Management, you can switch to using PostHog with very little effort.
To use PostHog feature flags with the .NET Feature Management library, implement the IPostHogFeatureFlagContextProvider
interface. The simplest way to do that is to inherit from the PostHogFeatureFlagContextProvider
class and override the GetDistinctId
and GetFeatureFlagOptionsAsync
methods. This is required so that .NET Feature Management can evaluate feature flags locally with the correct distinctId
and personProperties
.
public class MyFeatureFlagContextProvider(
IHttpContextAccessor httpContextAccessor)
: PostHogFeatureFlagContextProvider
{
protected override string? GetDistinctId() =>
httpContextAccessor.HttpContext?.User.Identity?.Name;
protected override ValueTask<FeatureFlagOptions> GetFeatureFlagOptionsAsync()
{
// In a real app, you might get this information from a database or other source for the current user.
return ValueTask.FromResult(
new FeatureFlagOptions
{
PersonProperties = new Dictionary<string, object?>
{
["email"] = "some-test@example.com",
["plan"] = "pro"
},
OnlyEvaluateLocally = true
});
}
}
Then, register your implementation in Program.cs
(or Startup.cs
):
using PostHog;
var builder = WebApplication.CreateBuilder(args);
builder.AddPostHog(options => {
options.UseFeatureManagement<MyFeatureFlagContextProvider>();
});
This registers a feature flag provider that uses your implementation of IPostHogFeatureFlagContextProvider
to evaluate feature flags against PostHog.
Non-ASP.NET Core usage
The PostHog.AspNetCore
package adds ASP.NET Core specific functionality on top of the core PostHog
package. But if you’re not using ASP.NET Core, you can use the core PostHog
package directly:
dotnet add package PostHog.AspNetCore
And then register it with your dependency injection container:
builder.Services.AddPostHog();
If you’re not using dependency injection, you can still use the registration method:
using PostHog;
var services = new ServiceCollection();
services.AddPostHog();
var serviceProvider = services.BuildServiceProvider();
var posthog = serviceProvider.GetRequiredService<IPostHogClient>();
For a console app (or apps not using dependency injection), you can also use the PostHogClient
directly, just make sure it’s a singleton:
using System;
using PostHog;
var posthog = new PostHogClient(
Environment.GetEnvironmentVariable("PostHog__PersonalApiKey"));
Examples
To see all this in action, the posthog-dotnet
GitHub repository has a samples directory with a growing number of example projects. For example, the HogTied.Web project is an ASP.NET Core web app that uses PostHog for analytics and feature flags and shows some advanced configuration.
What’s next?
With this release done, I’ll be focusing my attention on the Feature Flags product. Even so, I’ll continue to maintain the SDK and fix any reported bugs.
If anyone reports bugs, I’ll be sure to fix them. But I won’t be adding any new features for the moment.
Down the road, I’m hoping to add a PostHog.Unity
package. I just don’t have a lot of experience with Unity yet. My game development experience mostly consists of getting shot in the face by squaky voiced kids playing Fortnite. I’m hoping someone will contqribute a Unity sample project to the repo which I can use as a starting point.
If you have any feedback, questions, or issues with the PostHog .NET SDK, please reach file an issue at https://github.com/PostHog/posthog-dotnet.
Comments
0 responses