Skip to content

Commit

Permalink
Merge pull request #54 from PostHog/haacked/better-configuration
Browse files Browse the repository at this point in the history
Implement a more flexible configuration system
  • Loading branch information
haacked authored Feb 17, 2025
2 parents 3990a7a + a24fb39 commit c2068e0
Show file tree
Hide file tree
Showing 18 changed files with 626 additions and 79 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>1.0.0-beta.10</Version>
<Version>1.0.0-beta.11</Version>
<LangVersion>13.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
51 changes: 49 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ To run the tests, run the following command in the root of the repository:
$ dotnet test
```

## PUBLISHING RELEASES
## Publishing Releases

When it's time to cut a release, increment the version element at the top of [`Directory.Build.props`](Directory.Build.props) according to the [Semantic Versioning](http://semver.org/) guidelines.

Expand Down Expand Up @@ -89,9 +89,56 @@ When you create the Release, the [`main.yml`](../.github/.workflow.release.yml)
> then later publish it, the workflow will not run. If you find yourself in that position, you can [manually trigger the workflow run](https://github.com/PostHog/posthog-dotnet/actions/workflows/main.yaml)
> and select the tag to publish.
## Installation

For ASP.NET Core projects, install the `PostHog.AspNetCore` package:

```bash
$ dotnet add package PostHog.AspNetCore
```

And register the PostHog services in `Program.cs` (or `Startup.cs`) file by calling the `AddPostHog` extension
method on `IHostApplicationBuilder` like so:

```csharp
using PostHog;
var builder = WebApplication.CreateBuilder(args);
builder.AddPostHog();
```

For other .NET projects, install the `PostHog` package:

```bash
$ dotnet add package PostHog
```

And if your project supports dependency injection, register the PostHog services in `Program.cs` (or `Startup.cs`)
file by calling the `AddPostHog` extension method on `IServiceCollection`. Here's an example for a console app:

```csharp
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:

```csharp
using System;
using PostHog;

var posthog = new PostHogClient(Environment.GetEnvironmentVariable("PostHog__PersonalApiKey"));
```

The `AddPostHog` methods accept an optional `Action<PostHogOptions>` parameter that you can use to configure the
client. For examples, check out the [HogTied.Web sample project](../samples/HogTied.Web/Program.cs) and the unit tests.

## Usage

Inject the `Iposthog` interface into your controller or page:
Inject the `IPostHogClient` interface into your controller or page:

