Skip to content

Commit

Permalink
Added support for .NET Aspire and a client-lib.
Browse files Browse the repository at this point in the history
  • Loading branch information
IEvangelist committed Jan 7, 2025
1 parent b6e8043 commit e370e4b
Show file tree
Hide file tree
Showing 45 changed files with 621 additions and 227 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client.Core" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.10" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="9.0.0" />
Expand Down
6 changes: 6 additions & 0 deletions playground/ProfanityFilter.Api/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) David Pine. All rights reserved.
// Licensed under the MIT License.

global using Microsoft.AspNetCore.Mvc;
global using ProfanityFilter.Client;
global using ProfanityFilter.Shared.Api;
19 changes: 19 additions & 0 deletions playground/ProfanityFilter.Api/ProfanityFilter.Api.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>82eaa4db-aed7-4828-8fec-bb9d542765f4</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Swashbuckle.AspNetCore" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\ProfanityFilter.Client\ProfanityFilter.Client.csproj" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions playground/ProfanityFilter.Api/ProfanityFilter.Api.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@ProfanityFilter.Api_HostAddress = http://localhost:5225

GET {{ProfanityFilter.Api_HostAddress}}/weatherforecast/
Accept: application/json

###
37 changes: 37 additions & 0 deletions playground/ProfanityFilter.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) David Pine. All rights reserved.
// Licensed under the MIT License.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.AddProfanityFilterClient("profanity-filter");

// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}

app.UseSwagger();
app.UseSwaggerUI();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.MapPost(
pattern: "/filter",
handler: static async (
[FromBody] ProfanityFilterRequest request,
[FromServices] IRestClient client) =>
await client.ApplyFilterAsync(request))
.WithName("Filter");

app.Run();
8 changes: 8 additions & 0 deletions playground/ProfanityFilter.Api/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions playground/ProfanityFilter.Api/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

<ItemGroup>
<ProjectReference Include="..\..\src\ProfanityFilter.Hosting\ProfanityFilter.Hosting.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\ProfanityFilter.Api\ProfanityFilter.Api.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
8 changes: 6 additions & 2 deletions playground/ProfanityFilter.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

var builder = DistributedApplication.CreateBuilder(args);

_ = builder.AddProfanityFilter("profanity-filter")
.WithDataBindMount("./CustomData");
var filter = builder.AddProfanityFilter("profanity-filter")
.WithCustomDataBindMount("./CustomData");

builder.AddProject<Projects.ProfanityFilter_Api>("api")
.WithReference(filter)
.WaitFor(filter);

