Browse Source

Merge remote-tracking branch 'abpframework/dev' into dev

pull/3571/head
liangshiwei 7 years ago
parent
commit
d2b529430f
  1. 112
      docs/en/Object-Extensions.md
  2. 2
      docs/en/Tutorials/Part-2.md
  3. 6
      docs/en/UI/Common/Utils/Linked-List.md
  4. 3
      docs/zh-Hans/AutoMapper-Integration.md
  5. 18
      docs/zh-Hans/Best-Practices/Application-Services.md
  6. 1
      docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md
  7. 2
      docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md
  8. 99
      docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md
  9. 267
      docs/zh-Hans/Object-Extensions.md
  10. 17
      docs/zh-Hans/Object-To-Object-Mapping.md
  11. 3
      docs/zh-Hans/UI/AspNetCore/Libraries/DatatablesNet.md
  12. 1454
      docs/zh-Hans/UI/Common/Utils/Linked-List.md
  13. 17
      docs/zh-Hans/docs-nav.json
  14. 56
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundlerBase.cs
  15. 6
      framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs
  16. 35
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  17. 13
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs
  18. 94
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs
  19. 10
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProviderOptions.cs
  20. 103
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs
  21. 112
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs
  22. 44
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs
  23. 105
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs
  24. 6
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs
  25. 5
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs
  26. 48
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs
  27. 37
      framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs
  28. 17
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs
  29. 18
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs
  30. 47
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs
  31. 7
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs
  32. 6
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs
  33. 7
      framework/src/Volo.Abp.Minify/Volo/Abp/Minify/IMinifier.cs
  34. 18
      framework/src/Volo.Abp.Minify/Volo/Abp/Minify/NUglify/NUglifyMinifierBase.cs
  35. 13
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
  36. 2
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs
  37. 11
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs
  38. 200
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs
  39. 4
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs
  40. 9
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs
  41. 61
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs
  42. 53
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs
  43. 13
      framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs
  44. 11
      framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzPreOptions.cs
  45. 11
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs
  46. 1
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs
  47. 11
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs
  48. 23
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj
  49. 10
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs
  50. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs
  51. 19
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml
  52. 33
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml.cs
  53. 65
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs
  54. 19
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml
  55. 18
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml.cs
  56. 40
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage_Tests.cs
  57. 4
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs
  58. 19
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml
  59. 18
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs
  60. 56
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs
  61. 19
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml
  62. 27
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs
  63. 34
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage_Tests.cs
  64. 37
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs
  65. 22
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Tests.cs
  66. 19
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml
  67. 54
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs
  68. 4
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs
  69. 25
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs
  70. 45
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs
  71. 13
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMyAuthorizedServiceWithRole.cs
  72. 11
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMySimpleAuthorizedService.cs
  73. 2
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedService1.cs
  74. 27
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedServiceWithRole.cs
  75. 21
      framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MySimpleAuthorizedService.cs
  76. 19
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs
  77. 4
      framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs
  78. 7
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs
  79. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Auditing/Auditing_Tests.cs
  80. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Creation_Tests.cs
  81. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Filter_Tests.cs
  82. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Filter_Tests.cs
  83. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Tests.cs
  84. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ConcurrencyStamp_Tests.cs
  85. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ExtraProperties_Tests.cs
  86. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/DomainEvents_Tests.cs
  87. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/EntityChangeEvents_Tests.cs
  88. 16
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs
  89. 10
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoTestCollection.cs
  90. 1
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs
  91. 1
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests_With_Int_Pk.cs
  92. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Queryable_Tests.cs
  93. 2
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs
  94. 3
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/Uow_Completed_Tests.cs
  95. 172
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs
  96. 4
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs
  97. 4
      framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs
  98. 3
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs
  99. 1
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs
  100. 6
      modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs

112
docs/en/Object-Extensions.md

@ -156,11 +156,9 @@ ObjectExtensionManager.Instance
); );
```` ````
#### Property Configuration ### Property Configuration
`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition. `AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition:
Example:
````csharp ````csharp
ObjectExtensionManager.Instance ObjectExtensionManager.Instance
@ -168,13 +166,113 @@ ObjectExtensionManager.Instance
"SocialSecurityNumber", "SocialSecurityNumber",
options => options =>
{ {
options.CheckPairDefinitionOnMapping = false; //Configure options...
});
````
> `options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document.
The following sections explain the fundamental property configuration options.
#### CheckPairDefinitionOnMapping
Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better.
## Validation
You may want to add some **validation rules** for the extra properties you've defined. `AddOrUpdateProperty` method options allows two ways of performing validation:
1. You can add **data annotation attributes** for a property.
2. You can write an action (code block) to perform a **custom validation**.
Validation works when you use the object in a method that is **automatically validated** (e.g. controller actions, page handler methods, application service methods...). So, all extra properties are validated whenever the extended object is being validated.
### Data Annotation Attributes
All of the standard data annotation attributes are valid for extra properties. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.ValidationAttributes.Add(new RequiredAttribute());
options.ValidationAttributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
);
});
````
With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided.
### Custom Validation
If you need, you can add a custom action that is executed to validate the extra properties. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Validators.Add(context =>
{
var socialSecurityNumber = context.Value as string;
if (socialSecurityNumber == null ||
socialSecurityNumber.StartsWith("X"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Invalid social security number: " + socialSecurityNumber,
new[] { "SocialSecurityNumber" }
)
);
}
});
}); });
```` ````
> See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option. `context.ServiceProvider` can be used to resolve a service dependency for advanced scenarios.
In addition to add custom validation logic for a single property, you can add a custom validation logic that is executed in object level. Example:
`options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document. ````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUserCreateDto>(objConfig =>
{
//Define two properties with their own validation rules
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
{
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
{
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
//Write a common validation logic works on multiple properties
objConfig.Validators.Add(context =>
{
if (context.ValidatingObject.GetProperty<string>("Password") !=
context.ValidatingObject.GetProperty<string>("PasswordRepeat"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Please repeat the same password!",
new[] { "Password", "PasswordRepeat" }
)
);
}
});
});
````
## Object to Object Mapping ## Object to Object Mapping

2
docs/en/Tutorials/Part-2.md

@ -688,7 +688,7 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
* `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits. * `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits.
* We added `New book` button to the `AbpContentToolbar`. * We added `New book` button to the `AbpContentToolbar`.
Open `book-list.component.` file in `books\book-list` folder and replace the content as below: Open `book-list.component.ts` file in `books\book-list` folder and replace the content as below:
```js ```js
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';

6
docs/en/UI/Common/Utils/Linked-List.md

@ -1381,7 +1381,6 @@ list.forEach((node, index) => console.log(node.value + index));
``` ```
#### \*\[Symbol.iterator\]\(\) #### \*\[Symbol.iterator\]\(\)
A linked list is iterable. In other words, you may use methods like `for...of` on it. A linked list is iterable. In other words, you may use methods like `for...of` on it.
@ -1482,7 +1481,4 @@ const str = list.toString(value => value.x);
/* /*
str === '1 <-> 2 <-> 3 <-> 4 <-> 5' str === '1 <-> 2 <-> 3 <-> 4 <-> 5'
*/ */
``` ```

3
docs/zh-Hans/AutoMapper-Integration.md

@ -1,3 +0,0 @@
## AutoMapper Integration
TODO

18
docs/zh-Hans/Best-Practices/Application-Services.md

@ -17,7 +17,7 @@
##### 基础DTO ##### 基础DTO
**推荐** 为实体定义一个**基础**DTO. **推荐** 为聚合根定义一个**基础**DTO.
- 直接包含实体中所有的**原始属性**. - 直接包含实体中所有的**原始属性**.
- 例外: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`). - 例外: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`).
@ -27,7 +27,7 @@
```c# ```c#
[Serializable] [Serializable]
public class IssueDto : FullAuditedEntityDto<Guid> public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -57,7 +57,7 @@ public class IssueLabelDto
````C# ````C#
[Serializable] [Serializable]
public class IssueWithDetailsDto : FullAuditedEntityDto<Guid> public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -66,14 +66,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
} }
[Serializable] [Serializable]
public class MilestoneDto : EntityDto<Guid> public class MilestoneDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public bool IsClosed { get; set; } public bool IsClosed { get; set; }
} }
[Serializable] [Serializable]
public class LabelDto : EntityDto<Guid> public class LabelDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public string Color { get; set; } public string Color { get; set; }
@ -120,6 +120,7 @@ Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
* **推荐** 使用 `CreateAsync` 做为**方法名**. * **推荐** 使用 `CreateAsync` 做为**方法名**.
* **推荐** 使用**专门的输入DTO**来创建实体. * **推荐** 使用**专门的输入DTO**来创建实体.
* **推荐** DTO类从 `ExtensibleObject` 类继承(或任何实现 `ExtensibleObject`的类) 以允许在需要时传递额外的属性.
* **推荐** 使用 **data annotations** 进行输入验证. * **推荐** 使用 **data annotations** 进行输入验证.
* 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量). * 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量).
* **推荐** 只需要创建实体的**最少**信息, 但是提供了其他可选属性. * **推荐** 只需要创建实体的**最少**信息, 但是提供了其他可选属性.
@ -134,7 +135,7 @@ Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto);
````C# ````C#
[Serializable] [Serializable]
public class CreateQuestionDto public class CreateQuestionDto : ExtensibleObject
{ {
[Required] [Required]
[StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)] [StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)]
@ -151,6 +152,7 @@ public class CreateQuestionDto
- **推荐** 使用 `UpdateAsync` 做为**方法名**. - **推荐** 使用 `UpdateAsync` 做为**方法名**.
- **推荐** 使用**专门的输入DTO**来更新实体. - **推荐** 使用**专门的输入DTO**来更新实体.
- **推荐** DTO类从 `ExtensibleObject` 类继承(或任何实现 `ExtensibleObject`的类) 以允许在需要时传递额外的属性.
- **推荐** 获取实体的id做为分离的原始参数. 不要包含更新DTO. - **推荐** 获取实体的id做为分离的原始参数. 不要包含更新DTO.
- **推荐** 使用 **data annotations** 进行输入验证. - **推荐** 使用 **data annotations** 进行输入验证.
- 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量). - 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量).
@ -199,6 +201,10 @@ Task<int> VoteAsync(Guid id, VoteType type);
* **不推荐** 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询. * **不推荐** 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询.
#### 额外的属性
* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务.
#### 操作/删除 实体 #### 操作/删除 实体
* **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作. * **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作.

1
docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md

@ -2,6 +2,7 @@
* **推荐** 在 **application.contracts** 层中定义DTO. * **推荐** 在 **application.contracts** 层中定义DTO.
* **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` 等). * **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` 等).
* **推荐** 从**聚合根**的**扩展DTO**继承(如 `ExtensibleAuditedEntityDto<TKey>`), 因为聚合根是可扩展的额外的属性使用这种方式映射到DTO.
* **推荐** 定义 **public getter 和 setter** 的DTO成员 . * **推荐** 定义 **public getter 和 setter** 的DTO成员 .
* **推荐** 使用 **data annotations** **验证** service输入DTO的属性. * **推荐** 使用 **data annotations** **验证** service输入DTO的属性.
* **不推荐** 在DTO中添加任何 **逻辑**, 在必要的时候可以实现 `IValidatableObject` 接口. * **不推荐** 在DTO中添加任何 **逻辑**, 在必要的时候可以实现 `IValidatableObject` 接口.

2
docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md

@ -58,8 +58,6 @@ ObjectExtensionManager.Instance
## 创建新实体映射到同一个数据库表/Collection ## 创建新实体映射到同一个数据库表/Collection
尽管额外属性方法**易于使用**并且适用于一些场景,但它具有[实体文档](Entities.md)中描述的一些缺点.
另一个方法是**创建你自己的实体**映射到**同一个数据库库**(对于MongoDB数据库是collection) 另一个方法是**创建你自己的实体**映射到**同一个数据库库**(对于MongoDB数据库是collection)
[应用程序启动模板](Startup-Templates/Application.md)的 `AppUser` 已经实现了这种方法. [EF Core迁移文档](Entity-Framework-Core-Migrations.md)描述了在这些情况下如何实现和管理**EF Core数据库迁移**. 这种方法同样适用于MongoDB,但你不需要处理数据库迁移问题. [应用程序启动模板](Startup-Templates/Application.md)的 `AppUser` 已经实现了这种方法. [EF Core迁移文档](Entity-Framework-Core-Migrations.md)描述了在这些情况下如何实现和管理**EF Core数据库迁移**. 这种方法同样适用于MongoDB,但你不需要处理数据库迁移问题.

99
docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md

@ -161,6 +161,105 @@ public class MyIdentityUserManager : IdentityUserManager
控制器,框架服务,视图组件类以及其他类型注册到依赖注入的类都可以像上面的示例那样被重写. 控制器,框架服务,视图组件类以及其他类型注册到依赖注入的类都可以像上面的示例那样被重写.
## 扩展数据传输对象
你可以如[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md)所述扩展实体. 并使用上面介绍的重写相关服务**使用自定义属性****执行其他业务逻辑**.
应用程序使用的数据传输对象(**DTO**)同样可扩展. 这样你可以使服务返回其他属性并在UI(或其他客户端)得到其他属性.
### 示例
假设你已经按照[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md)中的说明添加了 `SocialSecurityNumber` 并希望从 `IdentityUserAppService的GetListAsync` 方法获取用户列表时包括此属性.
你可以使用[对象扩展系统](Object-Extensions.md)将属性添加到 `IdentityUserDto`. 在应用程序启动模板带有的 `YourProjectNameDtoExtensions` 类中编写以下代码:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserDto, string>(
"SocialSecurityNumber"
);
````
这段代码为 `IdentityUserDto` 类添加了 `string` 类型的 `SocialSecurityNumber` 属性. 现在你可以在RREST API客户端调用 `/api/identity/users` HTTP API(内部使用 `IdentityUserAppService`),你会在 `extraProperties` 部分看到 `SocialSecurityNumber` 值.
````json
{
"totalCount": 1,
"items": [{
"tenantId": null,
"userName": "admin",
"name": "admin",
"surname": null,
"email": "admin@abp.io",
"emailConfirmed": false,
"phoneNumber": null,
"phoneNumberConfirmed": false,
"twoFactorEnabled": false,
"lockoutEnabled": true,
"lockoutEnd": null,
"concurrencyStamp": "b4c371a0ab604de28af472fa79c3b70c",
"isDeleted": false,
"deleterId": null,
"deletionTime": null,
"lastModificationTime": "2020-04-09T21:25:47.0740706",
"lastModifierId": null,
"creationTime": "2020-04-09T21:25:46.8308744",
"creatorId": null,
"id": "8edecb8f-1894-a9b1-833b-39f4725db2a3",
"extraProperties": {
"SocialSecurityNumber": "123456789"
}
}]
}
````
手动添加了 `123456789` 值到数据库中.
所有预构建的模块都在DTO中支持额外属性,你可以对其轻松的配置.
### 定义检查
当为实体[定义](Customizing-Application-Modules-Extending-Entities.md)额外的属性时,由于安全性它不会自动出现在所有相关的DTO中. 额外属性可能包含敏感数据并且你可能不想默认公开给客户端.
因此如果要用于DTO,需要为相应的DTO显式定义相同的属性(如上所述). 如果要允许在用户创建时进行设置还需要为 `IdentityUserCreateDto` 定义.
如果属性并不是安全敏感,这可能会很枯燥. 对象扩展系统允许你忽略检查定义的属性. 参阅示例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
options.CheckPairDefinitionOnMapping = false;
}
);
````
这是定义实体属性的另一种方法( 有关 `ObjectExtensionManager` 更多信息,请参阅[文档](Object-Extensions.md)). 这次我们设置了 `CheckPairDefinitionOnMapping` 为false,在将实体映射到DTO时会跳过定义检查.
如果你不喜欢这种方法,但想简单的向多个对象(DTO)添加单个属, `AddOrUpdateProperty` 可以使用类型数组添加额外的属性:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
````
### 关于用户界面
该系统允许你向实体和DTO添加额外的属性并执行自定义业务代码,但它与用户界面无关.
参阅 [重写用户界面](Customizing-Application-Modules-Overriding-User-Interface.md) 指南了解关于UI部分.
## 如何找到服务? ## 如何找到服务?
[模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务. [模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务.

267
docs/zh-Hans/Object-Extensions.md

@ -0,0 +1,267 @@
# 对象扩展
ABP框架提供了 **实体扩展系统** 允许你 **添加额外属性** 到已存在的对象 **无需修改相关类**. 它允许你扩展[应用程序依赖模块](Modules/Index.md)实现的功能,尤其是当你要扩展[模块定义的实体](Customizing-Application-Modules-Extending-Entities.md)和[DTO](Customizing-Application-Modules-Overriding-Services.md)时.
> 你自己的对象通常不需要对象扩展系统,因为你可以轻松的添加常规属性到你的类中.
## IHasExtraProperties 接口
这是一个使类可扩展的接口. 它定义了 `Dictionary` 属性:
````csharp
Dictionary<string, object> ExtraProperties { get; }
````
然后你可以使用此字典添加或获取其他属性.
### 基类
默认以下基类实现了 `IHasExtraProperties` 接口:
* 由 `AggregateRoot` 类实现 (参阅 [entities](Entities.md)).
* 由 `ExtensibleEntityDto`, `ExtensibleAuditedEntityDto`... [DTO](Data-Transfer-Objects.md)基类实现.
* 由 `ExtensibleObject` 实现, 它是一个简单的基类,任何类型的对象都可以继承.
如果你的类从这些类继承,那么你的类也是可扩展的,如果没有,你也可以随时手动继承.
### 基本扩展方法
虽然可以直接使用类的 `ExtraProperties` 属性,但建议使用以下扩展方法使用额外属性.
#### SetProperty
用于设置额外属性值:
````csharp
user.SetProperty("Title", "My Title");
user.SetProperty("IsSuperUser", true);
````
`SetProperty` 返回相同的对象, 你可以使用链式编程:
````csharp
user.SetProperty("Title", "My Title")
.SetProperty("IsSuperUser", true);
````
#### GetProperty
用于读取额外属性的值:
````csharp
var title = user.GetProperty<string>("Title");
if (user.GetProperty<bool>("IsSuperUser"))
{
//...
}
````
* `GetProperty` 是一个泛型方法,对象类型做为泛型参数.
* 如果未设置给定的属性,则返回默认值 (`int` 的默认值为 `0` , `bool` 的默认值是 `false` ... 等).
##### 非基本属性类型
如果您的属性类型不是原始类型(int,bool,枚举,字符串等),你需要使用 `GetProperty` 的非泛型版本,它会返回 `object`.
#### HasProperty
用于检查对象之前是否设置了属性.
#### RemoveProperty
用于从对象中删除属性. 使用此方法代替为属性设置 `null` 值.
### 一些最佳实践
为属性名称使用魔术字符串很危险,因为你很容易输入错误的属性名称-这并不安全;
* 为你的额外属性名称定义一个常量.
* 使用扩展方法轻松设置你的属性.
示例:
````csharp
public static class IdentityUserExtensions
{
private const string TitlePropertyName = "Title";
public static void SetTitle(this IdentityUser user, string title)
{
user.SetProperty(TitlePropertyName, title);
}
public static string GetTitle(this IdentityUser user)
{
return user.GetProperty<string>(TitlePropertyName);
}
}
````
然后, 你可以很容易地设置或获取 `Title` 属性:
````csharp
user.SetTitle("My Title");
var title = user.GetTitle();
````
## Object Extension Manager
你可以为可扩展对象(实现 `IHasExtraProperties`接口)设置任意属性, `ObjectExtensionManager` 用于显式定义可扩展类的其他属性.
显式定义额外的属性有一些用例:
* 允许控制如何在对象到对象的映射上处理额外的属性 (参阅下面的部分).
* 允许定义属性的元数据. 例如你可以在使用[EF Core](Entity-Framework-Core.md)时将额外的属性映射到数据库中的表字段.
> `ObjectExtensionManager` 实现单例模式 (`ObjectExtensionManager.Instance`) ,你应该在应用程序启动之前定义对象扩展. [应用程序启动模板](Startup-Templates/Application.md) 有一些预定义的静态类,可以安全在内部定义对象扩展.
### AddOrUpdate
`AddOrUpdate` 是定义对象额外属性或更新对象额外属性的主要方法.
示例: 为 `IdentityUser` 实体定义额外属性:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUser>(options =>
{
options.AddOrUpdateProperty<string>("SocialSecurityNumber");
options.AddOrUpdateProperty<bool>("IsSuperUser");
}
);
````
### AddOrUpdateProperty
虽然可以如上所示使用 `AddOrUpdateProperty`, 但如果要定义单个额外的属性,也可以使用快捷的扩展方法:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>("SocialSecurityNumber");
````
有时将单个额外属性定义为多种类型是可行的. 你可以使用以下代码,而不是一个一个地定义:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
````
#### 属性配置
`AddOrUpdateProperty` 还可以为属性定义执行其他配置的操作.
Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.CheckPairDefinitionOnMapping = false;
});
````
> 参阅 "对象到对象映射" 部分了解 `CheckPairDefinitionOnMapping` 选项.
`options` 有一个名为 `Configuration` 的字典,该字典存储对象扩展定义甚至可以扩展. EF Core使用它来将其他属性映射到数据库中的表字段. 请参阅[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md).
## 对象到对象映射
假设你已向可扩展的实体对象添加了额外的属性并使用了自动[对象到对象的映射](Object-To-Object-Mapping.md)将该实体映射到可扩展的DTO类. 在这种情况下你需要格外小心,因为额外属性可能包含**敏感数据**,这些数据对于客户端不可用.
本节提供了一些**好的做法**,可以控制对象映射的额外属性。
### MapExtraPropertiesTo
`MapExtraPropertiesTo` 是ABP框架提供的扩展方法,用于以受控方式将额外的属性从一个对象复制到另一个对象. 示例:
````csharp
identityUser.MapExtraPropertiesTo(identityUserDto);
````
`MapExtraPropertiesTo` 需要在**两侧**(本例中是`IdentityUser` 和 `IdentityUserDto`)**定义属性**. 以将值复制到目标对象. 否则即使源对象(在此示例中为 `identityUser` )中确实存在该值,它也不会复制. 有一些重载此限制的方法.
#### MappingPropertyDefinitionChecks
`MapExtraPropertiesTo` 获取一个附加参数来控制单个映射操作的定义检查:
````csharp
identityUser.MapExtraPropertiesTo(
identityUserDto,
MappingPropertyDefinitionChecks.None
);
````
> 要小心,因为 `MappingPropertyDefinitionChecks.None` 会复制所有的额外属性而不进行任何检查. `MappingPropertyDefinitionChecks` 枚举还有其他成员.
如果要完全禁用属性的定义检查,可以在定义额外的属性(或更新现有定义)时进行,如下所示:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.CheckPairDefinitionOnMapping = false;
});
````
#### 忽略属性
你可能要在映射操作忽略某些属性:
````csharp
identityUser.MapExtraPropertiesTo(
identityUserDto,
ignoredProperties: new[] {"MySensitiveProp"}
);
````
忽略的属性不会复制到目标对象.
#### AutoMapper集成
如果您使用的是[AutoMapper](https://automapper.org/)库,ABP框架还提供了一种扩展方法来利用上面定义的 `MapExtraPropertiesTo` 方法.
你可以在映射配置文件中使用 `MapExtraProperties()` 方法.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<IdentityUser, IdentityUserDto>()
.MapExtraProperties();
}
}
````
它与 `MapExtraPropertiesTo()` 方法具有相同的参数。
## Entity Framework Core 数据库映射
如果你使用的是EF Core,可以将额外的属性映射到数据库中的表字段. 例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
}
);
````
参阅 [Entity Framework Core 集成文档](Entity-Framework-Core.md) 了解更多内容.

17
docs/zh-Hans/Object-To-Object-Mapping.md

@ -145,6 +145,23 @@ options.AddProfile<MyProfile>(validate: true);
> 如果你有多个配置文件,并且只需要为其中几个启用验证,那么首先使用`AddMaps`而不进行验证,然后为你想要验证的每个配置文件使用`AddProfile`. > 如果你有多个配置文件,并且只需要为其中几个启用验证,那么首先使用`AddMaps`而不进行验证,然后为你想要验证的每个配置文件使用`AddProfile`.
### 映射对象扩展
[对象扩展系统](Object-Extensions.md) 允许为已存在的类定义额外属性. ABP 框架提供了一个映射定义扩展可以正确的映射两个对象的额外属性.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<User, UserDto>()
.MapExtraProperties();
}
}
````
如果两个类都是可扩展对象(实现了 `IHasExtraProperties` 接口),建议使用 `MapExtraProperties` 方法. 更多信息请参阅[对象扩展文档](Object-Extensions.md).
## 高级主题 ## 高级主题
### IObjectMapper<TContext> 接口 ### IObjectMapper<TContext> 接口

3
docs/zh-Hans/UI/AspNetCore/Libraries/DatatablesNet.md

@ -0,0 +1,3 @@
# ABP ASP.NET Core UI Datatables.Net 集成
TODO

1454
docs/zh-Hans/UI/Common/Utils/Linked-List.md

File diff suppressed because it is too large

17
docs/zh-Hans/docs-nav.json

@ -140,6 +140,14 @@
{ {
"text": "设置管理", "text": "设置管理",
"path": "Settings.md" "path": "Settings.md"
},
{
"text": "数据过滤",
"path": "Data-Filtering.md"
},
{
"text": "对象扩展",
"path": "Object-Extensions.md"
} }
] ]
}, },
@ -313,6 +321,15 @@
"path": "UI/Angular/Component-Replacement.md" "path": "UI/Angular/Component-Replacement.md"
} }
] ]
},
{
"text": "通用",
"items": [
{
"text": "链表 (双向)",
"path": "UI/Common/Utils/Linked-List.md"
}
]
} }
] ]
}, },

56
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundlerBase.cs

@ -46,38 +46,54 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling
private void AddFileToBundle(IBundlerContext context, StringBuilder bundleContentBuilder, string fileName) private void AddFileToBundle(IBundlerContext context, StringBuilder bundleContentBuilder, string fileName)
{ {
string fileContent = null; var fileContent = GetFileContentConsideringMinification(context, fileName);
fileContent = ProcessBeforeAddingToTheBundle(context, fileName, fileContent);
bundleContentBuilder.Append(fileContent);
}
if (context.IsMinificationEnabled && !IsMinFile(fileName)) private string GetFileContentConsideringMinification(IBundlerContext context, string fileName)
{
var isMinFile = IsMinFile(fileName);
if (!context.IsMinificationEnabled || isMinFile)
{ {
var minFileInfo = GetMinFileInfoOrNull(fileName); var fileContent = GetFileInfo(context, fileName).ReadAsString();
if (minFileInfo != null) Logger.LogDebug($"- {fileName} ({fileContent.Length} bytes)");
if (context.IsMinificationEnabled && isMinFile)
{ {
Logger.LogDebug($"- {fileName} ({minFileInfo.Length} bytes) - already minified"); Logger.LogDebug(" > Already minified");
fileContent = minFileInfo.ReadAsString();
} }
return fileContent;
} }
if (fileContent == null) var minFileInfo = GetMinFileInfoOrNull(fileName);
if (minFileInfo != null)
{ {
fileContent = GetFileContent(context, fileName); var fileContent = minFileInfo.ReadAsString();
Logger.LogDebug($"- {fileName} ({fileContent.Length} bytes) - non minified"); Logger.LogDebug($"- {fileName}");
Logger.LogDebug($" > Using the pre-minified file: {minFileInfo.Name} ({fileContent.Length} bytes)");
if (context.IsMinificationEnabled) return fileContent;
{
var nonMinifiedSize = fileContent.Length;
fileContent = Minifier.Minify(fileContent, context.BundleRelativePath);
Logger.LogInformation($" > Minified {fileName} ({nonMinifiedSize} bytes -> {fileContent.Length} bytes)");
}
} }
fileContent = ProcessBeforeAddingToTheBundle(context, fileName, fileContent); return GetAndMinifyFileContent(context, fileName);
bundleContentBuilder.Append(fileContent);
} }
protected virtual string GetFileContent(IBundlerContext context, string file) private string GetAndMinifyFileContent(IBundlerContext context, string fileName)
{ {
return GetFileInfo(context, file).ReadAsString(); var fileContent = GetFileInfo(context, fileName).ReadAsString();
var nonMinifiedSize = fileContent.Length;
Logger.LogDebug($"- {fileName} ({nonMinifiedSize} bytes) - non minified, minifying...");
fileContent = Minifier.Minify(
fileContent,
context.BundleRelativePath,
fileName
);
Logger.LogInformation($" > Minified {fileName} ({nonMinifiedSize} bytes -> {fileContent.Length} bytes)");
return fileContent;
} }
protected virtual IFileInfo GetFileInfo(IBundlerContext context, string file) protected virtual IFileInfo GetFileInfo(IBundlerContext context, string file)

6
framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
@ -37,5 +38,10 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
{ {
return actionDescriptor is ControllerActionDescriptor; return actionDescriptor is ControllerActionDescriptor;
} }
public static bool IsPageAction(this ActionDescriptor actionDescriptor)
{
return actionDescriptor is PageActionDescriptor;
}
} }
} }

35
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
@ -11,15 +11,23 @@ using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Volo.Abp.ApiVersioning; using Volo.Abp.ApiVersioning;
using Volo.Abp.AspNetCore.Mvc.ApiExploring;
using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Json; using Volo.Abp.AspNetCore.Mvc.Json;
using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.VirtualFileSystem; using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Http.Modeling; using Volo.Abp.Http.Modeling;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -38,6 +46,9 @@ namespace Volo.Abp.AspNetCore.Mvc
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
DynamicProxyIgnoreTypes.Add<ControllerBase>();
DynamicProxyIgnoreTypes.Add<PageModel>();
context.Services.AddConventionalRegistrar(new AbpAspNetCoreMvcConventionalRegistrar()); context.Services.AddConventionalRegistrar(new AbpAspNetCoreMvcConventionalRegistrar());
} }
@ -50,6 +61,25 @@ namespace Volo.Abp.AspNetCore.Mvc
options.IgnoredInterfaces.AddIfNotContains(typeof(IActionFilter)); options.IgnoredInterfaces.AddIfNotContains(typeof(IActionFilter));
}); });
Configure<AbpRemoteServiceApiDescriptionProviderOptions>(options =>
{
var statusCodes = new List<int>
{
(int) HttpStatusCode.Forbidden,
(int) HttpStatusCode.Unauthorized,
(int) HttpStatusCode.BadRequest,
(int) HttpStatusCode.NotFound,
(int) HttpStatusCode.NotImplemented,
(int) HttpStatusCode.InternalServerError
};
options.SupportedResponseTypes.AddIfNotContains(statusCodes.Select(statusCode => new ApiResponseType
{
Type = typeof(RemoteServiceErrorResponse),
StatusCode = statusCode
}));
});
context.Services.PostConfigure<AbpAspNetCoreMvcOptions>(options => context.Services.PostConfigure<AbpAspNetCoreMvcOptions>(options =>
{ {
if (options.MinifyGeneratedScript == null) if (options.MinifyGeneratedScript == null)
@ -108,6 +138,9 @@ namespace Volo.Abp.AspNetCore.Mvc
//Use DI to create view components //Use DI to create view components
context.Services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>()); context.Services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());
//Use DI to create razor page
context.Services.Replace(ServiceDescriptor.Singleton<IPageModelActivatorProvider, ServiceBasedPageModelActivatorProvider>());
//Add feature providers //Add feature providers
var partManager = context.Services.GetSingletonInstance<ApplicationPartManager>(); var partManager = context.Services.GetSingletonInstance<ApplicationPartManager>();
var application = context.Services.GetSingletonInstance<IAbpApplication>(); var application = context.Services.GetSingletonInstance<IAbpApplication>();

13
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs

@ -16,7 +16,8 @@ namespace Volo.Abp.AspNetCore.Mvc
public static void AddAbp(this MvcOptions options, IServiceCollection services) public static void AddAbp(this MvcOptions options, IServiceCollection services)
{ {
AddConventions(options, services); AddConventions(options, services);
AddFilters(options); AddActionFilters(options);
AddPageFilters(options);
AddModelBinders(options); AddModelBinders(options);
AddMetadataProviders(options, services); AddMetadataProviders(options, services);
} }
@ -26,7 +27,7 @@ namespace Volo.Abp.AspNetCore.Mvc
options.Conventions.Add(new AbpServiceConventionWrapper(services)); options.Conventions.Add(new AbpServiceConventionWrapper(services));
} }
private static void AddFilters(MvcOptions options) private static void AddActionFilters(MvcOptions options)
{ {
options.Filters.AddService(typeof(AbpAuditActionFilter)); options.Filters.AddService(typeof(AbpAuditActionFilter));
options.Filters.AddService(typeof(AbpNoContentActionFilter)); options.Filters.AddService(typeof(AbpNoContentActionFilter));
@ -36,6 +37,14 @@ namespace Volo.Abp.AspNetCore.Mvc
options.Filters.AddService(typeof(AbpExceptionFilter)); options.Filters.AddService(typeof(AbpExceptionFilter));
} }
private static void AddPageFilters(MvcOptions options)
{
options.Filters.AddService(typeof(AbpExceptionPageFilter));
options.Filters.AddService(typeof(AbpAuditPageFilter));
options.Filters.AddService(typeof(AbpFeaturePageFilter));
options.Filters.AddService(typeof(AbpUowPageFilter));
}
private static void AddModelBinders(MvcOptions options) private static void AddModelBinders(MvcOptions options)
{ {
options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider()); options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider());

94
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs

@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.ApiExploring
{
public class AbpRemoteServiceApiDescriptionProvider : IApiDescriptionProvider, ITransientDependency
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly MvcOptions _mvcOptions;
private readonly AbpRemoteServiceApiDescriptionProviderOptions _options;
public AbpRemoteServiceApiDescriptionProvider(
IModelMetadataProvider modelMetadataProvider,
IOptions<MvcOptions> mvcOptionsAccessor,
IOptions<AbpRemoteServiceApiDescriptionProviderOptions> optionsAccessor)
{
_modelMetadataProvider = modelMetadataProvider;
_mvcOptions = mvcOptionsAccessor.Value;
_options = optionsAccessor.Value;
}
public void OnProvidersExecuted(ApiDescriptionProviderContext context)
{
}
/// <summary>
/// The order -999 ensures that this provider is executed right after the
/// Microsoft.AspNetCore.Mvc.ApiExplorer.DefaultApiDescriptionProvider.
/// </summary>
public int Order => -999;
public void OnProvidersExecuting(ApiDescriptionProviderContext context)
{
foreach (var apiResponseType in GetApiResponseTypes())
{
foreach (var result in context.Results.Where(x => IsRemoteService(x.ActionDescriptor)))
{
var actionProducesResponseTypeAttributes =
ReflectionHelper.GetAttributesOfMemberOrDeclaringType<ProducesResponseTypeAttribute>(
result.ActionDescriptor.GetMethodInfo());
if (actionProducesResponseTypeAttributes.Any(x => x.StatusCode == apiResponseType.StatusCode))
{
continue;
}
result.SupportedResponseTypes.AddIfNotContains(x => x.StatusCode == apiResponseType.StatusCode,
() => apiResponseType);
}
}
}
protected virtual IEnumerable<ApiResponseType> GetApiResponseTypes()
{
foreach (var apiResponse in _options.SupportedResponseTypes)
{
apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(apiResponse.Type);
foreach (var responseTypeMetadataProvider in _mvcOptions.OutputFormatters.OfType<IApiResponseTypeMetadataProvider>())
{
var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(null, apiResponse.Type);
if (formatterSupportedContentTypes == null)
{
continue;
}
foreach (var formatterSupportedContentType in formatterSupportedContentTypes)
{
apiResponse.ApiResponseFormats.Add(new ApiResponseFormat
{
Formatter = (IOutputFormatter) responseTypeMetadataProvider,
MediaType = formatterSupportedContentType
});
}
}
}
return _options.SupportedResponseTypes;
}
protected virtual bool IsRemoteService(ActionDescriptor actionDescriptor)
{
var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<RemoteServiceAttribute>(actionDescriptor.GetMethodInfo());
return remoteServiceAttr != null && remoteServiceAttr.IsEnabled;
}
}
}

10
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProviderOptions.cs

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
namespace Volo.Abp.AspNetCore.Mvc.ApiExploring
{
public class AbpRemoteServiceApiDescriptionProviderOptions
{
public HashSet<ApiResponseType> SupportedResponseTypes { get; set; } = new HashSet<ApiResponseType>();
}
}

103
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs

@ -0,0 +1,103 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
using Volo.Abp.Aspects;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.Auditing
{
public class AbpAuditPageFilter : IAsyncPageFilter, ITransientDependency
{
protected AbpAuditingOptions Options { get; }
private readonly IAuditingHelper _auditingHelper;
private readonly IAuditingManager _auditingManager;
public AbpAuditPageFilter(IOptions<AbpAuditingOptions> options, IAuditingHelper auditingHelper, IAuditingManager auditingManager)
{
Options = options.Value;
_auditingHelper = auditingHelper;
_auditingManager = auditingManager;
}
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
return Task.CompletedTask;
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context.HandlerMethod == null || !ShouldSaveAudit(context, out var auditLog, out var auditLogAction))
{
await next();
return;
}
using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.Auditing))
{
var stopwatch = Stopwatch.StartNew();
try
{
var result = await next();
if (result.Exception != null && !result.ExceptionHandled)
{
auditLog.Exceptions.Add(result.Exception);
}
}
catch (Exception ex)
{
auditLog.Exceptions.Add(ex);
throw;
}
finally
{
stopwatch.Stop();
auditLogAction.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);
auditLog.Actions.Add(auditLogAction);
}
}
}
private bool ShouldSaveAudit(PageHandlerExecutingContext context, out AuditLogInfo auditLog, out AuditLogActionInfo auditLogAction)
{
auditLog = null;
auditLogAction = null;
if (!Options.IsEnabled)
{
return false;
}
if (!context.ActionDescriptor.IsPageAction())
{
return false;
}
var auditLogScope = _auditingManager.Current;
if (auditLogScope == null)
{
return false;
}
if (!_auditingHelper.ShouldSaveAudit(context.HandlerMethod.MethodInfo, true))
{
return false;
}
auditLog = auditLogScope.Log;
auditLogAction = _auditingHelper.CreateAuditLogAction(
auditLog,
context.HandlerMethod.GetType(),
context.HandlerMethod.MethodInfo,
context.HandlerArguments
);
return true;
}
}
}

112
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs

@ -0,0 +1,112 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http;
using Volo.Abp.Json;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
{
public class AbpExceptionPageFilter : IAsyncPageFilter, ITransientDependency
{
public ILogger<AbpExceptionPageFilter> Logger { get; set; }
private readonly IExceptionToErrorInfoConverter _errorInfoConverter;
private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder;
private readonly IJsonSerializer _jsonSerializer;
public AbpExceptionPageFilter(
IExceptionToErrorInfoConverter errorInfoConverter,
IHttpExceptionStatusCodeFinder statusCodeFinder,
IJsonSerializer jsonSerializer)
{
_errorInfoConverter = errorInfoConverter;
_statusCodeFinder = statusCodeFinder;
_jsonSerializer = jsonSerializer;
Logger = NullLogger<AbpExceptionPageFilter>.Instance;
}
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
return Task.CompletedTask;
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context.HandlerMethod == null || !ShouldHandleException(context))
{
await next();
return;
}
var pageHandlerExecutedContext = await next();
if (pageHandlerExecutedContext.Exception == null)
{
return;;
}
await HandleAndWrapException(pageHandlerExecutedContext);
}
protected virtual bool ShouldHandleException(PageHandlerExecutingContext context)
{
//TODO: Create DontWrap attribute to control wrapping..?
if (context.ActionDescriptor.IsPageAction() &&
ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType))
{
return true;
}
if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json))
{
return true;
}
if (context.HttpContext.Request.IsAjax())
{
return true;
}
return false;
}
protected virtual async Task HandleAndWrapException(PageHandlerExecutedContext context)
{
//TODO: Trigger an AbpExceptionHandled event or something like that.
context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true");
context.HttpContext.Response.StatusCode = (int)_statusCodeFinder.GetStatusCode(context.HttpContext, context.Exception);
var remoteServiceErrorInfo = _errorInfoConverter.Convert(context.Exception);
context.Result = new ObjectResult(new RemoteServiceErrorResponse(remoteServiceErrorInfo));
var logLevel = context.Exception.GetLogLevel();
Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------");
Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true));
Logger.LogException(context.Exception, logLevel);
await context.HttpContext
.RequestServices
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(
new ExceptionNotificationContext(context.Exception)
);
context.Exception = null; //Handled!
}
}
}

44
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs

@ -0,0 +1,44 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Volo.Abp.Aspects;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
namespace Volo.Abp.AspNetCore.Mvc.Features
{
public class AbpFeaturePageFilter : IAsyncPageFilter, ITransientDependency
{
private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService;
public AbpFeaturePageFilter(IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService)
{
_methodInvocationAuthorizationService = methodInvocationAuthorizationService;
}
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
return Task.CompletedTask;
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context.HandlerMethod == null || !context.ActionDescriptor.IsPageAction())
{
await next();
return;
}
var methodInfo = context.HandlerMethod.MethodInfo;
using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.FeatureChecking))
{
await _methodInvocationAuthorizationService.CheckAsync(
new MethodInvocationFeatureCheckerContext(methodInfo)
);
await next();
}
}
}
}

105
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs

@ -0,0 +1,105 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Uow;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Uow;
namespace Volo.Abp.AspNetCore.Mvc.Uow
{
public class AbpUowPageFilter : IAsyncPageFilter, ITransientDependency
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly AbpUnitOfWorkDefaultOptions _defaultOptions;
public AbpUowPageFilter(IUnitOfWorkManager unitOfWorkManager, IOptions<AbpUnitOfWorkDefaultOptions> options)
{
_unitOfWorkManager = unitOfWorkManager;
_defaultOptions = options.Value;
}
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
return Task.CompletedTask;
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context.HandlerMethod == null || !context.ActionDescriptor.IsPageAction())
{
await next();
return;
}
var methodInfo = context.HandlerMethod.MethodInfo;
var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(methodInfo);
context.HttpContext.Items["_AbpActionInfo"] = new AbpActionInfoInHttpContext
{
IsObjectResult = ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType)
};
if (unitOfWorkAttr?.IsDisabled == true)
{
await next();
return;
}
var options = CreateOptions(context, unitOfWorkAttr);
//Trying to begin a reserved UOW by AbpUnitOfWorkMiddleware
if (_unitOfWorkManager.TryBeginReserved(AbpUnitOfWorkMiddleware.UnitOfWorkReservationName, options))
{
var result = await next();
if (!Succeed(result))
{
await RollbackAsync(context);
}
return;
}
//Begin a new, independent unit of work
using (var uow = _unitOfWorkManager.Begin(options))
{
var result = await next();
if (Succeed(result))
{
await uow.CompleteAsync(context.HttpContext.RequestAborted);
}
}
}
private AbpUnitOfWorkOptions CreateOptions(PageHandlerExecutingContext context, UnitOfWorkAttribute unitOfWorkAttribute)
{
var options = new AbpUnitOfWorkOptions();
unitOfWorkAttribute?.SetOptions(options);
if (unitOfWorkAttribute?.IsTransactional == null)
{
options.IsTransactional = _defaultOptions.CalculateIsTransactional(
autoValue: !string.Equals(context.HttpContext.Request.Method, HttpMethod.Get.Method, StringComparison.OrdinalIgnoreCase)
);
}
return options;
}
private async Task RollbackAsync(PageHandlerExecutingContext context)
{
var currentUow = _unitOfWorkManager.Current;
if (currentUow != null)
{
await currentUow.RollbackAsync(context.HttpContext.RequestAborted);
}
}
private static bool Succeed(PageHandlerExecutedContext result)
{
return result.Exception == null || result.ExceptionHandled;
}
}
}

6
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Auditing namespace Volo.Abp.Auditing
{ {
@ -16,6 +17,11 @@ namespace Volo.Abp.Auditing
private static bool ShouldIntercept(Type type) private static bool ShouldIntercept(Type type)
{ {
if (DynamicProxyIgnoreTypes.Contains(type))
{
return false;
}
if (ShouldAuditTypeByDefault(type)) if (ShouldAuditTypeByDefault(type))
{ {
return true; return true;

5
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs

@ -3,6 +3,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Authorization namespace Volo.Abp.Authorization
{ {
@ -18,8 +19,8 @@ namespace Volo.Abp.Authorization
private static bool ShouldIntercept(Type type) private static bool ShouldIntercept(Type type)
{ {
return type.IsDefined(typeof(AuthorizeAttribute), true) || return !DynamicProxyIgnoreTypes.Contains(type) &&
AnyMethodHasAuthorizeAttribute(type); (type.IsDefined(typeof(AuthorizeAttribute), true) || AnyMethodHasAuthorizeAttribute(type));
} }
private static bool AnyMethodHasAuthorizeAttribute(Type implementationType) private static bool AnyMethodHasAuthorizeAttribute(Type implementationType)

48
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs

@ -3,26 +3,21 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Clients;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace Volo.Abp.Authorization namespace Volo.Abp.Authorization
{ {
public class MethodInvocationAuthorizationService : IMethodInvocationAuthorizationService, ITransientDependency public class MethodInvocationAuthorizationService : IMethodInvocationAuthorizationService, ITransientDependency
{ {
private readonly IAuthorizationService _authorizationService; private readonly IAbpAuthorizationPolicyProvider _abpAuthorizationPolicyProvider;
private readonly ICurrentUser _currentUser; private readonly IAbpAuthorizationService _abpAuthorizationService;
private readonly ICurrentClient _currentClient;
public MethodInvocationAuthorizationService( public MethodInvocationAuthorizationService(
IAuthorizationService authorizationService, IAbpAuthorizationPolicyProvider abpAuthorizationPolicyProvider,
ICurrentUser currentUser, IAbpAuthorizationService abpAuthorizationService)
ICurrentClient currentClient)
{ {
_authorizationService = authorizationService; _abpAuthorizationPolicyProvider = abpAuthorizationPolicyProvider;
_currentUser = currentUser; _abpAuthorizationService = abpAuthorizationService;
_currentClient = currentClient;
} }
public async Task CheckAsync(MethodInvocationAuthorizationContext context) public async Task CheckAsync(MethodInvocationAuthorizationContext context)
@ -32,10 +27,17 @@ namespace Volo.Abp.Authorization
return; return;
} }
foreach (var authorizationAttribute in GetAuthorizationDataAttributes(context.Method)) var authorizationPolicy = await AuthorizationPolicy.CombineAsync(
_abpAuthorizationPolicyProvider,
GetAuthorizationDataAttributes(context.Method)
);
if (authorizationPolicy == null)
{ {
await CheckAsync(authorizationAttribute); return;
} }
await _abpAuthorizationService.CheckAsync(authorizationPolicy);
} }
protected virtual bool AllowAnonymous(MethodInvocationAuthorizationContext context) protected virtual bool AllowAnonymous(MethodInvocationAuthorizationContext context)
@ -49,7 +51,7 @@ namespace Volo.Abp.Authorization
.GetCustomAttributes(true) .GetCustomAttributes(true)
.OfType<IAuthorizeData>(); .OfType<IAuthorizeData>();
if (methodInfo.IsPublic) if (methodInfo.IsPublic && methodInfo.DeclaringType != null)
{ {
attributes = attributes attributes = attributes
.Union( .Union(
@ -61,23 +63,5 @@ namespace Volo.Abp.Authorization
return attributes; return attributes;
} }
protected async Task CheckAsync(IAuthorizeData authorizationAttribute)
{
if (authorizationAttribute.Policy == null)
{
//TODO: Can we find a better, unified, way of checking if current request has been authenticated
if (!_currentUser.IsAuthenticated && !_currentClient.IsAuthenticated)
{
throw new AbpAuthorizationException("Authorization failed! User has not logged in.");
}
}
else
{
await _authorizationService.CheckAsync(authorizationAttribute.Policy);
}
//TODO: What about roles and other props?
}
} }
} }

37
framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.DynamicProxy
{
/// <summary>
/// Castle's dynamic proxy class feature will have performance issues for some components, such as the controller of Asp net core MVC.
/// For related discussions, see: https://github.com/castleproject/Core/issues/486 https://github.com/abpframework/abp/issues/3180
/// The Abp framework may enable interceptors for certain components (UOW, Auditing, Authorization, etc.), which requires dynamic proxy classes, but will cause application performance to decline.
/// We need to use other methods for the controller to implement interception, such as middleware or MVC / Page filters.
/// So we provide some ignored types to avoid enabling dynamic proxy classes.
/// By default it is empty. When you use middleware or filters for these components in your application, you can add these types to the list.
/// </summary>
public static class DynamicProxyIgnoreTypes
{
private static HashSet<Type> IgnoredTypes { get; } = new HashSet<Type>();
public static void Add<T>()
{
lock (IgnoredTypes)
{
IgnoredTypes.AddIfNotContains(typeof(T));
}
}
public static bool Contains(Type type, bool includeDerivedTypes = true)
{
lock (IgnoredTypes)
{
return includeDerivedTypes
? IgnoredTypes.Any(t => t.IsAssignableFrom(type))
: IgnoredTypes.Contains(type);
}
}
}
}

17
framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs

@ -108,6 +108,23 @@ namespace Volo.Abp.Reflection
?? defaultValue; ?? defaultValue;
} }
/// <summary>
/// Tries to gets attributes defined for a class member and it's declaring type including inherited attributes.
/// </summary>
/// <typeparam name="TAttribute">Type of the attribute</typeparam>
/// <param name="memberInfo">MemberInfo</param>
/// <param name="inherit">Inherit attribute from base classes</param>
public static IEnumerable<TAttribute> GetAttributesOfMemberOrDeclaringType<TAttribute>(MemberInfo memberInfo, bool inherit = true)
where TAttribute : class
{
var customAttributes = memberInfo.GetCustomAttributes(true).OfType<TAttribute>();
var declaringTypeCustomAttributes =
memberInfo.DeclaringType?.GetTypeInfo().GetCustomAttributes(true).OfType<TAttribute>();
return declaringTypeCustomAttributes != null
? customAttributes.Concat(declaringTypeCustomAttributes).Distinct()
: customAttributes;
}
/// <summary> /// <summary>
/// Gets value of a property by it's full path from given object /// Gets value of a property by it's full path from given object
/// </summary> /// </summary>

18
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Domain.Entities namespace Volo.Abp.Domain.Entities
{ {
@ -56,6 +58,14 @@ namespace Volo.Abp.Domain.Entities
{ {
_distributedEvents.Clear(); _distributedEvents.Clear();
} }
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
this,
validationContext
);
}
} }
[Serializable] [Serializable]
@ -115,5 +125,13 @@ namespace Volo.Abp.Domain.Entities
{ {
_distributedEvents.Clear(); _distributedEvents.Clear();
} }
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
this,
validationContext
);
}
} }
} }

47
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -11,6 +13,9 @@ namespace Volo.Abp.Domain.Entities
/// </summary> /// </summary>
public static class EntityHelper public static class EntityHelper
{ {
private static readonly ConcurrentDictionary<string, PropertyInfo> CachedIdProperties =
new ConcurrentDictionary<string, PropertyInfo>();
public static bool IsEntity([NotNull] Type type) public static bool IsEntity([NotNull] Type type)
{ {
return typeof(IEntity).IsAssignableFrom(type); return typeof(IEntity).IsAssignableFrom(type);
@ -95,32 +100,36 @@ namespace Volo.Abp.Domain.Entities
var lambdaBody = Expression.Equal(leftExpression, rightExpression); var lambdaBody = Expression.Equal(leftExpression, rightExpression);
return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam); return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
} }
public static void TrySetId<TKey>( public static void TrySetId<TKey>(
IEntity<TKey> entity, IEntity<TKey> entity,
Func<TKey> idFactory, Func<TKey> idFactory,
bool checkForDisableGuidGenerationAttribute = false) bool checkForDisableIdGenerationAttribute = false)
{ {
//TODO: Can be optimized (by caching per entity type)? var property = CachedIdProperties.GetOrAdd(
var entityType = entity.GetType(); $"{entity.GetType().FullName}-{checkForDisableIdGenerationAttribute}", () =>
var idProperty = entityType.GetProperty( {
nameof(entity.Id) var idProperty = entity
); .GetType()
.GetProperties()
.FirstOrDefault(x => x.Name == nameof(entity.Id) &&
x.GetSetMethod(true) != null);
if (idProperty == null || idProperty.GetSetMethod(true) == null) if (idProperty == null)
{ {
return; return null;
} }
if (checkForDisableGuidGenerationAttribute) if (checkForDisableIdGenerationAttribute &&
{ idProperty.IsDefined(typeof(DisableIdGenerationAttribute), true))
if (idProperty.IsDefined(typeof(DisableIdGenerationAttribute), true)) {
{ return null;
return; }
}
} return idProperty;
});
idProperty.SetValue(entity, idFactory()); property?.SetValue(entity, idFactory());
} }
} }
} }

7
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs

@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.EntityFrameworkCore namespace Volo.Abp.EntityFrameworkCore
{ {
@ -8,7 +7,9 @@ namespace Volo.Abp.EntityFrameworkCore
{ {
public static bool IsRelational(this DatabaseFacade database) public static bool IsRelational(this DatabaseFacade database)
{ {
return database.GetInfrastructure().GetService<IRelationalConnection>() != null; #pragma warning disable EF1001 // Internal EF Core API usage.
return ((IDatabaseFacadeDependenciesAccessor)database).Dependencies is IRelationalDatabaseFacadeDependencies;
#pragma warning restore EF1001 // Internal EF Core API usage.
} }
} }
} }

6
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs

@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Features namespace Volo.Abp.Features
{ {
@ -17,8 +18,9 @@ namespace Volo.Abp.Features
private static bool ShouldIntercept(Type type) private static bool ShouldIntercept(Type type)
{ {
return type.IsDefined(typeof(RequiresFeatureAttribute), true) || return !DynamicProxyIgnoreTypes.Contains(type) &&
AnyMethodHasRequiresFeatureAttribute(type); (type.IsDefined(typeof(RequiresFeatureAttribute), true) ||
AnyMethodHasRequiresFeatureAttribute(type));
} }
private static bool AnyMethodHasRequiresFeatureAttribute(Type implementationType) private static bool AnyMethodHasRequiresFeatureAttribute(Type implementationType)

7
framework/src/Volo.Abp.Minify/Volo/Abp/Minify/IMinifier.cs

@ -1,7 +1,12 @@
using JetBrains.Annotations;
namespace Volo.Abp.Minify namespace Volo.Abp.Minify
{ {
public interface IMinifier public interface IMinifier
{ {
string Minify(string source, string fileName = null); string Minify(
string source,
[CanBeNull] string fileName = null,
[CanBeNull] string originalFileName = null);
} }
} }

18
framework/src/Volo.Abp.Minify/Volo/Abp/Minify/NUglify/NUglifyMinifierBase.cs

@ -8,21 +8,31 @@ namespace Volo.Abp.Minify.NUglify
{ {
public abstract class NUglifyMinifierBase : IMinifier, ITransientDependency public abstract class NUglifyMinifierBase : IMinifier, ITransientDependency
{ {
private static void CheckErrors(UglifyResult result) private static void CheckErrors(UglifyResult result, string originalFileName)
{ {
if (result.HasErrors) if (result.HasErrors)
{ {
var errorMessage = "There are some errors on uglifying the given source code!";
if (originalFileName != null)
{
errorMessage += " Original file: " + originalFileName;
}
throw new NUglifyException( throw new NUglifyException(
$"There are some errors on uglifying the given source code!{Environment.NewLine}{result.Errors.Select(err => err.ToString()).JoinAsString(Environment.NewLine)}", $"{errorMessage}{Environment.NewLine}{result.Errors.Select(err => err.ToString()).JoinAsString(Environment.NewLine)}",
result.Errors result.Errors
); );
} }
} }
public string Minify(string source, string fileName = null) public string Minify(
string source,
string fileName = null,
string originalFileName = null)
{ {
var result = UglifySource(source, fileName); var result = UglifySource(source, fileName);
CheckErrors(result); CheckErrors(result, originalFileName);
return result.Code; return result.Code;
} }

13
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs

@ -5,6 +5,8 @@ using Volo.Abp.Reflection;
namespace Volo.Abp.Data namespace Volo.Abp.Data
{ {
//TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0
public static class HasExtraPropertiesExtensions public static class HasExtraPropertiesExtensions
{ {
public static bool HasProperty(this IHasExtraProperties source, string name) public static bool HasProperty(this IHasExtraProperties source, string name)
@ -12,17 +14,18 @@ namespace Volo.Abp.Data
return source.ExtraProperties.ContainsKey(name); return source.ExtraProperties.ContainsKey(name);
} }
public static object GetProperty(this IHasExtraProperties source, string name) public static object GetProperty(this IHasExtraProperties source, string name, object defaultValue = null)
{ {
return source.ExtraProperties?.GetOrDefault(name); return source.ExtraProperties?.GetOrDefault(name)
?? defaultValue;
} }
public static TProperty GetProperty<TProperty>(this IHasExtraProperties source, string name) public static TProperty GetProperty<TProperty>(this IHasExtraProperties source, string name, TProperty defaultValue = default)
{ {
var value = source.GetProperty(name); var value = source.GetProperty(name);
if (value == default) if (value == null)
{ {
return default; return defaultValue;
} }
if (TypeHelper.IsPrimitiveExtended(typeof(TProperty), includeEnums: true)) if (TypeHelper.IsPrimitiveExtended(typeof(TProperty), includeEnums: true))

2
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs

@ -2,6 +2,8 @@
namespace Volo.Abp.Data namespace Volo.Abp.Data
{ {
//TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0
public interface IHasExtraProperties public interface IHasExtraProperties
{ {
Dictionary<string, object> ExtraProperties { get; } Dictionary<string, object> ExtraProperties { get; }

11
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs

@ -1,11 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Data; using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending namespace Volo.Abp.ObjectExtending
{ {
[Serializable] [Serializable]
public class ExtensibleObject : IHasExtraProperties public class ExtensibleObject : IHasExtraProperties, IValidatableObject
{ {
public Dictionary<string, object> ExtraProperties { get; protected set; } public Dictionary<string, object> ExtraProperties { get; protected set; }
@ -13,5 +14,13 @@ namespace Volo.Abp.ObjectExtending
{ {
ExtraProperties = new Dictionary<string, object>(); ExtraProperties = new Dictionary<string, object>();
} }
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
this,
validationContext
);
}
} }
} }

200
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs

@ -0,0 +1,200 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.Data;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.ObjectExtending
{
public static class ExtensibleObjectValidator
{
[NotNull]
public static bool IsValid(
[NotNull] IHasExtraProperties extensibleObject,
[CanBeNull] ValidationContext objectValidationContext = null)
{
return GetValidationErrors(
extensibleObject,
objectValidationContext
).Any();
}
[NotNull]
public static List<ValidationResult> GetValidationErrors(
[NotNull] IHasExtraProperties extensibleObject,
[CanBeNull] ValidationContext objectValidationContext = null)
{
var validationErrors = new List<ValidationResult>();
AddValidationErrors(
extensibleObject,
validationErrors,
objectValidationContext
);
return validationErrors;
}
public static void AddValidationErrors(
[NotNull] IHasExtraProperties extensibleObject,
[NotNull] List<ValidationResult> validationErrors,
[CanBeNull] ValidationContext objectValidationContext = null)
{
Check.NotNull(extensibleObject, nameof(extensibleObject));
Check.NotNull(validationErrors, nameof(validationErrors));
if (objectValidationContext == null)
{
objectValidationContext = new ValidationContext(
extensibleObject,
null,
new Dictionary<object, object>()
);
}
var objectType = ProxyHelper.UnProxy(extensibleObject).GetType();
var objectExtensionInfo = ObjectExtensionManager.Instance
.GetOrNull(objectType);
if (objectExtensionInfo == null)
{
return;
}
AddPropertyValidationErrors(
extensibleObject,
validationErrors,
objectValidationContext,
objectExtensionInfo
);
ExecuteCustomObjectValidationActions(
extensibleObject,
validationErrors,
objectValidationContext,
objectExtensionInfo
);
}
private static void AddPropertyValidationErrors(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionInfo objectExtensionInfo)
{
var properties = objectExtensionInfo.GetProperties();
if (!properties.Any())
{
return;
}
foreach (var property in properties)
{
AddPropertyValidationErrors(extensibleObject, validationErrors, objectValidationContext, property);
}
}
private static void AddPropertyValidationErrors(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionPropertyInfo property)
{
AddPropertyValidationAttributeErrors(
extensibleObject,
validationErrors,
objectValidationContext,
property
);
ExecuteCustomPropertyValidationActions(
extensibleObject,
validationErrors,
objectValidationContext,
property
);
}
private static void AddPropertyValidationAttributeErrors(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionPropertyInfo property)
{
if (!property.ValidationAttributes.Any())
{
return;
}
var propertyValidationContext = new ValidationContext(extensibleObject, objectValidationContext, null)
{
DisplayName = property.Name,
MemberName = property.Name
};
foreach (var attribute in property.ValidationAttributes)
{
var result = attribute.GetValidationResult(
extensibleObject.GetProperty(property.Name),
propertyValidationContext
);
if (result != null)
{
validationErrors.Add(result);
}
}
}
private static void ExecuteCustomPropertyValidationActions(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionPropertyInfo property)
{
if (!property.Validators.Any())
{
return;
}
var context = new ObjectExtensionPropertyValidationContext(
property,
extensibleObject,
validationErrors,
objectValidationContext,
extensibleObject.GetProperty(property.Name)
);
foreach (var validator in property.Validators)
{
validator(context);
}
}
private static void ExecuteCustomObjectValidationActions(
IHasExtraProperties extensibleObject,
List<ValidationResult> validationErrors,
ValidationContext objectValidationContext,
ObjectExtensionInfo objectExtensionInfo)
{
if (!objectExtensionInfo.Validators.Any())
{
return;
}
var context = new ObjectExtensionValidationContext(
objectExtensionInfo,
extensibleObject,
validationErrors,
objectValidationContext
);
foreach (var validator in objectExtensionInfo.Validators)
{
validator(context);
}
}
}
}

4
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs

@ -17,11 +17,15 @@ namespace Volo.Abp.ObjectExtending
[NotNull] [NotNull]
public Dictionary<object, object> Configuration { get; } public Dictionary<object, object> Configuration { get; }
[NotNull]
public List<Action<ObjectExtensionValidationContext>> Validators { get; }
public ObjectExtensionInfo([NotNull] Type type) public ObjectExtensionInfo([NotNull] Type type)
{ {
Type = Check.AssignableTo<IHasExtraProperties>(type, nameof(type)); Type = Check.AssignableTo<IHasExtraProperties>(type, nameof(type));
Properties = new Dictionary<string, ObjectExtensionPropertyInfo>(); Properties = new Dictionary<string, ObjectExtensionPropertyInfo>();
Configuration = new Dictionary<object, object>(); Configuration = new Dictionary<object, object>();
Validators = new List<Action<ObjectExtensionValidationContext>>();
} }
public virtual bool HasProperty(string propertyName) public virtual bool HasProperty(string propertyName)

9
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace Volo.Abp.ObjectExtending namespace Volo.Abp.ObjectExtending
@ -15,6 +16,12 @@ namespace Volo.Abp.ObjectExtending
[NotNull] [NotNull]
public Type Type { get; } public Type Type { get; }
[NotNull]
public List<ValidationAttribute> ValidationAttributes { get; }
[NotNull]
public List<Action<ObjectExtensionPropertyValidationContext>> Validators { get; }
/// <summary> /// <summary>
/// Indicates whether to check the other side of the object mapping /// Indicates whether to check the other side of the object mapping
/// if it explicitly defines the property. This property is used in; /// if it explicitly defines the property. This property is used in;
@ -42,6 +49,8 @@ namespace Volo.Abp.ObjectExtending
Name = Check.NotNull(name, nameof(name)); Name = Check.NotNull(name, nameof(name));
Configuration = new Dictionary<object, object>(); Configuration = new Dictionary<object, object>();
ValidationAttributes = new List<ValidationAttribute>();
Validators = new List<Action<ObjectExtensionPropertyValidationContext>>();
} }
} }
} }

61
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionPropertyValidationContext
{
/// <summary>
/// Related property extension information.
/// </summary>
[NotNull]
public ObjectExtensionPropertyInfo ExtensionPropertyInfo { get; }
/// <summary>
/// Reference to the validating object.
/// </summary>
[NotNull]
public IHasExtraProperties ValidatingObject { get; }
/// <summary>
/// Add validation errors to this list.
/// </summary>
[NotNull]
public List<ValidationResult> ValidationErrors { get; }
/// <summary>
/// Validation context comes from the <see cref="IValidatableObject.Validate"/> method.
/// </summary>
[NotNull]
public ValidationContext ValidationContext { get; }
/// <summary>
/// The value of the validating property of the <see cref="ValidatingObject"/>.
/// </summary>
[CanBeNull]
public object Value { get; }
/// <summary>
/// Can be used to resolve services from the dependency injection container.
/// </summary>
[CanBeNull]
public IServiceProvider ServiceProvider => ValidationContext;
public ObjectExtensionPropertyValidationContext(
[NotNull] ObjectExtensionPropertyInfo objectExtensionPropertyInfo,
[NotNull] IHasExtraProperties validatingObject,
[NotNull] List<ValidationResult> validationErrors,
[NotNull] ValidationContext validationContext,
[CanBeNull] object value)
{
ExtensionPropertyInfo = Check.NotNull(objectExtensionPropertyInfo, nameof(objectExtensionPropertyInfo));
ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject));
ValidationErrors = Check.NotNull(validationErrors, nameof(validationErrors));
ValidationContext = Check.NotNull(validationContext, nameof(validationContext));
Value = value;
}
}
}

53
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionValidationContext
{
/// <summary>
/// Related object extension information.
/// </summary>
[NotNull]
public ObjectExtensionInfo ObjectExtensionInfo { get; }
/// <summary>
/// Reference to the validating object.
/// </summary>
[NotNull]
public IHasExtraProperties ValidatingObject { get; }
/// <summary>
/// Add validation errors to this list.
/// </summary>
[NotNull]
public List<ValidationResult> ValidationErrors { get; }
/// <summary>
/// Validation context comes from the <see cref="IValidatableObject.Validate"/> method.
/// </summary>
[NotNull]
public ValidationContext ValidationContext { get; }
/// <summary>
/// Can be used to resolve services from the dependency injection container.
/// </summary>
[CanBeNull]
public IServiceProvider ServiceProvider => ValidationContext;
public ObjectExtensionValidationContext(
[NotNull] ObjectExtensionInfo objectExtensionInfo,
[NotNull] IHasExtraProperties validatingObject,
[NotNull] List<ValidationResult> validationErrors,
[NotNull] ValidationContext validationContext)
{
ObjectExtensionInfo = Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo));
ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject));
ValidationErrors = Check.NotNull(validationErrors, nameof(validationErrors));
ValidationContext = Check.NotNull(validationContext, nameof(validationContext));
}
}
}

13
framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Quartz; using Quartz;
using Quartz.Impl; using Quartz.Impl;
using Quartz.Spi; using Quartz.Spi;
@ -20,9 +21,19 @@ namespace Volo.Abp.Quartz
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)
{ {
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpQuartzPreOptions>>().Value;
_scheduler = context.ServiceProvider.GetService<IScheduler>(); _scheduler = context.ServiceProvider.GetService<IScheduler>();
_scheduler.JobFactory = context.ServiceProvider.GetService<IJobFactory>(); _scheduler.JobFactory = context.ServiceProvider.GetService<IJobFactory>();
AsyncHelper.RunSync(() => _scheduler.Start());
if (options.StartDelay.Ticks > 0)
{
AsyncHelper.RunSync(() => _scheduler.StartDelayed(options.StartDelay));
}
else
{
AsyncHelper.RunSync(() => _scheduler.Start());
}
} }
public override void OnApplicationShutdown(ApplicationShutdownContext context) public override void OnApplicationShutdown(ApplicationShutdownContext context)

11
framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzPreOptions.cs

@ -1,14 +1,23 @@
using System.Collections.Specialized; using System;
using System.Collections.Specialized;
namespace Volo.Abp.Quartz namespace Volo.Abp.Quartz
{ {
public class AbpQuartzPreOptions public class AbpQuartzPreOptions
{ {
/// <summary>
/// The quartz configuration. Available properties can be found within Quartz.Impl.StdSchedulerFactory.
/// </summary>
public NameValueCollection Properties { get; set; } public NameValueCollection Properties { get; set; }
/// <summary>
/// How long Quartz should wait before starting. Default: 0.
/// </summary>
public TimeSpan StartDelay { get; set; }
public AbpQuartzPreOptions() public AbpQuartzPreOptions()
{ {
Properties = new NameValueCollection(); Properties = new NameValueCollection();
StartDelay = new TimeSpan(0);
} }
} }
} }

11
framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs

@ -1,5 +1,7 @@
using System.Reflection; using System;
using System.Reflection;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Uow namespace Volo.Abp.Uow
{ {
@ -7,10 +9,15 @@ namespace Volo.Abp.Uow
{ {
public static void RegisterIfNeeded(IOnServiceRegistredContext context) public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{ {
if (UnitOfWorkHelper.IsUnitOfWorkType(context.ImplementationType.GetTypeInfo())) if (ShouldIntercept(context.ImplementationType))
{ {
context.Interceptors.TryAdd<UnitOfWorkInterceptor>(); context.Interceptors.TryAdd<UnitOfWorkInterceptor>();
} }
} }
private static bool ShouldIntercept(Type type)
{
return !DynamicProxyIgnoreTypes.Contains(type) && UnitOfWorkHelper.IsUnitOfWorkType(type.GetTypeInfo());
}
} }
} }

1
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs

@ -18,6 +18,7 @@ namespace Volo.Abp.Validation
context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded); context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded);
AutoAddObjectValidationContributors(context.Services); AutoAddObjectValidationContributors(context.Services);
} }
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
Configure<AbpVirtualFileSystemOptions>(options => Configure<AbpVirtualFileSystemOptions>(options =>

11
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs

@ -1,4 +1,6 @@
using Volo.Abp.DependencyInjection; using System;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Validation namespace Volo.Abp.Validation
{ {
@ -6,10 +8,15 @@ namespace Volo.Abp.Validation
{ {
public static void RegisterIfNeeded(IOnServiceRegistredContext context) public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{ {
if (typeof(IValidationEnabled).IsAssignableFrom(context.ImplementationType)) if (ShouldIntercept(context.ImplementationType))
{ {
context.Interceptors.TryAdd<ValidationInterceptor>(); context.Interceptors.TryAdd<ValidationInterceptor>();
} }
} }
private static bool ShouldIntercept(Type type)
{
return !DynamicProxyIgnoreTypes.Contains(type) && typeof(IValidationEnabled).IsAssignableFrom(type);
}
} }
} }

23
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj

@ -24,6 +24,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Mvc.UI\Volo.Abp.AspNetCore.Mvc.UI.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" /> <ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> <ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Tests\Volo.Abp.AspNetCore.Tests.csproj" /> <ProjectReference Include="..\Volo.Abp.AspNetCore.Tests\Volo.Abp.AspNetCore.Tests.csproj" />
@ -39,6 +40,28 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Update="Volo\Abp\AspNetCore\Mvc\Uow\UnitOfWorkTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\Auditing\AuditTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\Features\FeatureTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\ExceptionHandling\ExceptionTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\Authorization\AuthTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<!-- https://github.com/NuGet/Home/issues/4412. --> <!-- https://github.com/NuGet/Home/issues/4412. -->
<Target Name="CopyDepsFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''"> <Target Name="CopyDepsFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">

10
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs

@ -1,6 +1,9 @@
using System; using System;
using System.Linq;
using Localization.Resources.AbpUi; using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Authorization; using Volo.Abp.AspNetCore.Mvc.Authorization;
using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.Localization;
@ -73,6 +76,11 @@ namespace Volo.Abp.AspNetCore.Mvc
options.Languages.Add(new LanguageInfo("en", "en", "English")); options.Languages.Add(new LanguageInfo("en", "en", "English"));
options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));
}); });
Configure<RazorPagesOptions>(options =>
{
options.RootDirectory = "/Volo/Abp/AspNetCore/Mvc";
});
} }
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)

1
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs

@ -51,6 +51,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>());
} }
[Fact] [Fact]
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object() public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object()
{ {

19
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml

@ -0,0 +1,19 @@
@page
@model Volo.Abp.AspNetCore.Mvc.Auditing.AuditTestPage
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div>
</div>
</body>
</html>

33
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml.cs

@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Abp.Auditing;
namespace Volo.Abp.AspNetCore.Mvc.Auditing
{
public class AuditTestPage : AbpPageModel
{
private readonly AbpAuditingOptions _options;
public AuditTestPage(IOptions<AbpAuditingOptions> options)
{
_options = options.Value;
}
public IActionResult OnGetAuditSuccessForGetRequests()
{
return new OkResult();
}
public IActionResult OnGetAuditFailForGetRequests()
{
throw new UserFriendlyException("Exception occurred!");
}
public ObjectResult OnGetAuditFailForGetRequestsReturningObject()
{
throw new UserFriendlyException("Exception occurred!");
}
}
}

65
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs

@ -0,0 +1,65 @@
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using NSubstitute;
using Volo.Abp.Auditing;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Auditing
{
public class AuditTestPage_Tests : AspNetCoreMvcTestBase
{
private readonly AbpAuditingOptions _options;
private IAuditingStore _auditingStore;
public AuditTestPage_Tests()
{
_options = ServiceProvider.GetRequiredService<IOptions<AbpAuditingOptions>>().Value;
_auditingStore = ServiceProvider.GetRequiredService<IAuditingStore>();
}
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
_auditingStore = Substitute.For<IAuditingStore>();
services.Replace(ServiceDescriptor.Singleton(_auditingStore));
base.ConfigureServices(context, services);
}
[Fact]
public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests()
{
_options.IsEnabledForGetRequests = true;
_options.AlwaysLogOnException = false;
await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditSuccessForGetRequests");
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>());
}
[Fact]
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always()
{
_options.IsEnabled = true;
_options.AlwaysLogOnException = true;
try
{
await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditFailForGetRequests", System.Net.HttpStatusCode.Forbidden);
}
catch { }
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>());
}
[Fact]
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object()
{
_options.IsEnabled = true;
_options.AlwaysLogOnException = true;
await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditFailForGetRequestsReturningObject", System.Net.HttpStatusCode.Forbidden);
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>());
}
}
}

19
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml

@ -0,0 +1,19 @@
@page
@model Volo.Abp.AspNetCore.Mvc.Authorization.AuthTestPage
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div>
</div>
</body>
</html>

18
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml.cs

@ -0,0 +1,18 @@
using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace Volo.Abp.AspNetCore.Mvc.Authorization
{
[Authorize]
public class AuthTestPage : AbpPageModel
{
public static Guid FakeUserId { get; } = Guid.NewGuid();
public ActionResult OnGet()
{
return Content("OK");
}
}
}

40
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage_Tests.cs

@ -0,0 +1,40 @@
using System.Security.Claims;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.AspNetCore.TestBase;
using Volo.Abp.Autofac;
using Volo.Abp.MemoryDb;
using Volo.Abp.Modularity;
using Volo.Abp.Security.Claims;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Authorization
{
[DependsOn(
typeof(AbpAspNetCoreTestBaseModule),
typeof(AbpMemoryDbTestModule),
typeof(AbpAspNetCoreMvcModule),
typeof(AbpAutofacModule)
)]
public class AuthTestPage_Tests: AspNetCoreMvcTestBase
{
private readonly FakeUserClaims _fakeRequiredService;
public AuthTestPage_Tests()
{
_fakeRequiredService = GetRequiredService<FakeUserClaims>();
}
[Fact]
public async Task Should_Call_Simple_Authorized_Method_With_Authenticated_User()
{
_fakeRequiredService.Claims.AddRange(new[]
{
new Claim(AbpClaimTypes.UserId, AuthTestController.FakeUserId.ToString())
});
var result = await GetResponseAsStringAsync("/Authorization/AuthTestPage");
result.ShouldBe("OK");
}
}
}

4
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs

@ -30,9 +30,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
result.Error.ShouldNotBeNull(); result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe("This is a sample exception!"); result.Error.Message.ShouldBe("This is a sample exception!");
#pragma warning disable 4014
_fakeExceptionSubscriber _fakeExceptionSubscriber
.Received() .Received()
.HandleAsync(Arg.Any<ExceptionNotificationContext>()); .HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
} }
[Fact] [Fact]
@ -44,9 +46,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
) )
); );
#pragma warning disable 4014
_fakeExceptionSubscriber _fakeExceptionSubscriber
.DidNotReceive() .DidNotReceive()
.HandleAsync(Arg.Any<ExceptionNotificationContext>()); .HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
} }
} }
} }

19
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml

@ -0,0 +1,19 @@
@page
@model Volo.Abp.AspNetCore.Mvc.ExceptionHandling.ExceptionTestPage
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div>
</div>
</body>
</html>

18
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
{
public class ExceptionTestPage : AbpPageModel
{
public void OnGetUserFriendlyException1()
{
throw new UserFriendlyException("This is a sample exception!");
}
public IActionResult OnGetUserFriendlyException2()
{
throw new UserFriendlyException("This is a sample exception!");
}
}
}

56
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs

@ -0,0 +1,56 @@
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NSubstitute;
using Shouldly;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
{
public class ExceptionTestPage_Tests : AspNetCoreMvcTestBase
{
private IExceptionSubscriber _fakeExceptionSubscriber;
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
base.ConfigureServices(context, services);
_fakeExceptionSubscriber = Substitute.For<IExceptionSubscriber>();
services.AddSingleton(_fakeExceptionSubscriber);
}
[Fact]
public async Task Should_Return_RemoteServiceErrorResponse_For_UserFriendlyException_For_Void_Return_Value()
{
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException1", HttpStatusCode.Forbidden);
result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe("This is a sample exception!");
#pragma warning disable 4014
_fakeExceptionSubscriber
.Received()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
[Fact]
public async Task Should_Not_Handle_Exceptions_For_ActionResult_Return_Values()
{
await Assert.ThrowsAsync<UserFriendlyException>(
async () => await GetResponseAsObjectAsync<RemoteServiceErrorResponse>(
"/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException2"
)
);
#pragma warning disable 4014
_fakeExceptionSubscriber
.DidNotReceive()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
}
}

19
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml

@ -0,0 +1,19 @@
@page
@model Volo.Abp.AspNetCore.Mvc.Features.FeatureTestPage
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div>
</div>
</body>
</html>

27
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs

@ -0,0 +1,27 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Abp.Features;
namespace Volo.Abp.AspNetCore.Mvc.Features
{
public class FeatureTestPage : AbpPageModel
{
[RequiresFeature("AllowedFeature")]
public Task OnGetAllowedFeatureAsync()
{
return Task.CompletedTask;
}
[RequiresFeature("NotAllowedFeature")]
public void OnGetNotAllowedFeature()
{
}
public ObjectResult OnGetNoFeature()
{
return new ObjectResult(42);
}
}
}

34
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage_Tests.cs

@ -0,0 +1,34 @@
using System.Net;
using System.Threading.Tasks;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Features
{
public class FeatureTestPage_Tests : AspNetCoreMvcTestBase
{
[Fact]
public async Task Should_Allow_Enabled_Features()
{
await GetResponseAsStringAsync(
"/Features/FeatureTestPage?handler=AllowedFeature"
);
}
[Fact]
public async Task Should_Not_Allow_Not_Enabled_Features()
{
await GetResponseAsStringAsync(
"/Features/FeatureTestPage?handler=NotAllowedFeature",
HttpStatusCode.Unauthorized
);
}
[Fact]
public async Task Should_Allow_Actions_With_No_Feature()
{
await GetResponseAsStringAsync(
"/Features/FeatureTestPage?handler=NoFeature"
);
}
}
}

37
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs

@ -0,0 +1,37 @@
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Uow
{
public class UnitOfWorkPageFilter_Exception_Rollback_Tests : AspNetCoreMvcTestBase
{
[Fact]
public async Task Should_Rollback_Transaction_For_Handled_Exceptions()
{
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/Uow/UnitOfWorkTestPage?handler=HandledException", HttpStatusCode.Forbidden);
result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe("This is a sample exception!");
}
[Fact]
public async Task Should_Gracefully_Handle_Exceptions_On_Complete()
{
var response = await GetResponseAsync("/Uow/UnitOfWorkTestPage?handler=ExceptionOnComplete", HttpStatusCode.Forbidden);
response.Headers.GetValues(AbpHttpConsts.AbpErrorFormat).FirstOrDefault().ShouldBe("true");
var resultAsString = await response.Content.ReadAsStringAsync();
var result = ServiceProvider.GetRequiredService<IJsonSerializer>().Deserialize<RemoteServiceErrorResponse>(resultAsString);
result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe(TestUnitOfWorkConfig.ExceptionOnCompleteMessage);
}
}
}

22
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Tests.cs

@ -0,0 +1,22 @@
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Uow
{
public class UnitOfWorkPageFilter_Tests: AspNetCoreMvcTestBase
{
[Fact]
public async Task Get_Actions_Should_Not_Be_Transactional()
{
await GetResponseAsStringAsync("/Uow/UnitOfWorkTestPage?handler=RequiresUow");
}
[Fact]
public async Task Non_Get_Actions_Should_Be_Transactional()
{
var result = await Client.PostAsync("/Uow/UnitOfWorkTestPage?handler=RequiresUow", null);
result.IsSuccessStatusCode.ShouldBeTrue();
}
}
}

19
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml

@ -0,0 +1,19 @@
@page
@model Volo.Abp.AspNetCore.Mvc.Uow.UnitOfWorkTestPage
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div>
</div>
</body>
</html>

54
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs

@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Mvc;
using Shouldly;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Abp.Uow;
namespace Volo.Abp.AspNetCore.Mvc.Uow
{
[IgnoreAntiforgeryToken]
public class UnitOfWorkTestPage : AbpPageModel
{
private readonly TestUnitOfWorkConfig _testUnitOfWorkConfig;
public UnitOfWorkTestPage(TestUnitOfWorkConfig testUnitOfWorkConfig)
{
_testUnitOfWorkConfig = testUnitOfWorkConfig;
}
public IActionResult OnGetRequiresUow()
{
CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse();
return Content("OK");
}
public IActionResult OnPostRequiresUow()
{
CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue();
return Content("OK");
}
[UnitOfWork(isTransactional: true)]
public void OnGetHandledException()
{
CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue();
throw new UserFriendlyException("This is a sample exception!");
}
public ObjectResult OnGetExceptionOnComplete()
{
CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse();
_testUnitOfWorkConfig.ThrowExceptionOnComplete = true;
//Prevent rendering of pages.
return new ObjectResult("");
}
}
}

4
framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs

@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization.TestServices; using Volo.Abp.Authorization.TestServices;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Abp.Authorization namespace Volo.Abp.Authorization
@ -13,7 +14,8 @@ namespace Volo.Abp.Authorization
{ {
context.Services.OnRegistred(onServiceRegistredContext => context.Services.OnRegistred(onServiceRegistredContext =>
{ {
if (typeof(IMyAuthorizedService1).IsAssignableFrom(onServiceRegistredContext.ImplementationType)) if (typeof(IMyAuthorizedService1).IsAssignableFrom(onServiceRegistredContext.ImplementationType) &&
!DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType))
{ {
onServiceRegistredContext.Interceptors.TryAdd<AuthorizationInterceptor>(); onServiceRegistredContext.Interceptors.TryAdd<AuthorizationInterceptor>();
} }

25
framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs

@ -1,4 +1,10 @@
using Volo.Abp.Testing; using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading;
using Volo.Abp.Security.Claims;
using Volo.Abp.Testing;
namespace Volo.Abp.Authorization namespace Volo.Abp.Authorization
{ {
@ -8,5 +14,20 @@ namespace Volo.Abp.Authorization
{ {
options.UseAutofac(); options.UseAutofac();
} }
protected override void AfterAddApplication(IServiceCollection services)
{
var claims = new List<Claim>() {
new Claim(AbpClaimTypes.UserName, "Douglas"),
new Claim(AbpClaimTypes.UserId, "1fcf46b2-28c3-48d0-8bac-fa53268a2775"),
new Claim(AbpClaimTypes.Role, "MyRole")
};
var identity = new ClaimsIdentity(claims);
var claimsPrincipal = new ClaimsPrincipal(identity);
var principalAccessor = Substitute.For<ICurrentPrincipalAccessor>();
principalAccessor.Principal.Returns(ci => claimsPrincipal);
Thread.CurrentPrincipal = claimsPrincipal;
}
} }
} }

45
framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs

@ -9,14 +9,33 @@ namespace Volo.Abp.Authorization
public class Authorization_Tests : AuthorizationTestBase public class Authorization_Tests : AuthorizationTestBase
{ {
private readonly IMyAuthorizedService1 _myAuthorizedService1; private readonly IMyAuthorizedService1 _myAuthorizedService1;
private readonly IMySimpleAuthorizedService _simpleAuthorizedService;
private readonly IMyAuthorizedServiceWithRole _myAuthorizedServiceWithRole;
private readonly IPermissionDefinitionManager _permissionDefinitionManager; private readonly IPermissionDefinitionManager _permissionDefinitionManager;
public Authorization_Tests() public Authorization_Tests()
{ {
_myAuthorizedService1 = GetRequiredService<IMyAuthorizedService1>(); _myAuthorizedService1 = GetRequiredService<IMyAuthorizedService1>();
_simpleAuthorizedService = GetRequiredService<IMySimpleAuthorizedService>();
_myAuthorizedServiceWithRole = GetRequiredService<IMyAuthorizedServiceWithRole>();
_permissionDefinitionManager = GetRequiredService<IPermissionDefinitionManager>(); _permissionDefinitionManager = GetRequiredService<IPermissionDefinitionManager>();
} }
[Fact]
public async Task Should_Not_Allow_To_Call_Authorized_Method_For_Anonymous_User()
{
await Assert.ThrowsAsync<AbpAuthorizationException>(async () =>
{
await _simpleAuthorizedService.ProtectedByClassAsync();
});
}
[Fact]
public async Task Should_Allow_To_Call_Anonymous_Method_For_Anonymous_User()
{
(await _simpleAuthorizedService.AnonymousAsync()).ShouldBe(42);
}
[Fact] [Fact]
public async Task Should_Not_Allow_To_Call_Method_If_Has_No_Permission_ProtectedByClass() public async Task Should_Not_Allow_To_Call_Method_If_Has_No_Permission_ProtectedByClass()
{ {
@ -52,5 +71,31 @@ namespace Volo.Abp.Authorization
{ {
_permissionDefinitionManager.GetGroups().Count.ShouldBe(1); _permissionDefinitionManager.GetGroups().Count.ShouldBe(1);
} }
[Fact]
public async Task Should_Not_Allow_To_Call_Method_If_Has_No_Role_ProtectedByRole_Async()
{
await Assert.ThrowsAsync<AbpAuthorizationException>(async () =>
{
await _myAuthorizedServiceWithRole.ProtectedByAnotherRole().ConfigureAwait(false);
}).ConfigureAwait(false);
}
[Fact]
public async Task Should_Allow_To_Call_Method_If_Has_No_Role_ProtectedByRole_Async()
{
int result = await _myAuthorizedServiceWithRole.ProtectedByRole().ConfigureAwait(false);
result.ShouldBe(42);
}
[Fact]
public async Task Should_Not_Allow_To_Call_Method_If_Has_No_Role_ProtectedByScheme_Async()
{
await Assert.ThrowsAsync<AbpAuthorizationException>(async () =>
{
await _myAuthorizedServiceWithRole.ProtectedByScheme().ConfigureAwait(false);
}).ConfigureAwait(false);
}
} }
} }

13
framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMyAuthorizedServiceWithRole.cs

@ -0,0 +1,13 @@
using System.Threading.Tasks;
namespace Volo.Abp.Authorization.TestServices
{
public interface IMyAuthorizedServiceWithRole
{
Task<int> ProtectedByRole();
Task<int> ProtectedByScheme();
Task<int> ProtectedByAnotherRole();
}
}

11
framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMySimpleAuthorizedService.cs

@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace Volo.Abp.Authorization.TestServices
{
public interface IMySimpleAuthorizedService
{
Task<int> ProtectedByClassAsync();
Task<int> AnonymousAsync();
}
}

2
framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedService1.cs

@ -31,4 +31,4 @@ namespace Volo.Abp.Authorization.TestServices
return 42; return 42;
} }
} }
} }

27
framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedServiceWithRole.cs

@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Authorization.TestServices
{
[Authorize(Roles = "MyRole")]
public class MyAuthorizedServiceWithRole : IMyAuthorizedServiceWithRole, ITransientDependency
{
public virtual Task<int> ProtectedByRole()
{
return Task.FromResult(42);
}
[Authorize(Roles = "MyAnotherRole")]
public virtual Task<int> ProtectedByAnotherRole()
{
return Task.FromResult(42);
}
[Authorize(AuthenticationSchemes = "Bearer")]
public virtual Task<int> ProtectedByScheme()
{
return Task.FromResult(42);
}
}
}

21
framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MySimpleAuthorizedService.cs

@ -0,0 +1,21 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Authorization.TestServices
{
[Authorize]
public class MySimpleAuthorizedService : IMySimpleAuthorizedService, ITransientDependency
{
public Task<int> ProtectedByClassAsync()
{
return Task.FromResult(42);
}
[AllowAnonymous]
public Task<int> AnonymousAsync()
{
return Task.FromResult(42);
}
}
}

19
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
@ -33,6 +34,15 @@ namespace Volo.Abp.Domain.Entities
myEntityDisablesIdGeneration.Id.ShouldBe(default); myEntityDisablesIdGeneration.Id.ShouldBe(default);
} }
[Fact]
public static void SetId_NewIdFromBaseClass()
{
var idValue = Guid.NewGuid();
var myNewIdEntity = new NewIdEntity();
EntityHelper.TrySetId(myNewIdEntity, () => idValue, true);
myNewIdEntity.Id.ShouldBe(idValue);
}
private class MyEntityDerivedFromAggregateRoot : AggregateRoot<Guid> private class MyEntityDerivedFromAggregateRoot : AggregateRoot<Guid>
{ {
@ -53,5 +63,14 @@ namespace Volo.Abp.Domain.Entities
[DisableIdGeneration] [DisableIdGeneration]
public override Guid Id { get; protected set; } public override Guid Id { get; protected set; }
} }
public class NewIdEntity : Entity<Guid>
{
public new Guid Id
{
get => base.Id;
set => base.Id = value;
}
}
} }
} }

4
framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs

@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
using Shouldly; using Shouldly;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Testing; using Volo.Abp.Testing;
using Volo.Abp.Validation; using Volo.Abp.Validation;
@ -108,7 +109,8 @@ namespace Volo.Abp.FluentValidation
{ {
context.Services.OnRegistred(onServiceRegistredContext => context.Services.OnRegistred(onServiceRegistredContext =>
{ {
if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType)) if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType) &&
!DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType))
{ {
onServiceRegistredContext.Interceptors.TryAdd<ValidationInterceptor>(); onServiceRegistredContext.Interceptors.TryAdd<ValidationInterceptor>();
} }

7
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs

@ -1,6 +1,5 @@
using System; using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Mongo2Go;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.TestApp; using Volo.Abp.TestApp;
@ -15,13 +14,11 @@ namespace Volo.Abp.MongoDB
)] )]
public class AbpMongoDbTestModule : AbpModule public class AbpMongoDbTestModule : AbpModule
{ {
private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start();
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') +
"Db_" + "Db_" +
Guid.NewGuid().ToString("N"); Guid.NewGuid().ToString("N");
Configure<AbpDbConnectionOptions>(options => Configure<AbpDbConnectionOptions>(options =>
{ {

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Auditing/Auditing_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.Auditing namespace Volo.Abp.MongoDB.Auditing
{ {
[Collection(MongoTestCollection.Name)]
public class Auditing_Tests : Auditing_Tests<AbpMongoDbTestModule> public class Auditing_Tests : Auditing_Tests<AbpMongoDbTestModule>
{ {

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Creation_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.DataFiltering namespace Volo.Abp.MongoDB.DataFiltering
{ {
[Collection(MongoTestCollection.Name)]
public class MultiTenant_Creation_Tests : MultiTenant_Creation_Tests<AbpMongoDbTestModule> public class MultiTenant_Creation_Tests : MultiTenant_Creation_Tests<AbpMongoDbTestModule>
{ {

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Filter_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.DataFiltering namespace Volo.Abp.MongoDB.DataFiltering
{ {
[Collection(MongoTestCollection.Name)]
public class MultiTenant_Filter_Tests : MultiTenant_Filter_Tests<AbpMongoDbTestModule> public class MultiTenant_Filter_Tests : MultiTenant_Filter_Tests<AbpMongoDbTestModule>
{ {

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Filter_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.DataFiltering namespace Volo.Abp.MongoDB.DataFiltering
{ {
[Collection(MongoTestCollection.Name)]
public class SoftDelete_Filter_Tests : SoftDelete_Filter_Tests<AbpMongoDbTestModule> public class SoftDelete_Filter_Tests : SoftDelete_Filter_Tests<AbpMongoDbTestModule>
{ {

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.DataFiltering namespace Volo.Abp.MongoDB.DataFiltering
{ {
[Collection(MongoTestCollection.Name)]
public class SoftDelete_Tests : SoftDelete_Tests<AbpMongoDbTestModule> public class SoftDelete_Tests : SoftDelete_Tests<AbpMongoDbTestModule>
{ {

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ConcurrencyStamp_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.Domain namespace Volo.Abp.MongoDB.Domain
{ {
[Collection(MongoTestCollection.Name)]
public class ConcurrencyStamp_Tests : ConcurrencyStamp_Tests<AbpMongoDbTestModule> public class ConcurrencyStamp_Tests : ConcurrencyStamp_Tests<AbpMongoDbTestModule>
{ {

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ExtraProperties_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.Domain namespace Volo.Abp.MongoDB.Domain
{ {
[Collection(MongoTestCollection.Name)]
public class ExtraProperties_Tests : ExtraProperties_Tests<AbpMongoDbTestModule> public class ExtraProperties_Tests : ExtraProperties_Tests<AbpMongoDbTestModule>
{ {

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/DomainEvents_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.DomainEvents namespace Volo.Abp.MongoDB.DomainEvents
{ {
[Collection(MongoTestCollection.Name)]
public class DomainEvents_Tests : DomainEvents_Tests<AbpMongoDbTestModule> public class DomainEvents_Tests : DomainEvents_Tests<AbpMongoDbTestModule>
{ {
} }

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/EntityChangeEvents_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.DomainEvents namespace Volo.Abp.MongoDB.DomainEvents
{ {
[Collection(MongoTestCollection.Name)]
public class EntityChangeEvents_Tests : EntityChangeEvents_Tests<AbpMongoDbTestModule> public class EntityChangeEvents_Tests : EntityChangeEvents_Tests<AbpMongoDbTestModule>
{ {

16
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs

@ -0,0 +1,16 @@
using System;
using Mongo2Go;
namespace Volo.Abp.MongoDB
{
public class MongoDbFixture : IDisposable
{
private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start();
public static readonly string ConnectionString = MongoDbRunner.ConnectionString;
public void Dispose()
{
MongoDbRunner?.Dispose();
}
}
}

10
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoTestCollection.cs

@ -0,0 +1,10 @@
using Xunit;
namespace Volo.Abp.MongoDB
{
[CollectionDefinition(Name)]
public class MongoTestCollection : ICollectionFixture<MongoDbFixture>
{
public const string Name = "MongoDB Collection";
}
}

1
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs

@ -9,6 +9,7 @@ using Xunit;
namespace Volo.Abp.MongoDB.Repositories namespace Volo.Abp.MongoDB.Repositories
{ {
[Collection(MongoTestCollection.Name)]
public class Repository_Basic_Tests : Repository_Basic_Tests<AbpMongoDbTestModule> public class Repository_Basic_Tests : Repository_Basic_Tests<AbpMongoDbTestModule>
{ {
[Fact] [Fact]

1
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests_With_Int_Pk.cs

@ -4,6 +4,7 @@ using Xunit;
namespace Volo.Abp.MongoDB.Repositories namespace Volo.Abp.MongoDB.Repositories
{ {
[Collection(MongoTestCollection.Name)]
public class Repository_Basic_Tests_With_Int_Pk : Repository_Basic_Tests_With_Int_Pk<AbpMongoDbTestModule> public class Repository_Basic_Tests_With_Int_Pk : Repository_Basic_Tests_With_Int_Pk<AbpMongoDbTestModule>
{ {
[Fact(Skip = "Int PKs are not working for MongoDb")] [Fact(Skip = "Int PKs are not working for MongoDb")]

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Queryable_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.Repositories namespace Volo.Abp.MongoDB.Repositories
{ {
[Collection(MongoTestCollection.Name)]
public class Repository_Queryable_Tests : Repository_Queryable_Tests<AbpMongoDbTestModule> public class Repository_Queryable_Tests : Repository_Queryable_Tests<AbpMongoDbTestModule>
{ {

2
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs

@ -1,7 +1,9 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.Repositories namespace Volo.Abp.MongoDB.Repositories
{ {
[Collection(MongoTestCollection.Name)]
public class Repository_Specifications_Tests : Repository_Specifications_Tests<AbpMongoDbTestModule> public class Repository_Specifications_Tests : Repository_Specifications_Tests<AbpMongoDbTestModule>
{ {
} }

3
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/Uow_Completed_Tests.cs

@ -1,9 +1,10 @@
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.Uow namespace Volo.Abp.MongoDB.Uow
{ {
[Collection(MongoTestCollection.Name)]
public class Uow_Completed_Tests : Uow_Completed_Tests<AbpMongoDbTestModule> public class Uow_Completed_Tests : Uow_Completed_Tests<AbpMongoDbTestModule>
{ {
} }
} }

172
framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs

@ -0,0 +1,172 @@
using System.ComponentModel.DataAnnotations;
using Shouldly;
using Volo.Abp.Data;
using Volo.Abp.Threading;
using Xunit;
namespace Volo.Abp.ObjectExtending
{
public class ExtensibleObjectValidator_Tests
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
static ExtensibleObjectValidator_Tests()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.AddOrUpdate<ExtensiblePersonObject>(options =>
{
options.AddOrUpdateProperty<string>("Name", propertyInfo =>
{
propertyInfo.ValidationAttributes.Add(new RequiredAttribute());
propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(64) { MinimumLength = 2 });
});
options.AddOrUpdateProperty<string>("Address", propertyInfo =>
{
propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(255));
});
options.AddOrUpdateProperty<byte>("Age", propertyInfo =>
{
propertyInfo.ValidationAttributes.Add(new RequiredAttribute());
propertyInfo.ValidationAttributes.Add(new RangeAttribute(18, 99));
});
options.AddOrUpdateProperty<bool>("IsMarried", propertyInfo =>
{
});
options.AddOrUpdateProperty<string>("Password", propertyInfo =>
{
});
options.AddOrUpdateProperty<string>("PasswordRepeat", propertyInfo =>
{
propertyInfo.Validators.Add(context =>
{
if (context.ValidatingObject.HasProperty("Password"))
{
if (context.ValidatingObject.GetProperty<string>("Password") !=
context.Value as string)
{
context.ValidationErrors.Add(
new ValidationResult(
"If you specify a password, then please correctly repeat it!",
new[] {"Password", "PasswordRepeat"}
)
);
}
}
});
});
options.Validators.Add(context =>
{
if (context.ValidatingObject.GetProperty<string>("Name") == "BadValue")
{
context.ValidationErrors.Add(
new ValidationResult(
"Name can not be 'BadValue', sorry :(",
new[] { "Name" }
)
);
}
});
});
});
}
[Fact]
public void Should_Validate_If_The_Properties_Are_Valid()
{
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Name", "John"},
{"Age", "42"},
}
}
).Count.ShouldBe(0); //All Valid
}
[Fact]
public void Should_Not_Validate_If_The_Properties_Are_Not_Valid()
{
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject()
).Count.ShouldBe(2); // Name & Age
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Address", new string('x', 256) }
}
}
).Count.ShouldBe(3); // Name, Age & Address
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Age", "42" }
}
}
).Count.ShouldBe(1); // Name
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Address", new string('x', 256) },
{"Age", "100" }
}
}
).Count.ShouldBe(3); // Name, Age & Address
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Name", "John"},
{"Age", "42"},
{"Password", "123"},
{"PasswordRepeat", "1256"}
}
}
).Count.ShouldBe(1); // PasswordRepeat != Password
ExtensibleObjectValidator
.GetValidationErrors(
new ExtensiblePersonObject
{
ExtraProperties =
{
{"Name", "BadValue"},
{"Age", "42"},
}
}
).Count.ShouldBe(1); //Name is 'BadValue'!
}
private class ExtensiblePersonObject : ExtensibleObject
{
}
}
}

4
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs

@ -16,18 +16,22 @@ namespace Volo.Abp.TestApp.Testing
city.HasProperty("UnknownProperty").ShouldBeFalse(); city.HasProperty("UnknownProperty").ShouldBeFalse();
city.GetProperty("UnknownProperty").ShouldBeNull(); city.GetProperty("UnknownProperty").ShouldBeNull();
city.GetProperty<int>("UnknownProperty").ShouldBe(0); city.GetProperty<int>("UnknownProperty").ShouldBe(0);
city.GetProperty<int>("UnknownProperty", 42).ShouldBe(42);
city.SetProperty("IsHot", true); city.SetProperty("IsHot", true);
city.HasProperty("IsHot").ShouldBeTrue(); city.HasProperty("IsHot").ShouldBeTrue();
city.GetProperty<bool>("IsHot").ShouldBeTrue(); city.GetProperty<bool>("IsHot").ShouldBeTrue();
city.GetProperty<bool>("IsHot").ShouldBeTrue();
city.SetProperty("IsHot", false); city.SetProperty("IsHot", false);
city.HasProperty("IsHot").ShouldBeTrue(); city.HasProperty("IsHot").ShouldBeTrue();
city.GetProperty<bool>("IsHot").ShouldBeFalse(); city.GetProperty<bool>("IsHot").ShouldBeFalse();
city.GetProperty<bool>("IsHot", true).ShouldBeFalse();
city.RemoveProperty("IsHot"); city.RemoveProperty("IsHot");
city.HasProperty("IsHot").ShouldBeFalse(); city.HasProperty("IsHot").ShouldBeFalse();
city.GetProperty<bool>("IsHot").ShouldBeFalse(); city.GetProperty<bool>("IsHot").ShouldBeFalse();
city.GetProperty<bool>("IsHot", true).ShouldBeTrue();
} }
} }
} }

4
framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs

@ -8,6 +8,7 @@ using Shouldly;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Testing; using Volo.Abp.Testing;
using Xunit; using Xunit;
@ -198,7 +199,8 @@ namespace Volo.Abp.Validation
{ {
context.Services.OnRegistred(onServiceRegistredContext => context.Services.OnRegistred(onServiceRegistredContext =>
{ {
if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType)) if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType) &&
!DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType))
{ {
onServiceRegistredContext.Interceptors.TryAdd<ValidationInterceptor>(); onServiceRegistredContext.Interceptors.TryAdd<ValidationInterceptor>();
} }

3
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs

@ -76,7 +76,6 @@ namespace Volo.Abp.Account.Web.Pages.Account
return Page(); return Page();
} }
[UnitOfWork] //TODO: Will be removed when we implement action filter
public virtual async Task<IActionResult> OnPostAsync(string action) public virtual async Task<IActionResult> OnPostAsync(string action)
{ {
await CheckLocalLoginAsync(); await CheckLocalLoginAsync();
@ -147,7 +146,6 @@ namespace Volo.Abp.Account.Web.Pages.Account
.ToList(); .ToList();
} }
[UnitOfWork]
public virtual async Task<IActionResult> OnPostExternalLogin(string provider) public virtual async Task<IActionResult> OnPostExternalLogin(string provider)
{ {
var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }); var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash });
@ -157,7 +155,6 @@ namespace Volo.Abp.Account.Web.Pages.Account
return await Task.FromResult(Challenge(properties, provider)); return await Task.FromResult(Challenge(properties, provider));
} }
[UnitOfWork]
public virtual async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) public virtual async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null)
{ {
//TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample) //TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample)

1
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs

@ -36,7 +36,6 @@ namespace Volo.Abp.Account.Web.Pages.Account
await CheckSelfRegistrationAsync(); await CheckSelfRegistrationAsync();
} }
[UnitOfWork] //TODO: Will be removed when we implement action filter
public virtual async Task<IActionResult> OnPostAsync() public virtual async Task<IActionResult> OnPostAsync()
{ {
ValidateModel(); ValidateModel();

6
modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs

@ -1,6 +1,4 @@
using System; using System;
using Microsoft.Extensions.DependencyInjection;
using Mongo2Go;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -12,11 +10,9 @@ namespace Volo.Abp.AuditLogging.MongoDB
)] )]
public class AbpAuditLoggingMongoDbTestModule : AbpModule public class AbpAuditLoggingMongoDbTestModule : AbpModule
{ {
private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start();
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') +
"Db_" + "Db_" +
Guid.NewGuid().ToString("N"); Guid.NewGuid().ToString("N");

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save