diff --git a/OptixServe.Api/Configuration/AppSettings.cs b/OptixServe.Api/Configuration/ApiConfiguration.cs similarity index 53% rename from OptixServe.Api/Configuration/AppSettings.cs rename to OptixServe.Api/Configuration/ApiConfiguration.cs index 7a53c2f..2ebba14 100644 --- a/OptixServe.Api/Configuration/AppSettings.cs +++ b/OptixServe.Api/Configuration/ApiConfiguration.cs @@ -1,16 +1,15 @@ namespace OptixServe.Api.Configuration; -public record OptixServeSettings +public record ApiConfiguration { public ApiSettings? Api { get; set; } = new(); - public DatabaseSettings? Database { get; set; } = new(); public JwtSettings? Jwt { get; set; } = new(); } public record ApiSettings { - public string? Listen { get; set; } = "127.0.0.1"; - public int? Port { get; set; } = 10086; + public string? Listen { get; set; } + public int? Port { get; set; } } public record JwtSettings @@ -20,15 +19,3 @@ public record JwtSettings public string Audience { get; set; } = "OptixServeUsers"; public int TokenExpirationMinutes { get; set; } = 60; } - -public enum DatabaseType -{ - Sqlite, - MySQL -} - -public record DatabaseSettings -{ - public DatabaseType Type { get; set; } = DatabaseType.Sqlite; - public string? Host { get; set; } -} \ No newline at end of file diff --git a/OptixServe.Api/Endpoints/UserEndpoint.cs b/OptixServe.Api/Endpoints/UserEndpoint.cs index 24abe86..0f4a635 100644 --- a/OptixServe.Api/Endpoints/UserEndpoint.cs +++ b/OptixServe.Api/Endpoints/UserEndpoint.cs @@ -1,8 +1,7 @@ using System.Text.Json.Serialization; -using OptixServe.Core.Services; +using OptixServe.Application.Services; using OptixServe.Api.Dtos; using OptixServe.Api.Services; -using Microsoft.AspNetCore.Authorization; namespace OptixServe.Api.Endpoints; @@ -10,7 +9,7 @@ namespace OptixServe.Api.Endpoints; [JsonSerializable(typeof(UserDto))] [JsonSerializable(typeof(IEnumerable))] [JsonSerializable(typeof(LoginRequestDto))] -[JsonSerializable(typeof(LoginResponseDto))] // For returning the token string +[JsonSerializable(typeof(LoginResponseDto))] public partial class UserJsonContext : JsonSerializerContext { } public static class UserEndpoint diff --git a/OptixServe.Api/Endpoints/VersionEndpoint.cs b/OptixServe.Api/Endpoints/VersionEndpoint.cs index 6eb01ec..11f4f2f 100644 --- a/OptixServe.Api/Endpoints/VersionEndpoint.cs +++ b/OptixServe.Api/Endpoints/VersionEndpoint.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; using Microsoft.Extensions.Options; -using OptixServe.Api.Configuration; +using OptixServe.Infrastructure.Configuration; using OptixServe.Api.Dtos; namespace OptixServe.Api.Endpoints; @@ -22,14 +22,14 @@ public static class VersionEndpoint var group = parentGroup.MapGroup("/version"); group.MapGet("/", () => "v1"); - group.MapGet("/test/dbconfig", (IOptions appSettings) => + group.MapGet("/test/dbconfig", (IOptions infrastructureConfig) => { - var dbType = appSettings.Value.Database?.Type; - var dbHost = appSettings.Value.Database?.Host; + var dbType = infrastructureConfig.Value.Database?.Type; + var dbHost = infrastructureConfig.Value.Database?.Host; return Results.Ok(new CommonErrorDto { Message = $"Set up {dbType} database on {dbHost}" }); }); } -} \ No newline at end of file +} diff --git a/OptixServe.Api/OptixServe.Api.csproj b/OptixServe.Api/OptixServe.Api.csproj index c3e18e4..79d3b79 100644 --- a/OptixServe.Api/OptixServe.Api.csproj +++ b/OptixServe.Api/OptixServe.Api.csproj @@ -2,16 +2,13 @@ + + - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - diff --git a/OptixServe.Api/Program.cs b/OptixServe.Api/Program.cs index 6d25783..8be27ba 100644 --- a/OptixServe.Api/Program.cs +++ b/OptixServe.Api/Program.cs @@ -5,9 +5,10 @@ using Microsoft.IdentityModel.Tokens; using OptixServe.Api.Configuration; using OptixServe.Api.Endpoints; using OptixServe.Api.Services; -using OptixServe.Core.Data; -using OptixServe.Core.Services; -using OptixServe.Api.Utilites; +using OptixServe.Application.Services; +using OptixServe.Infrastructure.Configuration; +using OptixServe.Infrastructure.Data; +using OptixServe.Infrastructure.Utilites; class Program { @@ -89,12 +90,7 @@ static class StartupHelper public static void AddConfigurationWithCommand(this WebApplicationBuilder builder, FileInfo? file) { // Configure configuration sources in specified order - var configurationBuilder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: true) - .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true) - .AddJsonFile("config.json", optional: true) - .AddJsonFile($"config.{builder.Environment.EnvironmentName}.json", optional: true); + var configurationBuilder = ConfigurationHelper.CreateDefaultBuilder(); // Add command-line specified config file if provided if (file != null) @@ -123,13 +119,17 @@ static class StartupHelper /// WebApplicationBuilder instance public static void RegisterServices(this WebApplicationBuilder builder) { - // Add configuration class - var optixSettigns = builder.Configuration.GetSection("OptixServe"); - var onConfigSettings = optixSettigns.Get(); - builder.Services.Configure(optixSettigns); + // Add configuration classes + var apiConfigSection = builder.Configuration.GetSection("OptixServe:Api"); + builder.Services.Configure(apiConfigSection); + var apiConfig = apiConfigSection.Get(); + + var infraConfigSection = builder.Configuration.GetSection("OptixServe:Infrastructure"); + builder.Services.Configure(infraConfigSection); + var infraConfig = infraConfigSection.Get(); // Add DBContext class - builder.Services.AddAppDatabase(onConfigSettings?.Database!); + builder.Services.AddAppDatabase(infraConfig?.Database!); builder.Services.AddScoped(); // Application services @@ -140,7 +140,7 @@ static class StartupHelper builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { - var jwtSettings = onConfigSettings?.Jwt ?? throw new ArgumentNullException(nameof(builder), "JWT settings are not configured."); + var jwtSettings = apiConfig?.Jwt ?? throw new ArgumentNullException(nameof(builder), "JWT settings are not configured."); options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, @@ -178,4 +178,4 @@ static class StartupHelper VersionEndpoint.Register(rootGroup); } -} \ No newline at end of file +} diff --git a/OptixServe.Api/Services/TokenService.cs b/OptixServe.Api/Services/TokenService.cs index f124f92..f5d8aa9 100644 --- a/OptixServe.Api/Services/TokenService.cs +++ b/OptixServe.Api/Services/TokenService.cs @@ -13,9 +13,9 @@ public interface ITokenService public string GenerateToken(User user); } -public class TokenService(IOptions optixServeSettings) : ITokenService +public class TokenService(IOptions apiConfiguration) : ITokenService { - private readonly JwtSettings _jwtSettings = optixServeSettings.Value.Jwt ?? throw new ArgumentNullException(nameof(optixServeSettings), "JWT settings are not configured."); + private readonly JwtSettings _jwtSettings = apiConfiguration.Value.Jwt ?? throw new ArgumentNullException(nameof(apiConfiguration), "JWT settings are not configured."); public string GenerateToken(User user) { @@ -41,4 +41,4 @@ public class TokenService(IOptions optixServeSettings) : ITo var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } -} \ No newline at end of file +} diff --git a/OptixServe.Api/Utilites/DesignTimeDbContextFactory.cs b/OptixServe.Api/Utilites/DesignTimeDbContextFactory.cs deleted file mode 100644 index f9d01d8..0000000 --- a/OptixServe.Api/Utilites/DesignTimeDbContextFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; -using OptixServe.Api.Configuration; -using OptixServe.Core.Data; - -namespace OptixServe.Api.Utilites; - -public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory -{ - public AppDbContext CreateDbContext(string[] args) - { - var configuration = ConfigurationHelper.CreateDefaultBuilder().Build(); - - var dbSettings = configuration.GetSection("OptixServe:Database").Get()!; - var optionsBuilder = new DbContextOptionsBuilder(); - DatabaseHelper.ConfigureDbContext(optionsBuilder, dbSettings); - - return new AppDbContext(optionsBuilder.Options); - } -} \ No newline at end of file diff --git a/OptixServe.Api/appsettings.json b/OptixServe.Api/appsettings.json index e4a86f3..4ccf954 100644 --- a/OptixServe.Api/appsettings.json +++ b/OptixServe.Api/appsettings.json @@ -9,17 +9,19 @@ "OptixServe": { "Api": { "Listen": "0.0.0.0", - "Port": "54321" + "Port": "54321", + "Jwt": { + "Secret": "YOUR_SECRET_KEY_HERE_DO_NOT_SHARE_THIS_AND_MAKE_IT_LONG_ENOUGH", + "Issuer": "OptixServe", + "Audience": "OptixServeUsers", + "TokenExpirationMinutes": 60 + } }, - "Database": { - "Type": "Sqlite", - "Host": "optixserve.db" - }, - "Jwt": { - "Secret": "YOUR_SECRET_KEY_HERE_DO_NOT_SHARE_THIS_AND_MAKE_IT_LONG_ENOUGH", - "Issuer": "OptixServe", - "Audience": "OptixServeUsers", - "TokenExpirationMinutes": 60 + "Infrastructure": { + "Database": { + "Type": "Sqlite", + "Host": "optixserve.db" + } } } -} \ No newline at end of file +} diff --git a/OptixServe.Application/OptixServe.Application.csproj b/OptixServe.Application/OptixServe.Application.csproj new file mode 100644 index 0000000..05b72f3 --- /dev/null +++ b/OptixServe.Application/OptixServe.Application.csproj @@ -0,0 +1,18 @@ + + + + + + + + + + + + + net9.0 + enable + enable + + + diff --git a/OptixServe.Core/Services/UserService.cs b/OptixServe.Application/Services/UserService.cs similarity index 88% rename from OptixServe.Core/Services/UserService.cs rename to OptixServe.Application/Services/UserService.cs index 394f05c..0a8de66 100644 --- a/OptixServe.Core/Services/UserService.cs +++ b/OptixServe.Application/Services/UserService.cs @@ -1,7 +1,7 @@ -using OptixServe.Core.Data; using OptixServe.Core.Models; +using OptixServe.Infrastructure.Data; -namespace OptixServe.Core.Services; +namespace OptixServe.Application.Services; public interface IUserService { @@ -28,4 +28,4 @@ public class UserService(AppDbContext dbContext) : IUserService { return _dbContext.Users.AsEnumerable(); } -} \ No newline at end of file +} diff --git a/OptixServe.Core/OptixServe.Core.csproj b/OptixServe.Core/OptixServe.Core.csproj index 354caa7..069991c 100644 --- a/OptixServe.Core/OptixServe.Core.csproj +++ b/OptixServe.Core/OptixServe.Core.csproj @@ -6,8 +6,5 @@ enable - - - diff --git a/OptixServe.Api/Configuration/ConfigurationHelper.cs b/OptixServe.Infrastructure/Configuration/ConfigurationHelper.cs similarity index 67% rename from OptixServe.Api/Configuration/ConfigurationHelper.cs rename to OptixServe.Infrastructure/Configuration/ConfigurationHelper.cs index 4c222e3..216a8ca 100644 --- a/OptixServe.Api/Configuration/ConfigurationHelper.cs +++ b/OptixServe.Infrastructure/Configuration/ConfigurationHelper.cs @@ -1,18 +1,18 @@ -using System; +using Microsoft.Extensions.Configuration; -namespace OptixServe.Api.Configuration; +namespace OptixServe.Infrastructure.Configuration; public static class ConfigurationHelper { - public static IConfigurationBuilder CreateDefaultBuilder() + public static IConfigurationBuilder CreateDefaultBuilder(string basePath) { var aspEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); var netEnv = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); - // Console.WriteLine($"ASPNETCORE_ENVIRONMENT: {aspEnv}, DOTNET_ENVIRONMENT: {netEnv}"); var env = aspEnv ?? netEnv ?? null; var builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) + // .SetBasePath(Directory.GetCurrentDirectory()) + .SetBasePath(basePath) .AddJsonFile("appsettings.json", optional: true) .AddJsonFile("config.json", optional: true); @@ -24,4 +24,9 @@ public static class ConfigurationHelper return builder; } -} \ No newline at end of file + + public static IConfigurationBuilder CreateDefaultBuilder() + { + return CreateDefaultBuilder(Directory.GetCurrentDirectory()); + } +} diff --git a/OptixServe.Infrastructure/Configuration/InfrastructureConfiguration.cs b/OptixServe.Infrastructure/Configuration/InfrastructureConfiguration.cs new file mode 100644 index 0000000..4c6f591 --- /dev/null +++ b/OptixServe.Infrastructure/Configuration/InfrastructureConfiguration.cs @@ -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; } +} diff --git a/OptixServe.Core/Data/AppDbContext.cs b/OptixServe.Infrastructure/Data/AppDbContext.cs similarity index 92% rename from OptixServe.Core/Data/AppDbContext.cs rename to OptixServe.Infrastructure/Data/AppDbContext.cs index 2116fc3..4c20e7a 100644 --- a/OptixServe.Core/Data/AppDbContext.cs +++ b/OptixServe.Infrastructure/Data/AppDbContext.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using OptixServe.Core.Models; -namespace OptixServe.Core.Data; +namespace OptixServe.Infrastructure.Data; public class AppDbContext(DbContextOptions options) : DbContext(options) { @@ -18,4 +18,4 @@ public class AppDbContext(DbContextOptions options) : DbContext(options) new() {Id = "1", UserName = "admin", Password = "admin12345"} ]); } -} \ No newline at end of file +} diff --git a/OptixServe.Core/Data/DbInitializer.cs b/OptixServe.Infrastructure/Data/DbInitializer.cs similarity index 81% rename from OptixServe.Core/Data/DbInitializer.cs rename to OptixServe.Infrastructure/Data/DbInitializer.cs index bec11cc..f899469 100644 --- a/OptixServe.Core/Data/DbInitializer.cs +++ b/OptixServe.Infrastructure/Data/DbInitializer.cs @@ -1,4 +1,4 @@ -namespace OptixServe.Core.Data; +namespace OptixServe.Infrastructure.Data; public class DbInitializer(AppDbContext dbContext) { @@ -8,4 +8,4 @@ public class DbInitializer(AppDbContext dbContext) { _context.Database.EnsureCreated(); } -} \ No newline at end of file +} diff --git a/OptixServe.Infrastructure/Migrations/.gitignore b/OptixServe.Infrastructure/Migrations/.gitignore new file mode 100644 index 0000000..29aaaa2 --- /dev/null +++ b/OptixServe.Infrastructure/Migrations/.gitignore @@ -0,0 +1,2 @@ +# Migrations are ignored in development +*.cs \ No newline at end of file diff --git a/OptixServe.Infrastructure/OptixServe.Infrastructure.csproj b/OptixServe.Infrastructure/OptixServe.Infrastructure.csproj new file mode 100644 index 0000000..4f4b623 --- /dev/null +++ b/OptixServe.Infrastructure/OptixServe.Infrastructure.csproj @@ -0,0 +1,25 @@ + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + net9.0 + enable + enable + + + diff --git a/OptixServe.Api/Utilites/DatabaseHelper.cs b/OptixServe.Infrastructure/Utilites/DatabaseHelper.cs similarity index 88% rename from OptixServe.Api/Utilites/DatabaseHelper.cs rename to OptixServe.Infrastructure/Utilites/DatabaseHelper.cs index 3107d66..580e5fc 100644 --- a/OptixServe.Api/Utilites/DatabaseHelper.cs +++ b/OptixServe.Infrastructure/Utilites/DatabaseHelper.cs @@ -1,8 +1,7 @@ using Microsoft.EntityFrameworkCore; -using OptixServe.Api.Configuration; -using OptixServe.Core.Data; +using OptixServe.Infrastructure.Configuration; -namespace OptixServe.Api.Utilites; +namespace OptixServe.Infrastructure.Utilites; public static class DatabaseHelper { @@ -23,7 +22,7 @@ public static class DatabaseHelper var dbPath = dbSettings.Host ?? "optixserve.db"; var connectionString = $"Data Source={dbPath}"; - options.UseSqlite(connectionString, b => b.MigrationsAssembly("OptixServe.Api")); + options.UseSqlite(connectionString, b => b.MigrationsAssembly("OptixServe.Infrastructure")); } else @@ -31,4 +30,4 @@ public static class DatabaseHelper throw new NotImplementedException("Only SQLite database is currently supported"); } } -} \ No newline at end of file +} diff --git a/OptixServe.Infrastructure/Utilites/DesignTimeDbContextFactory.cs b/OptixServe.Infrastructure/Utilites/DesignTimeDbContextFactory.cs new file mode 100644 index 0000000..cbdc2cb --- /dev/null +++ b/OptixServe.Infrastructure/Utilites/DesignTimeDbContextFactory.cs @@ -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 +{ + 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; + } + + /// + /// Creates DbContext instance in Design-Time + /// + /// Custom arguments should be passed in dotnet command + /// + /// Example:
+ /// dotnet ef database update -- -c ../data/appsettings.Development.json -d ../data/` + /// + ///
+ /// + /// + /// + /// --config/-c: App configuration file to load with database connection settings. + /// + /// + /// --data-dir/-d: App data dir to work with. + /// Currently this only affects finding SQLite database when relative path + /// is specified in database settings. + /// + /// + /// + /// + 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()!; + + // 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(); + DatabaseHelper.ConfigureDbContext(optionsBuilder, dbSettings); + + return new AppDbContext(optionsBuilder.Options); + } +} diff --git a/OptixServe.sln b/OptixServe.sln index a852aa1..65be171 100644 --- a/OptixServe.sln +++ b/OptixServe.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptixServe.Core", "OptixSer EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptixServe.Api", "OptixServe.Api\OptixServe.Api.csproj", "{52559B29-A255-4BDC-8F2B-A984DEE69E7E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptixServe.Infrastructure", "OptixServe.Infrastructure\OptixServe.Infrastructure.csproj", "{E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptixServe.Application", "OptixServe.Application\OptixServe.Application.csproj", "{71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +45,30 @@ Global {52559B29-A255-4BDC-8F2B-A984DEE69E7E}.Release|x64.Build.0 = Release|Any CPU {52559B29-A255-4BDC-8F2B-A984DEE69E7E}.Release|x86.ActiveCfg = Release|Any CPU {52559B29-A255-4BDC-8F2B-A984DEE69E7E}.Release|x86.Build.0 = Release|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Debug|x64.ActiveCfg = Debug|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Debug|x64.Build.0 = Debug|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Debug|x86.ActiveCfg = Debug|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Debug|x86.Build.0 = Debug|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Release|Any CPU.Build.0 = Release|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Release|x64.ActiveCfg = Release|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Release|x64.Build.0 = Release|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Release|x86.ActiveCfg = Release|Any CPU + {E90B4BE9-BCE3-48AC-B60E-1ADF2D328408}.Release|x86.Build.0 = Release|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Debug|x64.ActiveCfg = Debug|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Debug|x64.Build.0 = Debug|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Debug|x86.ActiveCfg = Debug|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Debug|x86.Build.0 = Debug|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Release|Any CPU.Build.0 = Release|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Release|x64.ActiveCfg = Release|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Release|x64.Build.0 = Release|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Release|x86.ActiveCfg = Release|Any CPU + {71D84B31-775B-4EF8-9D0B-411A8CE1CC3A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE