diff --git a/.github/workflows/containerization-to-deploy_cms-kit-demo.yml b/.github/workflows/containerization-to-deploy_cms-kit-demo.yml new file mode 100644 index 0000000..2b25f70 --- /dev/null +++ b/.github/workflows/containerization-to-deploy_cms-kit-demo.yml @@ -0,0 +1,60 @@ +# # Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# # More GitHub Actions for Azure: https://github.com/Azure/actions + +# name: Build and deploy ASP.Net Core app to Azure Web App - cms-kit-demo + +# on: +# push: +# branches: +# - containerization-to-deploy +# workflow_dispatch: + +# jobs: +# build: +# runs-on: ubuntu-latest + +# steps: +# - uses: actions/checkout@v2 + +# - name: Set up .NET Core +# uses: actions/setup-dotnet@v1 +# with: +# dotnet-version: '7.x' +# include-prerelease: true + +# - name: Build with dotnet +# run: dotnet build --configuration Release + +# - name: dotnet publish +# run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp + +# - name: Copy CmsKitDemo.db to publish directory +# run: cp src/CmsKitDemo.db ${{env.DOTNET_ROOT}}/myapp + +# - name: Upload artifact for deployment job +# uses: actions/upload-artifact@v2 +# with: +# name: .net-app +# path: ${{env.DOTNET_ROOT}}/myapp + +# deploy: +# runs-on: ubuntu-latest +# needs: build +# environment: +# name: 'Production' +# url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + +# steps: +# - name: Download artifact from build job +# uses: actions/download-artifact@v2 +# with: +# name: .net-app + +# - name: Deploy to Azure Web App +# id: deploy-to-webapp +# uses: azure/webapps-deploy@v2 +# with: +# app-name: 'cms-kit-demo' +# slot-name: 'Production' +# publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_B8EE580013C3409F9EA266AF29C41EAD }} +# package: . diff --git a/src/CmsKitDemo/CmsKitDemoModule.cs b/src/CmsKitDemo/CmsKitDemoModule.cs index 7a1f78c..66b01c2 100644 --- a/src/CmsKitDemo/CmsKitDemoModule.cs +++ b/src/CmsKitDemo/CmsKitDemoModule.cs @@ -59,6 +59,7 @@ using Volo.Abp.IO; using Volo.CmsKit.Reactions; using Volo.CmsKit.Comments; +using Microsoft.EntityFrameworkCore.Query; namespace CmsKitDemo; @@ -318,7 +319,8 @@ private void ConfigureEfCore(ServiceConfigurationContext context) { options.Configure(configurationContext => { - configurationContext.UseSqlite(); + configurationContext.UseSqlite() + .ReplaceService(); }); }); diff --git a/src/CmsKitDemo/Data/CmsKitDemoSqliteMethodCallTranslatorProvider.cs b/src/CmsKitDemo/Data/CmsKitDemoSqliteMethodCallTranslatorProvider.cs new file mode 100644 index 0000000..a4d1c81 --- /dev/null +++ b/src/CmsKitDemo/Data/CmsKitDemoSqliteMethodCallTranslatorProvider.cs @@ -0,0 +1,75 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; +using System.Reflection; +using Volo.Abp; + +namespace CmsKitDemo.Data +{ + public class CmsKitDemoSqliteMethodCallTranslatorProvider : SqliteMethodCallTranslatorProvider + { + public CmsKitDemoSqliteMethodCallTranslatorProvider([NotNull] RelationalMethodCallTranslatorProviderDependencies dependencies) + : base(dependencies) + { + var sqlExpressionFactory = dependencies.SqlExpressionFactory; + + AddTranslators( + new IMethodCallTranslator[] + { + new SqliteMathTranslator(sqlExpressionFactory), + new SqliteDateTimeAddTranslator(sqlExpressionFactory.As()), + new CmsKitDemoSqliteStringMethodTranslator(sqlExpressionFactory) + }); + } + } + + public class CmsKitDemoSqliteStringMethodTranslator : SqliteStringMethodTranslator + { + private static readonly MethodInfo _containsMethodInfo + = typeof(string).GetRuntimeMethod(nameof(string.Contains), new[] { typeof(string) }); + + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + public CmsKitDemoSqliteStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) : base(sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + public override SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList arguments, IDiagnosticsLogger logger) + { + Check.NotNull(method, nameof(method)); + Check.NotNull(arguments, nameof(arguments)); + + if (_containsMethodInfo.Equals(method)) + { + var pattern = arguments[0]; + var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern); + + instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping); + pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping); + + // this basically changes query to "instr(upper(left_expression), upper(right_expression))" + return _sqlExpressionFactory.OrElse( + _sqlExpressionFactory.Equal( + pattern, + _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)), + _sqlExpressionFactory.GreaterThan( + _sqlExpressionFactory.Function( + "instr", + new[] + { + _sqlExpressionFactory.Function("upper", new[]{instance}, false, new []{ false }, typeof(string)), + _sqlExpressionFactory.Function("upper", new[]{pattern}, false, new []{ false }, typeof(string)) + }, + false, + new[] { false, false }, + typeof(int)), _sqlExpressionFactory.Constant(0))); + } + + return base.Translate(instance, method, arguments, logger); + } + } +} diff --git a/src/CmsKitDemo/DbMigrationMiddleware.cs b/src/CmsKitDemo/DbMigrationMiddleware.cs new file mode 100644 index 0000000..0704872 --- /dev/null +++ b/src/CmsKitDemo/DbMigrationMiddleware.cs @@ -0,0 +1,155 @@ +using Microsoft.Extensions.Options; +using System.Data.Common; +using System.Net; +using System.Text; +using System.Text.Unicode; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.Threading; + +namespace CmsKitDemo +{ + public class DbMigrationMiddleware : IMiddleware, ITransientDependency + { + private readonly IConnectionStringResolver _connectionStringResolver; + private readonly IConfiguration _configuration; + + public DbMigrationMiddleware(IConnectionStringResolver connectionStringResolver, IConfiguration configuration) + { + _connectionStringResolver = connectionStringResolver; + _configuration = configuration; + } + + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + var connString = await _connectionStringResolver.ResolveAsync(); + + var dbBuilder = new DbConnectionStringBuilder + { + ConnectionString = connString + }; + + + await next(context); + } + } + + [Dependency(ReplaceServices = true)] + public class DemoConnectionStringResolver : DefaultConnectionStringResolver + { + public const string DemoUserCookieName = "CmsKitDemoUser"; + + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IGuidGenerator _guidGenerator; + private readonly IConfiguration _configuration; + + public DemoConnectionStringResolver(IOptionsMonitor options, IConfiguration configuration, IHttpContextAccessor httpContextAccessor, IGuidGenerator guidGenerator) : base(options) + { + _configuration = configuration; + _httpContextAccessor = httpContextAccessor; + _guidGenerator = guidGenerator; + } + + [Obsolete("Use ResolveAsync method.")] + public override string Resolve(string connectionStringName) + { + return AsyncHelper.RunSync(() => ResolveInternalAsync(connectionStringName)); + } + + public async override Task ResolveAsync(string connectionStringName) + { + return await ResolveInternalAsync(connectionStringName); + } + + private async Task ResolveInternalAsync(string connectionStringName) + { + var dbFolder = _configuration["App:SqliteDbFolder"]?.EnsureEndsWith(Path.DirectorySeparatorChar); + if (dbFolder.IsNullOrWhiteSpace()) + { + return await base.ResolveAsync(connectionStringName); + } + + //string demoUserId = _configuration["App:DefaultDbName"]!; + string demoUserId = GetDemoUserIdOrNull() ?? _configuration["App:DefaultDbName"]!; + + var dbFilePath = $"{dbFolder}{demoUserId}.db"; + var connString = $"Data Source={dbFilePath};Mode=ReadWriteCreate;"; + + if (ShouldCreateDemoDatabase(dbFilePath)) + { + await CreateDemoDatabaseAsync(dbFilePath); + } + + return connString; + } + + public string GetDemoUserIdOrNull() + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) + { + return null; + } + + var demoUserId = httpContext.Items[DemoUserCookieName] as string; + if (demoUserId != null) + { + return demoUserId; + } + + demoUserId = httpContext.Request?.Cookies[DemoUserCookieName]; + if (demoUserId == null) + { + demoUserId = _guidGenerator.Create().ToString(); + } + + SetDemoUserCookie(demoUserId); + httpContext.Items[DemoUserCookieName] = demoUserId; + return demoUserId; + } + + private void SetDemoUserCookie(string value) + { + var option = new CookieOptions + { + Expires = DateTime.Now.AddMonths(12) + }; + + _httpContextAccessor.HttpContext?.Response.Cookies.Append(DemoUserCookieName, value, option); + } + + private bool ShouldCreateDemoDatabase(string dbFilePath) + { + if (!File.Exists(dbFilePath)) + { + return true; + } + + return new FileInfo(dbFilePath!).Length <= 0; + } + + + private async Task CreateDemoDatabaseAsync(string newDbFilePath) + { + try + { + var defaultDbFilePath = GetDefaultDbFilePath(); + + //copy the existing db file to the target + File.Copy(defaultDbFilePath, newDbFilePath, overwrite: true); + } + catch (Exception ex) + { + //ignore + } + } + + private string GetDefaultDbFilePath() + { + var dbFolder = _configuration["App:SqliteDbFolder"]?.EnsureEndsWith(Path.DirectorySeparatorChar); + + return $"{dbFolder}{_configuration["App:DefaultDbName"]}.db"; + } + } +} diff --git a/src/CmsKitDemo/Pages/Index.cshtml b/src/CmsKitDemo/Pages/Index.cshtml index 5008bfc..c5efdc4 100644 --- a/src/CmsKitDemo/Pages/Index.cshtml +++ b/src/CmsKitDemo/Pages/Index.cshtml @@ -1,8 +1,10 @@ @page @using Microsoft.AspNetCore.Mvc.Localization @using CmsKitDemo.Localization +@using Volo.Abp.Data; @inject IHtmlLocalizer L @model CmsKitDemo.Pages.IndexModel +@inject IConnectionStringResolver ConnectionStringResolver @section styles { @@ -32,7 +34,7 @@
-
ABP Framework
+
ABP Framework - @await ConnectionStringResolver.ResolveAsync()