```csharp
posthog.Capture(userId, "user signed up", new() { ["plan"] = "pro" });
Expand Down
13 changes: 10 additions & 3 deletions samples/HogTied.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using PostHog;
using PostHog.Config;
using PostHog.Library;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.AddPostHog(options =>
{
// Not needed as PostHog is the default, but wanted to show it can be set.
options.UseConfigurationSection("PostHog");
// In general this call is not needed. The default settings are in the "PostHoc" configuration section.
// This is here so I can easily switch testing against my local install and production.
options.UseConfigurationSection(builder.Configuration.GetSection("PostHog"));

options.PostConfigure(o =>
{
o.SuperProperties.Add("app_name", "HogTied");
});

// Logs requests and responses. Fine for a sample project. Probably not good for production.
options.ConfigureHttpClient(httpClientBuilder => httpClientBuilder.AddHttpMessageHandler<LoggingHttpMessageHandler>());
});
Expand Down
6 changes: 5 additions & 1 deletion samples/HogTied.Web/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
"PostHog": "Trace"
}
},
"PostHog" : {
"PostHogLocal" : {
"HostUrl": "http://localhost:8010",
"ProjectApiKey": "SET IN USER SECRETS!",
"MaxBatchSize": 100,
"MaxQueueSize": 1000,
"FlushAt": 5,
"FlushInterval": "0:0:10"
},
"PostHog" : {
"ProjectApiKey": "SET IN USER SECRETS!"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.Extensions.Configuration;

namespace PostHog.Config;

public interface IPostHogAspNetCoreConfigurationBuilder : IPostHogConfigurationBuilder
{
public IConfiguration Configuration { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using PostHog.Cache;
using PostHog.Library;

namespace PostHog.Config;
using static Ensure;

/// <summary>
/// Extension methods for <see cref="IPostHogConfigurationBuilder"/>.
/// </summary>
public static class PostHogConfigurationBuilderExtensions
{
/// <summary>
/// Registers ASP.NET Core specific implementations of PostHogClient services.
/// </summary>
/// <param name="builder">The <see cref="IPostHogConfigurationBuilder"/>.</param>
/// <returns>The passed in <see cref="IPostHogConfigurationBuilder"/>.</returns>
public static IPostHogConfigurationBuilder UseAspNetCore(this IPostHogConfigurationBuilder builder)
{
NotNull(builder).Use(services =>
{
services.AddSingleton<IFeatureFlagCache, HttpContextFeatureFlagCache>();
services.AddHttpContextAccessor();
});
return builder;
}
}
66 changes: 0 additions & 66 deletions src/PostHog.AspNetCore/PostHogOptionsBuilder.cs

This file was deleted.

26 changes: 22 additions & 4 deletions src/PostHog.AspNetCore/Registration.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using PostHog.Config;
using static PostHog.Library.Ensure;

namespace PostHog;

/// <summary>
/// Extension methods on <see cref="IHostApplicationBuilder"/> used to register
/// a <see cref="PostHogClient"/> configured for ASP.NET Core.
/// </summary>
public static class Registration
{
const string DefaultConfigurationSectionName = "PostHog";

/// <summary>
/// Registers <see cref="PostHogClient"/> as a singleton. Looks for client configuration in the "PostHog"
/// section of the configuration. See <see cref="PostHogOptions"/> for the configuration options.
Expand All @@ -23,12 +31,22 @@ public static IHostApplicationBuilder AddPostHog(this IHostApplicationBuilder bu
/// <exception cref="ArgumentNullException">If <see cref="builder"/> is null.</exception>
public static IHostApplicationBuilder AddPostHog(
this IHostApplicationBuilder builder,
Action<PostHogOptionsBuilder> options)
Action<IPostHogConfigurationBuilder> options)
{
builder = NotNull(builder);
options = NotNull(options);
var postHogOptionsBuilder = new PostHogOptionsBuilder(builder);
options(postHogOptionsBuilder);
postHogOptionsBuilder.Build();

builder.Services.AddPostHog(
o =>
{
if (builder.Services.All(service => service.ServiceType != typeof(IConfigureOptions<PostHogOptions>)))
{
// Set the default.
o.UseConfigurationSection(builder.Configuration.GetSection(DefaultConfigurationSectionName));
}
o.UseAspNetCore();
options(o);
});
return builder;
}
}
29 changes: 29 additions & 0 deletions src/PostHog/Config/IPostHogConfigurationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Extensions.DependencyInjection;

namespace PostHog.Config;

/// <summary>
/// Base interface for configuring the PostHog client.
/// </summary>
public interface IPostHogConfigurationBuilder
{
/// <summary>
/// Calls the specified configuration action on the <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="configurationAction">The configuration action to apply to <see cref="IServiceCollection"/>.</param>
/// <returns>The <see cref="IPostHogConfigurationBuilder"/>.</returns>
IPostHogConfigurationBuilder Use(Action<IServiceCollection> configurationAction);

/// <summary>
/// Allows the <see cref="HttpClient"/> used by <see cref="PostHogClient"/> to be additionally configured
/// such as adding <see cref="HttpMessageHandler"/>s.
/// </summary>
/// <remarks>
/// I was hoping I wouldn't have to special case this, but the AddHttpClient method creates and returns a new
/// <see cref="DefaultHttpClientBuilder">DefaultHttpClientBuilder</see> which we need to store to be able to access.
///
/// </remarks>
/// <param name="configureHttpClient">An action used to configure the HttpClient via the <see cref="IHttpClientBuilder"/>.</param>
/// <returns>The <see cref="IPostHogConfigurationBuilder"/>.</returns>
IPostHogConfigurationBuilder ConfigureHttpClient(Action<IHttpClientBuilder> configureHttpClient);
}
52 changes: 52 additions & 0 deletions src/PostHog/Config/PostHogConfigurationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using PostHog.Library;
using static PostHog.Library.Ensure;

namespace PostHog.Config;

/// <summary>
/// A builder for configuring the <see cref="PostHogClient"/>.
/// </summary>
public class PostHogConfigurationBuilder : IPostHogConfigurationBuilder
{
readonly IServiceCollection _services;
readonly IHttpClientBuilder _httpClientBuilder;

public PostHogConfigurationBuilder(IServiceCollection services)
{
_services = services;
_services.AddLogging();
_httpClientBuilder = _services.AddHttpClient(nameof(PostHogClient)); // Registers the IHttpClientFactory.
}

/// <inheritdoc />
public IPostHogConfigurationBuilder ConfigureHttpClient(Action<IHttpClientBuilder> configureHttpClient)
{
NotNull(configureHttpClient)(_httpClientBuilder);
return this;
}

/// <summary>
/// Builds the <see cref="IServiceCollection"/> with the configured services. This enables us to try and add
/// default services after we've configured the <see cref="PostHogClient"/>.
/// </summary>
/// <returns></returns>
public IServiceCollection Build()
{
// Try to set up defaults if they haven't been set yet.
_services.TryAddSingleton<IFeatureFlagCache>(_ => NullFeatureFlagCache.Instance);
_services.TryAddSingleton<ITaskScheduler, TaskRunTaskScheduler>();
_services.TryAddSingleton<IPostHogClient, PostHogClient>();
_services.TryAddSingleton<TimeProvider>(_ => TimeProvider.System);

return _services;
}

/// <inheritdoc />
public IPostHogConfigurationBuilder Use(Action<IServiceCollection>? configurationAction)
{
configurationAction?.Invoke(_services);
return this;
}
}
Loading

0 comments on commit c2068e0

Please sign in to comment.