45 changed files with 1403 additions and 2 deletions
			
			
		| @ -0,0 +1,99 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||||
| 
 | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net6.0</TargetFramework> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" /> | ||||
|     <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Autofac" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.AutoMapper" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Swashbuckle" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.EntityFrameworkCore.PostgreSql" Version="$(VoloAbpVersion)" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Volo.Abp.Account.Application" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Account.HttpApi" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Account.Web.IdentityServer" Version="$(VoloAbpVersion)" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Volo.Abp.PermissionManagement.Domain.Identity" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Identity.Application" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Identity.HttpApi" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Identity.EntityFrameworkCore" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.IdentityServer.EntityFrameworkCore" Version="$(VoloAbpVersion)" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Volo.Abp.PermissionManagement.Application" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.PermissionManagement.EntityFrameworkCore" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.PermissionManagement.HttpApi" Version="$(VoloAbpVersion)" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Volo.Abp.TenantManagement.Application" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.TenantManagement.EntityFrameworkCore" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.TenantManagement.HttpApi" Version="$(VoloAbpVersion)" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Volo.Abp.Featuremanagement.Application" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Featuremanagement.EntityFrameworkCore" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.Featuremanagement.HttpApi" Version="$(VoloAbpVersion)" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Volo.Abp.SettingManagement.Application" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.SettingManagement.EntityFrameworkCore" Version="$(VoloAbpVersion)" /> | ||||
|     <PackageReference Include="Volo.Abp.SettingManagement.HttpApi" Version="$(VoloAbpVersion)" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Volo.Abp.AuditLogging.EntityFrameworkCore" Version="$(VoloAbpVersion)" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic" Version="$(VoloAbpVersion)" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.1" /> | ||||
|     <PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="6.0.1" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1"> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> | ||||
|       <PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets> | ||||
|     </PackageReference> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <Content Remove="Localization\BookStore\*.json" /> | ||||
|     <EmbeddedResource Include="Localization\BookStore\*.json" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <Compile Remove="Logs\**" /> | ||||
|     <Content Remove="Logs\**" /> | ||||
|     <EmbeddedResource Remove="Logs\**" /> | ||||
|     <None Remove="Logs\**" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\..\modules\mvc\Sanhe.Abp.AspNetCore.Mvc.Wrapper\Sanhe.Abp.AspNetCore.Mvc.Wrapper.csproj" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
| @ -0,0 +1,345 @@ | ||||
| using BookStore.Data; | ||||
| using BookStore.Localization; | ||||
| using Microsoft.AspNetCore.Cors; | ||||
| using Microsoft.AspNetCore.DataProtection; | ||||
| using Microsoft.OpenApi.Models; | ||||
| using Sanhe.Abp.AspNetCore.Mvc.Wrapper; | ||||
| using Sanhe.Abp.Wrapper; | ||||
| using StackExchange.Redis; | ||||
| using Volo.Abp; | ||||
| using Volo.Abp.Account; | ||||
| using Volo.Abp.Account.Web; | ||||
| using Volo.Abp.AspNetCore.Authentication.JwtBearer; | ||||
| using Volo.Abp.AspNetCore.MultiTenancy; | ||||
| using Volo.Abp.AspNetCore.Mvc; | ||||
| using Volo.Abp.AspNetCore.Mvc.Localization; | ||||
| using Volo.Abp.AspNetCore.Mvc.UI.Bundling; | ||||
| using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; | ||||
| using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling; | ||||
| using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; | ||||
| using Volo.Abp.AspNetCore.Serilog; | ||||
| using Volo.Abp.AuditLogging.EntityFrameworkCore; | ||||
| using Volo.Abp.Autofac; | ||||
| using Volo.Abp.AutoMapper; | ||||
| using Volo.Abp.Caching.StackExchangeRedis; | ||||
| using Volo.Abp.EntityFrameworkCore; | ||||
| using Volo.Abp.EntityFrameworkCore.PostgreSql; | ||||
| using Volo.Abp.FeatureManagement; | ||||
| using Volo.Abp.FeatureManagement.EntityFrameworkCore; | ||||
| using Volo.Abp.Identity; | ||||
| using Volo.Abp.Identity.EntityFrameworkCore; | ||||
| using Volo.Abp.IdentityServer.EntityFrameworkCore; | ||||
| using Volo.Abp.Localization; | ||||
| using Volo.Abp.Localization.ExceptionHandling; | ||||
| using Volo.Abp.Modularity; | ||||
| using Volo.Abp.MultiTenancy; | ||||
| using Volo.Abp.PermissionManagement; | ||||
| using Volo.Abp.PermissionManagement.EntityFrameworkCore; | ||||
| using Volo.Abp.PermissionManagement.HttpApi; | ||||
| using Volo.Abp.PermissionManagement.Identity; | ||||
| using Volo.Abp.SettingManagement; | ||||
| using Volo.Abp.SettingManagement.EntityFrameworkCore; | ||||
| using Volo.Abp.Swashbuckle; | ||||
| using Volo.Abp.TenantManagement; | ||||
| using Volo.Abp.TenantManagement.EntityFrameworkCore; | ||||
| using Volo.Abp.UI.Navigation.Urls; | ||||
| using Volo.Abp.Validation.Localization; | ||||
| using Volo.Abp.VirtualFileSystem; | ||||
| 
 | ||||
| namespace BookStore; | ||||
| 
 | ||||
