26ff8d9b4f
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
295 lines
8.5 KiB
Markdown
295 lines
8.5 KiB
Markdown
# 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`:
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```csharp
|
|
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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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: `SectionName` for configuration binding
|
|
|
|
### DataAccessOptions
|
|
|
|
```csharp
|
|
public class DataAccessOptions
|
|
{
|
|
public const string SectionName = "DataAccess";
|
|
|
|
public int CommandTimeoutSeconds { get; set; } = 120;
|
|
public bool EnableDetailedLogging { get; set; } = false;
|
|
}
|
|
```
|
|
|
|
### DataSyncOptions
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```xml
|
|
<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
|
|
|
|
```xml
|
|
<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
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```csharp
|
|
// 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:
|
|
|
|
```csharp
|
|
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;
|
|
}
|
|
}
|
|
```
|