CMS Kit Module

This module provides CMS (Content Management System) capabilities for your application.

Documentation diff --git a/src/CmsKitDemo/sqliteDbs/CmsKitDemo.db b/src/CmsKitDemo/sqliteDbs/CmsKitDemo.db new file mode 100644 index 0000000..69562d1 Binary files /dev/null and b/src/CmsKitDemo/sqliteDbs/CmsKitDemo.db differ diff --git a/src/Dockerfile b/src/Dockerfile new file mode 100644 index 0000000..ac43784 --- /dev/null +++ b/src/Dockerfile @@ -0,0 +1,7 @@ +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +RUN apt-get update && apt-get install -y libgdiplus +WORKDIR /app +EXPOSE 80 +COPY CmsKitDemo/bin/Release/publish . +COPY CmsKitDemo.db . +ENTRYPOINT ["dotnet", "CmsKitDemo.dll"] diff --git a/src/Dockerfile copy.azure b/src/Dockerfile copy.azure new file mode 100644 index 0000000..5d01087 --- /dev/null +++ b/src/Dockerfile copy.azure @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS base +WORKDIR /src +COPY . . +RUN dotnet tool install -g Volo.Abp.Cli +ENV PATH="${PATH}:/root/.dotnet/tools" +WORKDIR /src/CmsKitDemo +RUN abp install-libs +RUN dotnet publish -c Release -o bin/Release/publish + +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS final +RUN apt-get update && apt-get install -y libgdiplus +WORKDIR /app/sqliteDb +COPY --from=base /src/CmsKitDemo.db . +RUN chmod 666 CmsKitDemo.db +WORKDIR /app +EXPOSE 80 +COPY --from=base /src/CmsKitDemo/bin/Release/publish . +ENV App__ConnectionStrings__Default="Data Source=/app/sqliteDb/CmsKitDemo.db;Version=3;" +ENTRYPOINT ["dotnet", "CmsKitDemo.dll"] \ No newline at end of file diff --git a/src/Dockerfile.azure b/src/Dockerfile.azure new file mode 100644 index 0000000..75c0bd7 --- /dev/null +++ b/src/Dockerfile.azure @@ -0,0 +1,17 @@ +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS base +WORKDIR /src +COPY . . +RUN dotnet tool install -g Volo.Abp.Cli --version 7.4.5 +ENV PATH="${PATH}:/root/.dotnet/tools" +WORKDIR /src/CmsKitDemo +RUN abp install-libs +RUN dotnet publish -c Release -o bin/Release/publish + +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS final +RUN apt-get update && apt-get install -y libgdiplus +WORKDIR /app +COPY --from=base /src/CmsKitDemo.db . +WORKDIR /app +EXPOSE 80 +COPY --from=base /src/CmsKitDemo/bin/Release/publish . +ENTRYPOINT ["dotnet", "CmsKitDemo.dll"] \ No newline at end of file diff --git a/src/azure-pipelines.yaml b/src/azure-pipelines.yaml new file mode 100644 index 0000000..73fa3c4 --- /dev/null +++ b/src/azure-pipelines.yaml @@ -0,0 +1,74 @@ +trigger: + tags: + include: + - "*.*.*" + +resources: + repositories: + - repository: devops + type: github + endpoint: github.com_skoc10 + name: volosoft/devops + ref: master + +variables: + # Container registry service connection established during pipeline creation + dockerRegistryServiceConnection: 'volosoft-reg' + # workDir: '/home/ubuntu/ws/1/s' + solutionDir: '$(Build.SourcesDirectory)/cms-kit-demo/src' + # tag: $[replace(variables['Build.SourceBranch'], 'refs/tags/', '')] + tag: $(Build.BuildNumber) + DOCKER_BUILDKIT: 1 + +pool: + name: aks-deployer-agent + + +stages: +- stage: Package + displayName: Package + jobs: + - job: Build + displayName: Package Helm Charts and Values + pool: + name: aks-deployer-agent + steps: + - checkout: self + - checkout: devops + + - task: Docker@2 + displayName: 'Build Docker Image' + inputs: + containerRegistry: $(dockerRegistryServiceConnection) + repository: 'demo/cms-kit-demo.abp.io' + command: 'build' + Dockerfile: '$(solutionDir)/Dockerfile.azure' + tags: | + $(tag) + + - task: Docker@2 + displayName: Push Docker Image + inputs: + containerRegistry: $(dockerRegistryServiceConnection) + repository: 'demo/cms-kit-demo.abp.io' + command: 'push' + tags: | + $(tag) + + - bash: | + mkdir -p $(Build.SourcesDirectory)/devops/aks/versions + + + cat < $(Build.SourcesDirectory)/devops/aks/versions/cms-kit-demo-version.yaml + image: + repository: volosoft.azurecr.io/demo/cms-kit-demo.abp.io + tag: "$(tag)" + EOF + + cat $(Build.SourcesDirectory)/devops/aks/versions/cms-kit-demo-version.yaml >> $(Build.SourcesDirectory)/devops/aks/helm/values/app/demo/cms-kit-demo.yaml + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: cms-kit-demo' + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/devops/aks/helm' + ArtifactName: 'cms-kit-demo' \ No newline at end of file diff --git a/src/azure-pipelines2.yaml b/src/azure-pipelines2.yaml new file mode 100644 index 0000000..1a4b69c --- /dev/null +++ b/src/azure-pipelines2.yaml @@ -0,0 +1,87 @@ +trigger: + tags: + include: + - "*.*.*" + +resources: + repositories: + - repository: devops + type: github + endpoint: github.com_skoc10 + name: volosoft/devops + ref: master + +variables: + # Container registry service connection established during pipeline creation + dockerRegistryServiceConnection: 'volosoft-reg' + # workDir: '/home/ubuntu/ws/1/s' + solutionDir: '$(Build.SourcesDirectory)/cms-kit-demo/src' + # tag: $[replace(variables['Build.SourceBranch'], 'refs/tags/', '')] + tag: $(Build.BuildNumber) + DOCKER_BUILDKIT: 1 + +pool: + vmImage: 'ubuntu-latest' + + +stages: +- stage: Package + displayName: Package + jobs: + - job: Build + displayName: Package Helm Charts and Values + pool: + vmImage: 'ubuntu-latest' + steps: + - checkout: self + - checkout: devops + + - script: | + dotnet tool install -g Volo.Abp.Cli + export PATH="$PATH:/root/.dotnet/tools" + abp --version + cd $(solutionDir)/CmsKitDemo && abp install-libs + displayName: 'Install ABP CLI' + + - script: | + cd $(solutionDir)/CmsKitDemo + dotnet publish -c Release -o bin/Release/publish + displayName: 'Publish CmsKitDemo' + + - task: Docker@2 + displayName: Build Docker Image + inputs: + containerRegistry: $(dockerRegistryServiceConnection) + repository: 'demo/cms-kit-demo.abp.io' + command: 'build' + Dockerfile: '$(solutionDir)/Dockerfile.azure' + buildContext: '$(solutionDir)' + tags: | + $(tag) + + - task: Docker@2 + displayName: Push Docker Image + inputs: + containerRegistry: $(dockerRegistryServiceConnection) + repository: 'demo/cms-kit-demo.abp.io' + command: 'push' + tags: | + $(tag) + + - bash: | + mkdir -p $(Build.SourcesDirectory)/devops/aks/versions + + + cat < $(Build.SourcesDirectory)/devops/aks/versions/cms-kit-demo-version.yaml + image: + repository: volosoft.azurecr.io/demo/cms-kit-demo.abp.io + tag: "$(tag)" + EOF + + cat $(Build.SourcesDirectory)/devops/aks/versions/cms-kit-demo-version.yaml >> $(Build.SourcesDirectory)/devops/aks/helm/values/app/demo/cms-kit-demo.yaml + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: cms-kit-demo' + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/devops/aks/helm' + ArtifactName: 'cms-kit-demo' \ No newline at end of file