| [DependsOn( | ||||
|     // ABP Framework packages | ||||
|     typeof(AbpAspNetCoreMvcModule), | ||||
|     typeof(AbpAspNetCoreMultiTenancyModule), | ||||
|     typeof(AbpAutofacModule), | ||||
|     typeof(AbpAutoMapperModule), | ||||
|     //typeof(AbpCachingStackExchangeRedisModule), | ||||
|     typeof(AbpEntityFrameworkCorePostgreSqlModule), | ||||
|     typeof(AbpAspNetCoreMvcUiBasicThemeModule), | ||||
|     typeof(AbpSwashbuckleModule), | ||||
|     typeof(AbpAspNetCoreAuthenticationJwtBearerModule), | ||||
|     typeof(AbpAspNetCoreSerilogModule), | ||||
| 
 | ||||
|     // Account module packages | ||||
|     typeof(AbpAccountApplicationModule), | ||||
|     typeof(AbpAccountHttpApiModule), | ||||
|     typeof(AbpAccountWebIdentityServerModule), | ||||
| 
 | ||||
|     // Identity module packages | ||||
|     typeof(AbpPermissionManagementDomainIdentityModule), | ||||
|     typeof(AbpIdentityApplicationModule), | ||||
|     typeof(AbpIdentityHttpApiModule), | ||||
|     typeof(AbpIdentityEntityFrameworkCoreModule), | ||||
|     typeof(AbpIdentityServerEntityFrameworkCoreModule), | ||||
| 
 | ||||
|     // Audit logging module packages | ||||
|     typeof(AbpAuditLoggingEntityFrameworkCoreModule), | ||||
| 
 | ||||
|     // Permission Management module packages | ||||
|     typeof(AbpPermissionManagementApplicationModule), | ||||
|     typeof(AbpPermissionManagementHttpApiModule), | ||||
|     typeof(AbpPermissionManagementEntityFrameworkCoreModule), | ||||
| 
 | ||||
|     // Tenant Management module packages | ||||
|     typeof(AbpTenantManagementApplicationModule), | ||||
|     typeof(AbpTenantManagementHttpApiModule), | ||||
|     typeof(AbpTenantManagementEntityFrameworkCoreModule), | ||||
| 
 | ||||
|     // Feature Management module packages | ||||
|     typeof(AbpFeatureManagementApplicationModule), | ||||
|     typeof(AbpFeatureManagementEntityFrameworkCoreModule), | ||||
|     typeof(AbpFeatureManagementHttpApiModule), | ||||
| 
 | ||||
|     // Setting Management module packages | ||||
|     typeof(AbpSettingManagementApplicationModule), | ||||
|     typeof(AbpSettingManagementEntityFrameworkCoreModule), | ||||
|     typeof(AbpSettingManagementHttpApiModule), | ||||
| 
 | ||||
|     typeof(AbpAspNetCoreMvcWrapperModule) | ||||
| )] | ||||
| public class BookStoreModule : AbpModule | ||||
| { | ||||
|     /* Single point to enable/disable multi-tenancy */ | ||||
|     private const bool IsMultiTenant = true; | ||||
| 
 | ||||
|     public override void PreConfigureServices(ServiceConfigurationContext context) | ||||
|     { | ||||
|         context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options => | ||||
|         { | ||||
|             options.AddAssemblyResource( | ||||
|                 typeof(BookStoreResource) | ||||
|             ); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public override void ConfigureServices(ServiceConfigurationContext context) | ||||
|     { | ||||
|         var hostingEnvironment = context.Services.GetHostingEnvironment(); | ||||
|         var configuration = context.Services.GetConfiguration(); | ||||
| 
 | ||||
|         context.Services.AddAlwaysAllowAuthorization(); | ||||
|         // wrap | ||||
|         Configure<AbpWrapperOptions>(options => | ||||
|         { | ||||
|             options.IsEnabled = true; | ||||
|             options.IgnoreNamespaces.Clear(); | ||||
|         }); | ||||
| 
 | ||||
|         ConfigureBundles(); | ||||
|         ConfigureMultiTenancy(); | ||||
|         ConfigureUrls(configuration); | ||||
|         ConfigureAutoMapper(); | ||||
|         ConfigureSwagger(context.Services); | ||||
|         ConfigureAutoApiControllers(); | ||||
|         ConfigureVirtualFiles(hostingEnvironment); | ||||
|         ConfigureLocalization(); | ||||
|         ConfigureAuthentication(context.Services, configuration); | ||||
|         ConfigureCors(context, configuration); | ||||
|         //ConfigureDataProtection(context, configuration, hostingEnvironment); | ||||
|         ConfigureEfCore(context); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureBundles() | ||||
|     { | ||||
|         Configure<AbpBundlingOptions>(options => | ||||
|         { | ||||
|             options.StyleBundles.Configure( | ||||
|                 BasicThemeBundles.Styles.Global, | ||||
|                 bundle => { bundle.AddFiles("/global-styles.css"); } | ||||
|             ); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureMultiTenancy() | ||||
|     { | ||||
|         Configure<AbpMultiTenancyOptions>(options => | ||||
|         { | ||||
|             options.IsEnabled = IsMultiTenant; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureUrls(IConfiguration configuration) | ||||
|     { | ||||
|         Configure<AppUrlOptions>(options => | ||||
|         { | ||||
|             options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"]; | ||||
|             options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(',')); | ||||
| 
 | ||||
|             options.Applications["Angular"].RootUrl = configuration["App:ClientUrl"]; | ||||
|             options.Applications["Angular"].Urls[AccountUrlNames.PasswordReset] = "account/reset-password"; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureAuthentication(IServiceCollection services, IConfiguration configuration) | ||||
|     { | ||||
|         services.AddAuthentication() | ||||
|             .AddJwtBearer(options => | ||||
|             { | ||||
|                 options.Authority = configuration["AuthServer:Authority"]; | ||||
|                 options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); | ||||
|                 options.Audience = "BookStore"; | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureLocalization() | ||||
|     { | ||||
|         Configure<AbpLocalizationOptions>(options => | ||||
|         { | ||||
|             options.Resources | ||||
|                 .Add<BookStoreResource>("en") | ||||
|                 .AddBaseTypes(typeof(AbpValidationResource)) | ||||
|                 .AddVirtualJson("/Localization/BookStore"); | ||||
| 
 | ||||
|             options.DefaultResourceType = typeof(BookStoreResource); | ||||
| 
 | ||||
|             options.Languages.Add(new LanguageInfo("en", "en", "English")); | ||||
|             options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); | ||||
|         }); | ||||
| 
 | ||||
|         Configure<AbpExceptionLocalizationOptions>(options => | ||||
|         { | ||||
|             options.MapCodeNamespace("BookStore", typeof(BookStoreResource)); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureVirtualFiles(IWebHostEnvironment hostingEnvironment) | ||||
|     { | ||||
|         Configure<AbpVirtualFileSystemOptions>(options => | ||||
|         { | ||||
|             options.FileSets.AddEmbedded<BookStoreModule>(); | ||||
|             if (hostingEnvironment.IsDevelopment()) | ||||
|             { | ||||
|                 /* Using physical files in development, so we don't need to recompile on changes */ | ||||
|                 options.FileSets.ReplaceEmbeddedByPhysical<BookStoreModule>(hostingEnvironment.ContentRootPath); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureAutoApiControllers() | ||||
|     { | ||||
|         Configure<AbpAspNetCoreMvcOptions>(options => | ||||
|         { | ||||
|             options.ConventionalControllers.Create(typeof(BookStoreModule).Assembly); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureSwagger(IServiceCollection services) | ||||
|     { | ||||
|         services.AddAbpSwaggerGen( | ||||
|             options => | ||||
|             { | ||||
|                 options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" }); | ||||
|                 options.DocInclusionPredicate((docName, description) => true); | ||||
|                 options.CustomSchemaIds(type => type.FullName); | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureAutoMapper() | ||||
|     { | ||||
|         Configure<AbpAutoMapperOptions>(options => | ||||
|         { | ||||
|             /* Uncomment `validate: true` if you want to enable the Configuration Validation feature. | ||||
|              * See AutoMapper's documentation to learn what it is: | ||||
|              * https://docs.automapper.org/en/stable/Configuration-validation.html | ||||
|              */ | ||||
|             options.AddMaps<BookStoreModule>(/* validate: true */); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration) | ||||
|     { | ||||
|         context.Services.AddCors(options => | ||||
|         { | ||||
|             options.AddDefaultPolicy(builder => | ||||
|             { | ||||
|                 builder | ||||
|                     .WithOrigins( | ||||
|                         configuration["App:CorsOrigins"] | ||||
|                             .Split(",", StringSplitOptions.RemoveEmptyEntries) | ||||
|                             .Select(o => o.RemovePostFix("/")) | ||||
|                             .ToArray() | ||||
|                     ) | ||||
|                     .WithAbpExposedHeaders() | ||||
|                     .SetIsOriginAllowedToAllowWildcardSubdomains() | ||||
|                     .AllowAnyHeader() | ||||
|                     .AllowAnyMethod() | ||||
|                     .AllowCredentials(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureDataProtection( | ||||
|         ServiceConfigurationContext context, | ||||
|         IConfiguration configuration, | ||||
|         IWebHostEnvironment hostingEnvironment) | ||||
|     { | ||||
|         var dataProtectionBuilder = context.Services.AddDataProtection().SetApplicationName("BookStore"); | ||||
|         if (!hostingEnvironment.IsDevelopment()) | ||||
|         { | ||||
|             var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); | ||||
|             dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "BookStore-Protection-Keys"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void ConfigureEfCore(ServiceConfigurationContext context) | ||||
|     { | ||||
|         context.Services.AddAbpDbContext<BookStoreDbContext>(options => | ||||
|         { | ||||
|             options.AddDefaultRepositories(includeAllEntities: true); | ||||
|         }); | ||||
| 
 | ||||
|         Configure<AbpDbContextOptions>(options => | ||||
|         { | ||||
|             options.Configure(configurationContext => | ||||
|             { | ||||
|                 configurationContext.UseNpgsql(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public override void OnApplicationInitialization(ApplicationInitializationContext context) | ||||
|     { | ||||
|         var app = context.GetApplicationBuilder(); | ||||
|         var env = context.GetEnvironment(); | ||||
| 
 | ||||
|         if (env.IsDevelopment()) | ||||
|         { | ||||
|             app.UseDeveloperExceptionPage(); | ||||
|         } | ||||
| 
 | ||||
|         app.UseAbpRequestLocalization(); | ||||
| 
 | ||||
|         if (!env.IsDevelopment()) | ||||
|         { | ||||
|             app.UseErrorPage(); | ||||
|         } | ||||
| 
 | ||||
|         app.UseCorrelationId(); | ||||
|         app.UseStaticFiles(); | ||||
|         app.UseRouting(); | ||||
|         app.UseCors(); | ||||
|         app.UseAuthentication(); | ||||
|         app.UseJwtTokenMiddleware(); | ||||
| 
 | ||||
|         if (IsMultiTenant) | ||||
|         { | ||||
|             app.UseMultiTenancy(); | ||||
|         } | ||||
| 
 | ||||
|         app.UseUnitOfWork(); | ||||
|         app.UseIdentityServer(); | ||||
|         app.UseAuthorization(); | ||||
| 
 | ||||
|         app.UseSwagger(); | ||||
|         app.UseAbpSwaggerUI(options => | ||||
|         { | ||||
|             options.SwaggerEndpoint("/swagger/v1/swagger.json", "BookStore API"); | ||||
|         }); | ||||
| 
 | ||||
|         app.UseAuditing(); | ||||
|         app.UseAbpSerilogEnrichers(); | ||||
|         app.UseConfiguredEndpoints(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,12 @@ | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Volo.Abp.AspNetCore.Mvc; | ||||
| 
 | ||||
| namespace BookStore.Controllers; | ||||
| 
 | ||||
| public class HomeController : AbpController | ||||
| { | ||||
|     public ActionResult Index() | ||||
|     { | ||||
|         return Redirect("~/swagger"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,46 @@ | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Volo.Abp; | ||||
| using Volo.Abp.AspNetCore.Mvc; | ||||
| using Volo.Abp.Authorization; | ||||
| 
 | ||||
| namespace BookStore.Controllers; | ||||
| 
 | ||||
| [Route("api/wrap")] | ||||
| public class WrapController : AbpControllerBase | ||||
| { | ||||
|     [HttpGet("string")] | ||||
|     public Task<string> GetResultAsync() | ||||
|     { | ||||
|         return Task.FromResult("Hello!"); | ||||
|     } | ||||
| 
 | ||||
|     [HttpGet("person")] | ||||
|     public Task<Person> GetPersonAsync() | ||||
|     { | ||||
|         var person = new Person | ||||
|         { | ||||
|             Name = "wwwk", | ||||
|             Age = 18 | ||||
|         }; | ||||
| 
 | ||||
|         return Task.FromResult(person); | ||||
|     } | ||||
| 
 | ||||
|     [HttpGet("throw-exception")] | ||||
|     public Task ThrowException() | ||||
|     { | ||||
|         throw new UserFriendlyException("触发了一个异常!"); | ||||
|     } | ||||
| 
 | ||||
|     [HttpGet("throw-auth-exception")] | ||||
|     public Task ThrowAuthException() | ||||
|     { | ||||
|         throw new AbpAuthorizationException(code: AbpAuthorizationErrorCodes.GivenPolicyHasNotGrantedForGivenResource); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| public class Person | ||||
| { | ||||
|     public string? Name { get; set; } | ||||
|     public int Age { get; set; } | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Volo.Abp.AuditLogging.EntityFrameworkCore; | ||||
| using Volo.Abp.EntityFrameworkCore; | ||||
| using Volo.Abp.FeatureManagement.EntityFrameworkCore; | ||||
| using Volo.Abp.Identity.EntityFrameworkCore; | ||||
| using Volo.Abp.IdentityServer.EntityFrameworkCore; | ||||
| using Volo.Abp.PermissionManagement.EntityFrameworkCore; | ||||
| using Volo.Abp.SettingManagement.EntityFrameworkCore; | ||||
| using Volo.Abp.TenantManagement.EntityFrameworkCore; | ||||
| 
 | ||||
| namespace BookStore.Data; | ||||
| 
 | ||||
| public class BookStoreDbContext : AbpDbContext<BookStoreDbContext> | ||||
| { | ||||
|     public BookStoreDbContext(DbContextOptions<BookStoreDbContext> options) | ||||
|         : base(options) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     protected override void OnModelCreating(ModelBuilder builder) | ||||
|     { | ||||
|         base.OnModelCreating(builder); | ||||
| 
 | ||||
|         /* Include modules to your migration db context */ | ||||
| 
 | ||||
|         builder.ConfigurePermissionManagement(); | ||||
|         builder.ConfigureSettingManagement(); | ||||
|         builder.ConfigureAuditLogging(); | ||||
|         builder.ConfigureIdentity(); | ||||
|         builder.ConfigureIdentityServer(); | ||||
|         builder.ConfigureFeatureManagement(); | ||||
|         builder.ConfigureTenantManagement(); | ||||
| 
 | ||||
|         /* Configure your own entities here */ | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,203 @@ | ||||
| using System.Diagnostics; | ||||
| using System.Runtime.InteropServices; | ||||
| using Microsoft.Extensions.Logging.Abstractions; | ||||
| using Volo.Abp.Data; | ||||
| using Volo.Abp.DependencyInjection; | ||||
| using Volo.Abp.Identity; | ||||
| using Volo.Abp.MultiTenancy; | ||||
| using Volo.Abp.TenantManagement; | ||||
| 
 | ||||
| namespace BookStore.Data; | ||||
| 
 | ||||
| public class BookStoreDbMigrationService : ITransientDependency | ||||
| { | ||||
|     public ILogger<BookStoreDbMigrationService> Logger { get; set; } | ||||
| 
 | ||||
|     private readonly IDataSeeder _dataSeeder; | ||||
|     private readonly BookStoreEFCoreDbSchemaMigrator _dbSchemaMigrator; | ||||
|     private readonly ITenantRepository _tenantRepository; | ||||
|     private readonly ICurrentTenant _currentTenant; | ||||
| 
 | ||||
|     public BookStoreDbMigrationService( | ||||
|         IDataSeeder dataSeeder, | ||||
|         BookStoreEFCoreDbSchemaMigrator dbSchemaMigrator, | ||||
|         ITenantRepository tenantRepository, | ||||
|         ICurrentTenant currentTenant) | ||||
|     { | ||||
|         _dataSeeder = dataSeeder; | ||||
|         _dbSchemaMigrator = dbSchemaMigrator; | ||||
|         _tenantRepository = tenantRepository; | ||||
|         _currentTenant = currentTenant; | ||||
| 
 | ||||
|         Logger = NullLogger<BookStoreDbMigrationService>.Instance; | ||||
|     } | ||||
| 
 | ||||
|     public async Task MigrateAsync() | ||||
|     { | ||||
|         var initialMigrationAdded = AddInitialMigrationIfNotExist(); | ||||
| 
 | ||||
|         if (initialMigrationAdded) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         Logger.LogInformation("Started database migrations..."); | ||||
| 
 | ||||
|         await MigrateDatabaseSchemaAsync(); | ||||
|         await SeedDataAsync(); | ||||
| 
 | ||||
|         Logger.LogInformation($"Successfully completed host database migrations."); | ||||
| 
 | ||||
|         var tenants = await _tenantRepository.GetListAsync(includeDetails: true); | ||||
| 
 | ||||
|         var migratedDatabaseSchemas = new HashSet<string>(); | ||||
|         foreach (var tenant in tenants) | ||||
|         { | ||||
|             using (_currentTenant.Change(tenant.Id)) | ||||
|             { | ||||
|                 if (tenant.ConnectionStrings.Any()) | ||||
|                 { | ||||
|                     var tenantConnectionStrings = tenant.ConnectionStrings | ||||
|                         .Select(x => x.Value) | ||||
|                         .ToList(); | ||||
| 
 | ||||
|                     if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings)) | ||||
|                     { | ||||
|                         await MigrateDatabaseSchemaAsync(tenant); | ||||
| 
 | ||||
|                         migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 await SeedDataAsync(tenant); | ||||
|             } | ||||
| 
 | ||||
|             Logger.LogInformation($"Successfully completed {tenant.Name} tenant database migrations."); | ||||
|         } | ||||
| 
 | ||||
|         Logger.LogInformation("Successfully completed all database migrations."); | ||||
|         Logger.LogInformation("You can safely end this process..."); | ||||
|     } | ||||
| 
 | ||||
|     private async Task MigrateDatabaseSchemaAsync(Tenant tenant = null) | ||||
|     { | ||||
|         Logger.LogInformation($"Migrating schema for {(tenant == null ? "host" : tenant.Name + " tenant")} database..."); | ||||
|         await _dbSchemaMigrator.MigrateAsync(); | ||||
|     } | ||||
| 
 | ||||
|     private async Task SeedDataAsync(Tenant tenant = null) | ||||
|     { | ||||
|         Logger.LogInformation($"Executing {(tenant == null ? "host" : tenant.Name + " tenant")} database seed..."); | ||||
| 
 | ||||
|         await _dataSeeder.SeedAsync(new DataSeedContext(tenant?.Id) | ||||
|             .WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, IdentityDataSeedContributor.AdminEmailDefaultValue) | ||||
|             .WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, IdentityDataSeedContributor.AdminPasswordDefaultValue) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private bool AddInitialMigrationIfNotExist() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (!DbMigrationsProjectExists()) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         catch (Exception) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         try | ||||
|         { | ||||
|             if (!MigrationsFolderExists()) | ||||
|             { | ||||
|                 AddInitialMigration(); | ||||
|                 return true; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         catch (Exception e) | ||||
|         { | ||||
|             Logger.LogWarning("Couldn't determinate if any migrations exist : " + e.Message); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private bool DbMigrationsProjectExists() | ||||
|     { | ||||
|         return Directory.Exists(GetEntityFrameworkCoreProjectFolderPath()); | ||||
|     } | ||||
| 
 | ||||
|     private bool MigrationsFolderExists() | ||||
|     { | ||||
|         var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath(); | ||||
| 
 | ||||
|         return Directory.Exists(Path.Combine(dbMigrationsProjectFolder, "Migrations")); | ||||
|     } | ||||
| 
 | ||||
|     private void AddInitialMigration() | ||||
|     { | ||||
|         Logger.LogInformation("Creating initial migration..."); | ||||
| 
 | ||||
|         string argumentPrefix; | ||||
|         string fileName; | ||||
| 
 | ||||
|         if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) | ||||
|         { | ||||
|             argumentPrefix = "-c"; | ||||
|             fileName = "/bin/bash"; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             argumentPrefix = "/C"; | ||||
|             fileName = "cmd.exe"; | ||||
|         } | ||||
| 
 | ||||
|         var procStartInfo = new ProcessStartInfo(fileName, | ||||
|             $"{argumentPrefix} \"abp create-migration-and-run-migrator \"{GetEntityFrameworkCoreProjectFolderPath()}\" --nolayers\"" | ||||
|         ); | ||||
| 
 | ||||
|         try | ||||
|         { | ||||
|             Process.Start(procStartInfo); | ||||
|         } | ||||
|         catch (Exception) | ||||
|         { | ||||
|             throw new Exception("Couldn't run ABP CLI..."); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private string GetEntityFrameworkCoreProjectFolderPath() | ||||
|     { | ||||
|         var slnDirectoryPath = GetSolutionDirectoryPath(); | ||||
| 
 | ||||
|         if (slnDirectoryPath == null) | ||||
|         { | ||||
|             throw new Exception("Solution folder not found!"); | ||||
|         } | ||||
| 
 | ||||
|         return Path.Combine(slnDirectoryPath, "BookStore"); | ||||
|     } | ||||
| 
 | ||||
|     private string GetSolutionDirectoryPath() | ||||
|     { | ||||
|         var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); | ||||
| 
 | ||||
|         while (Directory.GetParent(currentDirectory.FullName) != null) | ||||
|         { | ||||
|             currentDirectory = Directory.GetParent(currentDirectory.FullName); | ||||
| 
 | ||||
|             if (Directory.GetFiles(currentDirectory.FullName).FirstOrDefault(f => f.EndsWith(".sln")) != null) | ||||
|             { | ||||
|                 return currentDirectory.FullName; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Volo.Abp.DependencyInjection; | ||||
| 
 | ||||
| namespace BookStore.Data; | ||||
| 
 | ||||
| public class BookStoreEFCoreDbSchemaMigrator : ITransientDependency | ||||
| { | ||||
|     private readonly IServiceProvider _serviceProvider; | ||||
| 
 | ||||
|     public BookStoreEFCoreDbSchemaMigrator( | ||||
|         IServiceProvider serviceProvider) | ||||
|     { | ||||
|         _serviceProvider = serviceProvider; | ||||
|     } | ||||
| 
 | ||||
|     public async Task MigrateAsync() | ||||
|     { | ||||
|         /* We intentionally resolving the BookStoreDbContext | ||||
|          * from IServiceProvider (instead of directly injecting it) | ||||
|          * to properly get the connection string of the current tenant in the | ||||
|          * current scope. | ||||
|          */ | ||||
| 
 | ||||
|         await _serviceProvider | ||||
|             .GetRequiredService<BookStoreDbContext>() | ||||
|             .Database | ||||
|             .MigrateAsync(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,271 @@ | ||||
| using IdentityServer4.Models; | ||||
| using Volo.Abp.Authorization.Permissions; | ||||
| using Volo.Abp.Data; | ||||
| using Volo.Abp.DependencyInjection; | ||||
| using Volo.Abp.Guids; | ||||
| using Volo.Abp.IdentityServer.ApiResources; | ||||
| using Volo.Abp.IdentityServer.ApiScopes; | ||||
| using Volo.Abp.IdentityServer.Clients; | ||||
| using Volo.Abp.IdentityServer.IdentityResources; | ||||
| using Volo.Abp.MultiTenancy; | ||||
| using Volo.Abp.PermissionManagement; | ||||
| using Volo.Abp.Uow; | ||||
| using ApiResource = Volo.Abp.IdentityServer.ApiResources.ApiResource; | ||||
| using ApiScope = Volo.Abp.IdentityServer.ApiScopes.ApiScope; | ||||
| using Client = Volo.Abp.IdentityServer.Clients.Client; | ||||
| 
 | ||||
| namespace BookStore.Data; | ||||
| 
 | ||||
| public class IdentityServerDataSeedContributor : IDataSeedContributor, ITransientDependency | ||||
| { | ||||
|     private readonly IApiResourceRepository _apiResourceRepository; | ||||
|     private readonly IApiScopeRepository _apiScopeRepository; | ||||
|     private readonly IClientRepository _clientRepository; | ||||
|     private readonly IIdentityResourceDataSeeder _identityResourceDataSeeder; | ||||
|     private readonly IGuidGenerator _guidGenerator; | ||||
|     private readonly IPermissionDataSeeder _permissionDataSeeder; | ||||
|     private readonly IConfiguration _configuration; | ||||
|     private readonly ICurrentTenant _currentTenant; | ||||
| 
 | ||||
|     public IdentityServerDataSeedContributor( | ||||
|         IClientRepository clientRepository, | ||||
|         IApiResourceRepository apiResourceRepository, | ||||
|         IApiScopeRepository apiScopeRepository, | ||||
|         IIdentityResourceDataSeeder identityResourceDataSeeder, | ||||
|         IGuidGenerator guidGenerator, | ||||
|         IPermissionDataSeeder permissionDataSeeder, | ||||
|         IConfiguration configuration, | ||||
|         ICurrentTenant currentTenant) | ||||
|     { | ||||
|         _clientRepository = clientRepository; | ||||
|         _apiResourceRepository = apiResourceRepository; | ||||
|         _apiScopeRepository = apiScopeRepository; | ||||
|         _identityResourceDataSeeder = identityResourceDataSeeder; | ||||
|         _guidGenerator = guidGenerator; | ||||
|         _permissionDataSeeder = permissionDataSeeder; | ||||
|         _configuration = configuration; | ||||
|         _currentTenant = currentTenant; | ||||
|     } | ||||
| 
 | ||||
|     [UnitOfWork] | ||||
|     public virtual async Task SeedAsync(DataSeedContext context) | ||||
|     { | ||||
|         using (_currentTenant.Change(context?.TenantId)) | ||||
|         { | ||||
|             await _identityResourceDataSeeder.CreateStandardResourcesAsync(); | ||||
|             await CreateApiResourcesAsync(); | ||||
|             await CreateApiScopesAsync(); | ||||
|             await CreateClientsAsync(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private async Task CreateApiScopesAsync() | ||||
|     { | ||||
|         await CreateApiScopeAsync("BookStore"); | ||||
|     } | ||||
| 
 | ||||
|     private async Task CreateApiResourcesAsync() | ||||
|     { | ||||
|         var commonApiUserClaims = new[] {"email", "email_verified", "name", "phone_number", "phone_number_verified", "role"}; | ||||
|         await CreateApiResourceAsync("BookStore", commonApiUserClaims); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<ApiResource> CreateApiResourceAsync(string name, IEnumerable<string> claims) | ||||
|     { | ||||
|         var apiResource = await _apiResourceRepository.FindByNameAsync(name); | ||||
|         if (apiResource == null) | ||||
|         { | ||||
|             apiResource = await _apiResourceRepository.InsertAsync( | ||||
|                 new ApiResource( | ||||
|                     _guidGenerator.Create(), | ||||
|                     name, | ||||
|                     name + " API" | ||||
|                 ), | ||||
|                 autoSave: true | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         foreach (var claim in claims) | ||||
|         { | ||||
|             if (apiResource.FindClaim(claim) == null) | ||||
|             { | ||||
|                 apiResource.AddUserClaim(claim); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return await _apiResourceRepository.UpdateAsync(apiResource); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<ApiScope> CreateApiScopeAsync(string name) | ||||
|     { | ||||
|         var apiScope = await _apiScopeRepository.FindByNameAsync(name); | ||||
|         if (apiScope == null) | ||||
|         { | ||||
|             apiScope = await _apiScopeRepository.InsertAsync( | ||||
|                 new ApiScope( | ||||
|                     _guidGenerator.Create(), | ||||
|                     name, | ||||
|                     name + " API" | ||||
|                 ), | ||||
|                 autoSave: true | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         return apiScope; | ||||
|     } | ||||
| 
 | ||||
|     private async Task CreateClientsAsync() | ||||
|     { | ||||
|         var commonScopes = new[] | ||||
|         { | ||||
|             "email", | ||||
|             "openid", | ||||
|             "profile", | ||||
|             "role", | ||||
|             "phone", | ||||
|             "address", | ||||
|             "BookStore" | ||||
|         }; | ||||
| 
 | ||||
|         var configurationSection = _configuration.GetSection("IdentityServer:Clients"); | ||||
| 
 | ||||
|         // Angular Client | ||||
|         var consoleAndAngularClientId = configurationSection["BookStore_App:ClientId"]; | ||||
|         if (!consoleAndAngularClientId.IsNullOrWhiteSpace()) | ||||
|         { | ||||
|             var webClientRootUrl = configurationSection["BookStore_App:RootUrl"]?.TrimEnd('/'); | ||||
| 
 | ||||
|             await CreateClientAsync( | ||||
|                 name: consoleAndAngularClientId, | ||||
|                 scopes: commonScopes, | ||||
|                 grantTypes: new[] { "password", "client_credentials", "authorization_code" }, | ||||
|                 secret: (configurationSection["BookStore_App:ClientSecret"] ?? "1q2w3e*").Sha256(), | ||||
|                 requireClientSecret: false, | ||||
|                 redirectUri: webClientRootUrl, | ||||
|                 postLogoutRedirectUri: webClientRootUrl, | ||||
|                 corsOrigins: new[] { webClientRootUrl.RemovePostFix("/") } | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         // Swagger Client | ||||
|         var swaggerClientId = configurationSection["BookStore_Swagger:ClientId"]; | ||||
|         if (!swaggerClientId.IsNullOrWhiteSpace()) | ||||
|         { | ||||
|             var swaggerRootUrl = configurationSection["BookStore_Swagger:RootUrl"].TrimEnd('/'); | ||||
| 
 | ||||
|             await CreateClientAsync( | ||||
|                 name: swaggerClientId, | ||||
|                 scopes: commonScopes, | ||||
|                 grantTypes: new[] { "authorization_code" }, | ||||
|                 secret: configurationSection["BookStore_Swagger:ClientSecret"]?.Sha256(), | ||||
|                 requireClientSecret: false, | ||||
|                 redirectUri: $"{swaggerRootUrl}/swagger/oauth2-redirect.html", | ||||
|                 corsOrigins: new[] { swaggerRootUrl.RemovePostFix("/") } | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private async Task<Client> CreateClientAsync( | ||||
|         string name, | ||||
|         IEnumerable<string> scopes, | ||||
|         IEnumerable<string> grantTypes, | ||||
|         string secret = null, | ||||
|         string redirectUri = null, | ||||
|         string postLogoutRedirectUri = null, | ||||
|         string frontChannelLogoutUri = null, | ||||
|         bool requireClientSecret = true, | ||||
|         bool requirePkce = false, | ||||
|         IEnumerable<string> permissions = null, | ||||
|         IEnumerable<string> corsOrigins = null) | ||||
|     { | ||||
|         var client = await _clientRepository.FindByClientIdAsync(name); | ||||
|         if (client == null) | ||||
|         { | ||||
|             client = await _clientRepository.InsertAsync( | ||||
|                 new Client( | ||||
|                     _guidGenerator.Create(), | ||||
|                     name | ||||
|                 ) | ||||
|                 { | ||||
|                     ClientName = name, | ||||
|                     ProtocolType = "oidc", | ||||
|                     Description = name, | ||||
|                     AlwaysIncludeUserClaimsInIdToken = true, | ||||
|                     AllowOfflineAccess = true, | ||||
|                     AbsoluteRefreshTokenLifetime = 31536000, //365 days | ||||
|                         AccessTokenLifetime = 31536000, //365 days | ||||
|                         AuthorizationCodeLifetime = 300, | ||||
|                     IdentityTokenLifetime = 300, | ||||
|                     RequireConsent = false, | ||||
|                     FrontChannelLogoutUri = frontChannelLogoutUri, | ||||
|                     RequireClientSecret = requireClientSecret, | ||||
|                     RequirePkce = requirePkce | ||||
|                 }, | ||||
|                 autoSave: true | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         foreach (var scope in scopes) | ||||
|         { | ||||
|             if (client.FindScope(scope) == null) | ||||
|             { | ||||
|                 client.AddScope(scope); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         foreach (var grantType in grantTypes) | ||||
|         { | ||||
|             if (client.FindGrantType(grantType) == null) | ||||
|             { | ||||
|                 client.AddGrantType(grantType); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (!secret.IsNullOrEmpty()) | ||||
|         { | ||||
|             if (client.FindSecret(secret) == null) | ||||
|             { | ||||
|                 client.AddSecret(secret); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (redirectUri != null) | ||||
|         { | ||||
|             if (client.FindRedirectUri(redirectUri) == null) | ||||
|             { | ||||
|                 client.AddRedirectUri(redirectUri); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (postLogoutRedirectUri != null) | ||||
|         { | ||||
|             if (client.FindPostLogoutRedirectUri(postLogoutRedirectUri) == null) | ||||
|             { | ||||
|                 client.AddPostLogoutRedirectUri(postLogoutRedirectUri); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (permissions != null) | ||||
|         { | ||||
|             await _permissionDataSeeder.SeedAsync( | ||||
|                 ClientPermissionValueProvider.ProviderName, | ||||
|                 name, | ||||
|                 permissions, | ||||
|                 null | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         if (corsOrigins != null) | ||||
|         { | ||||
|             foreach (var origin in corsOrigins) | ||||
|             { | ||||
|                 if (!origin.IsNullOrWhiteSpace() && client.FindCorsOrigin(origin) == null) | ||||
|                 { | ||||
|                     client.AddCorsOrigin(origin); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return await _clientRepository.UpdateAsync(client); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "ar", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "مرحبا", | ||||
|     "Welcome_Text": "هذا هو قالب بدء تشغيل تطبيق ذو طبقة واحدة مبسط لإطار عمل ABP.", | ||||
|     "Menu:Home": "الرئيسية" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "cs", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Vítejte", | ||||
|     "Welcome_Text": "Toto je minimalistická šablona pro spuštění aplikace s jednou vrstvou pro ABP Framework.", | ||||
|     "Menu:Home": "Úvod" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "de-DE", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Willkommen", | ||||
|     "Welcome_Text": "Dies ist eine minimalistische, einschichtige Anwendungsstartvorlage für das ABP-Framework.", | ||||
|     "Menu:Home": "Home" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "en-GB", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Welcome_Title", | ||||
|     "Welcome_Text": "This is a minimalist, single layer application startup template for the ABP Framework.", | ||||
|     "Menu:Home": "Home" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "en", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Welcome", | ||||
|     "Welcome_Text": "This is a minimalist, single layer application startup template for the ABP Framework.", | ||||
|     "Menu:Home": "Home" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "es", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Bienvenido", | ||||
|     "Welcome_Text": "Esta es una plantilla de inicio de aplicación minimalista de una sola capa para ABP Framework.", | ||||
|     "Menu:Home": "Inicio" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "fi", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Tervetuloa", | ||||
|     "Welcome_Text": "Tämä on minimalistinen yksikerroksinen sovelluksen käynnistysmalli ABP Frameworkille.", | ||||
|     "Menu:Home": "Koti" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|  "culture": "fr", | ||||
|  "texts": { | ||||
|   "Welcome_Title": "Bienvenue", | ||||
|   "Welcome_Text": "Il s'agit d'un modèle de démarrage d'application minimaliste à une seule couche pour le cadre ABP.", | ||||
|   "Menu:Home": "Accueil" | ||||
|  } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "hu", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Üdvözlöm", | ||||
|     "Welcome_Text": "Ez egy minimalista, egyrétegű alkalmazásindítási sablon az ABP-keretrendszerhez.", | ||||
|     "Menu:Home": "Kezdőlap" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "is", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Velkomin", | ||||
|     "Welcome_Text": "Þetta er lægstur, eins lags ræsingarsniðmát fyrir ABP Framework.", | ||||
|     "Menu:Home": "Heim" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "it", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Benvenuto", | ||||
|     "Welcome_Text": "Questo è un modello di avvio dell'applicazione minimalista a livello singolo per ABP Framework.", | ||||
|     "Menu:Home": "Home" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "nl", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Welkom", | ||||
|     "Welcome_Text": "Dit is een minimalistische, enkellaagse applicatie-opstartsjabloon voor het ABP Framework.", | ||||
|     "Menu:Home": "Home" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "pl-PL", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Witaj", | ||||
|     "Welcome_Text": "Jest to minimalistyczny, jednowarstwowy szablon uruchamiania aplikacji dla ABP Framework.", | ||||
|     "Menu:Home": "Home" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "pt-BR", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Seja bem-vindo!", | ||||
|     "Welcome_Text": "Este é um modelo de inicialização de aplicativo de camada única minimalista para o ABP Framework.", | ||||
|     "Menu:Home": "Principal" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "ro-RO", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Bun venit", | ||||
|     "Welcome_Text": "Acesta este un șablon de pornire a aplicației minimaliste, cu un singur strat, pentru Cadrul ABP.", | ||||
|     "Menu:Home": "Acasă" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "ru", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Bine ati venit", | ||||
|     "Welcome_Text": "Acesta este un șablon de pornire a aplicației minimaliste, cu un singur strat, pentru Cadrul ABP.", | ||||
|     "Menu:Home": "Главная" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "sk", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Vitajte", | ||||
|     "Welcome_Text": "Toto je minimalistická šablóna na spustenie aplikácie s jednou vrstvou pre rámec ABP.", | ||||
|     "Menu:Home": "Domov" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "sl", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Dobrodošli", | ||||
|     "Welcome_Text": "To je minimalistična enoslojna predloga za zagon aplikacije za ABP Framework.", | ||||
|     "Menu:Home": "Domov" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "tr", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Hoşgeldiniz", | ||||
|     "Welcome_Text": "Bu proje tek katmanlı ABP uygulamaları yapmak için bir başlangıç şablonudur.", | ||||
|     "Menu:Home": "Ana sayfa" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "vi", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "Chào mừng bạn", | ||||
|     "Welcome_Text": "Đây là một mẫu khởi động ứng dụng lớp đơn, tối giản cho ABP Framework.", | ||||
|     "Menu:Home": "Trang chủ" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "zh-Hans", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "欢迎", | ||||
|     "Welcome_Text": "这是ABP框架的极简单层应用程序启动模板.", | ||||
|     "Menu:Home": "首页" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "culture": "zh-Hant", | ||||
|   "texts": { | ||||
|     "Welcome_Title": "歡迎", | ||||
|     "Welcome_Text": "這是 ABP 框架的極簡單層應用程序啟動模板.", | ||||
|     "Menu:Home": "首頁" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| using Volo.Abp.Localization; | ||||
| 
 | ||||
| namespace BookStore.Localization; | ||||
| 
 | ||||
| [LocalizationResourceName("BookStore")] | ||||
| public class BookStoreResource | ||||
| { | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,11 @@ | ||||
| using AutoMapper; | ||||
| 
 | ||||
| namespace BookStore.ObjectMapping; | ||||
| 
 | ||||
| public class BookStoreAutoMapperProfile : Profile | ||||
| { | ||||
|     public BookStoreAutoMapperProfile() | ||||
|     { | ||||
|         /* Create your AutoMapper object mappings here */ | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,77 @@ | ||||
| using System; | ||||
| using BookStore.Data; | ||||
| using Serilog; | ||||
| using Serilog.Events; | ||||
| 
 | ||||
| namespace BookStore; | ||||
| 
 | ||||
| public class Program | ||||
| { | ||||
|     public async static Task<int> Main(string[] args) | ||||
|     { | ||||
|         AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); | ||||
|         var loggerConfiguration = new LoggerConfiguration() | ||||
| #if DEBUG | ||||
|             .MinimumLevel.Debug() | ||||
| #else | ||||
|             .MinimumLevel.Information() | ||||
| #endif | ||||
|             .MinimumLevel.Override("Microsoft", LogEventLevel.Information) | ||||
|             .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) | ||||
|             .Enrich.FromLogContext() | ||||
| #if DEBUG | ||||
|             .WriteTo.Async(c => c.File("Logs/logs.txt")) | ||||
|             .WriteTo.Async(c => c.Console()); | ||||
| #else | ||||
|             .WriteTo.Async(c => c.File("Logs/logs.txt")); | ||||
| #endif | ||||
|         if (IsMigrateDatabase(args)) | ||||
|         { | ||||
|             loggerConfiguration.MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning); | ||||
|             loggerConfiguration.MinimumLevel.Override("Microsoft", LogEventLevel.Warning); | ||||
|             loggerConfiguration.MinimumLevel.Override("IdentityServer4.Startup", LogEventLevel.Warning); | ||||
|         } | ||||
| 
 | ||||
|         Log.Logger = loggerConfiguration.CreateLogger(); | ||||
| 
 | ||||
|         try | ||||
|         { | ||||
|             var builder = WebApplication.CreateBuilder(args); | ||||
|             builder.Host.AddAppSettingsSecretsJson() | ||||
|                 .UseAutofac() | ||||
|                 .UseSerilog(); | ||||
|             await builder.AddApplicationAsync<BookStoreModule>(); | ||||
|             var app = builder.Build(); | ||||
|             await app.InitializeApplicationAsync(); | ||||
| 
 | ||||
|             if (IsMigrateDatabase(args)) | ||||
|             { | ||||
|                 await app.Services.GetRequiredService<BookStoreDbMigrationService>().MigrateAsync(); | ||||
|                 return 0; | ||||
|             } | ||||
| 
 | ||||
|             Log.Information("Starting BookStore."); | ||||
|             await app.RunAsync(); | ||||
|             return 0; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             if (ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) | ||||
|             { | ||||
|                 throw; | ||||
|             } | ||||
| 
 | ||||
|             Log.Fatal(ex, "BookStore terminated unexpectedly!"); | ||||
|             return 1; | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             Log.CloseAndFlush(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static bool IsMigrateDatabase(string[] args) | ||||
|     { | ||||
|         return args.Any(x => x.Contains("--migrate-database", StringComparison.OrdinalIgnoreCase)); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| { | ||||
|   "iisSettings": { | ||||
|     "windowsAuthentication": false, | ||||
|     "anonymousAuthentication": true, | ||||
|     "iisExpress": { | ||||
|       "applicationUrl": "https://localhost:44357", | ||||
|       "sslPort": 44357 | ||||
|     } | ||||
|   }, | ||||
|   "profiles": { | ||||
|     "BookStore": { | ||||
|       "commandName": "Project", | ||||
|       "launchBrowser": true, | ||||
|       "applicationUrl": "https://localhost:44357", | ||||
|       "environmentVariables": { | ||||
|         "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| { | ||||
|   "App": { | ||||
|     "SelfUrl": "https://localhost:44357", | ||||
|     "ClientUrl": "http://localhost:4200", | ||||
|     "CorsOrigins": "https://*.BookStore.com,http://localhost:4200", | ||||
|     "RedirectAllowedUrls": "http://localhost:4200" | ||||
|   }, | ||||
|   "ConnectionStrings": { | ||||
|     "Default": "Host=localhost;Port=5432;Database=BookStore;User ID=postgres;Password=123456;" | ||||
|   }, | ||||
|   "Redis": { | ||||
|     "Configuration": "127.0.0.1" | ||||
|   }, | ||||
|   "AuthServer": { | ||||
|     "Authority": "https://localhost:44357", | ||||
|     "RequireHttpsMetadata": "false", | ||||
|     "SwaggerClientId": "BookStore_Swagger", | ||||
|     "SwaggerClientSecret": "1q2w3e*" | ||||
|   }, | ||||
|   "StringEncryption": { | ||||
|     "DefaultPassPhrase": "Q4ki8okY7ZyMNWDw" | ||||
|   }, | ||||
|   "IdentityServer": { | ||||
|     "Clients": { | ||||
|       "BookStore_App": { | ||||
|         "ClientId": "BookStore_App", | ||||
|         "ClientSecret": "1q2w3e*", | ||||
|         "RootUrl": "http://localhost:4200" | ||||
|       }, | ||||
|       "BookStore_Swagger": { | ||||
|         "ClientId": "BookStore_Swagger", | ||||
|         "ClientSecret": "1q2w3e*", | ||||
|         "RootUrl": "https://localhost:44357" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "version": "1.0.0", | ||||
|   "name": "my-app", | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@abp/aspnetcore.mvc.ui.theme.basic": "^5.2.0-rc.2" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| {"AdditionalData":{},"Alg":"RS256","Crv":null,"D":"PqIhXe397CUD48Ulbw-kT8MwuWFckXy3hGj-huiX63FpBSRCRd9cmV81h5WI0RX6i0nrKZ2go5cakNgIqx_8gFoOP5N5j8umcU32UtwXePilRjP9RrY_9ub-P0iSmqIEQEZSOg93W7sOVL9jnvd0BLBavqOmlErz9JhU3QU9ebY3lDSdTHZWqYSVb6yeXjZGCRYZgkCgVYx1CiKkFRA5MEEM_WUBhLxVhNmbvFbwlp54PPaBBBEA3RYilv8aK0RBmhdgKRLpfMegEeM3yJh2D92EV2lx3DEhycia72AuqFQ-ZEZBAXVIjxcA8YjtsJXhaOqLnd30EbXHA-sP81kAcQ","DP":"OL_P-Fktuq1YC4YzLuhbRuO0QnkV2xYqPPYNTFJenhOafyPzT2yIAFlJooLcZX6Fe10yFsS918ozn50huIxdfgxrNw2I011-sXYGNuhWnej7OdIUnJkXdSo2Ub8mJpro3iN_524P5cxda9MD5bQAKN1SmgSDHsOZZJALVJBO4dE","DQ":"C2I3Kqu0zaLtYl4OuJxyiMtA2UmK88yFTnDm4pvSWj9XXiiBs2VYLwGevhDTcYI1gT8zar4xeUoWimHzSQxbOcp7EXsjqo_eMTt_t7gwjDRs9tSOu0_mTUhisuM996wAa-QVVzvonxtVPyzHSuoXoxNHfRAUDl1MPVcqV9imTJs","E":"AQAB","K":null,"KeyId":"E845B5C99290199A39E6D83B31E223FA","Kid":"E845B5C99290199A39E6D83B31E223FA","Kty":"RSA","N":"qjXtEBYJMcDnFV59Tk9wTd516-U6Pq_NDujOBZh3ccSvsKc0-NdkAST45WjBscXzh6ByAQcHss1oqrj6YttvzfcReA6IrPVSz1fvBi67eoCjuBVv_94aCoT1fmUpxgv2V9ZqEp23goJ2oppkyTcE7VIoKGgAQREzE4kJ3YssMPYNfR9g_14DbPTMkDYfxfGI7j4wjAOFFNPlWfBToyH7U7dgBtBeu-5NYxFrBZ_Ze0tOoJ20D_u9UZ93rAi7FVhY5787MltstmJ2R-ZNWgZjBad8s_xA6vXHP_7rBh-taAu4ID23U2DeL8w_4XncAPDNdQAZmDJ8_BcN_0uvkaVPNQ","Oth":null,"P":"05r8Rgv5TEAghb4HiCZeI6QcDi1VAXTzHI1f7r_0lliulzk_jc48i9iHGt2cJTaTba_Ln2LDAhkaIKMmptUXVvQiEzy02yeH93Iv9myQHORIwDkSvapMadOn2aCRCa_TJ3K_Bem8Ac9XKV8x4dG1nOtPOchCstfrJRM5g0HiqtM","Q":"zeuwW8cnB0JHvYpbn0dgZZf2tgwHcYNLCbHxsTGZsI6QZJxKC2EgmVFPwKiPLh2_qUegCkns4qUwt7_1eLtoqEplCmbR-rz_TO0zdKcO7-NA4jfud5nMgIIDMvECiaSSrpCoHy204823HXeEv3G6-jMyi7v2kBmfGZt_5RQByNc","QI":"aknrgXL1ZUNAbK4TcVPjG6j-9iCJTsV5LYtzud0DW1usLd67k_XSrCptK3ova__OfnnlzzJpqtCL1LFxk-WTn4ZVT8E1YcjYeaW9DDUQ80DtpYXaL0mv1P8b7ma7FE-m4nuknZeB6D5pYKXT7Tm2tvOHpYn38zzDpt7elFoohVU","Use":null,"X":null,"X5t":null,"X5tS256":null,"X5u":null,"Y":null,"KeySize":2048,"HasPrivateKey":true,"CryptoProviderFactory":{"CryptoProviderCache":{},"CustomCryptoProvider":null,"CacheSignatureProviders":true,"SignatureProviderObjectPoolCacheSize":32}} | ||||
					Loading…
					
					
				
		Reference in new issue