Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

Add extension to EFCore package to expose DB sync configuration in an HTTP endpoint in an ASP.NET Core app.

## 0.5.2

Replace sync mode configuration with an option to the deploy CLI task.
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@ If all is configured correctly, whenever any of the applications in your solutio
a `dfe-analytics` directory should be created in the output directory.
This directory contains a `config.json` file with the configuration for your DbContext, including which tables and columns are configured to sync to BigQuery.

### Exposing the sync configuration over HTTP

If your DbContext is hosted in an ASP.NET Core application, you can expose its sync configuration over an HTTP endpoint.
This is useful for verifying at runtime which tables and columns are configured to sync to BigQuery.

Map the endpoint in your application's entry point (e.g. `Program.cs`):

```cs
using Dfe.Analytics.EFCore;

//...
app.MapDfeAnalyticsDbConfiguration<MyDbContext>();
```

By default the configuration is served as JSON from `/_dfe-analytics/db-config.json`.
You can supply a custom path if you prefer:

```cs
app.MapDfeAnalyticsDbConfiguration<MyDbContext>("/my-custom-path.json");
```

The `MyDbContext` type must be registered in the application's service provider (e.g. via `AddDbContext<MyDbContext>()`).


## Capturing web request events in ASP.NET Core

Expand Down
39 changes: 39 additions & 0 deletions src/Dfe.Analytics.EFCore/AspNetCoreExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Dfe.Analytics.EFCore.Configuration;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Dfe.Analytics.EFCore;

public static class AspNetCoreExtensions
{
private const string DefaultConfigurationPath = "/_dfe-analytics/db-config.json";

public static IEndpointConventionBuilder MapDfeAnalyticsDbConfiguration<TDbContext>(
this IEndpointRouteBuilder endpointBuilder)
where TDbContext : DbContext
{
return MapDfeAnalyticsDbConfiguration<TDbContext>(endpointBuilder, DefaultConfigurationPath);
}

public static IEndpointConventionBuilder MapDfeAnalyticsDbConfiguration<TDbContext>(
this IEndpointRouteBuilder endpointBuilder,
string path)
where TDbContext : DbContext
{
ArgumentNullException.ThrowIfNull(endpointBuilder);
ArgumentNullException.ThrowIfNull(path);

return endpointBuilder.MapGet(
path,
ctx =>
{
var configurationProvider = new AnalyticsConfigurationProvider();
var dbContext = ctx.RequestServices.GetRequiredService<TDbContext>();
var configuration = configurationProvider.GetConfiguration(dbContext);
return ctx.Response.WriteAsJsonAsync(configuration, DatabaseSyncConfiguration.JsonSerializerOptions);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ namespace Dfe.Analytics.EFCore.Configuration;

public sealed class DatabaseSyncConfiguration : IEquatable<DatabaseSyncConfiguration>
{
private static readonly JsonSerializerOptions _jsonSerializerOptions = new(JsonSerializerDefaults.Web)
{
WriteIndented = true
};
internal static JsonSerializerOptions JsonSerializerOptions { get; } = new(JsonSerializerDefaults.Web) { WriteIndented = true };

public required string DbContextName { get; init; }

Expand All @@ -24,15 +21,15 @@ public static async Task<DatabaseSyncConfiguration> ReadFromFileAsync(string con

var json = await File.ReadAllTextAsync(configurationPath, cancellationToken);

return JsonSerializer.Deserialize<DatabaseSyncConfiguration>(json, _jsonSerializerOptions)
return JsonSerializer.Deserialize<DatabaseSyncConfiguration>(json, JsonSerializerOptions)
?? throw new InvalidOperationException("Failed to deserialize DatabaseSyncConfiguration.");
}

public Task WriteToFileAsync(string configurationPath, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(configurationPath);

var json = JsonSerializer.Serialize(this, _jsonSerializerOptions);
var json = JsonSerializer.Serialize(this, JsonSerializerOptions);

return File.WriteAllTextAsync(configurationPath, json, cancellationToken);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Dfe.Analytics.EFCore/Dfe.Analytics.EFCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.9" />
<PackageReference Include="System.CommandLine" Version="2.0.0" />
</ItemGroup>

Expand Down Expand Up @@ -46,6 +46,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Dfe.Analytics.AspNetCore\Dfe.Analytics.AspNetCore.csproj" PrivateAssets="all" />
<ProjectReference Include="..\Dfe.Analytics.Core\Dfe.Analytics.Core.csproj" />
</ItemGroup>

Expand Down
57 changes: 57 additions & 0 deletions tests/Dfe.Analytics.EFCore.Tests/AspNetCoreExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Net;
using System.Text.Json;
using Dfe.Analytics.EFCore.Configuration;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Dfe.Analytics.EFCore.Tests;

public class AspNetCoreExtensionsTests
{
[Fact]
public async Task MapDfeAnalyticsDbConfiguration_ReturnsConfigurationForDbContext()
{
// Arrange
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddRouting();
services.AddScoped(_ => new TestDbContext());
})
.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDfeAnalyticsDbConfiguration<TestDbContext>("/db-configuration");
});
});
})
.StartAsync(TestContext.Current.CancellationToken);

var client = host.GetTestClient();

// Act
var response = await client.GetAsync("/db-configuration", TestContext.Current.CancellationToken);

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);

var json = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var configuration = JsonSerializer.Deserialize<DatabaseSyncConfiguration>(
json,
DatabaseSyncConfiguration.JsonSerializerOptions);

var expectedConfiguration = new AnalyticsConfigurationProvider().GetConfiguration(new TestDbContext());

Assert.NotNull(configuration);
Assert.Equal(expectedConfiguration, configuration);
}
}
13 changes: 13 additions & 0 deletions tests/Dfe.Analytics.EFCore.Tests/Dfe.Analytics.EFCore.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,21 @@
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5"/>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="8.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="9.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<PackageReference Include="System.CodeDom" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.0" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
Expand Down