From 4863196b2aa48808e0bb2020ac7d7ad8e09edccc Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 27 Dec 2021 13:11:01 +0800 Subject: [PATCH 01/30] Add `AbpTenantManagementEntityFrameworkCoreModule` to `HttpApi.Host` project. --- .../MyCompanyName.MyProjectName.HttpApi.Host.csproj | 1 + .../MyProjectNameHttpApiHostModule.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index adb73d2e66..e2b1af07db 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -25,6 +25,7 @@ + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index 9a11f0cb5f..5b8d77b972 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -31,6 +31,7 @@ using Volo.Abp.PermissionManagement.EntityFrameworkCore; using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement.EntityFrameworkCore; using Volo.Abp.Swashbuckle; +using Volo.Abp.TenantManagement.EntityFrameworkCore; using Volo.Abp.VirtualFileSystem; namespace MyCompanyName.MyProjectName @@ -46,6 +47,7 @@ namespace MyCompanyName.MyProjectName typeof(AbpAuditLoggingEntityFrameworkCoreModule), typeof(AbpPermissionManagementEntityFrameworkCoreModule), typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpTenantManagementEntityFrameworkCoreModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpSwashbuckleModule) )] From d4ffac79138251bfbc8aa70f4e38f84ada8a7384 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 27 Dec 2021 15:04:54 +0800 Subject: [PATCH 02/30] Use `AsyncHelper` to call the async method. --- .../AbpBackgroundJobsRabbitMqModule.cs | 4 ++-- .../BackgroundJobs/AbpBackgroundJobsModule.cs | 6 +----- .../AbpBackgroundWorkersQuartzModule.cs | 12 +---------- .../AbpBackgroundWorkersModule.cs | 20 ++----------------- .../Volo/Abp/EventBus/AbpEventBusModule.cs | 3 +-- .../Volo/Abp/Quartz/AbpQuartzModule.cs | 11 ++-------- .../Volo/Docs/DocsDomainModule.cs | 9 +-------- .../AbpIdentityServerDomainModule.cs | 11 +--------- 8 files changed, 11 insertions(+), 65 deletions(-) diff --git a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/AbpBackgroundJobsRabbitMqModule.cs b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/AbpBackgroundJobsRabbitMqModule.cs index c601ca5549..a759479910 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/AbpBackgroundJobsRabbitMqModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/AbpBackgroundJobsRabbitMqModule.cs @@ -30,12 +30,12 @@ public class AbpBackgroundJobsRabbitMqModule : AbpModule public override void OnApplicationInitialization(ApplicationInitializationContext context) { - AsyncHelper.RunSync(() => StartJobQueueManagerAsync(context)); + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } public override void OnApplicationShutdown(ApplicationShutdownContext context) { - AsyncHelper.RunSync(() => StopJobQueueManagerAsync(context)); + AsyncHelper.RunSync(() => OnApplicationShutdownAsync(context)); } private async static Task StartJobQueueManagerAsync(ApplicationInitializationContext context) diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs index 47a165ca83..6c09a80f73 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs @@ -30,10 +30,6 @@ public class AbpBackgroundJobsModule : AbpModule public override void OnApplicationInitialization(ApplicationInitializationContext context) { - var options = context.ServiceProvider.GetRequiredService>().Value; - if (options.IsJobExecutionEnabled) - { - AsyncHelper.RunSync(() => context.AddBackgroundWorkerAsync()); - } + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } } diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs index 07586b461b..ff72cf138c 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs @@ -52,16 +52,6 @@ public class AbpBackgroundWorkersQuartzModule : AbpModule public override void OnApplicationInitialization(ApplicationInitializationContext context) { - var quartzBackgroundWorkerOptions = context.ServiceProvider.GetRequiredService>().Value; - if (quartzBackgroundWorkerOptions.IsAutoRegisterEnabled) - { - var backgroundWorkerManager = context.ServiceProvider.GetRequiredService(); - var works = context.ServiceProvider.GetServices().Where(x => x.AutoRegister); - - foreach (var work in works) - { - AsyncHelper.RunSync(() => backgroundWorkerManager.AddAsync(work)); - } - } + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } } diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs index 32341f101e..12e24c8f81 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs @@ -35,27 +35,11 @@ public class AbpBackgroundWorkersModule : AbpModule public override void OnApplicationInitialization(ApplicationInitializationContext context) { - var options = context.ServiceProvider.GetRequiredService>().Value; - if (options.IsEnabled) - { - AsyncHelper.RunSync( - () => context.ServiceProvider - .GetRequiredService() - .StartAsync() - ); - } + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } public override void OnApplicationShutdown(ApplicationShutdownContext context) { - var options = context.ServiceProvider.GetRequiredService>().Value; - if (options.IsEnabled) - { - AsyncHelper.RunSync( - () => context.ServiceProvider - .GetRequiredService() - .StopAsync() - ); - } + AsyncHelper.RunSync(() => OnApplicationShutdownAsync(context)); } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs index b4a873dcd4..30e2a9e5b0 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs @@ -40,8 +40,7 @@ public class AbpEventBusModule : AbpModule public override void OnApplicationInitialization(ApplicationInitializationContext context) { - AsyncHelper.RunSync(() => context.AddBackgroundWorkerAsync()); - AsyncHelper.RunSync(() => context.AddBackgroundWorkerAsync()); + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } private static void AddEventHandlers(IServiceCollection services) diff --git a/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs b/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs index db790a717d..10e09444c5 100644 --- a/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs +++ b/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs @@ -81,18 +81,11 @@ public class AbpQuartzModule : AbpModule public override void OnApplicationInitialization(ApplicationInitializationContext context) { - var options = context.ServiceProvider.GetRequiredService>().Value; - - _scheduler = context.ServiceProvider.GetRequiredService(); - - AsyncHelper.RunSync(() => options.StartSchedulerFactory.Invoke(_scheduler)); + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } public override void OnApplicationShutdown(ApplicationShutdownContext context) { - if (_scheduler.IsStarted) - { - AsyncHelper.RunSync(() => _scheduler.Shutdown()); - } + AsyncHelper.RunSync(() => OnApplicationShutdownAsync(context)); } } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs index d6c7a3a0a6..14919379fe 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs @@ -80,14 +80,7 @@ namespace Volo.Docs public override void OnApplicationInitialization(ApplicationInitializationContext context) { - using (var scope = context.ServiceProvider.CreateScope()) - { - if (scope.ServiceProvider.GetRequiredService>().Value.Enable) - { - var documentFullSearch = scope.ServiceProvider.GetRequiredService(); - AsyncHelper.RunSync(() => documentFullSearch.CreateIndexIfNeededAsync()); - } - } + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } } } diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs index 1f8fb93e4b..96ab4abd89 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs @@ -150,15 +150,6 @@ public class AbpIdentityServerDomainModule : AbpModule public override void OnApplicationInitialization(ApplicationInitializationContext context) { - var options = context.ServiceProvider.GetRequiredService>().Value; - if (options.IsCleanupEnabled) - { - AsyncHelper.RunSync(() => context.ServiceProvider - .GetRequiredService() - .AddAsync( - context.ServiceProvider - .GetRequiredService() - )); - } + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } } From 91503616d351df05119d951f9318a55e6623f57d Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 27 Dec 2021 21:06:03 +0800 Subject: [PATCH 03/30] Add `IObjectToPath`. --- .../AbpHttpClientProxyingOptions.cs | 3 + .../ClientProxyRequestPayloadBuilder.cs | 6 +- .../ClientProxying/ClientProxyUrlBuilder.cs | 59 +++++++++++++++---- .../ClientProxying/IObjectToFormData.cs | 3 +- .../Client/ClientProxying/IObjectToPath.cs | 10 ++++ .../ClientProxying/IObjectToQueryString.cs | 3 +- .../Volo/Abp/Http/AbpHttpClientTestModule.cs | 1 + .../DynamicProxying/IRegularTestController.cs | 4 ++ .../DynamicProxying/RegularTestController.cs | 16 +++++ .../RegularTestControllerClientProxy_Tests.cs | 17 ++++++ .../DynamicProxying/TestObjectToFormData.cs | 3 +- .../Http/DynamicProxying/TestObjectToPath.cs | 23 ++++++++ .../TestObjectToQueryString.cs | 3 +- 13 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToPath.cs create mode 100644 framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToPath.cs diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs index 87ee7de37c..ecf9199c1a 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs @@ -9,10 +9,13 @@ namespace Volo.Abp.Http.Client.ClientProxying public Dictionary FormDataConverts { get; set; } + public Dictionary PathConverts { get; set; } + public AbpHttpClientProxyingOptions() { QueryStringConverts = new Dictionary(); FormDataConverts = new Dictionary(); + PathConverts = new Dictionary(); } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs index 69066199f9..db0c73379c 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs @@ -113,6 +113,8 @@ namespace Volo.Abp.Http.Client.ClientProxying .Invoke(this, new object[] { scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.FormDataConverts[value.GetType()]), + action, + parameter, value }); @@ -168,9 +170,9 @@ namespace Volo.Abp.Http.Client.ClientProxying return formData; } - protected virtual async Task>> ObjectToFormDataAsync(IObjectToFormData converter, T value) + protected virtual async Task>> ObjectToFormDataAsync(IObjectToFormData converter, ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, T value) { - return await converter.ConvertAsync(value); + return await converter.ConvertAsync(actionApiDescription, parameterApiDescription, value); } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs index 12f7bc1ac8..7934cdbf8d 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs @@ -21,11 +21,17 @@ namespace Volo.Abp.Http.Client.ClientProxying { protected static MethodInfo CallObjectToQueryStringAsyncMethod { get; } + protected static MethodInfo CallObjectToPathAsyncMethod { get; } + static ClientProxyUrlBuilder() { CallObjectToQueryStringAsyncMethod = typeof(ClientProxyUrlBuilder) .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) .First(m => m.Name == nameof(ObjectToQueryStringAsync) && m.IsGenericMethodDefinition); + + CallObjectToPathAsyncMethod = typeof(ClientProxyUrlBuilder) + .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .First(m => m.Name == nameof(ObjectToPathAsync) && m.IsGenericMethodDefinition); } protected IServiceScopeFactory ServiceScopeFactory { get; } @@ -46,22 +52,22 @@ namespace Volo.Abp.Http.Client.ClientProxying { var urlBuilder = new StringBuilder(action.Url); - await ReplacePathVariablesAsync(urlBuilder, action.Parameters, methodArguments, apiVersion); - await AddQueryStringParametersAsync(urlBuilder, action.Parameters, methodArguments, apiVersion); + await ReplacePathVariablesAsync(urlBuilder, action, methodArguments, apiVersion); + await AddQueryStringParametersAsync(urlBuilder, action, methodArguments, apiVersion); return urlBuilder.ToString(); } } - protected virtual Task ReplacePathVariablesAsync(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected virtual async Task ReplacePathVariablesAsync(StringBuilder urlBuilder, ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { - var pathParameters = actionParameters + var pathParameters = action.Parameters .Where(p => p.BindingSourceId == ParameterBindingSources.Path) .ToArray(); if (!pathParameters.Any()) { - return Task.CompletedTask; + return; } if (pathParameters.Any(p => p.Name == "apiVersion")) @@ -81,7 +87,7 @@ namespace Volo.Abp.Http.Client.ClientProxying } else if (pathParameter.DefaultValue != null) { - urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", pathParameter.DefaultValue.ToString()); + urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", await ConvertValueToStringAsync(pathParameter.DefaultValue)); } else { @@ -90,16 +96,36 @@ namespace Volo.Abp.Http.Client.ClientProxying } else { - urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", value.ToString()); + if (HttpClientProxyingOptions.PathConverts.ContainsKey(value.GetType())) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var path = await (Task)CallObjectToPathAsyncMethod + .MakeGenericMethod(value.GetType()) + .Invoke(this, new object[] + { + scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.PathConverts[value.GetType()]), + action, + pathParameter, + value + }); + + if (path != null) + { + urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", path); + continue; + } + } + } + + urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", await ConvertValueToStringAsync(value)); } } - - return Task.CompletedTask; } - protected virtual async Task AddQueryStringParametersAsync(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected virtual async Task AddQueryStringParametersAsync(StringBuilder urlBuilder, ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { - var queryStringParameters = actionParameters + var queryStringParameters = action.Parameters .Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query)) .ToArray(); @@ -122,6 +148,8 @@ namespace Volo.Abp.Http.Client.ClientProxying .Invoke(this, new object[] { scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.QueryStringConverts[value.GetType()]), + action, + queryStringParameter, value }); @@ -147,9 +175,14 @@ namespace Volo.Abp.Http.Client.ClientProxying } } - protected virtual async Task ObjectToQueryStringAsync(IObjectToQueryString converter, T value) + protected virtual async Task ObjectToQueryStringAsync(IObjectToQueryString converter, ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, T value) + { + return await converter.ConvertAsync(actionApiDescription, parameterApiDescription, value); + } + + protected virtual async Task ObjectToPathAsync(IObjectToPath converter, ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, T value) { - return await converter.ConvertAsync(value); + return await converter.ConvertAsync(actionApiDescription, parameterApiDescription, value); } protected virtual async Task AddQueryStringParameterAsync( diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs index 5aec4252e3..23a0972937 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; +using Volo.Abp.Http.Modeling; namespace Volo.Abp.Http.Client.ClientProxying { public interface IObjectToFormData { - Task>> ConvertAsync(TValue value); + Task>> ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, TValue value); } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToPath.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToPath.cs new file mode 100644 index 0000000000..2da0a3b90e --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToPath.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.Client.ClientProxying +{ + public interface IObjectToPath + { + Task ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, TValue value); + } +} diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs index ef223a903d..08b7b05dcb 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs @@ -1,9 +1,10 @@ using System.Threading.Tasks; +using Volo.Abp.Http.Modeling; namespace Volo.Abp.Http.Client.ClientProxying { public interface IObjectToQueryString { - Task ConvertAsync(TValue value); + Task ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, TValue value); } } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs index d90ef50006..5d3eec6542 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs @@ -59,6 +59,7 @@ namespace Volo.Abp.Http { options.QueryStringConverts.Add(typeof(List), typeof(TestObjectToQueryString)); options.FormDataConverts.Add(typeof(List), typeof(TestObjectToFormData)); + options.PathConverts.Add(typeof(int), typeof(TestObjectToPath)); }); } } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs index d1c1275db7..65ad05b16b 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs @@ -26,6 +26,10 @@ namespace Volo.Abp.Http.DynamicProxying Task GetObjectandIdAsync(int id, Car bodyValue); + Task GetObjectandFirstReleaseDateAsync(DateTime time, Car bodyValue); + + Task GetObjectandCountAsync(int count, Car bodyValue); + Task GetObjectAndIdWithQueryAsync(int id, Car bodyValue); Task PutValueWithBodyAsync(string bodyValue); diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs index 782b839626..8101fb143e 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs @@ -86,6 +86,22 @@ namespace Volo.Abp.Http.DynamicProxying return Task.FromResult(bodyValue); } + [HttpGet] + [Route("post-object-and-first-release-date-with-url/{time:datetime}")] + public Task GetObjectandFirstReleaseDateAsync(DateTime time, Car bodyValue) + { + bodyValue.FirstReleaseDate = time; + return Task.FromResult(bodyValue); + } + + [HttpGet] + [Route("post-object-and-count-with-url/{count}")] + public Task GetObjectandCountAsync(int count, Car bodyValue) + { + bodyValue.Year = count; + return Task.FromResult(bodyValue); + } + [HttpGet] [Route("post-object-and-id-with-url-and-query/{id}")] public Task GetObjectAndIdWithQueryAsync(int id, Car bodyValue) diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs index c1c46b298f..5c60410bb4 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs @@ -124,6 +124,23 @@ namespace Volo.Abp.Http.DynamicProxying result.Model.ShouldBe("Ford"); } + [Fact] + public async Task GetObjectandFirstReleaseDateAsync() + { + var time = DateTime.Now; + var result = await _controller.GetObjectandFirstReleaseDateAsync(time, new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); + result.FirstReleaseDate.ToUniversalTime().ShouldBe(time.ToUniversalTime()); + result.Model.ShouldBe("Ford"); + } + + [Fact] + public async Task GetObjectandCountAsync() + { + var result = await _controller.GetObjectandCountAsync(-1, new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); + result.Year.ShouldBe(888); + result.Model.ShouldBe("Ford"); + } + [Fact] public async Task GetObjectAndIdWithQueryAsync() { diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs index 65b65fc54b..face0c7ac0 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs @@ -4,13 +4,14 @@ using System.Text; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.Http.Modeling; using Volo.Abp.TestApp.Application.Dto; namespace Volo.Abp.Http.DynamicProxying { public class TestObjectToFormData : IObjectToFormData>, ITransientDependency { - public Task>> ConvertAsync(List values) + public Task>> ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, List values) { if (values.IsNullOrEmpty()) { diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToPath.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToPath.cs new file mode 100644 index 0000000000..a9b7936578 --- /dev/null +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToPath.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.DynamicProxying; + +public class TestObjectToPath : IObjectToPath, ITransientDependency +{ + public Task ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, int value) + { + if (actionApiDescription.Name == nameof(IRegularTestController.GetObjectandCountAsync)) + { + if (value <= 0) + { + value = 888; + } + return Task.FromResult(value.ToString()); + } + + return Task.FromResult(null); + } +} diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs index ae11a7bf2f..73bd570bb9 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs @@ -3,13 +3,14 @@ using System.Text; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.Http.Modeling; using Volo.Abp.TestApp.Application.Dto; namespace Volo.Abp.Http.DynamicProxying { public class TestObjectToQueryString : IObjectToQueryString>, ITransientDependency { - public Task ConvertAsync(List values) + public Task ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, List values) { if (values.IsNullOrEmpty()) { From 409a397036d3f337967f5006f360cf1ef3bca5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 27 Dec 2021 18:05:59 +0300 Subject: [PATCH 04/30] Increase to v5.1. --- common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.props b/common.props index eb7060b4ec..4eb29c0b8e 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 5.0.0 + 5.1.0 $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ From bfbcd3e7679a6c79e20516850211a46eb3006558 Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Tue, 28 Dec 2021 01:56:14 +0800 Subject: [PATCH 05/30] Fix the installer project in module template. --- .../MyCompanyName.MyProjectName.Installer/FodyWeavers.xml | 2 +- .../MyCompanyName.MyProjectName.Installer.csproj | 5 ++--- .../MyProjectNameInstallerModule.cs | 2 +- .../MyProjectNameInstallerPipelineBuilder.cs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/FodyWeavers.xml b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/FodyWeavers.xml index 00e1d9a1c1..be0de3a908 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/FodyWeavers.xml +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/FodyWeavers.xml @@ -1,3 +1,3 @@  - + \ No newline at end of file diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyCompanyName.MyProjectName.Installer.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyCompanyName.MyProjectName.Installer.csproj index 6fd3cf892a..04e01c4466 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyCompanyName.MyProjectName.Installer.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyCompanyName.MyProjectName.Installer.csproj @@ -1,12 +1,11 @@ - - + net5.0 true - + MyCompanyName.MyProjectName diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyProjectNameInstallerModule.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyProjectNameInstallerModule.cs index 805342825b..7b530fd9d3 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyProjectNameInstallerModule.cs +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyProjectNameInstallerModule.cs @@ -2,7 +2,7 @@ using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; -namespace MyCompany.MyProjectName; +namespace MyCompanyName.MyProjectName; [DependsOn( typeof(AbpStudioModuleInstallerModule), diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyProjectNameInstallerPipelineBuilder.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyProjectNameInstallerPipelineBuilder.cs index c2106e92d6..1a0eae46a5 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyProjectNameInstallerPipelineBuilder.cs +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Installer/MyProjectNameInstallerPipelineBuilder.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; using Volo.Abp.Studio.ModuleInstalling; -namespace MyCompany.MyProjectName; +namespace MyCompanyName.MyProjectName; [Dependency(ServiceLifetime.Transient, ReplaceServices = true)] [ExposeServices(typeof(IModuleInstallingPipelineBuilder))] From 1fd6f3e4120e68ddcb3a1b4171e1c9b1b3e59df6 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 28 Dec 2021 08:48:35 +0800 Subject: [PATCH 06/30] Upgrade `Npgsql.EntityFrameworkCore.PostgreSQL` to 6.0.2. --- .../Volo.Abp.EntityFrameworkCore.PostgreSql.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj index 9acb67c7ea..ec983f6891 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj @@ -19,7 +19,7 @@ - + From 8f66bf303e182c5378866039bd001313d6de00ee Mon Sep 17 00:00:00 2001 From: Luke Ni Date: Tue, 28 Dec 2021 13:10:56 +0800 Subject: [PATCH 07/30] translate to zh-Hans --- docs/en/Tutorials/Part-5.md | 4 +- docs/zh-Hans/Tutorials/Part-5.md | 636 ++++++++++++++++++++++++++++++- 2 files changed, 637 insertions(+), 3 deletions(-) diff --git a/docs/en/Tutorials/Part-5.md b/docs/en/Tutorials/Part-5.md index 4a4611809a..9c2863ca0a 100644 --- a/docs/en/Tutorials/Part-5.md +++ b/docs/en/Tutorials/Part-5.md @@ -10,7 +10,7 @@ In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: -* **{{DB_Value}}** as the ORM provider. +* **{{DB_Value}}** as the ORM provider. * **{{UI_Value}}** as the UI Framework. This tutorial is organized as the following parts; @@ -142,7 +142,7 @@ Now, you can use the permissions to authorize the book management. ### Application Layer & HTTP API -Open the `BookAppService` class and add set the policy names as the permission names defined above: +Open the `BookAppService` class and set the policy names as the permission names defined above: ````csharp using System; diff --git a/docs/zh-Hans/Tutorials/Part-5.md b/docs/zh-Hans/Tutorials/Part-5.md index 602306c484..df1ef80ad9 100644 --- a/docs/zh-Hans/Tutorials/Part-5.md +++ b/docs/zh-Hans/Tutorials/Part-5.md @@ -1 +1,635 @@ -TODO.. \ No newline at end of file +# Web应用程序开发教程 - 第五章: 授权 +````json +//[doc-params] +{ + "UI": ["MVC","Blazor","BlazorServer","NG"], + "DB": ["EF","Mongo"] +} +```` +## About This Tutorial + +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: + +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. + + This tutorial is organized as the following parts; + +- [Part 1: 创建服务端](Part-1.md) +- [Part 2: 图书列表页面](Part-2.md) +- [Part 3: 创建,更新和删除图书](Part-2.md) +- [Part 4: 集成测试](Part-4.md) +- **Part 5: 授权**(本章) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) + +## 下载源码 + +本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合: + +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) + + > 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later). + + > 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path + > `git config --system core.longpaths true` + + {{if UI == "MVC" && DB == "EF"}} + +### 视频教程 + +本章也被录制为视频教程 **发布在YouTube**. + +{{end}} + +## 权限 + +ABP框架提供了一个基于ASP.NET Core[授权基础架构](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction)的[授权系统](../Authorization.md). 基于标准授权基础架构的一个主要功能是添加了 **权限系统**, 这个系统允许定义权限并且根据角色, 用户或客户端启用/禁用权限. + +### 权限名称 + +权限必须有唯一的名称 (一个 `字符串`). 最好的方法是把它定义为一个 `常量`, 这样我们就可以重用这个权限名称了. + +打开 `Acme.BookStore.Application.Contracts` 项目中的 `BookStorePermissions` 类 (位于 `Permissions` 文件夹) 并替换为以下代码: + +````csharp +namespace Acme.BookStore.Permissions +{ + public static class BookStorePermissions + { + public const string GroupName = "BookStore"; + + public static class Books + { + public const string Default = GroupName + ".Books"; + public const string Create = Default + ".Create"; + public const string Edit = Default + ".Edit"; + public const string Delete = Default + ".Delete"; + } + } +} +```` + +权限名称具有层次结构. 例如, "创建图书" 权限被定义为 `BookStore.Books.Create`. ABP不强制必须如此, 但这是一种有益的做法. + +### 权限定义 + +在使用权限前必须定义它们. + +打开 `Acme.BookStore.Application.Contracts` 项目中的 `BookStorePermissionDefinitionProvider` 类 (位于 `Permissions` 文件夹) 并替换为以下代码: + +````csharp +using Acme.BookStore.Localization; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; + +namespace Acme.BookStore.Permissions +{ + public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore")); + + var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books")); + booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create")); + booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit")); + booksPermission.AddChild(BookStorePermissions.Books.Delete, L("Permission:Books.Delete")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} +```` + +这个类定义了一个 **权限组** (在UI上分组权限, 下文会看到) 和 权限组中的**4个权限**. 而且, **创建**, **编辑** 和 **删除** 是 `BookStorePermissions.Books.Default` 权限的子权限. **仅当父权限被选择**时, 子权限才能被选择. + +最后, 编辑本地化文件 (`Acme.BookStore.Domain.Shared` 项目的 `Localization/BookStore` 文件夹中的 `en.json`) 定义上面使用的本地化键: + +````json +"Permission:BookStore": "Book Store", +"Permission:Books": "Book Management", +"Permission:Books.Create": "Creating new books", +"Permission:Books.Edit": "Editing the books", +"Permission:Books.Delete": "Deleting the books" +```` + +> 本地化键名可以是任意的, 并没有强制的规则. 但我们推荐上面使用的约定. + +### 权限管理界面 + +完成权限定义后, 可以在**权限管理模态窗口**看到它们. + +在*管理 -> Identity -> 角色* 页面, 选择admin角色的 *权限* 操作, 打开权限管理模态窗口: + +![bookstore-permissions-ui](images/bookstore-permissions-ui.png) + +授予你希望的权限并保存. + +> **提示**: 如果运行 `Acme.BookStore.DbMigrator` 应用程序, 新权限会被自动授予admin. + +## 授权 + +现在, 你可以使用权限授权图书管理. + +### 应用层 和 HTTP API + +打开 the `BookAppService` 类, 设置策略名称为上面定义的权限名称. + +````csharp +using System; +using Acme.BookStore.Permissions; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore.Books +{ + public class BookAppService : + CrudAppService< + Book, //The Book entity + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto>, //Used to create/update a book + IBookAppService //implement the IBookAppService + { + public BookAppService(IRepository repository) + : base(repository) + { + GetPolicyName = BookStorePermissions.Books.Default; + GetListPolicyName = BookStorePermissions.Books.Default; + CreatePolicyName = BookStorePermissions.Books.Create; + UpdatePolicyName = BookStorePermissions.Books.Edit; + DeletePolicyName = BookStorePermissions.Books.Delete; + } + } +} +```` + +加入代码到构造器. 基类中的 `CrudAppService` 自动在CRUD操作中使用这些权限. 这不仅实现了 **应用服务** 的安全性, 也实现了 **HTTP API** 安全性, 因为如前解释的, HTTP API 自动使用这些服务. (参阅 [自动 API controllers](../API/Auto-API-Controllers.md)). + +> 在稍后开发作者管理功能时, 你将会看到声明式授权, 使用 `[Authorize(...)]` 特性. + +{{if UI == "MVC"}} + +### Razor 页面 + +虽然安全的 HTTP API和应用服务阻止未授权用户使用服务, 但他们依然可以导航到图书管理页面. 虽然当页面发起第一个访问服务器的AJAX请求时会收到授权异常, 但为了更好的用户体验和安全性, 我们应该对页面进行授权. + +打开 `BookStoreWebModule` 在 `ConfigureServices` 方法中加入以下代码: + +````csharp +Configure(options => +{ + options.Conventions.AuthorizePage("/Books/Index", BookStorePermissions.Books.Default); + options.Conventions.AuthorizePage("/Books/CreateModal", BookStorePermissions.Books.Create); + options.Conventions.AuthorizePage("/Books/EditModal", BookStorePermissions.Books.Edit); +}); +```` + +现在未授权用户会被重定向至**登录页面**. + +#### 隐藏新建图书按钮 + +图书管理页面有一个 *新建图书* 按钮, 当用户没有 *图书新建* 权限时就不可见的. + +![bookstore-new-book-button-small](images/bookstore-new-book-button-small.png) + +打开 the `Pages/Books/Index.cshtml` 文件, 替换内容为以下代码: + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Permissions +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.AspNetCore.Authorization +@using Microsoft.Extensions.Localization +@model IndexModel +@inject IStringLocalizer L +@inject IAuthorizationService AuthorizationService +@section scripts +{ + +} + + + + + + @L["Books"] + + + @if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create)) + { + + } + + + + + + + +```` + +* 加入 `@inject IAuthorizationService AuthorizationService` 以访问授权服务. +* 使用 `@if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create))` 检查图书创建权限, 条件显示 *新建图书* 按钮. + +### JavaScript端 + +图书管理页面中的图书表格每行都有操作按钮. 操作按钮包括 *编辑* 和 *删除* 操作: + +![bookstore-edit-delete-actions](images/bookstore-edit-delete-actions.png) + +如果用户没有权限, 应该隐藏相关的操作. 表格行中的操作有一个 `visible` 属性, 可以设置为 `false` 隐藏操作项. + +打开 `Acme.BookStore.Web` 项目中的 `Pages/Books/Index.js`, 为 `编辑` 操作加入 `visible` 属性: + +````js +{ + text: l('Edit'), + visible: abp.auth.isGranted('BookStore.Books.Edit'), //CHECK for the PERMISSION + action: function (data) { + editModal.open({ id: data.record.id }); + } +} +```` + +对 `Delete` 操作进行同样的操作: + +````js +visible: abp.auth.isGranted('BookStore.Books.Delete') +```` + +* `abp.auth.isGranted(...)` 检查前面定义的权限. +* `visible` 也可以是一个返回 `bool` 值的函数. 这个函数可以稍后根据某些条件计算. + +### 菜单项 + +即使我们在图书管理页面的所有层都控制了权限, 应用程序的主菜单依然会显示. 我们应该隐藏用户没有权限的菜单项. + +打开 `BookStoreMenuContributor` 类, 找到下面的代码: + +````csharp +context.Menu.AddItem( + new ApplicationMenuItem( + "BooksStore", + l["Menu:BookStore"], + icon: "fa fa-book" + ).AddItem( + new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/Books" + ) + ) +); +```` + +替换为以下代码: + +````csharp +var bookStoreMenu = new ApplicationMenuItem( + "BooksStore", + l["Menu:BookStore"], + icon: "fa fa-book" +); + +context.Menu.AddItem(bookStoreMenu); + +//CHECK the PERMISSION +if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) +{ + bookStoreMenu.AddItem(new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/Books" + )); +} +```` + +你需要为 `ConfigureMenuAsync` 方法加入 `async` 关键字, 并重新组织返回值. 最终的 `BookStoreMenuContributor` 类应该如下: + +````csharp +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Acme.BookStore.Localization; +using Acme.BookStore.MultiTenancy; +using Acme.BookStore.Permissions; +using Volo.Abp.TenantManagement.Web.Navigation; +using Volo.Abp.UI.Navigation; + +namespace Acme.BookStore.Web.Menus +{ + public class BookStoreMenuContributor : IMenuContributor + { + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + await ConfigureMainMenuAsync(context); + } + } + + private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + if (!MultiTenancyConsts.IsEnabled) + { + var administration = context.Menu.GetAdministration(); + administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName); + } + + var l = context.GetLocalizer(); + + context.Menu.Items.Insert(0, new ApplicationMenuItem("BookStore.Home", l["Menu:Home"], "~/")); + + var bookStoreMenu = new ApplicationMenuItem( + "BooksStore", + l["Menu:BookStore"], + icon: "fa fa-book" + ); + + context.Menu.AddItem(bookStoreMenu); + + //CHECK the PERMISSION + if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) + { + bookStoreMenu.AddItem(new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/Books" + )); + } + } + } +} +```` + +{{else if UI == "NG"}} + +### Angular Guard 配置 + +UI的第一步是防止未认证用户看见"图书"菜单项并进入图书管理页面. + +打开 `/src/app/book/book-routing.module.ts` 替换为以下代码: + +````js +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { AuthGuard, PermissionGuard } from '@abp/ng.core'; +import { BookComponent } from './book.component'; + +const routes: Routes = [ + { path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class BookRoutingModule {} +```` + +* 从 `@abp/ng.core` 引入 `AuthGuard` 和 `PermissionGuard`. +* 在路由定义中添加 `canActivate: [AuthGuard, PermissionGuard]`. + +打开 `/src/app/route.provider.ts`, 在 `/books` 路由中添加 `requiredPolicy: 'BookStore.Books'`. `/books` 路由应该如以下配置: + +````js +{ + path: '/books', + name: '::Menu:Books', + parentName: '::Menu:BookStore', + layout: eLayoutType.application, + requiredPolicy: 'BookStore.Books', +} +```` + +### 隐藏新建图书按钮 + +当用户没有 *图书新建* 权限时, 图书管理页面上的 *新建图书* 按钮应该不可见. + +![bookstore-new-book-button-small](images/bookstore-new-book-button-small.png) + +打开 `/src/app/book/book.component.html` 文件, 替换创建按钮的HTML内容如下: + +````html + + +```` + +* 加入 `*abpPermission="'BookStore.Books.Create'"`, 当用户没有权限时隐藏按钮. + +### 隐藏编辑和删除操作 + +图书管理页面中的图书表格每行都有操作按钮. 操作按钮包括 *编辑* 和 *删除* 操作: + +![bookstore-edit-delete-actions](images/bookstore-edit-delete-actions.png) + +如果用户没有权限, 应该隐藏相关的操作. + +打开 `/src/app/book/book.component.html` 文件, 替换编辑和删除按钮的内容如下: + +````html + + + + + +```` + +* 加入 `*abpPermission="'BookStore.Books.Create'"`, 当用户没有编辑权限时隐藏按钮. +* 加入 `*abpPermission="'BookStore.Books.Delete'"`, 当用户没有删除权限时隐藏按钮. + +{{else if UI == "Blazor"}} + +### Razor验证组件 + +打开 `Acme.BookStore.Blazor` 项目中的 `/Pages/Books.razor` 文件, 在` @page` 指令和命名空间引入(`@using` 行)后添加 `Authorize` 特性, 如下所示: + +````html +@page "/books" +@attribute [Authorize(BookStorePermissions.Books.Default)] +@using Acme.BookStore.Permissions +@using Microsoft.AspNetCore.Authorization +... +```` + +添加这个特性阻止未登录用户或未授权用户访问这个页面. 用户重试后, 会被重定向到登录页面. + +### 显示/隐藏操作 + +图书管理页面上的每一种图书都有 *新建* 按钮和 *编辑*, *删除* 操作. 如果用户没有相关权限, 这些按钮/操作应该被隐藏. + +基类 `AbpCrudPageBase` 已经具有这些操作需要的功能. + +#### 设置策略 (权限) 名称 + +加入以下代码到 `Books.razor` 文件结尾: + +````csharp +@code +{ + public Books() // Constructor + { + CreatePolicyName = BookStorePermissions.Books.Create; + UpdatePolicyName = BookStorePermissions.Books.Edit; + DeletePolicyName = BookStorePermissions.Books.Delete; + } +} +```` + +基类 `AbpCrudPageBase` 自动检查相关操作的权限. 如果需要手动检查, 它也定义了相应的属性. + +* `HasCreatePermission`: True, 如果用户具有新建实体的权限. +* `HasUpdatePermission`: True, 如果用户具有编辑/更新实体的权限. +* `HasDeletePermission`: True, 如果用户具有删除实体的权限. + +> **Blazor 提示**: 当添加少量代码到 `@code` 是没有问题的. 当添加的代码变长时, 建议使用代码后置方法以便于维护. 我们将在作者部分使用这个方法. + +#### 隐藏新建图书按钮 + +检查 *新建图书* 按钮权限: + +````xml +@if (HasCreatePermission) +{ + +} +```` + +#### 隐藏编辑/删除操作 + +`EntityAction` 组件定义了 `Visible` 属性 (参数) 以条件显示操作. + +更新 `EntityActions` 部分: + +````xml + + + + +```` + +#### 关于权限缓存 + +你可以运行和测试权限. 从admin角色中移除一个图书相关权限, 观察到相关按钮/操作从UI上消失. + +在客户端, **ABP框架缓存当前用户的权限** . 所以, 当你修改了你的权限, 你需要手工 **刷新页面**. 如果不刷新并试图使用被禁的操作, 你会从服务器收到一个HTTP 403 (forbidden) 响应. + +> 修改角色或用户的权限在服务端立即生效. 所以, 缓存系统不会导致安全问题. + +### 菜单项 + +即使我们在图书管理页面的所有层都控制了权限, 应用程序的主菜单依然会显示. 我们应该隐藏用户没有权限的菜单项. + +打开 `Acme.BookStore.Blazor` 项目中的 `BookStoreMenuContributor` 类, 找到以下代码: + +````csharp +context.Menu.AddItem( + new ApplicationMenuItem( + "BooksStore", + l["Menu:BookStore"], + icon: "fa fa-book" + ).AddItem( + new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/books" + ) + ) +); +```` + +替换为以下代码: + +````csharp +var bookStoreMenu = new ApplicationMenuItem( + "BooksStore", + l["Menu:BookStore"], + icon: "fa fa-book" +); + +context.Menu.AddItem(bookStoreMenu); + +//CHECK the PERMISSION +if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) +{ + bookStoreMenu.AddItem(new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/books" + )); +} +```` + +你需要为 `ConfigureMenuAsync` 方法加入 `async` 关键字并重新整理返回值. 最终的 `ConfigureMainMenuAsync` 方法如下: + +````csharp +private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) +{ + var l = context.GetLocalizer(); + + context.Menu.Items.Insert( + 0, + new ApplicationMenuItem( + "BookStore.Home", + l["Menu:Home"], + "/", + icon: "fas fa-home" + ) + ); + + var bookStoreMenu = new ApplicationMenuItem( + "BooksStore", + l["Menu:BookStore"], + icon: "fa fa-book" + ); + + context.Menu.AddItem(bookStoreMenu); + + //CHECK the PERMISSION + if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) + { + bookStoreMenu.AddItem(new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/books" + )); + } +} +```` + +{{end}} + +## 下一章 + +查看本教程的[下一章](Part-6.md). From 918c465156628fbf20df49a46ba3db5feb138ce8 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 28 Dec 2021 14:28:55 +0800 Subject: [PATCH 08/30] Upgrade tui editor to 3.1.2 --- .../wwwroot/libs/tui-editor/toastui-editor-all.min.js | 6 +++--- .../wwwroot/libs/tui-editor/toastui-editor.min.css | 2 +- .../wwwroot/libs/tui-editor/toastui-editor-all.min.js | 6 +++--- .../wwwroot/libs/tui-editor/toastui-editor.min.css | 2 +- npm/packs/tui-editor/src/toastui-editor-all.min.js | 6 +++--- npm/packs/tui-editor/src/toastui-editor.min.css | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/tui-editor/toastui-editor-all.min.js b/modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/tui-editor/toastui-editor-all.min.js index 72d2ce7815..9803b8d7a0 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/tui-editor/toastui-editor-all.min.js +++ b/modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/tui-editor/toastui-editor-all.min.js @@ -1,12 +1,12 @@ /*! * @toast-ui/editor - * @version 3.1.1 | Wed Oct 27 2021 + * @version 3.1.2 | Mon Dec 27 2021 * @author NHN FE Development Lab * @license MIT */ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.toastui=e():(t.toastui=t.toastui||{},t.toastui.Editor=e())}(self,(function(){return function(){var t={368:function(t){ /*! @license DOMPurify 2.3.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.3/LICENSE */ -t.exports=function(){"use strict";function t(t){if(Array.isArray(t)){for(var e=0,n=Array(t.length);e1?n-1:0),o=1;o/gm),j=a(/^data-[\-\w.\u00B7-\uFFFF]/),_=a(/^aria-[\-\w]+$/),q=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),V=a(/^(?:\w+script|data):/i),$=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),U="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};function W(t){if(Array.isArray(t)){for(var e=0,n=Array(t.length);e0&&void 0!==arguments[0]?arguments[0]:J(),e=function(t){return K(t)};if(e.version="2.3.3",e.removed=[],!t||!t.document||9!==t.document.nodeType)return e.isSupported=!1,e;var n=t.document,r=t.document,o=t.DocumentFragment,i=t.HTMLTemplateElement,a=t.Node,c=t.Element,l=t.NodeFilter,u=t.NamedNodeMap,p=void 0===u?t.NamedNodeMap||t.MozNamedAttrMap:u,x=t.Text,C=t.Comment,Z=t.DOMParser,X=t.trustedTypes,Q=c.prototype,Y=S(Q,"cloneNode"),tt=S(Q,"nextSibling"),et=S(Q,"childNodes"),nt=S(Q,"parentNode");if("function"==typeof i){var rt=r.createElement("template");rt.content&&rt.content.ownerDocument&&(r=rt.content.ownerDocument)}var ot=G(X,n),it=ot&&Ft?ot.createHTML(""):"",st=r,at=st.implementation,ct=st.createNodeIterator,lt=st.createDocumentFragment,ut=st.getElementsByTagName,pt=n.importNode,dt={};try{dt=M(r).documentMode?r.documentMode:{}}catch(t){}var ft={};e.isSupported="function"==typeof nt&&at&&void 0!==at.createHTMLDocument&&9!==dt;var ht=H,mt=z,vt=j,gt=_,yt=V,bt=$,wt=q,kt=null,xt=T({},[].concat(W(E),W(N),W(O),W(A),W(I))),Ct=null,Tt=T({},[].concat(W(R),W(P),W(B),W(F))),Mt=null,St=null,Et=!0,Nt=!0,Ot=!1,Dt=!1,At=!1,Lt=!1,It=!1,Rt=!1,Pt=!1,Bt=!0,Ft=!1,Ht=!0,zt=!0,jt=!1,_t={},qt=null,Vt=T({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),$t=null,Ut=T({},["audio","video","img","source","image","track"]),Wt=null,Jt=T({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Gt="http://www.w3.org/1998/Math/MathML",Kt="http://www.w3.org/2000/svg",Zt="http://www.w3.org/1999/xhtml",Xt=Zt,Qt=!1,Yt=void 0,te=["application/xhtml+xml","text/html"],ee="text/html",ne=void 0,re=null,oe=r.createElement("form"),ie=function(t){re&&re===t||(t&&"object"===(void 0===t?"undefined":U(t))||(t={}),t=M(t),kt="ALLOWED_TAGS"in t?T({},t.ALLOWED_TAGS):xt,Ct="ALLOWED_ATTR"in t?T({},t.ALLOWED_ATTR):Tt,Wt="ADD_URI_SAFE_ATTR"in t?T(M(Jt),t.ADD_URI_SAFE_ATTR):Jt,$t="ADD_DATA_URI_TAGS"in t?T(M(Ut),t.ADD_DATA_URI_TAGS):Ut,qt="FORBID_CONTENTS"in t?T({},t.FORBID_CONTENTS):Vt,Mt="FORBID_TAGS"in t?T({},t.FORBID_TAGS):{},St="FORBID_ATTR"in t?T({},t.FORBID_ATTR):{},_t="USE_PROFILES"in t&&t.USE_PROFILES,Et=!1!==t.ALLOW_ARIA_ATTR,Nt=!1!==t.ALLOW_DATA_ATTR,Ot=t.ALLOW_UNKNOWN_PROTOCOLS||!1,Dt=t.SAFE_FOR_TEMPLATES||!1,At=t.WHOLE_DOCUMENT||!1,Rt=t.RETURN_DOM||!1,Pt=t.RETURN_DOM_FRAGMENT||!1,Bt=!1!==t.RETURN_DOM_IMPORT,Ft=t.RETURN_TRUSTED_TYPE||!1,It=t.FORCE_BODY||!1,Ht=!1!==t.SANITIZE_DOM,zt=!1!==t.KEEP_CONTENT,jt=t.IN_PLACE||!1,wt=t.ALLOWED_URI_REGEXP||wt,Xt=t.NAMESPACE||Zt,Yt=Yt=-1===te.indexOf(t.PARSER_MEDIA_TYPE)?ee:t.PARSER_MEDIA_TYPE,ne="application/xhtml+xml"===Yt?function(t){return t}:m,Dt&&(Nt=!1),Pt&&(Rt=!0),_t&&(kt=T({},[].concat(W(I))),Ct=[],!0===_t.html&&(T(kt,E),T(Ct,R)),!0===_t.svg&&(T(kt,N),T(Ct,P),T(Ct,F)),!0===_t.svgFilters&&(T(kt,O),T(Ct,P),T(Ct,F)),!0===_t.mathMl&&(T(kt,A),T(Ct,B),T(Ct,F))),t.ADD_TAGS&&(kt===xt&&(kt=M(kt)),T(kt,t.ADD_TAGS)),t.ADD_ATTR&&(Ct===Tt&&(Ct=M(Ct)),T(Ct,t.ADD_ATTR)),t.ADD_URI_SAFE_ATTR&&T(Wt,t.ADD_URI_SAFE_ATTR),t.FORBID_CONTENTS&&(qt===Vt&&(qt=M(qt)),T(qt,t.FORBID_CONTENTS)),zt&&(kt["#text"]=!0),At&&T(kt,["html","head","body"]),kt.table&&(T(kt,["tbody"]),delete Mt.tbody),s&&s(t),re=t)},se=T({},["mi","mo","mn","ms","mtext"]),ae=T({},["foreignobject","desc","title","annotation-xml"]),ce=T({},N);T(ce,O),T(ce,D);var le=T({},A);T(le,L);var ue=function(t){var e=nt(t);e&&e.tagName||(e={namespaceURI:Zt,tagName:"template"});var n=m(t.tagName),r=m(e.tagName);if(t.namespaceURI===Kt)return e.namespaceURI===Zt?"svg"===n:e.namespaceURI===Gt?"svg"===n&&("annotation-xml"===r||se[r]):Boolean(ce[n]);if(t.namespaceURI===Gt)return e.namespaceURI===Zt?"math"===n:e.namespaceURI===Kt?"math"===n&&ae[r]:Boolean(le[n]);if(t.namespaceURI===Zt){if(e.namespaceURI===Kt&&!ae[r])return!1;if(e.namespaceURI===Gt&&!se[r])return!1;var o=T({},["title","style","font","a","script"]);return!le[n]&&(o[n]||!ce[n])}return!1},pe=function(t){h(e.removed,{element:t});try{t.parentNode.removeChild(t)}catch(e){try{t.outerHTML=it}catch(e){t.remove()}}},de=function(t,n){try{h(e.removed,{attribute:n.getAttributeNode(t),from:n})}catch(t){h(e.removed,{attribute:null,from:n})}if(n.removeAttribute(t),"is"===t&&!Ct[t])if(Rt||Pt)try{pe(n)}catch(t){}else try{n.setAttribute(t,"")}catch(t){}},fe=function(t){var e=void 0,n=void 0;if(It)t=""+t;else{var o=v(t,/^[\r\n\t ]+/);n=o&&o[0]}"application/xhtml+xml"===Yt&&(t=''+t+"");var i=ot?ot.createHTML(t):t;if(Xt===Zt)try{e=(new Z).parseFromString(i,Yt)}catch(t){}if(!e||!e.documentElement){e=at.createDocument(Xt,"template",null);try{e.documentElement.innerHTML=Qt?"":i}catch(t){}}var s=e.body||e.documentElement;return t&&n&&s.insertBefore(r.createTextNode(n),s.childNodes[0]||null),Xt===Zt?ut.call(e,At?"html":"body")[0]:At?e.documentElement:s},he=function(t){return ct.call(t.ownerDocument||t,t,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT,null,!1)},me=function(t){return!(t instanceof x||t instanceof C||"string"==typeof t.nodeName&&"string"==typeof t.textContent&&"function"==typeof t.removeChild&&t.attributes instanceof p&&"function"==typeof t.removeAttribute&&"function"==typeof t.setAttribute&&"string"==typeof t.namespaceURI&&"function"==typeof t.insertBefore)},ve=function(t){return"object"===(void 0===a?"undefined":U(a))?t instanceof a:t&&"object"===(void 0===t?"undefined":U(t))&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName},ge=function(t,n,r){ft[t]&&d(ft[t],(function(t){t.call(e,n,r,re)}))},ye=function(t){var n=void 0;if(ge("beforeSanitizeElements",t,null),me(t))return pe(t),!0;if(v(t.nodeName,/[\u0080-\uFFFF]/))return pe(t),!0;var r=ne(t.nodeName);if(ge("uponSanitizeElement",t,{tagName:r,allowedTags:kt}),!ve(t.firstElementChild)&&(!ve(t.content)||!ve(t.content.firstElementChild))&&w(/<[/\w]/g,t.innerHTML)&&w(/<[/\w]/g,t.textContent))return pe(t),!0;if("select"===r&&w(/