26ff8d9b4f
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
8.5 KiB
8.5 KiB
Solution Foundation Design
Overview
This document describes the infrastructure architecture for the .NET 10 solution, including project structure, dependency injection patterns, and configuration management.
Project Structure
NEW/src/
├── JdeScoping.Core/ # Domain models, interfaces, shared logic
│ ├── Models/ # Entity classes (Search, WorkOrder, Lot, etc.)
│ ├── Interfaces/ # Service contracts (ISearchRepository, etc.)
│ ├── Options/ # Configuration binding classes
│ └── Extensions/ # Service registration extension methods
├── JdeScoping.Host/ # ASP.NET Core host (Web API + BackgroundServices)
│ ├── Program.cs # Application entry point, DI configuration
│ ├── appsettings.json # Production configuration
│ ├── appsettings.Development.json # Development overrides
│ └── Controllers/ # API endpoints
├── JdeScoping.Client/ # Blazor WebAssembly UI
│ └── (deferred to UI phase)
└── JdeScoping.Database/ # DbUp migrations (already exists)
├── Scripts/ # Migration SQL files
└── DatabaseMigrator.cs # DbUp configuration
DI Registration Pattern
Extension Method Convention
Each module provides an extension method on IServiceCollection:
public static class DataAccessServiceExtensions
{
public static IServiceCollection AddDataAccess(
this IServiceCollection services,
IConfiguration configuration)
{
// Bind configuration
services.Configure<DataAccessOptions>(
configuration.GetSection(DataAccessOptions.SectionName));
// Register services
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
services.AddScoped<IJdeRepository, JdeRepository>();
services.AddScoped<ICmsRepository, CmsRepository>();
return services;
}
}
Module Registration Order
Extensions are called in dependency order in Program.cs:
builder.Services
.AddDataAccess(builder.Configuration) // 1. Database access
.AddDataSync(builder.Configuration) // 2. Cache synchronization
.AddSearchProcessing(builder.Configuration) // 3. Search execution
.AddExcelExport(builder.Configuration) // 4. Result export
.AddAuth(builder.Configuration); // 5. Authentication
Lifetime Guidelines
| Service Type | Lifetime | Rationale |
|---|---|---|
| Repository | Scoped | Database connection per request |
| DbContext (if used) | Scoped | EF Core default |
| Options classes | Singleton | Cached configuration |
| HttpClient | Singleton | Connection pooling |
| BackgroundService | Singleton | Long-running workers |
| Processors | Transient | Stateless operations |
Configuration Sections
appsettings.json Structure
{
"ConnectionStrings": {
"LotFinder": "Server=...;Database=LotFinder;...",
"JDE": "Data Source=...;User ID=...;Password=...",
"CMS": "Data Source=...;Port=...;Database=..."
},
"DataAccess": {
"CommandTimeoutSeconds": 120,
"EnableDetailedLogging": false
},
"DataSync": {
"MassRefreshCronSchedule": "0 0 6 * * SAT",
"DailyRefreshCronSchedule": "0 0 4 * * *",
"HourlyRefreshCronSchedule": "0 0 * * * *",
"MaxConcurrentUpdates": 4
},
"Auth": {
"LdapUrl": "LDAP://directory.company.com",
"LdapGroup": "CN=LotFinderUsers,OU=Groups,DC=company,DC=com",
"CookieExpirationMinutes": 480
},
"ExcelExport": {
"TempDirectory": "/tmp/lotfinder",
"MaxRowsPerSheet": 1048576,
"DefaultDateFormat": "yyyy-MM-dd HH:mm:ss"
},
"SearchProcessing": {
"PollingIntervalSeconds": 5,
"MaxConcurrentSearches": 2,
"SearchTimeoutMinutes": 30
}
}
appsettings.Development.json Overrides
{
"ConnectionStrings": {
"LotFinder": "Server=localhost,1434;Database=LotFinder;User Id=scopingapp;Password=...;TrustServerCertificate=True"
},
"DataAccess": {
"EnableDetailedLogging": true
},
"DataSync": {
"MassRefreshCronSchedule": "",
"DailyRefreshCronSchedule": "",
"HourlyRefreshCronSchedule": ""
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Warning"
}
}
}
Options Classes
Naming Convention
- Class name:
{Module}Options - Section name: Same as class name without "Options" suffix
- Static constant:
SectionNamefor configuration binding
DataAccessOptions
public class DataAccessOptions
{
public const string SectionName = "DataAccess";
public int CommandTimeoutSeconds { get; set; } = 120;
public bool EnableDetailedLogging { get; set; } = false;
}
DataSyncOptions
public class DataSyncOptions
{
public const string SectionName = "DataSync";
public string MassRefreshCronSchedule { get; set; } = "0 0 6 * * SAT";
public string DailyRefreshCronSchedule { get; set; } = "0 0 4 * * *";
public string HourlyRefreshCronSchedule { get; set; } = "0 0 * * * *";
public int MaxConcurrentUpdates { get; set; } = 4;
}
AuthOptions
public class AuthOptions
{
public const string SectionName = "Auth";
public string LdapUrl { get; set; } = string.Empty;
public string LdapGroup { get; set; } = string.Empty;
public int CookieExpirationMinutes { get; set; } = 480;
}
ExcelExportOptions
public class ExcelExportOptions
{
public const string SectionName = "ExcelExport";
public string TempDirectory { get; set; } = "/tmp/lotfinder";
public int MaxRowsPerSheet { get; set; } = 1048576;
public string DefaultDateFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
}
SearchProcessingOptions
public class SearchProcessingOptions
{
public const string SectionName = "SearchProcessing";
public int PollingIntervalSeconds { get; set; } = 5;
public int MaxConcurrentSearches { get; set; } = 2;
public int SearchTimeoutMinutes { get; set; } = 30;
}
NuGet Package Dependencies
JdeScoping.Core
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
JdeScoping.Host
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="EPPlus" Version="7.0.0" />
<PackageReference Include="DbUp-SqlServer" Version="5.0.37" />
<PackageReference Include="Quartz" Version="3.8.0" />
Program.cs Structure
var builder = WebApplication.CreateBuilder(args);
// Database migrations
DatabaseMigrator.Migrate(builder.Configuration.GetConnectionString("LotFinder")!);
// Module registration
builder.Services
.AddDataAccess(builder.Configuration)
.AddDataSync(builder.Configuration)
.AddSearchProcessing(builder.Configuration)
.AddExcelExport(builder.Configuration)
.AddAuth(builder.Configuration);
// ASP.NET Core services
builder.Services.AddControllers();
builder.Services.AddSignalR();
var app = builder.Build();
// Middleware pipeline
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHub<StatusHub>("/statushub");
app.Run();
Validation Approach
Startup Validation
Validate critical services are registered at startup:
// In Program.cs after building
using var scope = app.Services.CreateScope();
_ = scope.ServiceProvider.GetRequiredService<ILotFinderRepository>();
_ = scope.ServiceProvider.GetRequiredService<IOptions<DataAccessOptions>>();
// ... validate other critical services
Configuration Validation
Use DataAnnotations or IValidateOptions for configuration:
public class DataAccessOptionsValidator : IValidateOptions<DataAccessOptions>
{
public ValidateOptionsResult Validate(string? name, DataAccessOptions options)
{
if (options.CommandTimeoutSeconds <= 0)
return ValidateOptionsResult.Fail("CommandTimeoutSeconds must be positive");
return ValidateOptionsResult.Success;
}
}