Skip to content

Commit bb49586

Browse files
authored
Merge pull request #8 from abpframework/EngincanV/add-migration-logic
Engincan v/add migration logic
2 parents 6528bf3 + f38d3ac commit bb49586

13 files changed

+258
-21
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -396,3 +396,4 @@ FodyWeavers.xsd
396396

397397
# JetBrains Rider
398398
*.sln.iml
399+
.idea/*

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ This is a sample application designed to demonstrate the capabilities of the [AB
88

99
## How to run?
1010

11-
This application uses SQLite as the database and the database can be found in the solution folder. Therefore, you don't need to create the database and instead, you can directly run the `CmsKitDemo` project to see the application.
11+
Before running the application, you should run the following command in the `CmsKitDemo` folder to install all NPM packages for the application:
12+
13+
```bash
14+
abp install-libs
15+
```
16+
17+
After installing the NPM packages, you can directly run the `CmsKitDemo` project to see the application. This application uses SQLite as the database and the database can be found in the solution folder. Therefore, you don't need to create the database manually.
1218

1319
> Default credentials: `admin` as username and `1q2w3E*` as the password.
1420

src/CmsKitDemo/CmsKitDemo.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@
102102
<Content Remove="Logs\**" />
103103
<EmbeddedResource Remove="Logs\**" />
104104
<None Remove="Logs\**" />
105+
<None Update="CmsKitDemoDb\CmsKitDemo.db">
106+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
107+
</None>
105108
</ItemGroup>
106109

107110
<ItemGroup>

src/CmsKitDemo/CmsKitDemoConsts.cs

+2
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ public class CmsKitDemoConsts
99
public const string ImageGalleryEntityType = "Image";
1010

1111
public const int MaxDescriptionLength = 512;
12+
13+
public static readonly List<string> IgnoredUserAgents = new();
1214
}
1315
}
Binary file not shown.

src/CmsKitDemo/CmsKitDemoModule.cs

+11
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
using Volo.CmsKit.Web;
5656
using Volo.Abp.Threading;
5757
using Microsoft.AspNetCore.Mvc.RazorPages;
58+
using Volo.Abp.Data;
59+
using Volo.Abp.IO;
5860
using Volo.CmsKit.Reactions;
5961
using Volo.CmsKit.Comments;
6062
using Microsoft.EntityFrameworkCore.Query;
@@ -360,6 +362,13 @@ private void ConfigureCmsKit(ServiceConfigurationContext context)
360362
});
361363
}
362364

365+
public override async Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
366+
{
367+
DirectoryHelper.CreateIfNotExists(context.GetConfiguration()["App:DbFolderName"] ?? "CmsKitDemoDb");
368+
var connString = await context.ServiceProvider.GetRequiredService<IConnectionStringResolver>().ResolveAsync();
369+
await context.ServiceProvider.GetRequiredService<CmsKitDemoDbMigrationService>().MigrateAsync(connString);
370+
}
371+
363372
public override void OnApplicationInitialization(ApplicationInitializationContext context)
364373
{
365374
var app = context.GetApplicationBuilder();
@@ -377,6 +386,8 @@ public override void OnApplicationInitialization(ApplicationInitializationContex
377386
app.UseErrorPage();
378387
}
379388

389+
app.UseMiddleware<DbMigrationMiddleware>();
390+
380391
app.UseCorrelationId();
381392
app.UseStaticFiles();
382393
app.UseRouting();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Microsoft.Extensions.Options;
2+
using Volo.Abp.Data;
3+
using Volo.Abp.DependencyInjection;
4+
using Volo.Abp.Threading;
5+
6+
namespace CmsKitDemo.Data;
7+
8+
[Dependency(ReplaceServices = true)]
9+
public class CmsKitConnectionStringResolver : DefaultConnectionStringResolver
10+
{
11+
private readonly CmsKitDemoUserIdResolver _demoNameResolver;
12+
private readonly IConfiguration _configuration;
13+
14+
public CmsKitConnectionStringResolver(
15+
IOptionsMonitor<AbpDbConnectionOptions> options,
16+
CmsKitDemoUserIdResolver demoNameResolver,
17+
IConfiguration configuration)
18+
: base(options)
19+
{
20+
_demoNameResolver = demoNameResolver;
21+
_configuration = configuration;
22+
}
23+
24+
[Obsolete("Use ResolveAsync method.")]
25+
public override string Resolve(string connectionStringName = null)
26+
{
27+
return AsyncHelper.RunSync(() => ResolveInternalAsync(connectionStringName));
28+
}
29+
30+
public async override Task<string> ResolveAsync(string connectionStringName = null)
31+
{
32+
return await ResolveInternalAsync(connectionStringName);
33+
}
34+
35+
private async Task<string> ResolveInternalAsync(string connectionStringName = null)
36+
{
37+
var dbFolder = _configuration["App:DbFolderName"]?.EnsureEndsWith(Path.DirectorySeparatorChar);
38+
if (dbFolder.IsNullOrWhiteSpace())
39+
{
40+
return await base.ResolveAsync(connectionStringName);
41+
}
42+
43+
var demoUserId = _demoNameResolver.GetDemoUserIdOrNull() ?? _configuration["App:DefaultDbName"];
44+
45+
var dbFilePath = $"{dbFolder}{demoUserId}.db";
46+
var connString = $"Data Source={dbFilePath};Cache=Shared";
47+
48+
return connString;
49+
}
50+
}

src/CmsKitDemo/Data/CmsKitDemoDbContextFactory.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ public class CmsKitDemoDbContextFactory : IDesignTimeDbContextFactory<CmsKitDemo
77
{
88
public CmsKitDemoDbContext CreateDbContext(string[] args)
99
{
10-
1110
var configuration = BuildConfiguration();
1211

13-
var dbFolder = configuration["App:SqliteDbFolder"]?.EnsureEndsWith(Path.DirectorySeparatorChar);
12+
var dbFolder = configuration["App:DbFolderName"]!.EnsureEndsWith(Path.DirectorySeparatorChar);
13+
1414
var builder = new DbContextOptionsBuilder<CmsKitDemoDbContext>()
15-
.UseSqlite($"Data Source={dbFolder}{configuration["App:DefaultDbName"]}.db");
15+
.UseSqlite($"Data Source={dbFolder}{configuration["App:DefaultDbName"]}.db;");
1616

1717
return new CmsKitDemoDbContext(builder.Options);
1818
}

src/CmsKitDemo/Data/CmsKitDemoDbMigrationService.cs

+52-2
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,73 @@ public class CmsKitDemoDbMigrationService : ITransientDependency
1717
private readonly CmsKitDemoEFCoreDbSchemaMigrator _dbSchemaMigrator;
1818
private readonly ITenantRepository _tenantRepository;
1919
private readonly ICurrentTenant _currentTenant;
20+
private readonly IConfiguration _configuration;
2021

2122
public CmsKitDemoDbMigrationService(
2223
IDataSeeder dataSeeder,
2324
CmsKitDemoEFCoreDbSchemaMigrator dbSchemaMigrator,
2425
ITenantRepository tenantRepository,
25-
ICurrentTenant currentTenant)
26+
ICurrentTenant currentTenant,
27+
IConfiguration configuration)
2628
{
2729
_dataSeeder = dataSeeder;
2830
_dbSchemaMigrator = dbSchemaMigrator;
2931
_tenantRepository = tenantRepository;
3032
_currentTenant = currentTenant;
33+
_configuration = configuration;
3134

3235
Logger = NullLogger<CmsKitDemoDbMigrationService>.Instance;
3336
}
37+
38+
private async Task MigrateHostDatabaseAsync(string connectionString)
39+
{
40+
Logger.LogInformation("Migrating host database schema...");
41+
42+
if (!connectionString.Contains(_configuration["App:DefaultDbName"] + ".db"))
43+
{
44+
TryToCopyFromDefaultDb(connectionString);
45+
}
46+
else
47+
{
48+
await _dbSchemaMigrator.MigrateAsync(connectionString);
49+
}
3450

35-
public async Task MigrateAsync()
51+
Logger.LogInformation("Executing host database seed...");
52+
53+
await _dataSeeder.SeedAsync(
54+
new DataSeedContext()
55+
.WithProperty("AdminPassword", "123456")
56+
);
57+
58+
Logger.LogInformation("Successfully completed host database migrations.");
59+
}
60+
61+
private void TryToCopyFromDefaultDb(string connectionString)
3662
{
63+
try
64+
{
65+
var filePath = connectionString!.Replace("Data Source=", "").Split(";")[0];
66+
File.Copy(_configuration["App:DbFolderName"]?.EnsureEndsWith(Path.DirectorySeparatorChar) + _configuration["App:DefaultDbName"] + ".db", filePath);
67+
}
68+
catch
69+
{
70+
//...
71+
}
72+
}
73+
74+
public async Task MigrateAsync(string? connectionString = null)
75+
{
76+
if (!connectionString.IsNullOrWhiteSpace())
77+
{
78+
Logger.LogInformation("Started database migrations...");
79+
80+
await MigrateHostDatabaseAsync(connectionString!);
81+
82+
Logger.LogInformation("Successfully completed database migrations.");
83+
84+
return;
85+
}
86+
3787
var initialMigrationAdded = AddInitialMigrationIfNotExist();
3888

3989
if (initialMigrationAdded)

src/CmsKitDemo/Data/CmsKitDemoEFCoreDbSchemaMigrator.cs

+16-13
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,26 @@ public class CmsKitDemoEFCoreDbSchemaMigrator : ITransientDependency
77
{
88
private readonly IServiceProvider _serviceProvider;
99

10-
public CmsKitDemoEFCoreDbSchemaMigrator(
11-
IServiceProvider serviceProvider)
10+
public CmsKitDemoEFCoreDbSchemaMigrator(IServiceProvider serviceProvider)
1211
{
1312
_serviceProvider = serviceProvider;
1413
}
1514

16-
public async Task MigrateAsync()
15+
public async Task MigrateAsync(string? connectionString = null)
1716
{
18-
/* We intentionally resolve the CmsKitDemoDbContext
19-
* from IServiceProvider (instead of directly injecting it)
20-
* to properly get the connection string of the current tenant in the
21-
* current scope.
22-
*/
17+
if (connectionString.IsNullOrWhiteSpace())
18+
{
19+
await _serviceProvider.GetRequiredService<CmsKitDemoDbContext>().Database.MigrateAsync();
20+
return;
21+
}
2322

