BRAKING refactor project structure.
Refactor: the project is now divided into a more clear structure, with **Infrastructure** and **Application** layers added. Refactor: configurations are split into sections for different layers. Fix: now EF Core related operations, such as migration, should be invoked in `OptixServe.Infrastructure`, with config file and data dir passed into `dotnet ef` command. See `OptixServe.Infrastructure/Utilites/DesignTimeDbContextFactory.cs` for details. Fix: EF migrations are ignored in gitignore on purpose in early development.
This commit is contained in:
@ -0,0 +1,32 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace OptixServe.Infrastructure.Configuration;
|
||||
|
||||
public static class ConfigurationHelper
|
||||
{
|
||||
public static IConfigurationBuilder CreateDefaultBuilder(string basePath)
|
||||
{
|
||||
var aspEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
|
||||
var netEnv = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
|
||||
var env = aspEnv ?? netEnv ?? null;
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
// .SetBasePath(Directory.GetCurrentDirectory())
|
||||
.SetBasePath(basePath)
|
||||
.AddJsonFile("appsettings.json", optional: true)
|
||||
.AddJsonFile("config.json", optional: true);
|
||||
|
||||
if (env != null)
|
||||
{
|
||||
builder.AddJsonFile($"appsettings.{env}.json", optional: true)
|
||||
.AddJsonFile($"config.{env}.json", optional: true);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder CreateDefaultBuilder()
|
||||
{
|
||||
return CreateDefaultBuilder(Directory.GetCurrentDirectory());
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
namespace OptixServe.Infrastructure.Configuration;
|
||||
|
||||
public record InfrastructureConfiguration
|
||||
{
|
||||
public DatabaseSettings? Database { get; set; } = new();
|
||||
}
|
||||
|
||||
public enum DatabaseType
|
||||
{
|
||||
Sqlite,
|
||||
MySQL
|
||||
}
|
||||
|
||||
public record DatabaseSettings
|
||||
{
|
||||
public DatabaseType Type { get; set; } = DatabaseType.Sqlite;
|
||||
public string? Host { get; set; }
|
||||
}
|
21
OptixServe.Infrastructure/Data/AppDbContext.cs
Normal file
21
OptixServe.Infrastructure/Data/AppDbContext.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OptixServe.Core.Models;
|
||||
|
||||
namespace OptixServe.Infrastructure.Data;
|
||||
|
||||
public class AppDbContext(DbContextOptions options) : DbContext(options)
|
||||
{
|
||||
public DbSet<User> Users { get; set; } = null!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<User>(user =>
|
||||
{
|
||||
user.HasKey(u => u.Id);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<User>().HasData([
|
||||
new() {Id = "1", UserName = "admin", Password = "admin12345"}
|
||||
]);
|
||||
}
|
||||
}
|
11
OptixServe.Infrastructure/Data/DbInitializer.cs
Normal file
11
OptixServe.Infrastructure/Data/DbInitializer.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace OptixServe.Infrastructure.Data;
|
||||
|
||||
public class DbInitializer(AppDbContext dbContext)
|
||||
{
|
||||
private readonly AppDbContext _context = dbContext;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_context.Database.EnsureCreated();
|
||||
}
|
||||
}
|
2
OptixServe.Infrastructure/Migrations/.gitignore
vendored
Normal file
2
OptixServe.Infrastructure/Migrations/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Migrations are ignored in development
|
||||
*.cs
|
25
OptixServe.Infrastructure/OptixServe.Infrastructure.csproj
Normal file
25
OptixServe.Infrastructure/OptixServe.Infrastructure.csproj
Normal file
@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OptixServe.Core\OptixServe.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
33
OptixServe.Infrastructure/Utilites/DatabaseHelper.cs
Normal file
33
OptixServe.Infrastructure/Utilites/DatabaseHelper.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OptixServe.Infrastructure.Configuration;
|
||||
|
||||
namespace OptixServe.Infrastructure.Utilites;
|
||||
|
||||
public static class DatabaseHelper
|
||||
{
|
||||
public static string BuildConnectionString(DatabaseSettings dbSettings)
|
||||
{
|
||||
return dbSettings.Type switch
|
||||
{
|
||||
DatabaseType.Sqlite => $"Data Source={dbSettings.Host ?? "optixserve.db"}",
|
||||
DatabaseType.MySQL => throw new NotSupportedException("MySQL connection is not yet implemented"),
|
||||
_ => throw new NotSupportedException($"Database type {dbSettings.Type} is not supported")
|
||||
};
|
||||
}
|
||||
|
||||
public static void ConfigureDbContext(DbContextOptionsBuilder options, DatabaseSettings dbSettings)
|
||||
{
|
||||
if (dbSettings?.Type == DatabaseType.Sqlite)
|
||||
{
|
||||
var dbPath = dbSettings.Host ?? "optixserve.db";
|
||||
var connectionString = $"Data Source={dbPath}";
|
||||
|
||||
options.UseSqlite(connectionString, b => b.MigrationsAssembly("OptixServe.Infrastructure"));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Only SQLite database is currently supported");
|
||||
}
|
||||
}
|
||||
}
|
129
OptixServe.Infrastructure/Utilites/DesignTimeDbContextFactory.cs
Normal file
129
OptixServe.Infrastructure/Utilites/DesignTimeDbContextFactory.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using OptixServe.Infrastructure.Configuration;
|
||||
using OptixServe.Infrastructure.Data;
|
||||
|
||||
namespace OptixServe.Infrastructure.Utilites;
|
||||
|
||||
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
|
||||
{
|
||||
private record CommandParseResult
|
||||
{
|
||||
public string? ConfigPath { get; set; } = null;
|
||||
public string? DataDir { get; set; } = null;
|
||||
}
|
||||
|
||||
private static CommandParseResult ParseArgs(string[] args)
|
||||
{
|
||||
var result = new CommandParseResult();
|
||||
bool configSet = false;
|
||||
bool dataDirSet = false;
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
string arg = args[i];
|
||||
|
||||
if (arg == "--config" || arg == "-c")
|
||||
{
|
||||
if (configSet)
|
||||
{
|
||||
throw new ArgumentException("The --config/-c option can only be specified once.");
|
||||
}
|
||||
if (i + 1 >= args.Length)
|
||||
{
|
||||
throw new ArgumentException("Missing value for --config/-c option.");
|
||||
}
|
||||
|
||||
result.ConfigPath = args[i + 1];
|
||||
configSet = true;
|
||||
i++;
|
||||
}
|
||||
else if (arg == "--data-dir" || arg == "-d")
|
||||
{
|
||||
if (dataDirSet)
|
||||
{
|
||||
throw new ArgumentException("The --data-dir/-d option can only be specified once.");
|
||||
}
|
||||
if (i + 1 >= args.Length)
|
||||
{
|
||||
throw new ArgumentException("Missing value for --data-dir/-d option.");
|
||||
}
|
||||
|
||||
result.DataDir = args[i + 1];
|
||||
dataDirSet = true;
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Unknown argument: {arg}");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates DbContext instance in Design-Time
|
||||
///
|
||||
/// <para>Custom arguments should be passed in <i>dotnet</i> command </para>
|
||||
///
|
||||
/// <b>Example:</b><br/>
|
||||
/// <example><c>dotnet ef database update -- -c ../data/appsettings.Development.json -d ../data/`</c></example>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="args">
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// --config/-c: App configuration file to load with database connection settings.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// --data-dir/-d: App data dir to work with.
|
||||
/// Currently this only affects finding SQLite database when relative path
|
||||
/// is specified in database settings.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public AppDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
var parsedArgs = ParseArgs(args);
|
||||
|
||||
IConfigurationRoot configuration;
|
||||
|
||||
if (!string.IsNullOrEmpty(parsedArgs.ConfigPath))
|
||||
{
|
||||
var absConfigPath = Path.GetFullPath(parsedArgs.ConfigPath);
|
||||
var configDirectory = Path.GetDirectoryName(absConfigPath);
|
||||
var configFileName = Path.GetFileName(absConfigPath);
|
||||
// Use the provided config file path
|
||||
configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(configDirectory ?? "")
|
||||
.AddJsonFile(configFileName, optional: false)
|
||||
.Build();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default search configuration path
|
||||
configuration = ConfigurationHelper.CreateDefaultBuilder().Build();
|
||||
}
|
||||
|
||||
var dbSettings = configuration.GetSection("OptixServe:Infrastructure:Database").Get<DatabaseSettings>()!;
|
||||
|
||||
// Resolve SQLite database if `--data-dir` is specified and `dbSettings.Host` is relative
|
||||
if (parsedArgs.DataDir != null)
|
||||
{
|
||||
if (dbSettings.Type == DatabaseType.Sqlite && !Path.IsPathFullyQualified(dbSettings.Host!))
|
||||
{
|
||||
var dbRelPath = Path.Combine(parsedArgs.DataDir, dbSettings.Host!);
|
||||
var dbAbsPath = Path.GetFullPath(dbRelPath);
|
||||
dbSettings.Host = dbAbsPath;
|
||||
}
|
||||
}
|
||||
|
||||
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
|
||||
DatabaseHelper.ConfigureDbContext(optionsBuilder, dbSettings);
|
||||
|
||||
return new AppDbContext(optionsBuilder.Options);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user