Framework-first doctrine, DI logger factory, config refactor
Introduced framework-first design rules and updated documentation to clarify framework vs. consumer boundaries. Added AcLoggerOptions and DI-based logger factory extensions to AyCode.Core, enabling per-category logger instantiation from appsettings.json. Standardized SignalR connection setup with AddAcDefaults, replacing project-specific code. Enhanced protocol configuration for DI scope isolation. Refactored appsettings.json structure and added MSBuild targets for config propagation. Removed obsolete code and updated comments to match new patterns.
This commit is contained in:
parent
33d84a8257
commit
711c3c8ec0
|
|
@ -26,7 +26,7 @@ internal static class TestSignalRClientFactory
|
|||
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, testCategoryName)
|
||||
};
|
||||
|
||||
Func<string, AcLoggerBase> loggerFactory =
|
||||
Func<string, LoggerClient> loggerFactory =
|
||||
categoryName => new LoggerClient(categoryName, logWriters.ToArray());
|
||||
|
||||
var connectionOptions = new AcHubConnectionOptions
|
||||
|
|
@ -45,7 +45,7 @@ internal static class TestSignalRClientFactory
|
|||
|
||||
var logger = loggerFactory(nameof(FruitBankSignalRClient));
|
||||
|
||||
var hubBuilder = new HubConnectionBuilder().AddFruitBankDefaults(logger, connectionOptions);
|
||||
var hubBuilder = new HubConnectionBuilder().AddAcDefaults(logger, connectionOptions);
|
||||
hubBuilder.AddAcBinaryProtocol(opts => opts.ProtocolMode = BinaryProtocolMode.AsyncSegment);
|
||||
|
||||
return new FruitBankSignalRClient(hubBuilder, loggerFactory);
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
using AyCode.Core.Loggers;
|
||||
using AyCode.Services.SignalRs;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FruitBankHybrid.Shared.Services.SignalRs;
|
||||
|
||||
/// <summary>
|
||||
/// FruitBank-specific hub-connection setup.
|
||||
/// <para>
|
||||
/// Connection-level configuration (URL, buffers, timeouts, reconnect) is delegated to the
|
||||
/// framework's <see cref="AcSignalRConnectionExtensions.AddAcConnection"/>, driven by
|
||||
/// <see cref="AcHubConnectionOptions"/> from <c>appsettings.json</c>. The logger is supplied
|
||||
/// by the caller (typically via a DI-registered <c>Func<string, AcLoggerBase></c> factory
|
||||
/// in <c>Program.cs</c>), keeping this extension free of <c>LoggerClient</c> construction.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class FruitBankHubConnectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies <see cref="AcSignalRConnectionExtensions.AddAcConnection"/> with the given
|
||||
/// <paramref name="connectionOptions"/>, then bridges <paramref name="logger"/> into
|
||||
/// SignalR's internal <c>ILogger</c> pipeline.
|
||||
/// </summary>
|
||||
public static IHubConnectionBuilder AddFruitBankDefaults(this IHubConnectionBuilder builder, AcLoggerBase logger, AcHubConnectionOptions connectionOptions)
|
||||
{
|
||||
return builder
|
||||
.AddAcConnection(connectionOptions)
|
||||
.ConfigureLogging(logging =>
|
||||
{
|
||||
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information);
|
||||
logging.AddAcLogger(_ => logger);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
|||
{
|
||||
public class FruitBankSignalRClient : AcSignalRClientBase, IFruitBankDataControllerClient, ICustomOrderSignalREndpointClient, IStockSignalREndpointClient
|
||||
{
|
||||
public FruitBankSignalRClient(IHubConnectionBuilder hubBuilder, Func<string, AcLoggerBase> loggerFactory)
|
||||
public FruitBankSignalRClient(IHubConnectionBuilder hubBuilder, Func<string, LoggerClient> loggerFactory)
|
||||
: base(hubBuilder, loggerFactory(nameof(FruitBankSignalRClient)))
|
||||
{
|
||||
EnableBinaryDiagnostics = FruitBankConstClient.SignalRSerializerDiagnosticLog;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AyCode": {
|
||||
"ProjectId": "aad53443-2ee2-4650-8a99-97e907265e4e",
|
||||
"Urls": {
|
||||
"BaseUrl": "https://localhost:7144",
|
||||
"ApiBaseUrl": "https://localhost:7144"
|
||||
},
|
||||
"Logger": {
|
||||
"AppType": "Server",
|
||||
"LogLevel": "Detail",
|
||||
"LogWriters": [
|
||||
{
|
||||
"LogLevel": "Detail",
|
||||
"LogWriterType": "FruitBank.Common.Server.Services.Loggers.ConsoleLogWriter, FruitBank.Common.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
"AcHubConnection": {
|
||||
"Url": "https://localhost:59579/fbHub",
|
||||
"TransportMaxBufferSize": 30000000,
|
||||
"ApplicationMaxBufferSize": 30000000,
|
||||
"CloseTimeout": "00:00:10",
|
||||
"KeepAliveInterval": "00:01:00",
|
||||
"ServerTimeout": "00:03:00",
|
||||
"SkipNegotiation": true,
|
||||
"Transports": "WebSockets",
|
||||
"UseAutomaticReconnect": true,
|
||||
"UseStatefulReconnect": true
|
||||
},
|
||||
"AcBinaryHubProtocol": {
|
||||
"ProtocolMode": "AsyncSegment",
|
||||
"BufferSize": 4096,
|
||||
"WaitForFlush": false,
|
||||
"FlushTimeout": "00:00:10"
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,27 @@
|
|||
<ProjectReference Include="..\FruitBankHybrid.Shared\FruitBankHybrid.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Shared appsettings.json synced into wwwroot at build time.
|
||||
Approach: pre-build Copy Target (not <Content Include Link=...>), because:
|
||||
- <Content Include="..\..." Link="wwwroot\..."/> triggers StaticWebAssets.Normalize() "Illegal characters"
|
||||
- pre-normalizing via [System.IO.Path]::GetFullPath(...) still fails (the absolute path also trips the validator)
|
||||
- a pre-build Copy creates a physical wwwroot/appsettings.json which the StaticWebAssets auto-discovery
|
||||
picks up naturally, same as any other wwwroot file
|
||||
BeforeTargets lists multiple early targets to ensure the Copy runs before static-asset discovery,
|
||||
regardless of which one triggers first in a given SDK version.
|
||||
NOTE: a clean build (delete obj/) is required the first time, because the static-asset manifest
|
||||
is cached in obj/ and stale entries persist across incremental builds.
|
||||
The physical wwwroot/appsettings.json is a build artifact — commit or gitignore per team policy;
|
||||
edits should always be made in FruitBankHybrid.Shared/appsettings.json (the canonical source). -->
|
||||
<Target Name="CopySharedAppSettings"
|
||||
BeforeTargets="CollectPackageReferences;AssignTargetPaths;ResolveStaticWebAssetsInputs;BeforeBuild"
|
||||
Inputs="..\FruitBankHybrid.Shared\appsettings.json"
|
||||
Outputs="wwwroot\appsettings.json">
|
||||
<Copy SourceFiles="..\FruitBankHybrid.Shared\appsettings.json"
|
||||
DestinationFiles="wwwroot\appsettings.json"
|
||||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="AyCode.Core">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
|
||||
|
|
|
|||
|
|
@ -42,22 +42,26 @@ builder.Services.Configure<AcBinaryHubProtocolOptions>(opts =>
|
|||
if (opts.ProtocolMode == BinaryProtocolMode.AsyncSegment) opts.ProtocolMode = BinaryProtocolMode.Segment;
|
||||
});
|
||||
|
||||
// Logger factory — thin Logger, DI-singleton writers. Each call yields a fresh LoggerClient
|
||||
// with the caller's categoryName. Mirrors Microsoft's ILoggerFactory / ILogger<T> pattern.
|
||||
builder.Services.AddSingleton<Func<string, AcLoggerBase>>(sp => categoryName => new LoggerClient(categoryName, sp.GetServices<IAcLogWriterClientBase>().ToArray()));
|
||||
// Logger options + framework factory. LoggerClient instances are created per caller category,
|
||||
// with AppType+LogLevel from appsettings and writers resolved from DI via IAcLogWriterClientBase.
|
||||
builder.Services.Configure<AcLoggerOptions>(builder.Configuration.GetSection("AyCode:Logger"));
|
||||
builder.Services.AddAcLoggerFactory<LoggerClient, IAcLogWriterClientBase>();
|
||||
|
||||
// HubConnectionBuilder — transient so each consumer gets a fresh builder to Build().
|
||||
// All connection and protocol configuration flows from appsettings.json via IOptions<T>;
|
||||
// AddFruitBankDefaults only bridges the provided logger into SignalR's internal pipeline.
|
||||
// AddAcDefaults (framework) applies AcHubConnectionOptions and bridges the provided logger into SignalR's internal pipeline.
|
||||
// NOTE: AcBinaryHubProtocolOptions is resolved from the OUTER service provider and passed
|
||||
// explicitly — HubConnectionBuilder's inner DI cannot see outer services.Configure<T>() registrations.
|
||||
builder.Services.AddTransient<IHubConnectionBuilder>(sp =>
|
||||
{
|
||||
var loggerFactory = sp.GetRequiredService<Func<string, AcLoggerBase>>();
|
||||
var loggerFactory = sp.GetRequiredService<Func<string, LoggerClient>>();
|
||||
var connectionOpts = sp.GetRequiredService<IOptions<AcHubConnectionOptions>>().Value;
|
||||
var protocolOpts = sp.GetRequiredService<IOptions<AcBinaryHubProtocolOptions>>().Value;
|
||||
|
||||
var logger = loggerFactory(nameof(FruitBankSignalRClient));
|
||||
var hubBuilder = new HubConnectionBuilder().AddFruitBankDefaults(logger, connectionOpts);
|
||||
var hubBuilder = new HubConnectionBuilder().AddAcDefaults(logger, connectionOpts);
|
||||
|
||||
hubBuilder.AddAcBinaryProtocol(); // IOptions<AcBinaryHubProtocolOptions> from DI
|
||||
hubBuilder.AddAcBinaryProtocol(protocolOpts);
|
||||
return hubBuilder;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,44 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AyCode": {
|
||||
"ProjectId": "aad53443-2ee2-4650-8a99-97e907265e4e",
|
||||
"Urls": {
|
||||
"BaseUrl": "https://localhost:7144",
|
||||
"ApiBaseUrl": "https://localhost:7144"
|
||||
},
|
||||
"Logger": {
|
||||
"AppType": "Server",
|
||||
"LogLevel": "Detail",
|
||||
"LogWriters": [
|
||||
{
|
||||
"LogLevel": "Detail",
|
||||
"LogWriterType": "FruitBank.Common.Server.Services.Loggers.ConsoleLogWriter, FruitBank.Common.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
"AcHubConnection": {
|
||||
"Url": "https://localhost:59579/fbHub",
|
||||
"TransportMaxBufferSize": 30000000,
|
||||
"ApplicationMaxBufferSize": 30000000,
|
||||
"CloseTimeout": "00:00:10",
|
||||
"KeepAliveInterval": "00:01:00",
|
||||
"ServerTimeout": "00:03:00",
|
||||
"SkipNegotiation": true,
|
||||
"Transports": "WebSockets",
|
||||
"UseAutomaticReconnect": true,
|
||||
"UseStatefulReconnect": true
|
||||
},
|
||||
"AcBinaryHubProtocol": {
|
||||
"ProtocolMode": "AsyncSegment",
|
||||
"BufferSize": 4096,
|
||||
"WaitForFlush": false,
|
||||
"FlushTimeout": "00:00:10"
|
||||
}
|
||||
},
|
||||
|
||||
"AcHubConnection": {
|
||||
"Url": "https://localhost:59579/fbHub",
|
||||
"TransportMaxBufferSize": 30000000,
|
||||
"ApplicationMaxBufferSize": 30000000,
|
||||
"CloseTimeout": "00:00:10",
|
||||
"KeepAliveInterval": "00:01:00",
|
||||
"ServerTimeout": "00:03:00",
|
||||
"SkipNegotiation": true,
|
||||
"Transports": "WebSockets",
|
||||
"UseAutomaticReconnect": true,
|
||||
"UseStatefulReconnect": true
|
||||
},
|
||||
|
||||
"AcBinaryHubProtocol": {
|
||||
"ProtocolMode": "Segment",
|
||||
"BufferSize": 4096,
|
||||
"WaitForFlush": true,
|
||||
"FlushTimeout": "00:00:10"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,19 @@
|
|||
<Folder Include="Services\SignalRs\" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Shared appsettings.json copied into project root at build time.
|
||||
Why a Copy target instead of <Content Include="..\..." Link="appsettings.json" CopyToOutputDirectory="PreserveNewest"/>:
|
||||
the Link approach only copies the file to bin/Debug output, but ASP.NET Core's WebApplicationBuilder
|
||||
in development reads appsettings.json from the ContentRoot (= project directory), not from the output folder.
|
||||
Materializing the file physically in the project root makes it discoverable by the default configuration
|
||||
loader in every run mode (F5 / dotnet run / published), and the SDK auto-include for appsettings*.json
|
||||
takes care of copy-to-output from there. -->
|
||||
<Target Name="CopySharedAppSettings" BeforeTargets="BeforeBuild" Inputs="..\FruitBankHybrid.Shared\appsettings.json" Outputs="appsettings.json">
|
||||
<Copy SourceFiles="..\FruitBankHybrid.Shared\appsettings.json"
|
||||
DestinationFiles="appsettings.json"
|
||||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
@ -5,7 +5,6 @@ using FruitBank.Common;
|
|||
using FruitBank.Common.Models;
|
||||
using FruitBank.Common.Services;
|
||||
using FruitBank.Common.Server.Services.Loggers;
|
||||
using FruitBank.Common.Server.Services.SignalRs;
|
||||
using FruitBankHybrid.Shared.Databases;
|
||||
using FruitBankHybrid.Shared.Services;
|
||||
using FruitBankHybrid.Shared.Services.Loggers;
|
||||
|
|
@ -21,14 +20,15 @@ builder.Services.AddRazorComponents().AddInteractiveWebAssemblyComponents();
|
|||
builder.Services.AddDevExpressBlazor(configure => configure.SizeMode = DevExpress.Blazor.SizeMode.Medium);
|
||||
builder.Services.AddMvc();
|
||||
|
||||
builder.Services.AddSignalR(options => options.MaximumReceiveMessageSize = 256 * 1024);
|
||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||
builder.Services.AddSingleton<ISecureCredentialService, ServerSecureCredentialService>();
|
||||
|
||||
builder.Services.AddSingleton<IAcLogWriterBase, ConsoleLogWriter>();
|
||||
// Logger factory — thin Logger, DI-singleton writers. Each call yields a fresh LoggerClient
|
||||
// with the caller's categoryName. Mirrors Microsoft's ILoggerFactory / ILogger<T> pattern.
|
||||
builder.Services.AddSingleton<Func<string, AcLoggerBase>>(sp => categoryName => new LoggerClient(categoryName, sp.GetServices<IAcLogWriterClientBase>().ToArray()));
|
||||
|
||||
// Logger options + framework factory. LoggerClient instances are created per caller category,
|
||||
// with AppType+LogLevel from appsettings and writers resolved from DI via IAcLogWriterBase.
|
||||
builder.Services.Configure<AcLoggerOptions>(builder.Configuration.GetSection("AyCode:Logger"));
|
||||
builder.Services.AddAcLoggerFactory<LoggerClient>();
|
||||
|
||||
builder.Services.AddSingleton<LoggedInModel>(sp => new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||
|
||||
|
|
@ -48,16 +48,19 @@ builder.Services.Configure<AcBinaryHubProtocolOptions>(opts =>
|
|||
|
||||
// HubConnectionBuilder — transient so each consumer gets a fresh builder to Build().
|
||||
// All connection and protocol configuration flows from appsettings.json via IOptions<T>;
|
||||
// AddFruitBankDefaults only bridges the provided logger into SignalR's internal pipeline.
|
||||
// AddAcDefaults (framework) applies AcHubConnectionOptions and bridges the provided logger into SignalR's internal pipeline.
|
||||
// NOTE: AcBinaryHubProtocolOptions is resolved from the OUTER service provider and passed
|
||||
// explicitly — HubConnectionBuilder's inner DI cannot see outer services.Configure<T>() registrations.
|
||||
builder.Services.AddTransient<IHubConnectionBuilder>(sp =>
|
||||
{
|
||||
var loggerFactory = sp.GetRequiredService<Func<string, AcLoggerBase>>();
|
||||
var loggerFactory = sp.GetRequiredService<Func<string, LoggerClient>>();
|
||||
var connectionOpts = sp.GetRequiredService<IOptions<AcHubConnectionOptions>>().Value;
|
||||
var protocolOpts = sp.GetRequiredService<IOptions<AcBinaryHubProtocolOptions>>().Value;
|
||||
|
||||
var logger = loggerFactory(nameof(FruitBankSignalRClient));
|
||||
var hubBuilder = new HubConnectionBuilder().AddFruitBankDefaults(logger, connectionOpts);
|
||||
var hubBuilder = new HubConnectionBuilder().AddAcDefaults(logger, connectionOpts);
|
||||
|
||||
hubBuilder.AddAcBinaryProtocol(); // IOptions<AcBinaryHubProtocolOptions> from DI
|
||||
hubBuilder.AddAcBinaryProtocol(protocolOpts);
|
||||
return hubBuilder;
|
||||
});
|
||||
|
||||
|
|
@ -82,9 +85,6 @@ else
|
|||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.MapHub<LoggerSignalRHub>($"/{FruitBankConstClient.LoggerHubName}");
|
||||
app.MapHub<DevAdminSignalRHub>($"/{FruitBankConstClient.DefaultHubName}");
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +1,44 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AyCode": {
|
||||
"ProjectId": "aad53443-2ee2-4650-8a99-97e907265e4e",
|
||||
"Urls": {
|
||||
"BaseUrl": "https://localhost:7144",
|
||||
"ApiBaseUrl": "https://localhost:7144"
|
||||
},
|
||||
"Logger": {
|
||||
"AppType": "Server",
|
||||
"LogLevel": "Detail",
|
||||
"LogWriters": [
|
||||
{
|
||||
"LogLevel": "Detail",
|
||||
"LogWriterType": "FruitBank.Common.Server.Services.Loggers.ConsoleLogWriter, FruitBank.Common.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
"AcHubConnection": {
|
||||
"Url": "https://localhost:59579/fbHub",
|
||||
"TransportMaxBufferSize": 30000000,
|
||||
"ApplicationMaxBufferSize": 30000000,
|
||||
"CloseTimeout": "00:00:10",
|
||||
"KeepAliveInterval": "00:01:00",
|
||||
"ServerTimeout": "00:03:00",
|
||||
"SkipNegotiation": true,
|
||||
"Transports": "WebSockets",
|
||||
"UseAutomaticReconnect": true,
|
||||
"UseStatefulReconnect": true
|
||||
},
|
||||
|
||||
"AcBinaryHubProtocol": {
|
||||
"ProtocolMode": "AsyncSegment",
|
||||
"BufferSize": 4096,
|
||||
"WaitForFlush": true,
|
||||
"FlushTimeout": "00:00:10"
|
||||
},
|
||||
|
||||
"AyCode": {
|
||||
"ProjectId": "aad53443-2ee2-4650-8a99-97e907265e4e",
|
||||
"Urls": {
|
||||
"BaseUrl": "https://localhost:7144",
|
||||
"ApiBaseUrl": "https://localhost:7144"
|
||||
},
|
||||
"Logger": {
|
||||
"AppType": "Server",
|
||||
"LogLevel": "Detail",
|
||||
"LogWriters": [
|
||||
{
|
||||
"LogLevel": "Detail",
|
||||
"LogWriterType": "FruitBank.Common.Server.Services.Loggers.ConsoleLogWriter, FruitBank.Common.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"AcHubConnection": {
|
||||
"Url": "https://localhost:59579/fbHub",
|
||||
"TransportMaxBufferSize": 30000000,
|
||||
"ApplicationMaxBufferSize": 30000000,
|
||||
"CloseTimeout": "00:00:10",
|
||||
"KeepAliveInterval": "00:01:00",
|
||||
"ServerTimeout": "00:03:00",
|
||||
"SkipNegotiation": true,
|
||||
"Transports": "WebSockets",
|
||||
"UseAutomaticReconnect": true,
|
||||
"UseStatefulReconnect": true
|
||||
},
|
||||
"AcBinaryHubProtocol": {
|
||||
"ProtocolMode": "AsyncSegment",
|
||||
"BufferSize": 4096,
|
||||
"WaitForFlush": false,
|
||||
"FlushTimeout": "00:00:10"
|
||||
}
|
||||
}
|
||||
|
|
@ -79,8 +79,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- appsettings.json — embedded so MauiAppBuilder can load via GetManifestResourceStream -->
|
||||
<EmbeddedResource Include="appsettings.json" />
|
||||
<!-- Shared appsettings.json linked from FruitBankHybrid.Shared, embedded so MauiAppBuilder can load via GetManifestResourceStream.
|
||||
LogicalName preserves the original manifest resource name so the loader in MauiProgram.cs doesn't need changes. -->
|
||||
<EmbeddedResource Include="..\FruitBankHybrid.Shared\appsettings.json" Link="appsettings.json" LogicalName="FruitBankHybrid.appsettings.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@ namespace FruitBankHybrid
|
|||
#endif
|
||||
builder.Services.AddSingleton<IAcLogWriterClientBase, SignaRClientLogItemWriter>();
|
||||
|
||||
// Logger factory — the Logger itself is a thin wrapper; writers (the real sinks)
|
||||
// are DI singletons. Each call produces a fresh LoggerClient with the caller's categoryName.
|
||||
// Mirrors the Microsoft ILoggerFactory / ILogger<T> pattern.
|
||||
builder.Services.AddSingleton<Func<string, AcLoggerBase>>(sp => categoryName => new LoggerClient(categoryName, sp.GetServices<IAcLogWriterClientBase>().ToArray()));
|
||||
// Logger options + framework factory. LoggerClient instances are created per caller category,
|
||||
// with AppType+LogLevel from appsettings and writers resolved from DI via IAcLogWriterClientBase.
|
||||
builder.Services.Configure<AcLoggerOptions>(builder.Configuration.GetSection("AyCode:Logger"));
|
||||
builder.Services.AddAcLoggerFactory<LoggerClient, IAcLogWriterClientBase>();
|
||||
|
||||
// Bind SignalR options from configuration.
|
||||
// Precedence: code default → appsettings.json (this line) → any later Configure<T> action.
|
||||
|
|
@ -68,16 +68,19 @@ namespace FruitBankHybrid
|
|||
|
||||
// SignalR HubConnectionBuilder — transient so each consumer gets a fresh builder to Build().
|
||||
// All connection and protocol configuration flows from appsettings.json via IOptions<T>;
|
||||
// AddFruitBankDefaults only bridges the provided logger into SignalR's internal pipeline.
|
||||
// AddAcDefaults (framework) applies AcHubConnectionOptions and bridges the provided logger into SignalR's internal pipeline.
|
||||
// NOTE: AcBinaryHubProtocolOptions is resolved from the OUTER service provider and passed
|
||||
// explicitly — HubConnectionBuilder's inner DI cannot see outer services.Configure<T>() registrations.
|
||||
builder.Services.AddTransient<IHubConnectionBuilder>(sp =>
|
||||
{
|
||||
var loggerFactory = sp.GetRequiredService<Func<string, AcLoggerBase>>();
|
||||
var loggerFactory = sp.GetRequiredService<Func<string, LoggerClient>>();
|
||||
var connectionOpts = sp.GetRequiredService<IOptions<AcHubConnectionOptions>>().Value;
|
||||
var protocolOpts = sp.GetRequiredService<IOptions<AcBinaryHubProtocolOptions>>().Value;
|
||||
|
||||
var logger = loggerFactory(nameof(FruitBankSignalRClient));
|
||||
var hubBuilder = new HubConnectionBuilder().AddFruitBankDefaults(logger, connectionOpts);
|
||||
var hubBuilder = new HubConnectionBuilder().AddAcDefaults(logger, connectionOpts);
|
||||
|
||||
hubBuilder.AddAcBinaryProtocol(); // IOptions<AcBinaryHubProtocolOptions> from DI
|
||||
hubBuilder.AddAcBinaryProtocol(protocolOpts);
|
||||
return hubBuilder;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"AcHubConnection": {
|
||||
"Url": "https://localhost:59579/fbHub",
|
||||
"TransportMaxBufferSize": 30000000,
|
||||
"ApplicationMaxBufferSize": 30000000,
|
||||
"CloseTimeout": "00:00:10",
|
||||
"KeepAliveInterval": "00:01:00",
|
||||
"ServerTimeout": "00:03:00",
|
||||
"SkipNegotiation": true,
|
||||
"Transports": "WebSockets",
|
||||
"UseAutomaticReconnect": true,
|
||||
"UseStatefulReconnect": true
|
||||
},
|
||||
"AcBinaryHubProtocol": {
|
||||
"ProtocolMode": "AsyncSegment",
|
||||
"BufferSize": 4096,
|
||||
"WaitForFlush": true,
|
||||
"FlushTimeout": "00:00:10"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue