diff --git a/common.props b/common.props index 6bad8e8068..6ce5cae828 100644 --- a/common.props +++ b/common.props @@ -19,4 +19,13 @@ runtime; build; native; contentfiles; analyzers + + + + + true + Never + + + diff --git a/docs/en/API/Swagger-Integration.md b/docs/en/API/Swagger-Integration.md new file mode 100644 index 0000000000..48ce252940 --- /dev/null +++ b/docs/en/API/Swagger-Integration.md @@ -0,0 +1,140 @@ +# Swagger Integration + +[Swagger (OpenAPI)](https://swagger.io/) is a language-agnostic specification for describing REST APIs. It allows both computers and humans to understand the capabilities of a REST API without direct access to the source code. Its main goals are to: + +- Minimize the amount of work needed to connect decoupled services. +- Reduce the amount of time needed to accurately document a service. + +ABP Framework offers a prebuilt module for full Swagger integration with small configurations. + +## Installation + +> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. + +If installation is needed, it is suggested to use the [ABP CLI](CLI.md) to install this package. + +### Using the ABP CLI + +Open a command line window in the folder of the `Web` or `HttpApi.Host` project (.csproj file) and type the following command: + +```bash +abp add-package Volo.Abp.Swashbuckle +``` + +### Manual Installation + +If you want to manually install; + +1. Add the [Volo.Abp.Swashbuckle](https://www.nuget.org/packages/Volo.Abp.Swashbuckle) NuGet package to your `Web` or `HttpApi.Host` project: + + `Install-Package Volo.Abp.Swashbuckle` + +2. Add the `AbpSwashbuckleModule` to the dependency list of your module: + + ```csharp + [DependsOn( + //...other dependencies + typeof(AbpSwashbuckleModule) // <-- Add module dependency like that + )] + public class YourModule : AbpModule + { + } + ``` + +## Configuration + +First, we need to use `AddAbpSwaggerGen` extension to configure Swagger in `ConfigureServices` method of our module. + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + var services = contex.Services; + + //... other configarations. + + services.AddAbpSwaggerGen( + options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test API", Version = "v1" }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + } + ); +} +``` + +Then we can use Swagger UI by calling `UseAbpSwaggerUI` method in the `OnApplicationInitialization` method of our module. + +```csharp +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + var app = context.GetApplicationBuilder(); + + //... other configarations. + + app.UseAbpSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API"); + }); + + //... other configarations. +} +``` + +## Using Swagger with OAUTH + +For non MVC/Tiered applications, we need to configure Swagger with OAUTH to handle authorization. + +> ABP Framework uses IdentityServer by default. To get more information about IDS, check this [documentation](Modules/IdentityServer.md). + + + +To do that, we need to use `AddAbpSwaggerGenWithOAuth` extension to configure Swagger with OAuth issuer and scopes in `ConfigureServices` method of our module. + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + var services = contex.Services; + + //... other configarations. + + services.AddAbpSwaggerGenWithOAuth( + "https://localhost:44341", // authority issuer + new Dictionary // + { // scopes + {"Test", "Test API"} // + }, // + options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test API", Version = "v1" }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + } + ); +} +``` + +Then we can use Swagger UI by calling `UseAbpSwaggerUI` method in the `OnApplicationInitialization` method of our module. + +> Do not forget to set `OAuthClientId` and `OAuthClientSecret`. + + +```csharp +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + var app = context.GetApplicationBuilder(); + + //... other configarations. + + app.UseAbpSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API"); + + var configuration = context.ServiceProvider.GetRequiredService(); + options.OAuthClientId("Test_Swagger"); // clientId + options.OAuthClientSecret("1q2w3e*"); // clientSecret + }); + + //... other configarations. +} +``` \ No newline at end of file diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index cfa2e12a6b..a690d9d17b 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -79,3 +79,26 @@ After you have installed these NuGet packages, you need to configure your projec } ```` + +### Dashboard Authorization + +Hangfire Dashboard provides information about your background jobs, including method names and serialized arguments as well as gives you an opportunity to manage them by performing different actions – retry, delete, trigger, etc. So it is important to restrict access to the Dashboard. +To make it secure by default, only local requests are allowed, however you can change this by following the [official documentation](http://docs.hangfire.io/en/latest/configuration/using-dashboard.html) of Hangfire. + +You can integrate the Hangfire dashboard to [ABP authorization system](Authorization.md) using the **AbpHangfireAuthorizationFilter** +class. This class is defined in the `Volo.Abp.Hangfire` package. The following example, checks if the current user is logged in to the application: + + app.UseHangfireDashboard("/hangfire", new DashboardOptions + { + AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() } + }); + +If you want to require an additional permission, you can pass it into the constructor as below: + + app.UseHangfireDashboard("/hangfire", new DashboardOptions + { + AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter("MyHangFireDashboardPermissionName") } + }); + +**Important**: `UseHangfireDashboard` should be called after the authentication middleware in your `Startup` class (probably at the last line). Otherwise, +authorization will always fail! diff --git a/docs/en/Swagger.md b/docs/en/Swagger.md deleted file mode 100644 index 1fda8a0cd7..0000000000 --- a/docs/en/Swagger.md +++ /dev/null @@ -1,3 +0,0 @@ -# Swagger UI Integration - -TODO \ No newline at end of file diff --git a/docs/en/Validation.md b/docs/en/Validation.md index c4821adf65..1d24c5e2a6 100644 --- a/docs/en/Validation.md +++ b/docs/en/Validation.md @@ -149,8 +149,8 @@ Once ABP determines a validation error, it throws an exception of type `AbpValid In addition to the automatic validation, you may want to manually validate an object. In this case, [inject](Dependency-Injection.md) and use the `IObjectValidator` service: -* `Validate` method validates the given object based on the validation rules and throws an `AbpValidationException` if it is not in a valid state. -* `GetErrors` doesn't throw an exception, but only returns the validation errors. +* `ValidateAsync` method validates the given object based on the validation rules and throws an `AbpValidationException` if it is not in a valid state. +* `GetErrorsAsync` doesn't throw an exception, but only returns the validation errors. `IObjectValidator` is implemented by the `ObjectValidator` by default. `ObjectValidator` is extensible; you can implement `IObjectValidationContributor` interface to contribute a custom logic. Example: @@ -158,13 +158,14 @@ In addition to the automatic validation, you may want to manually validate an ob public class MyObjectValidationContributor : IObjectValidationContributor, ITransientDependency { - public void AddErrors(ObjectValidationContext context) + public Task AddErrorsAsync(ObjectValidationContext context) { //Get the validating object var obj = context.ValidatingObject; //Add the validation errors if available context.Errors.Add(...); + return Task.CompletedTask; } } ```` diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 5a2b18d365..398da988e4 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -578,6 +578,10 @@ "path": "API/Application-Configuration.md" } ] + }, + { + "text": "Swagger Integration", + "path": "API/Swagger-Integration.md" } ] }, diff --git a/docs/zh-Hans/Validation.md b/docs/zh-Hans/Validation.md index f6b2daf31f..6e46b07aed 100644 --- a/docs/zh-Hans/Validation.md +++ b/docs/zh-Hans/Validation.md @@ -130,8 +130,8 @@ namespace Acme.BookStore 除了自动验证你可能需要手动验证对象,这种情况下[注入](Dependency-Injection.md)并使用 `IObjectValidator` 服务: -* `Validate` 方法根据验证​​规则验证给定对象,如果对象没有被验证通过会抛出 `AbpValidationException` 异常. -* `GetErrors` 不会抛出异常,只返回验证错误. +* `ValidateAsync` 方法根据验证​​规则验证给定对象,如果对象没有被验证通过会抛出 `AbpValidationException` 异常. +* `GetErrorsAsync` 不会抛出异常,只返回验证错误. `IObjectValidator` 默认由 `ObjectValidator` 实现. `ObjectValidator`是可扩展的; 可以实现`IObjectValidationContributor`接口提供自定义逻辑. 示例 : @@ -140,13 +140,14 @@ namespace Acme.BookStore public class MyObjectValidationContributor : IObjectValidationContributor, ITransientDependency { - public void AddErrors(ObjectValidationContext context) + public Task AddErrorsAsync(ObjectValidationContext context) { //Get the validating object var obj = context.ValidatingObject; //Add the validation errors if available context.Errors.Add(...); + return Task.CompletedTask; } } ```` diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs index 273f011f41..62d490d64d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs @@ -576,11 +576,12 @@ namespace Volo.Abp.Cli.ProjectModification return; } - var dbMigrationsProject = projectFiles.FirstOrDefault(p => p.EndsWith(".DbMigrations.csproj")); + var dbMigrationsProject = projectFiles.FirstOrDefault(p => p.EndsWith(".DbMigrations.csproj")) + ?? projectFiles.FirstOrDefault(p => p.EndsWith(".EntityFrameworkCore.csproj")) ; if (dbMigrationsProject == null) { - Logger.LogDebug("Solution doesn't have a \".DbMigrations\" project."); + Logger.LogDebug("Solution doesn't have a Migrations project."); if (!skipDbMigrations) { diff --git a/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentObjectValidationContributor.cs b/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentObjectValidationContributor.cs index ccc290f354..e2d0f39372 100644 --- a/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentObjectValidationContributor.cs +++ b/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentObjectValidationContributor.cs @@ -2,6 +2,7 @@ using FluentValidation; using System; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Validation; @@ -17,7 +18,7 @@ namespace Volo.Abp.FluentValidation _serviceProvider = serviceProvider; } - public void AddErrors(ObjectValidationContext context) + public virtual async Task AddErrorsAsync(ObjectValidationContext context) { var serviceType = typeof(IValidator<>).MakeGenericType(context.ValidatingObject.GetType()); var validator = _serviceProvider.GetService(serviceType) as IValidator; @@ -26,7 +27,7 @@ namespace Volo.Abp.FluentValidation return; } - var result = validator.Validate((IValidationContext) Activator.CreateInstance( + var result = await validator.ValidateAsync((IValidationContext) Activator.CreateInstance( typeof(ValidationContext<>).MakeGenericType(context.ValidatingObject.GetType()), context.ValidatingObject)); diff --git a/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj b/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj index 4ec9d0a3b5..6b598d0edd 100644 --- a/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj +++ b/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj @@ -19,6 +19,7 @@ + diff --git a/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireAuthorizationFilter.cs b/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireAuthorizationFilter.cs new file mode 100644 index 0000000000..15fbb4f60c --- /dev/null +++ b/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireAuthorizationFilter.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using Hangfire.Dashboard; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Users; + +namespace Volo.Abp.Hangfire +{ + public class AbpHangfireAuthorizationFilter : IDashboardAsyncAuthorizationFilter + { + private readonly string _requiredPermissionName; + + public AbpHangfireAuthorizationFilter(string requiredPermissionName = null) + { + _requiredPermissionName = requiredPermissionName; + } + + public async Task AuthorizeAsync(DashboardContext context) + { + if (!IsLoggedIn(context)) + { + return false; + } + + if (_requiredPermissionName.IsNullOrEmpty()) + { + return true; + } + + return await IsPermissionGrantedAsync(context, _requiredPermissionName); + } + + private static bool IsLoggedIn(DashboardContext context) + { + var currentUser = context.GetHttpContext().RequestServices.GetRequiredService(); + return currentUser.IsAuthenticated; + } + + private static async Task IsPermissionGrantedAsync(DashboardContext context, string requiredPermissionName) + { + var permissionChecker = context.GetHttpContext().RequestServices.GetRequiredService(); + return await permissionChecker.IsGrantedAsync(requiredPermissionName); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireModule.cs b/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireModule.cs index d69edb0b5d..1628d2f37f 100644 --- a/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireModule.cs +++ b/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireModule.cs @@ -1,10 +1,12 @@ using Hangfire; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Volo.Abp.Authorization; using Volo.Abp.Modularity; namespace Volo.Abp.Hangfire { + [DependsOn(typeof(AbpAuthorizationAbstractionsModule))] public class AbpHangfireModule : AbpModule { private BackgroundJobServer _backgroundJobServer; diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index b94a5a339d..df890812bb 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -174,7 +174,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying var response = await client.SendAsync( requestMessage, HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/, - GetCancellationToken() + GetCancellationToken(invocation) ); if (!response.IsSuccessStatusCode) @@ -306,8 +306,18 @@ namespace Volo.Abp.Http.Client.DynamicProxying return input; } - protected virtual CancellationToken GetCancellationToken() + protected virtual CancellationToken GetCancellationToken(IAbpMethodInvocation invocation) { + var cancellationTokenArg = invocation.Arguments.LastOrDefault(x => x is CancellationToken); + if (cancellationTokenArg != null) + { + var cancellationToken = (CancellationToken) cancellationTokenArg; + if (cancellationToken != default) + { + return cancellationToken; + } + } + return CancellationTokenProvider.Token; } } diff --git a/framework/src/Volo.Abp.Sms.Aliyun/Volo.Abp.Sms.Aliyun.csproj b/framework/src/Volo.Abp.Sms.Aliyun/Volo.Abp.Sms.Aliyun.csproj index cae6301daf..83cea09848 100644 --- a/framework/src/Volo.Abp.Sms.Aliyun/Volo.Abp.Sms.Aliyun.csproj +++ b/framework/src/Volo.Abp.Sms.Aliyun/Volo.Abp.Sms.Aliyun.csproj @@ -18,6 +18,7 @@ + diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs index 912f68e4af..15e367cefc 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -26,9 +27,10 @@ namespace Volo.Abp.Validation Options = options.Value; } - public void AddErrors(ObjectValidationContext context) + public Task AddErrorsAsync(ObjectValidationContext context) { ValidateObjectRecursively(context.Errors, context.ValidatingObject, currentDepth: 1); + return Task.CompletedTask; } protected virtual void ValidateObjectRecursively(List errors, object validatingObject, int currentDepth) diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IMethodInvocationValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IMethodInvocationValidator.cs index fd413bc498..89eec62116 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IMethodInvocationValidator.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IMethodInvocationValidator.cs @@ -1,7 +1,9 @@ -namespace Volo.Abp.Validation +using System.Threading.Tasks; + +namespace Volo.Abp.Validation { public interface IMethodInvocationValidator { - void Validate(MethodInvocationValidationContext context); + Task ValidateAsync(MethodInvocationValidationContext context); } } diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidationContributor.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidationContributor.cs index ca50901bcd..45d0ddcd9b 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidationContributor.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidationContributor.cs @@ -1,7 +1,9 @@ -namespace Volo.Abp.Validation +using System.Threading.Tasks; + +namespace Volo.Abp.Validation { public interface IObjectValidationContributor { - void AddErrors(ObjectValidationContext context); + Task AddErrorsAsync(ObjectValidationContext context); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs index 0ce723bab8..1b7c5881ee 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs @@ -1,20 +1,21 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; namespace Volo.Abp.Validation { public interface IObjectValidator { - void Validate( + Task ValidateAsync( object validatingObject, string name = null, bool allowNull = false ); - List GetErrors( + Task> GetErrorsAsync( object validatingObject, string name = null, bool allowNull = false ); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/MethodInvocationValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/MethodInvocationValidator.cs index 584f787a6e..3aeaf57fc7 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/MethodInvocationValidator.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/MethodInvocationValidator.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Reflection; @@ -16,7 +17,7 @@ namespace Volo.Abp.Validation _objectValidator = objectValidator; } - public virtual void Validate(MethodInvocationValidationContext context) + public virtual async Task ValidateAsync(MethodInvocationValidationContext context) { Check.NotNull(context, nameof(context)); @@ -46,7 +47,7 @@ namespace Volo.Abp.Validation ThrowValidationError(context); } - AddMethodParameterValidationErrors(context); + await AddMethodParameterValidationErrorsAsync(context); if (context.Errors.Any()) { @@ -60,7 +61,7 @@ namespace Volo.Abp.Validation { return false; } - + if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(context.Method) != null) { return true; @@ -82,22 +83,22 @@ namespace Volo.Abp.Validation ); } - protected virtual void AddMethodParameterValidationErrors(MethodInvocationValidationContext context) + protected virtual async Task AddMethodParameterValidationErrorsAsync(MethodInvocationValidationContext context) { for (var i = 0; i < context.Parameters.Length; i++) { - AddMethodParameterValidationErrors(context, context.Parameters[i], context.ParameterValues[i]); + await AddMethodParameterValidationErrorsAsync(context, context.Parameters[i], context.ParameterValues[i]); } } - protected virtual void AddMethodParameterValidationErrors(IAbpValidationResult context, ParameterInfo parameterInfo, object parameterValue) + protected virtual async Task AddMethodParameterValidationErrorsAsync(IAbpValidationResult context, ParameterInfo parameterInfo, object parameterValue) { var allowNulls = parameterInfo.IsOptional || parameterInfo.IsOut || TypeHelper.IsPrimitiveExtended(parameterInfo.ParameterType, includeEnums: true); context.Errors.AddRange( - _objectValidator.GetErrors( + await _objectValidator.GetErrorsAsync( parameterValue, parameterInfo.Name, allowNulls @@ -105,4 +106,4 @@ namespace Volo.Abp.Validation ); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs index d8e0c60b65..8e13d72938 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Options; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; @@ -18,9 +19,9 @@ namespace Volo.Abp.Validation Options = options.Value; } - public virtual void Validate(object validatingObject, string name = null, bool allowNull = false) + public virtual async Task ValidateAsync(object validatingObject, string name = null, bool allowNull = false) { - var errors = GetErrors(validatingObject, name, allowNull); + var errors = await GetErrorsAsync(validatingObject, name, allowNull); if (errors.Any()) { @@ -31,7 +32,7 @@ namespace Volo.Abp.Validation } } - public virtual List GetErrors(object validatingObject, string name = null, bool allowNull = false) + public virtual async Task> GetErrorsAsync(object validatingObject, string name = null, bool allowNull = false) { if (validatingObject == null) { @@ -58,7 +59,7 @@ namespace Volo.Abp.Validation { var contributor = (IObjectValidationContributor) scope.ServiceProvider.GetRequiredService(contributorType); - contributor.AddErrors(context); + await contributor.AddErrorsAsync(context); } } diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs index b4ce642471..0a0f4fcf5f 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs @@ -15,13 +15,13 @@ namespace Volo.Abp.Validation public override async Task InterceptAsync(IAbpMethodInvocation invocation) { - Validate(invocation); + await ValidateAsync(invocation); await invocation.ProceedAsync(); } - protected virtual void Validate(IAbpMethodInvocation invocation) + protected virtual async Task ValidateAsync(IAbpMethodInvocation invocation) { - _methodInvocationValidator.Validate( + await _methodInvocationValidator.ValidateAsync( new MethodInvocationValidationContext( invocation.TargetObject, invocation.Method, diff --git a/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs index a93eabda1d..6263d03ddd 100644 --- a/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs +++ b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs @@ -39,7 +39,8 @@ namespace Volo.Abp.FluentValidation }, MyMethodInput3 = new MyMethodInput3 { - MyStringValue3 = "ccc" + MyStringValue3 = "ccc", + MyBoolValue3 = true } }); @@ -62,12 +63,13 @@ namespace Volo.Abp.FluentValidation }, MyMethodInput3 = new MyMethodInput3 { - MyStringValue3 = "c" + MyStringValue3 = "c", + MyBoolValue3 = false } } ) ); - + exception.ValidationErrors.ShouldContain(x => x.MemberNames.Contains("MyStringValue")); exception.ValidationErrors.ShouldContain(x => x.MemberNames.Contains("MyMethodInput2.MyStringValue2")); exception.ValidationErrors.ShouldContain(x => x.MemberNames.Contains("MyMethodInput3.MyStringValue3")); @@ -100,7 +102,7 @@ namespace Volo.Abp.FluentValidation output.ShouldBe("444"); } - + [DependsOn(typeof(AbpAutofacModule))] [DependsOn(typeof(AbpFluentValidationModule))] public class TestModule : AbpModule @@ -162,6 +164,8 @@ namespace Volo.Abp.FluentValidation { public string MyStringValue3 { get; set; } + + public bool MyBoolValue3 { get; set; } } public class MyMethodInput4 @@ -175,7 +179,8 @@ namespace Volo.Abp.FluentValidation { RuleFor(x => x.MyStringValue).Equal("aaa"); RuleFor(x => x.MyMethodInput2.MyStringValue2).Equal("bbb"); - RuleFor(customer => customer.MyMethodInput3).SetValidator(new MyMethodInput3Validator()); + RuleFor(x => x.MyMethodInput3).SetValidator(new MyMethodInput3Validator()); + RuleFor(x => x.MyMethodInput3).SetValidator(new MyMethodInput3AsyncValidator()); } } @@ -194,5 +199,15 @@ namespace Volo.Abp.FluentValidation RuleFor(x => x.MyStringValue3).Equal("ccc"); } } + + public class MyMethodInput3AsyncValidator : MethodInputBaseValidator + { + public MyMethodInput3AsyncValidator() + { + RuleFor(x => x.MyStringValue3).Equal("ccc"); + + RuleFor(x => x.MyBoolValue3).MustAsync((myBookValue3, cancellation) => Task.FromResult(myBookValue3)); + } + } } -} \ No newline at end of file +} 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 23694e4e72..d1c1275db7 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 @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Volo.Abp.Http.DynamicProxying @@ -36,5 +37,7 @@ namespace Volo.Abp.Http.DynamicProxying Task PatchValueWithHeaderAndQueryStringAsync(string headerValue, string qsValue); Task DeleteByIdAsync(int id); + + Task AbortRequestAsync(CancellationToken cancellationToken = default); } } 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 e18c2f8f4a..782b839626 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 @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Volo.Abp.Application.Services; using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.UI; namespace Volo.Abp.Http.DynamicProxying { @@ -129,6 +128,14 @@ namespace Volo.Abp.Http.DynamicProxying { return Task.FromResult(id + 1); } + + [HttpGet] + [Route("abort-request")] + public async Task AbortRequestAsync(CancellationToken cancellationToken = default) + { + await Task.Delay(100, cancellationToken); + return "AbortRequestAsync"; + } } public class Car 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 bd35016989..8df3e94b3c 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 @@ -1,10 +1,10 @@ using System; +using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; using Shouldly; using Volo.Abp.Http.Client; -using Volo.Abp.Http.Localization; using Volo.Abp.Localization; using Xunit; @@ -159,5 +159,17 @@ namespace Volo.Abp.Http.DynamicProxying (await _controller.DeleteByIdAsync(42)).ShouldBe(43); } + [Fact] + public async Task AbortRequestAsync() + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(10); + + var result = await _controller.AbortRequestAsync(default); + result.ShouldBe("AbortRequestAsync"); + + var exception = await Assert.ThrowsAsync(async () => await _controller.AbortRequestAsync(cts.Token)); + exception.InnerException.InnerException.Message.ShouldBe("The client aborted the request."); + } } } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs index 6abd112533..6b911c6e36 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs @@ -25,6 +25,12 @@ namespace Volo.Docs.Documents string version, CancellationToken cancellationToken = default); + Task> GetListAsync( + Guid? projectId, + string version, + string name, + CancellationToken cancellationToken = default); + Task> GetAllAsync( Guid? projectId, string name, @@ -67,4 +73,4 @@ namespace Volo.Docs.Documents Task GetAsync(Guid id, CancellationToken cancellationToken = default); } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs index e44c52c61c..a322120c3d 100644 --- a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs @@ -38,6 +38,15 @@ namespace Volo.Docs.Documents return await (await GetDbSetAsync()).Where(d => d.ProjectId == projectId).ToListAsync(cancellationToken: cancellationToken); } + public async Task> GetListAsync(Guid? projectId, string version, string name, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .WhereIf(version != null, x => x.Version == version) + .WhereIf(name != null, x => x.Name == name) + .WhereIf(projectId.HasValue, x => x.ProjectId == projectId) + .ToListAsync(cancellationToken: cancellationToken); + } + public async Task> GetAllAsync( Guid? projectId, string name, diff --git a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs index 955319d848..3a57330557 100644 --- a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs @@ -57,6 +57,16 @@ namespace Volo.Docs.Documents x.Version == version, cancellationToken: cancellationToken); } + public async Task> GetListAsync(Guid? projectId, string version, string name, CancellationToken cancellationToken = default) + { + return await (await GetMongoQueryableAsync(cancellationToken)) + .WhereIf(version != null, x => x.Version == version) + .WhereIf(name != null, x => x.Name == name) + .WhereIf(projectId.HasValue, x => x.ProjectId == projectId) + .As>() + .ToListAsync(cancellationToken); + } + public async Task> GetAllAsync( Guid? projectId, string name,