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