builder.Build().Run();
21 changes: 21 additions & 0 deletions profanity-filter.sln
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "playground", "playground",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfanityFilter.AppHost", "playground\ProfanityFilter.AppHost\ProfanityFilter.AppHost.csproj", "{B5E3CB82-3306-4DD6-96BB-17995027FCA3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfanityFilter.Api", "playground\ProfanityFilter.Api\ProfanityFilter.Api.csproj", "{5380A680-6C10-4EA5-92F2-FA7DBF78900C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfanityFilter.Client.Tests", "tests\ProfanityFilter.Client.Tests\ProfanityFilter.Client.Tests.csproj", "{2DDAB9F2-3BC8-4191-B345-7DCA606BD46E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfanityFilter.Shared.Tests", "tests\ProfanityFilter.Shared.Tests\ProfanityFilter.Shared.Tests.csproj", "{A742317F-FFB8-4A42-B9DC-8F79DA3B8EC6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -84,6 +90,18 @@ Global
{B5E3CB82-3306-4DD6-96BB-17995027FCA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5E3CB82-3306-4DD6-96BB-17995027FCA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5E3CB82-3306-4DD6-96BB-17995027FCA3}.Release|Any CPU.Build.0 = Release|Any CPU
{5380A680-6C10-4EA5-92F2-FA7DBF78900C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5380A680-6C10-4EA5-92F2-FA7DBF78900C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5380A680-6C10-4EA5-92F2-FA7DBF78900C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5380A680-6C10-4EA5-92F2-FA7DBF78900C}.Release|Any CPU.Build.0 = Release|Any CPU
{2DDAB9F2-3BC8-4191-B345-7DCA606BD46E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2DDAB9F2-3BC8-4191-B345-7DCA606BD46E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2DDAB9F2-3BC8-4191-B345-7DCA606BD46E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2DDAB9F2-3BC8-4191-B345-7DCA606BD46E}.Release|Any CPU.Build.0 = Release|Any CPU
{A742317F-FFB8-4A42-B9DC-8F79DA3B8EC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A742317F-FFB8-4A42-B9DC-8F79DA3B8EC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A742317F-FFB8-4A42-B9DC-8F79DA3B8EC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A742317F-FFB8-4A42-B9DC-8F79DA3B8EC6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -98,6 +116,9 @@ Global
{482EDC8C-D8F1-4934-8274-F24D14D9E4B0} = {28623E11-D15F-4448-82F6-B86A7D59B1D4}
{ED750950-FD39-4FF9-B3DC-48FC171164F7} = {28623E11-D15F-4448-82F6-B86A7D59B1D4}
{B5E3CB82-3306-4DD6-96BB-17995027FCA3} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{5380A680-6C10-4EA5-92F2-FA7DBF78900C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{2DDAB9F2-3BC8-4191-B345-7DCA606BD46E} = {EAC63754-09D3-49C9-ACDF-ECF73AA1D922}
{A742317F-FFB8-4A42-B9DC-8F79DA3B8EC6} = {EAC63754-09D3-49C9-ACDF-ECF73AA1D922}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {72DBDAF8-D0CD-4A55-A944-0E1787FFA5C6}
Expand Down
87 changes: 56 additions & 31 deletions src/ProfanityFilter.Client/DefaultRealtimeClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,54 @@
namespace ProfanityFilter.Client;

internal sealed class DefaultRealtimeClient(
IConfiguration configuration,
IOptions<ProfanityFilterOptions> options,
ILogger<DefaultRealtimeClient> logger) : IRealtimeClient
{
private readonly HubConnection _connection = new HubConnectionBuilder()
.WithUrl(options.Value.RealtimeUri)
.WithAutomaticReconnect()
.WithStatefulReconnect()
.Build();
private readonly Uri _hubUrl = new UriBuilder(
options.Value.ApiBaseAddress ?? throw new ArgumentException("""
The API base address must be provided.
"""))
{
Path = "profanity/hub"
}.Uri;

private HubConnection? _connection = null;

[MemberNotNull(nameof(_connection))]
private void EnsureInitialized()
{
_connection ??= new HubConnectionBuilder()
.WithUrl(_hubUrl, options =>
{
// Only apply these options when running in a container.
if (!configuration.IsRunningInContainer())
{
return;
}

options.UseDefaultCredentials = true;
options.HttpMessageHandlerFactory = handler =>
{
if (handler is HttpClientHandler clientHandler)
{
clientHandler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}

return handler;
};
})
.WithAutomaticReconnect()
.WithStatefulReconnect()
.Build();
}

/// <inheritdoc />
async ValueTask IRealtimeClient.StartAsync(CancellationToken cancellationToken)
{
EnsureInitialized();

_connection.Closed += OnHubConnectionClosedAsync;
_connection.Reconnected += OnHubConnectionReconnectedAsync;
_connection.Reconnecting += OnHubConnectionReconnectingAsync;
Expand All @@ -26,6 +62,8 @@ async ValueTask IRealtimeClient.StartAsync(CancellationToken cancellationToken)
/// <inheritdoc />
async ValueTask IRealtimeClient.StopAsync(CancellationToken cancellationToken)
{
EnsureInitialized();

_connection.Closed -= OnHubConnectionClosedAsync;
_connection.Reconnected -= OnHubConnectionReconnectedAsync;
_connection.Reconnecting -= OnHubConnectionReconnectingAsync;
Expand Down Expand Up @@ -55,34 +93,21 @@ private Task OnHubConnectionReconnectedAsync(string? arg)
}

/// <inheritdoc />
async IAsyncEnumerable<ProfanityFilterResponse> IRealtimeClient.LiveStreamAsync(
IAsyncEnumerable<ProfanityFilterRequest> liveRequests,
[EnumeratorCancellation] CancellationToken cancellationToken)
bool IRealtimeClient.IsConnected => _connection is
{
var channel = Channel.CreateUnbounded<ProfanityFilterResponse>();
State: HubConnectionState.Connected
};

_connection.On<ProfanityFilterResponse>(
"live", response => channel.Writer.TryWrite(response));

var sendTask = Task.Run(async () =>
{
await foreach (var request in liveRequests.WithCancellation(cancellationToken))
{
await _connection.SendAsync("LiveStream", request, cancellationToken);
}

channel.Writer.Complete();
},
cancellationToken);

while (await channel.Reader.WaitToReadAsync(cancellationToken))
{
while (channel.Reader.TryRead(out var response))
{
yield return response;
}
}
/// <inheritdoc />
IAsyncEnumerable<ProfanityFilterResponse> IRealtimeClient.StreamAsync(
IAsyncEnumerable<ProfanityFilterRequest> liveRequests,
CancellationToken cancellationToken)
{
EnsureInitialized();

await sendTask;
return _connection.StreamAsync<ProfanityFilterResponse>(
methodName: "live",
arg1: liveRequests,
cancellationToken: cancellationToken);
}
}
10 changes: 5 additions & 5 deletions src/ProfanityFilter.Client/DefaultRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal sealed class DefaultRestClient(HttpClient client) : IRestClient
async Task<IMaybe<ProfanityFilterResponse>> IRestClient.ApplyFilterAsync(ProfanityFilterRequest request)
{
using var response = await client.PostAsJsonAsync(
"filter", request, JsonSerializationContext.Default.ProfanityFilterRequest);
"profanity/filter", request, JsonSerializationContext.Default.ProfanityFilterRequest);

response.EnsureSuccessStatusCode();

Expand All @@ -23,25 +23,25 @@ async Task<IMaybe<ProfanityFilterResponse>> IRestClient.ApplyFilterAsync(Profani
/// <inheritdoc />
Task<IMaybe<string[]>> IRestClient.GetDataByNameAsync(string name)
{
return GetMaybeAsync($"data/{name}", JsonSerializationContext.Default.StringArray);
return GetMaybeAsync($"profanity/data/{name}", JsonSerializationContext.Default.StringArray);
}

/// <inheritdoc />
Task<IMaybe<string[]>> IRestClient.GetDataNamesAsync()
{
return GetMaybeAsync("data", JsonSerializationContext.Default.StringArray);
return GetMaybeAsync("profanity/data", JsonSerializationContext.Default.StringArray);
}

/// <inheritdoc />
Task<IMaybe<StrategyResponse[]>> IRestClient.GetStrategiesAsync()
{
return GetMaybeAsync("strategies", JsonSerializationContext.Default.StrategyResponseArray);
return GetMaybeAsync("profanity/strategies", JsonSerializationContext.Default.StrategyResponseArray);
}

/// <inheritdoc />
Task<IMaybe<FilterTargetResponse[]>> IRestClient.GetTargetsAsync()
{
return GetMaybeAsync("targets", JsonSerializationContext.Default.FilterTargetResponseArray);
return GetMaybeAsync("profanity/targets", JsonSerializationContext.Default.FilterTargetResponseArray);
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,40 @@ private static void AddProfanityFilterClient(
configSection.Bind(options);
namedConfigSection.Bind(options);

if (builder.Configuration.GetConnectionString(connectionName) is string connectionString &&
Uri.TryCreate(connectionString, UriKind.Absolute, out var baseAddress))
builder.Services.AddOptions<ProfanityFilterOptions>()
.Bind(configSection)
.Bind(namedConfigSection);

//builder.Services.Configure<ProfanityFilterOptions>(configSection);
//builder.Services.Configure<ProfanityFilterOptions>(namedConfigSection);

var connectionString = builder.Configuration.GetConnectionString(connectionName)
?? configSection["ApiBaseAddress"]
?? namedConfigSection["ApiBaseAddress"];

if (connectionString is string potentialUri &&
Uri.TryCreate(potentialUri, UriKind.Absolute, out var baseAddress))
{
builder.Services.Configure<ProfanityFilterOptions>(
configureOptions: o => o.ApiBaseAddress = baseAddress);

options.ApiBaseAddress = baseAddress;
}

configure?.Invoke(options);
if (configure is not null)
{
configure.Invoke(options);
builder.Services.PostConfigure(configure);
}

builder.Services.AddOptions<ProfanityFilterOptions>();
builder.Services.AddLogging();
builder.Services.AddHttpClient<IRestClient, DefaultRestClient>((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<ProfanityFilterOptions>>();

client.BaseAddress = options.Value.ApiBaseAddress;
});

builder.Services.AddScoped<IRestClient, DefaultRestClient>();
builder.Services.AddScoped<IRealtimeClient, DefaultRealtimeClient>();

if (serviceKey is null)
Expand Down
10 changes: 2 additions & 8 deletions src/ProfanityFilter.Client/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
// Copyright (c) David Pine. All rights reserved.
// Licensed under the MIT License.

global using System;
global using System.Collections.Frozen;
global using System.ComponentModel.DataAnnotations;
global using System.Diagnostics.CodeAnalysis;
global using System.Net.Http.Json;
global using System.Runtime.CompilerServices;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using System.Text.Json.Serialization.Metadata;
global using System.Threading.Channels;

global using Microsoft.AspNetCore.SignalR.Client;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options;
global using Microsoft.Extensions.Hosting;

global using ProfanityFilter.Client;
global using ProfanityFilter.Client.Options;
Expand Down
Loading

0 comments on commit e370e4b

Please sign in to comment.