Skip to content

Commit 6a1e600

Browse files
authored
Merge pull request #1940 from haacked/haacked/posthog-dotnet
Blog post about posthog-dotnet
2 parents 4afd570 + dc2e24f commit 6a1e600

File tree

2 files changed

+266
-1
lines changed

2 files changed

+266
-1
lines changed

_posts/2025/2025-01-07-new-year-new-job.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ excerpt_image: https://github.com/user-attachments/assets/0a825e78-c3e5-4aa4-ac1
77

88
Last year I wrote a post, [career chutes and ladders](/archive/2024/12/10/chutes-and-ladder), where I proposed that a linear climb to the C-suite is not the only approach to a satisfying career. At the end of the post, I mentioned I was stepping off the ladder to take on an IC role.
99

10-
![Hedge hog typing on a keyboard](https://github.com/user-attachments/assets/0a825e78-c3e5-4aa4-ac1f-13ba597da4a5)
10+
![Hedge hog typing on a keyboard](https://github.com/user-attachments/assets/0a825e78-c3e5-4aa4-ac1f-13ba597da4a5 "Might be easier to code if my arms were longer")
1111

1212
After over a year of being on a personally funded sabbatical, I started a new job at [PostHog](https://posthog.com) as a Senior Product Engineer. This week is my orientation where I get to [drink from the firehose](https://haacked.com/archive/2007/10/26/drinking-from-the-firehose.aspx/) once again.
1313

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
---
2+
title: "What are your ASP.NET Core users up to? Find out with PostHog."
3+
description: "PostHog is a product analytics platform that helps you build better products faster. It contains a set of tools to help you capture analytics and leverage feature flags."
4+
tags: [career, work]
5+
excerpt_image: https://github.com/user-attachments/assets/b7c64431-0232-4707-b016-c0161ea6b0eb
6+
---
7+
8+
PostHog helps you build better products. It tracks what users do. It controls features in production. And now it works with .NET!
9+
10+
[I joined PostHog at the beginning of the year](https://haacked.com/archive/2025/01/07/new-year-new-job/) as a Product Engineer on [the Feature Flags team](https://posthog.com/teams/feature-flags). Feature flags are just one of the many tools PostHog offers to help product engineers build better products.
11+
12+
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!
13+
14+
![Hedgehog giving two thumbs up in front of a computer showing the .NET logo](https://github.com/user-attachments/assets/b7c64431-0232-4707-b016-c0161ea6b0eb "Who has two thumbs and loves .NET?")
15+
16+
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](https://www.nuget.org/packages/PostHog.AspNetCore).
17+
18+
```bash
19+
dotnet add package PostHog.AspNetCore
20+
```
21+
22+
You can find documentation for the library on [the PostHog docs site](https://posthog.com/docs/libraries/dotnet), but I'll cover some of the basics here. I'll also cover non-ASP.NET Core usage later in this post.
23+
24+
## Configuration
25+
26+
To configure the client SDK, you'll need:
27+
28+
1. Project API Key - _from the [PostHog dashboard](https://us.posthog.com/settings/project)_
29+
2. Personal API Key - _for local evaluation (Optional, but recommended)_
30+
31+
> [!NOTE]
32+
> 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.
33+
34+
By default, the PostHog client looks for settings in the `PostHog` section of the configuration system such as in the `appSettings.json` file:
35+
36+
```json
37+
{
38+
"PostHog": {
39+
"ProjectApiKey": "phc_..."
40+
}
41+
}
42+
```
43+
44+
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:
45+
46+
```bash
47+
dotnet user-secrets init
48+
dotnet user-secrets set "PostHog:PersonalApiKey" "phx_..."
49+
```
50+
51+
In production, you might use Azure Key Vault or a similar service to provide the personal API key.
52+
53+
## Register the client
54+
55+
Once you set up configuration, register the client with the dependency injection container.
56+
57+
In your `Program.cs` file, call the `AddPostHog` extension method on the `WebApplicationBuilder` instance. It'll look something like this:
58+
59+
```csharp
60+
using PostHog;
61+
62+
var builder = WebApplication.CreateBuilder(args);
63+
64+
builder.AddPostHog();
65+
```
66+
67+
Calling `builder.AddPostHog()` adds a singleton implementation of `IPostHogClient` to the dependency injection container. Inject it into your controllers or pages like so:
68+
69+
```csharp
70+
public class MyController(IPostHogClient posthog) : Controller
71+
{
72+
}
73+
74+
public class MyPage(IPostHogClient posthog) : PageModel
75+
{
76+
}
77+
```
78+
79+
## Usage
80+
81+
Use the `IPostHogClient` service to identify users, capture analytics, and evaluate feature flags.
82+
83+
Use the `IdentifyAsync` method to identify users:
84+
85+
```csharp
86+
// This stores information about the user in PostHog.
87+
await posthog.IdentifyAsync(
88+
distinctId,
89+
user.Email,
90+
user.UserName,
91+
// Properties to set on the person. If they're already
92+
// set, they will be overwritten.
93+
personPropertiesToSet: new()
94+
{
95+
["phone"] = user.PhoneNumber ?? "unknown",
96+
["email_confirmed"] = user.EmailConfirmed,
97+
},
98+
// Properties to set once. If they're already set
99+
// on the person, they won't be overwritten.
100+
personPropertiesToSetOnce: new()
101+
{
102+
["joined"] = DateTime.UtcNow
103+
});
104+
```
105+
Some things to note about the `IdentifyAsync` method:
106+
107+
- 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 same `distinctId` here as you do on the client.
108+
- The `personPropertiesToSet` and `personPropertiesToSetOnce` are optional. You can use them to set properties about the user.
109+
- If you choose a `distinctId` that can change (such as username or email), you can use the `AliasAsync` method to alias the old `distinctId` with the new one so that the user can be tracked across different `distinctIds`.
110+
111+
To capture an event, call the `Capture` method:
112+
113+
```csharp
114+
posthog.Capture("some-distinct-id", "my-event");
115+
```
116+
117+
This will capture an event with the distinct id, the event name, and the current timestamp. You can also include properties:
118+
119+
```csharp
120+
posthog.Capture(
121+
"some-distinct-id",
122+
"user signed up",
123+
new() { ["plan"] = "pro" });
124+
```
125+
126+
The `Capture` method is synchronous and returns immediately. The actual batching and sending of events is done in the background.
127+
128+
## Feature flags
129+
130+
To evaluate a feature flag, call the `IsFeatureEnabledAsync` method:
131+
132+
```csharp
133+
if (await posthog.IsFeatureEnabledAsync(
134+
"new_user_feature",
135+
"some-distinct-id")) {
136+
// The feature flag is enabled.
137+
}
138+
```
139+
140+
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`.
141+
142+
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.
143+
144+
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.
145+
146+
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:
147+
148+
```csharp
149+
await posthog.IsFeatureEnabledAsync(
150+
featureKey: "person-flag",
151+
distinctId: "some-distinct-id",
152+
personProperties: new() { ["plan"] = "pro" });
153+
```
154+
155+
This will evaluate the feature flag and return `true` if the feature flag is enabled and the user's plan is "pro".
156+
157+
## .NET Feature Management
158+
159+
[.NET Feature Management](https://learn.microsoft.com/en-us/azure/azure-app-configuration/feature-management-dotnet-reference) 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.
160+
161+
```csharp
162+
<feature name="my-feature">
163+
<p>This is a feature flag.</p>
164+
</feature>
165+
```
166+
167+
You can also use the `FeatureGateAttribute` in your controllers and pages to conditionally execute code based on the state of a feature flag.
168+
169+
```csharp
170+
[FeatureGate("my-feature")]
171+
public class MyController : Controller
172+
{
173+
}
174+
```
175+
176+
If your app already uses .NET Feature Management, you can switch to using PostHog with very little effort.
177+
178+
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`.
179+
180+
```csharp
181+
public class MyFeatureFlagContextProvider(
182+
IHttpContextAccessor httpContextAccessor)
183+
: PostHogFeatureFlagContextProvider
184+
{
185+
protected override string? GetDistinctId() =>
186+
httpContextAccessor.HttpContext?.User.Identity?.Name;
187+
188+
protected override ValueTask<FeatureFlagOptions> GetFeatureFlagOptionsAsync()
189+
{
190+
// In a real app, you might get this information from a database or other source for the current user.
191+
return ValueTask.FromResult(
192+
new FeatureFlagOptions
193+
{
194+
PersonProperties = new Dictionary<string, object?>
195+
{
196+
["email"] = "[email protected]",
197+
["plan"] = "pro"
198+
},
199+
OnlyEvaluateLocally = true
200+
});
201+
}
202+
}
203+
```
204+
205+
Then, register your implementation in `Program.cs` (or `Startup.cs`):
206+
207+
```csharp
208+
using PostHog;
209+
210+
var builder = WebApplication.CreateBuilder(args);
211+
212+
builder.AddPostHog(options => {
213+
options.UseFeatureManagement<MyFeatureFlagContextProvider>();
214+
});
215+
```
216+
217+
This registers a feature flag provider that uses your implementation of `IPostHogFeatureFlagContextProvider` to evaluate feature flags against PostHog.
218+
219+
## Non-ASP.NET Core usage
220+
221+
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:
222+
223+
```bash
224+
dotnet add package PostHog.AspNetCore
225+
```
226+
227+
And then register it with your dependency injection container:
228+
229+
```csharp
230+
builder.Services.AddPostHog();
231+
```
232+
233+
If you're not using dependency injection, you can still use the registration method:
234+
235+
```csharp
236+
using PostHog;
237+
var services = new ServiceCollection();
238+
services.AddPostHog();
239+
var serviceProvider = services.BuildServiceProvider();
240+
var posthog = serviceProvider.GetRequiredService<IPostHogClient>();
241+
```
242+
243+
For a console app (or apps not using dependency injection), you can also use the `PostHogClient` directly, just make sure it's a singleton:
244+
245+
```csharp
246+
using System;
247+
using PostHog;
248+
249+
var posthog = new PostHogClient(
250+
Environment.GetEnvironmentVariable("PostHog__PersonalApiKey"));
251+
```
252+
253+
## Examples
254+
255+
To see all this in action, the [`posthog-dotnet` GitHub repository](https://github.com/posthog/posthog-dotnet) has a [samples directory](https://github.com/PostHog/posthog-dotnet/tree/main/samples) with a growing number of example projects. For example, the [HogTied.Web](https://github.com/PostHog/posthog-dotnet/tree/main/samples/HogTied.Web) project is an ASP.NET Core web app that uses PostHog for analytics and feature flags and shows some advanced configuration.
256+
257+
## What's next?
258+
259+
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.
260+
261+
If anyone reports bugs, I'll be sure to fix them. But I won't be adding any new features for the moment.
262+
263+
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.
264+
265+
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.

0 commit comments

Comments
 (0)