24-
await _serviceProvider
25-
.GetRequiredService<CmsKitDemoDbContext>()
26-
.Database
27-
.MigrateAsync();
23+
var options = new DbContextOptionsBuilder<CmsKitDemoDbContext>()
24+
.UseSqlite(connectionString)
25+
.Options;
26+
27+
using (var dbContext = new CmsKitDemoDbContext(options))
28+
{
29+
await dbContext.Database.MigrateAsync();
30+
}
2831
}
29-
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Volo.Abp.DependencyInjection;
2+
using Volo.Abp.Guids;
3+
4+
namespace CmsKitDemo.Data;
5+
6+
public class CmsKitDemoUserIdResolver : ITransientDependency
7+
{
8+
private const string DemoUserCookieName = "CmsKitDemoUser";
9+
10+
private readonly IHttpContextAccessor _httpContextAccessor;
11+
private readonly IGuidGenerator _guidGenerator;
12+
13+
public CmsKitDemoUserIdResolver(IHttpContextAccessor httpContextAccessor, IGuidGenerator guidGenerator)
14+
{
15+
_httpContextAccessor = httpContextAccessor;
16+
_guidGenerator = guidGenerator;
17+
}
18+
19+
public string? GetDemoUserIdOrNull()
20+
{
21+
var httpContext = _httpContextAccessor.HttpContext;
22+
if (httpContext == null)
23+
{
24+
return null;
25+
}
26+
27+
var demoUserId = httpContext.Items[DemoUserCookieName] as string;
28+
if (demoUserId != null)
29+
{
30+
return demoUserId;
31+
}
32+
33+
demoUserId = GetDemoUserFromQueryStringOrNull(httpContext) ?? GetDemoUserFromCookieOrNull(httpContext);
34+
if (demoUserId == null)
35+
{
36+
demoUserId = _guidGenerator.Create().ToString();
37+
}
38+
39+
SetDemoUserCookie(demoUserId);
40+
httpContext.Items[DemoUserCookieName] = demoUserId;
41+
return demoUserId;
42+
}
43+
44+
private static string? GetDemoUserFromCookieOrNull(HttpContext httpContext)
45+
{
46+
return httpContext.Request?.Cookies[DemoUserCookieName];
47+
}
48+
49+
private static string? GetDemoUserFromQueryStringOrNull(HttpContext httpContext)
50+
{
51+
return httpContext.Request?.Query[DemoUserCookieName];
52+
}
53+
54+
private void SetDemoUserCookie(string value)
55+
{
56+
var option = new CookieOptions
57+
{
58+
Expires = DateTime.Now.AddMonths(12)
59+
};
60+
61+
_httpContextAccessor.HttpContext?.Response.Cookies.Append(DemoUserCookieName, value, option);
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Volo.Abp.Data;
2+
using Volo.Abp.DependencyInjection;
3+
4+
namespace CmsKitDemo.Data;
5+
6+
public class DbMigrationMiddleware : IMiddleware, ITransientDependency
7+
{
8+
private readonly CmsKitDemoDbMigrationService _dbMigrationService;
9+
10+
private readonly IConnectionStringResolver _connectionStringResolver;
11+
12+
public DbMigrationMiddleware(CmsKitDemoDbMigrationService dbMigrationService, IConnectionStringResolver connectionStringResolver)
13+
{
14+
_dbMigrationService = dbMigrationService;
15+
_connectionStringResolver = connectionStringResolver;
16+
}
17+
18+
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
19+
{
20+
var connString = await _connectionStringResolver.ResolveAsync();
21+
var dbFilePath = connString.Replace("Data Source=", "").Replace(";Cache=Shared", "");
22+
23+
if (ShouldCreateUserDb(context, dbFilePath))
24+
{
25+
await _dbMigrationService.MigrateAsync(connString);
26+
}
27+
28+
await next(context);
29+
}
30+
31+
private static bool ShouldCreateUserDb(HttpContext context, string dbFilePath)
32+
{
33+
if (!context.Request.Headers.ContainsKey("User-Agent"))
34+
{
35+
return !File.Exists(dbFilePath);
36+
}
37+
38+
var userAgent = context.Request.Headers.FirstOrDefault(e=> e.Key == "User-Agent");
39+
if (CmsKitDemoConsts.IgnoredUserAgents.Any(ignoredUserAgent => userAgent.Value.ToString().Contains(ignoredUserAgent)))
40+
{
41+
return false;
42+
}
43+
44+
return !File.Exists(dbFilePath);
45+
}
46+
}

src/CmsKitDemo/appsettings.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
{
22
"App": {
3-
"SelfUrl": "https://localhost:44373"
3+
"SelfUrl": "https://localhost:44373",
4+
"DefaultDbName": "CmsKitDemo",
5+
"DbFolderName": "CmsKitDemoDb"
46
},
57
"ConnectionStrings": {
6-
"Default": "Data Source=CmsKitDemo.db;"
8+
"Default": "Data Source=/CmsKitDemoDb/CmsKitDemo.db;"
79
},
810
"StringEncryption": {
911
"DefaultPassPhrase": "cZl2tCN43sVGDX6m"

0 commit comments

Comments
 (0)