Browse Source

Merge remote-tracking branch 'abpframework/master' into Translate

pull/1033/head
liangshiwei 8 years ago
parent
commit
568d7083a4
  1. 2
      docs/en/Dependency-Injection.md
  2. 10
      docs/en/Entities.md
  3. 2
      docs/en/Exception-Handling.md
  4. 26
      docs/en/Multi-Tenancy.md
  5. 75
      docs/zh-Hans/Dependency-Injection.md
  6. 10
      docs/zh-Hans/Module-Development-Basics.md
  7. 26
      docs/zh-Hans/Multi-Tenancy.md
  8. 20
      docs/zh-Hans/Virtual-File-System.md
  9. 2
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheOptions.cs
  10. 77
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
  11. 76
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs
  12. 10
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs
  13. 8
      framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/AbpFluentValidationModule.cs
  14. 55
      framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentMethodInvocationValidator.cs
  15. 41
      framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentObjectValidationContributor.cs
  16. 6
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/zh-Hans.json
  17. 21
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs
  18. 4
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationOptions.cs
  19. 122
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs
  20. 71
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationValidator.cs
  21. 12
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IDataAnnotationValidator.cs
  22. 7
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidationContributor.cs
  23. 6
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs
  24. 20
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidationContext.cs
  25. 92
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs
  26. 33
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs
  27. 3
      framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs
  28. 2
      framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs
  29. 12
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs
  30. 8
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js
  31. 8
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/index.js
  32. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Localization/Resources/FeatureManagement/en.json
  33. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Localization/Resources/FeatureManagement/tr.json
  34. 4
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs
  35. 10
      modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js
  36. 12
      modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js
  37. 9
      modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs
  38. 1
      modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementPermissionDefinitionProvider.cs
  39. 1
      modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json
  40. 1
      modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/tr.json
  41. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantManagementPermissions.cs
  42. 1
      modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/AbpTenantManagementApplicationModule.cs
  43. 3
      modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs
  44. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementDbContextModelCreatingExtensions.cs
  45. 1
      modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs
  46. 12
      modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js

2
docs/en/Dependency-Injection.md

@ -96,7 +96,7 @@ public class TaxCalculator : ITransientDependency
Another way of configuring a service for dependency injection is to use ``DependencyAttribute``. It has the following properties: Another way of configuring a service for dependency injection is to use ``DependencyAttribute``. It has the following properties:
* ``Lifetime``: Lifetime of the registration: ``Singleton``, ``Transient`` or ``Scoped``. * ``Lifetime``: Lifetime of the registration: ``Singleton``, ``Transient`` or ``Scoped``.
* ``TryRegister``: Set ``true`` to register the service only it's not registered before. Uses TryAdd... extension methods of IServiceCollection. * ``TryRegister``: Set ``true`` to register the service only if it's not registered before. Uses TryAdd... extension methods of IServiceCollection.
* ``ReplaceServices``: Set ``true`` to replace services if they are already registered before. Uses Replace extension method of IServiceCollection. * ``ReplaceServices``: Set ``true`` to replace services if they are already registered before. Uses Replace extension method of IServiceCollection.
Example: Example:

10
docs/en/Entities.md

@ -22,7 +22,7 @@ public class Person : Entity<int>
} }
``` ```
> If you do not want derive your entity from the base `Entity<TKey>` class, you can directly implement `IEntity<TKey>` interface. > If you do not want to derive your entity from the base `Entity<TKey>` class, you can directly implement `IEntity<TKey>` interface.
`Entity<TKey>` class just defines an `Id` property with the given primary **key type**, which is `int` in the sample above. It can be other types like `string`, `Guid`, `long` or whatever you need. `Entity<TKey>` class just defines an `Id` property with the given primary **key type**, which is `int` in the sample above. It can be other types like `string`, `Guid`, `long` or whatever you need.
@ -57,9 +57,9 @@ For the example above, the composite key is composed of `UserId` and `RoleId`. F
Entities with composite keys should implement the `GetKeys()` method as shown above. Entities with composite keys should implement the `GetKeys()` method as shown above.
Notice that you also need to define keys of the entity in your **object-to-relational mapping** (ORM) configuration. Notice that you also need to define keys of the entity in your **object-relational mapping** (ORM) configuration.
> Composite primary keys has a restriction with repositories. Since it has not known Id property, you can not use `IRepository<TEntity, TKey>` for these entities. However, you can always use `IRepository<TEntity>`. See repository documentation (TODO: link) for more. > Composite primary keys has a restriction with repositories. Since it has not known Id property, you can not use `IRepository<TEntity, TKey>` for these entities. However, you can always use `IRepository<TEntity>`. See [repositories documentation](Repositories.md) for more.
### AggregateRoot Class ### AggregateRoot Class
@ -67,7 +67,7 @@ Notice that you also need to define keys of the entity in your **object-to-relat
`AggregateRoot` class extends the `Entity` class. So, it also has an `Id` property by default. `AggregateRoot` class extends the `Entity` class. So, it also has an `Id` property by default.
> Notice that ABP creates default repositories only for aggregate roots by default. However, it's possible to include all entities. See repository documentation (TODO: link) for more. > Notice that ABP creates default repositories only for aggregate roots by default. However, it's possible to include all entities. See [repositories documentation](Repositories.md) for more.
ABP does not force you to use aggregate roots, you can in fact use the `Entity` class as defined before. However, if you want to implement DDD and want to create aggregate root classes, there are some best practices you may want to consider: ABP does not force you to use aggregate roots, you can in fact use the `Entity` class as defined before. However, if you want to implement DDD and want to create aggregate root classes, there are some best practices you may want to consider:
@ -160,7 +160,7 @@ public class OrderLine : Entity
> If you do not want to derive your aggregate root from the base `AggregateRoot<TKey>` class, you can directly implement the `IAggregateRoot<TKey>` interface. > If you do not want to derive your aggregate root from the base `AggregateRoot<TKey>` class, you can directly implement the `IAggregateRoot<TKey>` interface.
`Order` is an **aggregate root** with `Guid` type `Id` property. It has a collection of `OrderLine` entities. `OrderLine` is another entity with a composite primary key (`OrderLine` and ` ProductId`). `Order` is an **aggregate root** with `Guid` type `Id` property. It has a collection of `OrderLine` entities. `OrderLine` is another entity with a composite primary key (`OrderId` and ` ProductId`).
While this example may not implement all the best practices of an aggregate root, it still follows good practices: While this example may not implement all the best practices of an aggregate root, it still follows good practices:

2
docs/en/Exception-Handling.md

@ -9,7 +9,7 @@ ABP provides a built-in infrastructure and offers a standard model for handling
### Automatic Exception Handling ### Automatic Exception Handling
`AbpExceptionFilter` handles an exception if **any of the following conditions** are meet: `AbpExceptionFilter` handles an exception if **any of the following conditions** are met:
* Exception is thrown by a **controller action** which returns an **object result** (not a view result). * Exception is thrown by a **controller action** which returns an **object result** (not a view result).
* The request is an AJAX request (`X-Requested-With` HTTP header value is `XMLHttpRequest`). * The request is an AJAX request (`X-Requested-With` HTTP header value is `XMLHttpRequest`).

26
docs/en/Multi-Tenancy.md

@ -90,32 +90,6 @@ namespace MyCompany.MyProject
TODO: ... TODO: ...
### Volo.Abp.MultiTenancy Package
Volo.Abp.MultiTenancy is the actual package that makes your application multi-tenant. Install it into your project using PMC:
````
Install-Package Volo.Abp.MultiTenancy
````
Then you can add **AbpMultiTenancyModule** dependency to your module:
````C#
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
namespace MyCompany.MyProject
{
[DependsOn(typeof(AbpMultiTenancyModule))]
public class MyModule : AbpModule
{
//...
}
}
````
> If you add AbpMultiTenancyModule dependency to your module, then you don't need to add AbpMultiTenancyModule dependency separately since AbpMultiTenancyModule already depends on it.
#### Determining Current Tenant #### Determining Current Tenant
The first thing for a multi-tenant application is to determine the current tenant on the runtime. Volo.Abp.MultiTenancy package only provides abstractions (named as tenant resolver) for determining the current tenant, however it does not have any implementation out of the box. The first thing for a multi-tenant application is to determine the current tenant on the runtime. Volo.Abp.MultiTenancy package only provides abstractions (named as tenant resolver) for determining the current tenant, however it does not have any implementation out of the box.

75
docs/zh-Hans/Dependency-Injection.md

@ -2,9 +2,11 @@
ABP的依赖注入系统是基于Microsoft的[依赖注入扩展](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection)库(Microsoft.Extensions.DependencyInjection nuget包)开发的.因此,它的文档在ABP中也是有效的. ABP的依赖注入系统是基于Microsoft的[依赖注入扩展](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection)库(Microsoft.Extensions.DependencyInjection nuget包)开发的.因此,它的文档在ABP中也是有效的.
> 虽然ABP框架没有对任何第三方DI提供程序的核心依赖, 但它必须使用一个提供程序来支持动态代理(dynamic proxying)和一些高级特性以便ABP特性能正常工作.启动模板中已安装了Autofac. 更多信息请参阅 [Autofac 集成](Autofac-Integration.md) 文档.
### 模块化 ### 模块化
由于ABP是一个模块化框架,因此每个模块都通过依赖注入定义它自己的服务并通过它自己的单独[模块类](Module-Development-Basics.md)进行注册.例: 由于ABP是一个模块化框架,因此每个模块都定义它自己的服务并在它自己的单独[模块类](Module-Development-Basics.md)中通过依赖注入进行注册.例:
````C# ````C#
public class BlogModule : AbpModule public class BlogModule : AbpModule
@ -69,15 +71,15 @@ public class BlogPostAppService : ApplicationService
} }
```` ````
``BlogPostAppService`` 由于它是从已知的基类派生的,因此会自动注册transient. ``BlogPostAppService`` 由于它是从已知的基类派生的,因此会自动注册为transient生命周期.
#### 依赖接口 #### 依赖接口
如果实现这些接口,则会自动将类注册到依赖注入: 如果实现这些接口,则会自动将类注册到依赖注入:
* ``ITransientDependency`` 注册为transient. * ``ITransientDependency`` 注册为transient生命周期.
* ``ISingletonDependency`` 注册为singleton. * ``ISingletonDependency`` 注册为singleton生命周期.
* ``IScopedDependency`` 注册为scoped. * ``IScopedDependency`` 注册为scoped生命周期.
示例: 示例:
@ -87,9 +89,9 @@ public class TaxCalculator : ITransientDependency
} }
```` ````
``TaxCalculator``因为实现了``ITransientDependency``,所以它会自动注册为transient. ``TaxCalculator``因为实现了``ITransientDependency``,所以它会自动注册为transient生命周期.
#### Dependency 属性 #### Dependency 特性
配置依赖注入服务的另一种方法是使用``DependencyAttribute``.它具有以下属性: 配置依赖注入服务的另一种方法是使用``DependencyAttribute``.它具有以下属性:
@ -108,9 +110,9 @@ public class TaxCalculator
```` ````
``Dependency``如果定义``Lifetime``属性,则具有比其他依赖接口更高的优先级. 如果定义了``Lifetime``属性,则``Dependency``特性具有比其他依赖接口更高的优先级.
#### ExposeServices 属性 #### ExposeServices 特性
``ExposeServicesAttribute``用于控制相关类提供了什么服务.例: ``ExposeServicesAttribute``用于控制相关类提供了什么服务.例:
@ -133,7 +135,7 @@ public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransie
#### 组合到一起 #### 组合到一起
只要有意义,就可以组合属性和接口. 只要有意义,特性和接口是可以组合在一起使用的.
````C# ````C#
[Dependency(ReplaceServices = true)] [Dependency(ReplaceServices = true)]
@ -189,7 +191,7 @@ public class TaxAppService : ApplicationService
``TaxAppService``在构造方法中得到``ITaxCalculator``.依赖注入系统在运行时自动提供所请求的服务. ``TaxAppService``在构造方法中得到``ITaxCalculator``.依赖注入系统在运行时自动提供所请求的服务.
构造方法注入是将依赖项注入类的优先方式.这样,除非提供了所有构造方法注入的依赖项,否则无法构造类.因此,该类明确的声明了它必需的服务. 构造方法注入是将依赖项注入类的首选方式.这样,除非提供了所有构造方法注入的依赖项,否则无法构造类.因此,该类明确的声明了它必需的服务.
#### 属性注入 #### 属性注入
@ -266,6 +268,55 @@ using (var scope = _serviceProvider.CreateScope())
两个服务在创建的scope被处理时(在using块的末尾)释放. 两个服务在创建的scope被处理时(在using块的末尾)释放.
## 高级特性
### IServiceCollection.OnRegistred 事件
你可能想在注册到依赖注入的每个服务上执行一个操作, 在你的模块的 `PreConfigureServices` 方法中, 使用 `OnRegistred` 方法注册一个回调(callback) , 如下所示:
````csharp
public class AppModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnRegistred(ctx =>
{
var type = ctx.ImplementationType;
//...
});
}
}
````
`ImplementationType` 提供了服务类型. 该回调(callback)通常用于向服务添加拦截器. 例如:
````csharp
public class AppModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnRegistred(ctx =>
{
if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true))
{
ctx.Interceptors.TryAdd<MyLogInterceptor>();
}
});
}
}
````
这个示例判断一个服务类是否具有 `MyLogAttribute` 特性, 如果有的话就添加一个 `MyLogInterceptor` 到拦截器集合中.
> 注意, 如果服务类公开了多于一个服务或接口, `OnRegistred` 回调(callback)可能被同一服务类多次调用. 因此, 较安全的方法是使用 `Interceptors.TryAdd` 方法而不是 `Interceptors.Add` 方法. 请参阅动态代理(dynamic proxying)/拦截器 [文档](Dynamic-Proxying-Interceptors.md).
## 第三方提供程序
虽然ABP框架没有对任何第三方DI提供程序的核心依赖, 但它必须使用一个提供程序来支持动态代理(dynamic proxying)和一些高级特性以便ABP特性能正常工作.
启动模板中已安装了Autofac. 更多信息请参阅 [Autofac 集成](Autofac-Integration.md) 文档.
### 请参阅 ### 请参阅
* [ASP.NET Core依赖注入最佳实践,提示和技巧](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) * [ASP.NET Core依赖注入最佳实践,提示和技巧](https://cn.abp.io/blog/Abp/asp-net-core-dependency-injection-best-practices-tips-tricks)

10
docs/zh-Hans/Module-Development-Basics.md

@ -2,7 +2,7 @@
### 介绍 ### 介绍
ABP本身是一个模块化框架.它还提供了一个基础架构和架构模型来开发你自己的模块. ABP本身是一个包含许多nuget包的模块化框架.它还提供了一个完整的基础架构来开发你自己的具有实体, 服务, 数据库集成, API, UI组件等等功能的应用程序模块.
### 模块类 ### 模块类
@ -111,7 +111,7 @@ public class AppModule : AbpModule
### 模块依赖 ### 模块依赖
在模块化应用程序中,一个模块依赖于另一个模块并不罕见.如果一个Abp模块依赖于另一个模块,它必须声明``[DependsOn]``属性,如下所示: 在模块化应用程序中,一个模块依赖于另一个或几个模块并不罕见.如果一个Abp模块依赖于另一个模块,它必须声明``[DependsOn]``特性,如下所示:
````C# ````C#
[DependsOn(typeof(AbpAspNetCoreMvcModule))] [DependsOn(typeof(AbpAspNetCoreMvcModule))]
@ -122,7 +122,7 @@ public class BlogModule
} }
```` ````
你可以根据需要使用多个``DependsOn``属性或将多个模块类型传递给单个``DependsOn``属性. 你可以根据需要使用多个``DependsOn``特性或将多个模块类型传递给单个``DependsOn``特性.
依赖模块可能依赖于另一个模块,但你只需要定义直接依赖项.ABP在启动时会调查应用程序的依赖关系,并以正确的顺序初始化/关闭模块. 依赖模块可能依赖于另一个模块,但你只需要定义直接依赖项.ABP在启动时会调查应用程序的依赖关系,并以正确的顺序初始化/关闭模块.
@ -130,5 +130,5 @@ public class BlogModule
**模块分为两种类型.** 这两种类型并没有任何结构上的区别,只是按功能和用途分类: **模块分为两种类型.** 这两种类型并没有任何结构上的区别,只是按功能和用途分类:
- **框架模块**: 这些是**框架的核心模块** 如缓存, 邮件, 主题, 安全, 序列化, 验证, EF Core集成, MongoDB集成... 等. 它们没有应用/业务功能,它们提供了日常开发经常用到的基础设施,集成和抽象. - **框架模块**: 这些是**框架的核心模块** 如缓存, 邮件, 主题, 安全, 序列化, 验证, EF Core集成, MongoDB集成... 等. 它们没有应用/业务功能,它们提供了日常开发经常用到的通用基础设施,集成和抽象.
- **应用程序模块**: 这些模块实现了 **特定的应用/业务功能** 像博客, 文档管理, 身份管理, 租房管理... 等等. 它们通过有自己的实体,服务,API和UI组件. 请参阅 [预构建的应用程序模块](Modules/Index.md). - **应用程序模块**: 这些模块实现了 **特定的应用/业务功能** 像博客, 文档管理, 身份管理, 租户管理... 等等. 它们通常有自己的实体,服务,API和UI组件. 请参阅 [预构建的应用程序模块](Modules/Index.md).

26
docs/zh-Hans/Multi-Tenancy.md

@ -90,32 +90,6 @@ namespace MyCompany.MyProject
TODO: ... TODO: ...
### Volo.Abp.MultiTenancy
Volo.Abp.MultiTenancy 才是让你的程序实现多租户的真正的包.使用PMC将它安装到你的项目中:
````
Install-Package Volo.Abp.MultiTenancy
````
然后添加 **AbpMultiTenancyModule** 依赖到你的模块中:
````C#
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
namespace MyCompany.MyProject
{
[DependsOn(typeof(AbpMultiTenancyModule))]
public class MyModule : AbpModule
{
//...
}
}
````
> 如果你添加了AbpMultiTenancyModule依赖,就不需要再另外添加AbpMultiTenancyModule依赖了,因为AbpMultiTenancyModule已经依赖它了.
#### 确定当前租户 #### 确定当前租户
多租户的应用程序运行的时候首先要做的就是确定当前租户. 多租户的应用程序运行的时候首先要做的就是确定当前租户.

20
docs/zh-Hans/Virtual-File-System.md

@ -1,6 +1,6 @@
## 虚拟文件系统 ## 虚拟文件系统
虚拟文件系统可以管理文件系统(磁盘)上实际上不存在的文件. 它主要用于将(js, css, image, cshtml ...)文件嵌入到程序集中, 并在运行时将它们用作物理文件. 虚拟文件系统使得管理物理上不存在于文件系统中(磁盘)的文件成为可能. 它主要用于将(js, css, image, cshtml ...)文件嵌入到程序集中, 并在运行时将它们象物理文件一样使用.
### Volo.Abp.VirtualFileSystem nuget包 ### Volo.Abp.VirtualFileSystem nuget包
@ -30,12 +30,12 @@ namespace MyCompany.MyProject
#### 注册嵌入式文件 #### 注册嵌入式文件
首先需要把文件标记为嵌入式资源将文件嵌入到程序集中. 最简单的方式是在 **解决方案管理器** 中选择文件, 然后找到 **"属性"** 窗口将 **"生成操作"** 设置为 **"嵌入式资源"**. 要将文件嵌入到程序集中, 首先需要把该文件标记为嵌入式资源. 最简单的方式是在 **解决方案管理器** 中选择文件, 然后找到 **"属性"** 窗口将 **"生成操作"** 设置为 **"嵌入式资源"**.
例如: 例如:
![build-action-embedded-resource-sample](images/build-action-embedded-resource-sample.png) ![build-action-embedded-resource-sample](images/build-action-embedded-resource-sample.png)
如果需要添加多个文件, 这会很乏味. 这时可以直接编辑 **.csproj** 文件: 如果需要添加多个文件, 这样做会很乏味. 作为选择, 你可以直接编辑 **.csproj** 文件:
````C# ````C#
<ItemGroup> <ItemGroup>
@ -45,7 +45,7 @@ namespace MyCompany.MyProject
此配置以递归方式添加项目的 **MyResources** 文件夹下的所有文件(包括将来新添加的文件). 此配置以递归方式添加项目的 **MyResources** 文件夹下的所有文件(包括将来新添加的文件).
然后应该使用 `VirtualFileSystemOptions` 来配置模块, 以便将嵌入式文件注册到虚拟文件系统. 例如: 然后需要使用 `VirtualFileSystemOptions` 来配置模块, 以便将嵌入式文件注册到虚拟文件系统. 例如:
````C# ````C#
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -105,9 +105,9 @@ public class MyService
#### 在开发过程中处理嵌入式文件 #### 在开发过程中处理嵌入式文件
通过引用程序集(或添加nuget包)将文件嵌入模块程序集并从另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难. 将文件嵌入到模块程序集中并能够通过引用程序集(或添加nuget包)在另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难.
假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以进行更改. 显然, 这是非常耗时和乏味的. 假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以使更改生效. 显然, 这是非常耗时和乏味的.
我们需要的是应用程序在开发时直接使用物理文件的能力, 让浏览器刷新时同步JavaScript文件的任何更改. `ReplaceEmbeddedByPhysical` 方法使其成为可能. 我们需要的是应用程序在开发时直接使用物理文件的能力, 让浏览器刷新时同步JavaScript文件的任何更改. `ReplaceEmbeddedByPhysical` 方法使其成为可能.
@ -145,7 +145,7 @@ public class MyWebAppModule : AbpModule
* 虚拟文件可以像Web应用程序上的物理(静态)文件一样使用. * 虚拟文件可以像Web应用程序上的物理(静态)文件一样使用.
* Razor Views, Razor Pages, js, css, 图像文件和所有其他Web内容可以嵌入到程序集中并像物理文件一样使用. * Razor Views, Razor Pages, js, css, 图像文件和所有其他Web内容可以嵌入到程序集中并像物理文件一样使用.
* 应用程序(或其他模块)可以覆盖模块的虚拟文件, 就像将具有同名和同扩展名文件放入虚拟文件的同一文件夹中一样. * 应用程序(或其他模块)可以覆盖模块的虚拟文件, 就像将具有相同名称和扩展名的文件放入虚拟文件的同一文件夹中一样.
#### 虚拟文件中间件 #### 虚拟文件中间件
@ -155,12 +155,12 @@ public class MyWebAppModule : AbpModule
app.UseVirtualFiles(); app.UseVirtualFiles();
```` ````
在静态文件中间件之后添加虚拟文件中间件, 可以通过放置在同一位置,使用物理文件来覆盖虚拟文件. 在静态文件中间件之后添加虚拟文件中间件, 使得通过在虚拟文件相同的位置放置物理文件, 从而用物理文件覆盖虚拟文件成为可能.
> 虚拟文件中间件只是像静态文件一样提供虚拟wwwroot文件夹内容. > 虚拟文件中间件可以虚拟wwwroot文件夹中的内容 - 就像静态文件一样.
#### Views & Pages #### Views & Pages
无需任何配置即可在应用程序中使用嵌入式的 razor Views/pages. 只需要将它们放入模块开发中的标准 Views/Pages 虚拟文件夹即可. 无需任何配置即可在应用程序中使用嵌入式的 razor Views/pages. 只需要将它们放置在要开发的模块中的标准 Views/Pages 虚拟文件夹即可.
如果模块/应用程序将新文件放置同一位置, 则会覆盖嵌入式的 Views/Pages. 如果模块/应用程序将新文件放置同一位置, 则会覆盖嵌入式的 Views/Pages.

2
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheOptions.cs

@ -10,11 +10,13 @@ namespace Volo.Abp.Caching
/// Global Cache entry options. /// Global Cache entry options.
/// </summary> /// </summary>
public DistributedCacheEntryOptions GlobalCacheEntryOptions { get; set; } public DistributedCacheEntryOptions GlobalCacheEntryOptions { get; set; }
/// <summary> /// <summary>
/// List of all cache configurators. /// List of all cache configurators.
/// (func argument:Name of cache) /// (func argument:Name of cache)
/// </summary> /// </summary>
public List<Func<string, DistributedCacheEntryOptions>> CacheConfigurators { get; set; } //TODO list item use a configurator interface instead? public List<Func<string, DistributedCacheEntryOptions>> CacheConfigurators { get; set; } //TODO list item use a configurator interface instead?
public CacheOptions() public CacheOptions()
{ {
CacheConfigurators = new List<Func<string, DistributedCacheEntryOptions>>(); CacheConfigurators = new List<Func<string, DistributedCacheEntryOptions>>();

77
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs

@ -12,6 +12,10 @@ using Volo.Abp.Threading;
namespace Volo.Abp.Caching namespace Volo.Abp.Caching
{ {
/// <summary>
/// Represents a distributed cache of <typeparamref name="TCacheItem" /> type.
/// </summary>
/// <typeparam name="TCacheItem">The type of cache item being cached.</typeparam>
public class DistributedCache<TCacheItem> : IDistributedCache<TCacheItem> public class DistributedCache<TCacheItem> : IDistributedCache<TCacheItem>
where TCacheItem : class where TCacheItem : class
{ {
@ -57,6 +61,12 @@ namespace Volo.Abp.Caching
SetDefaultOptions(); SetDefaultOptions();
} }
/// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item, or null.</returns>
public virtual TCacheItem Get( public virtual TCacheItem Get(
string key, string key,
bool? hideErrors = null) bool? hideErrors = null)
@ -88,6 +98,13 @@ namespace Volo.Abp.Caching
return Serializer.Deserialize<TCacheItem>(cachedBytes); return Serializer.Deserialize<TCacheItem>(cachedBytes);
} }
/// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item, or null.</returns>
public virtual async Task<TCacheItem> GetAsync( public virtual async Task<TCacheItem> GetAsync(
string key, string key,
bool? hideErrors = null, bool? hideErrors = null,
@ -123,6 +140,15 @@ namespace Volo.Abp.Caching
return Serializer.Deserialize<TCacheItem>(cachedBytes); return Serializer.Deserialize<TCacheItem>(cachedBytes);
} }
/// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item.</returns>
public TCacheItem GetOrAdd( public TCacheItem GetOrAdd(
string key, string key,
Func<TCacheItem> factory, Func<TCacheItem> factory,
@ -150,6 +176,16 @@ namespace Volo.Abp.Caching
return value; return value;
} }
/// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item.</returns>
public async Task<TCacheItem> GetOrAddAsync( public async Task<TCacheItem> GetOrAddAsync(
string key, string key,
Func<Task<TCacheItem>> factory, Func<Task<TCacheItem>> factory,
@ -179,6 +215,13 @@ namespace Volo.Abp.Caching
return value; return value;
} }
/// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
public virtual void Set( public virtual void Set(
string key, string key,
TCacheItem value, TCacheItem value,
@ -207,6 +250,15 @@ namespace Volo.Abp.Caching
} }
} }
/// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
public virtual async Task SetAsync( public virtual async Task SetAsync(
string key, string key,
TCacheItem value, TCacheItem value,
@ -237,6 +289,11 @@ namespace Volo.Abp.Caching
} }
} }
/// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
public virtual void Refresh( public virtual void Refresh(
string key, string key,
bool? hideErrors = null) bool? hideErrors = null)
@ -259,6 +316,13 @@ namespace Volo.Abp.Caching
} }
} }
/// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
public virtual async Task RefreshAsync( public virtual async Task RefreshAsync(
string key, string key,
bool? hideErrors = null, bool? hideErrors = null,
@ -282,6 +346,12 @@ namespace Volo.Abp.Caching
} }
} }
/// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
public virtual void Remove( public virtual void Remove(
string key, string key,
bool? hideErrors = null) bool? hideErrors = null)
@ -303,6 +373,13 @@ namespace Volo.Abp.Caching
} }
} }
/// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
public virtual async Task RemoveAsync( public virtual async Task RemoveAsync(
string key, string key,
bool? hideErrors = null, bool? hideErrors = null,

76
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs

@ -6,20 +6,46 @@ using Microsoft.Extensions.Caching.Distributed;
namespace Volo.Abp.Caching namespace Volo.Abp.Caching
{ {
/// <summary>
/// Represents a distributed cache of <typeparamref name="TCacheItem" /> type.
/// </summary>
/// <typeparam name="TCacheItem">The type of cache item being cached.</typeparam>
public interface IDistributedCache<TCacheItem> public interface IDistributedCache<TCacheItem>
where TCacheItem : class where TCacheItem : class
{ {
/// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item, or null.</returns>
TCacheItem Get( TCacheItem Get(
string key, string key,
bool? hideErrors = null bool? hideErrors = null
); );
/// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item, or null.</returns>
Task<TCacheItem> GetAsync( Task<TCacheItem> GetAsync(
[NotNull] string key, [NotNull] string key,
bool? hideErrors = null, bool? hideErrors = null,
CancellationToken token = default CancellationToken token = default
); );
/// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <returns>The cache item.</returns>
TCacheItem GetOrAdd( TCacheItem GetOrAdd(
string key, string key,
Func<TCacheItem> factory, Func<TCacheItem> factory,
@ -27,6 +53,16 @@ namespace Volo.Abp.Caching
bool? hideErrors = null bool? hideErrors = null
); );
/// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item.</returns>
Task<TCacheItem> GetOrAddAsync( Task<TCacheItem> GetOrAddAsync(
[NotNull] string key, [NotNull] string key,
Func<Task<TCacheItem>> factory, Func<Task<TCacheItem>> factory,
@ -35,6 +71,13 @@ namespace Volo.Abp.Caching
CancellationToken token = default CancellationToken token = default
); );
/// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
void Set( void Set(
string key, string key,
TCacheItem value, TCacheItem value,
@ -42,6 +85,15 @@ namespace Volo.Abp.Caching
bool? hideErrors = null bool? hideErrors = null
); );
/// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task SetAsync( Task SetAsync(
[NotNull] string key, [NotNull] string key,
[NotNull] TCacheItem value, [NotNull] TCacheItem value,
@ -50,22 +102,46 @@ namespace Volo.Abp.Caching
CancellationToken token = default CancellationToken token = default
); );
/// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
void Refresh( void Refresh(
string key, string key,
bool? hideErrors = null bool? hideErrors = null
); );
/// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task RefreshAsync( Task RefreshAsync(
string key, string key,
bool? hideErrors = null, bool? hideErrors = null,
CancellationToken token = default CancellationToken token = default
); );
/// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
void Remove( void Remove(
string key, string key,
bool? hideErrors = null bool? hideErrors = null
); );
/// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task RemoveAsync( Task RemoveAsync(
string key, string key,
bool? hideErrors = null, bool? hideErrors = null,

10
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs

@ -6,6 +6,7 @@ using Newtonsoft.Json;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
namespace Volo.Abp.EntityFrameworkCore.Modeling namespace Volo.Abp.EntityFrameworkCore.Modeling
@ -260,6 +261,15 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling
} }
} }
public static void ConfigureFullAuditedAggregateRoot<T>(this EntityTypeBuilder<T> b)
where T : class
{
b.As<EntityTypeBuilder>().TryConfigureFullAudited();
b.As<EntityTypeBuilder>().TryConfigureExtraProperties();
b.As<EntityTypeBuilder>().TryConfigureConcurrencyStamp();
}
//TODO: Add other interfaces (IAuditedObject<TUser>...) //TODO: Add other interfaces (IAuditedObject<TUser>...)
} }
} }

8
framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/AbpFluentValidationModule.cs

@ -13,13 +13,5 @@ namespace Volo.Abp.FluentValidation
{ {
context.Services.AddConventionalRegistrar(new AbpFluentValidationConventionalRegistrar()); context.Services.AddConventionalRegistrar(new AbpFluentValidationConventionalRegistrar());
} }
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpValidationOptions>(options =>
{
options.MethodValidationContributors.Add<FluentMethodInvocationValidator>();
});
}
} }
} }

55
framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentMethodInvocationValidator.cs

@ -1,55 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using FluentValidation;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Validation;
namespace Volo.Abp.FluentValidation
{
public class FluentMethodInvocationValidator : IMethodInvocationValidator, ITransientDependency
{
private readonly IServiceProvider _serviceProvider;
public FluentMethodInvocationValidator(
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Validate(MethodInvocationValidationContext context)
{
var validationResult = new AbpValidationResult();
foreach (var parameterValue in context.ParameterValues)
{
var serviceType = typeof(IValidator<>).MakeGenericType(parameterValue.GetType());
var validator = _serviceProvider.GetService(serviceType) as IValidator;
if (validator == null)
{
continue;
}
var result = validator.Validate(parameterValue);
if (!result.IsValid)
{
validationResult.Errors.AddRange(
result.Errors.Select(
error =>
new ValidationResult(error.ErrorMessage)
)
);
}
}
if (validationResult.Errors.Any())
{
//TODO: How to localize messages?
throw new AbpValidationException(
"Method arguments are not valid! See ValidationErrors for details.",
context.Errors
);
}
}
}
}

41
framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentObjectValidationContributor.cs

@ -0,0 +1,41 @@
using FluentValidation;
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Validation;
namespace Volo.Abp.FluentValidation
{
public class FluentObjectValidationContributor : IObjectValidationContributor, ITransientDependency
{
private readonly IServiceProvider _serviceProvider;
public FluentObjectValidationContributor(
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void AddErrors(ObjectValidationContext context)
{
var serviceType = typeof(IValidator<>).MakeGenericType(context.ValidatingObject.GetType());
var validator = _serviceProvider.GetService(serviceType) as IValidator;
if (validator == null)
{
return;
}
var result = validator.Validate(context.ValidatingObject);
if (!result.IsValid)
{
context.Errors.AddRange(
result.Errors.Select(
error =>
new ValidationResult(error.ErrorMessage)
)
);
}
}
}
}

6
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/zh-Hans.json

@ -0,0 +1,6 @@
{
"culture": "zh-Hans",
"texts": {
"Menu:Administration": "管理"
}
}

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

@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection; using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Abp.Validation namespace Volo.Abp.Validation
@ -8,13 +10,24 @@ namespace Volo.Abp.Validation
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded); context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded);
AutoAddObjectValidationContributors(context.Services);
} }
public override void ConfigureServices(ServiceConfigurationContext context) private static void AutoAddObjectValidationContributors(IServiceCollection services)
{ {
Configure<AbpValidationOptions>(options => var contributorTypes = new List<Type>();
services.OnRegistred(context =>
{
if (typeof(IObjectValidationContributor).IsAssignableFrom(context.ImplementationType))
{
contributorTypes.Add(context.ImplementationType);
}
});
services.Configure<AbpValidationOptions>(options =>
{ {
options.MethodValidationContributors.Add<MethodInvocationValidator>(); options.ObjectValidationContributors.AddIfNotContains(contributorTypes);
}); });
} }
} }

4
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationOptions.cs

@ -8,12 +8,12 @@ namespace Volo.Abp.Validation
{ {
public List<Type> IgnoredTypes { get; } public List<Type> IgnoredTypes { get; }
public ITypeList<IMethodInvocationValidator> MethodValidationContributors { get; set; } public ITypeList<IObjectValidationContributor> ObjectValidationContributors { get; set; }
public AbpValidationOptions() public AbpValidationOptions()
{ {
IgnoredTypes = new List<Type>(); IgnoredTypes = new List<Type>();
MethodValidationContributors = new TypeList<IMethodInvocationValidator>(); ObjectValidationContributors = new TypeList<IObjectValidationContributor>();
} }
} }
} }

122
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs

@ -0,0 +1,122 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Reflection;
namespace Volo.Abp.Validation
{
public class DataAnnotationObjectValidationContributor : IObjectValidationContributor, ITransientDependency
{
public const int MaxRecursiveParameterValidationDepth = 8;
protected AbpValidationOptions Options { get; }
public DataAnnotationObjectValidationContributor(IOptions<AbpValidationOptions> options)
{
Options = options.Value;
}
public void AddErrors(ObjectValidationContext context)
{
ValidateObjectRecursively(context.Errors, context.ValidatingObject, currentDepth: 1);
}
protected virtual void ValidateObjectRecursively(List<ValidationResult> errors, object validatingObject, int currentDepth)
{
if (currentDepth > MaxRecursiveParameterValidationDepth)
{
return;
}
if (validatingObject == null)
{
return;
}
AddErrors(errors, validatingObject);
//Validate items of enumerable
if (validatingObject is IEnumerable)
{
if (!(validatingObject is IQueryable))
{
foreach (var item in (validatingObject as IEnumerable))
{
ValidateObjectRecursively(errors, item, currentDepth + 1);
}
}
return;
}
var validatingObjectType = validatingObject.GetType();
//Do not recursively validate for primitive objects
if (TypeHelper.IsPrimitiveExtended(validatingObjectType))
{
return;
}
if (Options.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject)))
{
return;
}
var properties = TypeDescriptor.GetProperties(validatingObject).Cast<PropertyDescriptor>();
foreach (var property in properties)
{
if (property.Attributes.OfType<DisableValidationAttribute>().Any())
{
continue;
}
ValidateObjectRecursively(errors, property.GetValue(validatingObject), currentDepth + 1);
}
}
public void AddErrors(List<ValidationResult> errors, object validatingObject)
{
var properties = TypeDescriptor.GetProperties(validatingObject).Cast<PropertyDescriptor>();
foreach (var property in properties)
{
AddPropertyErrors(validatingObject, property, errors);
}
if (validatingObject is IValidatableObject validatableObject)
{
errors.AddRange(
validatableObject.Validate(new ValidationContext(validatableObject))
);
}
}
protected virtual void AddPropertyErrors(object validatingObject, PropertyDescriptor property, List<ValidationResult> errors)
{
var validationAttributes = property.Attributes.OfType<ValidationAttribute>().ToArray();
if (validationAttributes.IsNullOrEmpty())
{
return;
}
var validationContext = new ValidationContext(validatingObject)
{
DisplayName = property.DisplayName,
MemberName = property.Name
};
foreach (var attribute in validationAttributes)
{
var result = attribute.GetValidationResult(property.GetValue(validatingObject), validationContext);
if (result != null)
{
errors.Add(result);
}
}
}
}
}

71
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationValidator.cs

@ -1,71 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Validation
{
public class DataAnnotationValidator : IDataAnnotationValidator, ITransientDependency
{
public void Validate(object validatingObject)
{
var errors = GetErrors(validatingObject);
if (errors.Any())
{
throw new AbpValidationException(
"Object state is not valid! See ValidationErrors for details.",
errors
);
}
}
/// <summary>
/// Gets all errors from properties for DataAnnotations attributes and IValidatableObject interface.
/// </summary>
public virtual List<ValidationResult> GetErrors(object validatingObject)
{
var errors = new List<ValidationResult>();
var properties = TypeDescriptor.GetProperties(validatingObject).Cast<PropertyDescriptor>();
foreach (var property in properties)
{
AddPropertyErrors(validatingObject, property, errors);
}
if (validatingObject is IValidatableObject validatableObject)
{
errors.AddRange(
validatableObject.Validate(new ValidationContext(validatableObject))
);
}
return errors;
}
protected virtual void AddPropertyErrors(object validatingObject, PropertyDescriptor property, List<ValidationResult> errors)
{
var validationAttributes = property.Attributes.OfType<ValidationAttribute>().ToArray();
if (validationAttributes.IsNullOrEmpty())
{
return;
}
var validationContext = new ValidationContext(validatingObject)
{
DisplayName = property.DisplayName,
MemberName = property.Name
};
foreach (var attribute in validationAttributes)
{
var result = attribute.GetValidationResult(property.GetValue(validatingObject), validationContext);
if (result != null)
{
errors.Add(result);
}
}
}
}
}

12
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IDataAnnotationValidator.cs

@ -1,12 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Volo.Abp.Validation
{
public interface IDataAnnotationValidator
{
void Validate(object validatingObject);
List<ValidationResult> GetErrors(object validatingObject);
}
}

7
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidationContributor.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.Validation
{
public interface IObjectValidationContributor
{
void AddErrors(ObjectValidationContext context);
}
}

6
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs

@ -11,6 +11,10 @@ namespace Volo.Abp.Validation
bool allowNull = false bool allowNull = false
); );
List<ValidationResult> GetErrors(object validatingObject, string name = null, bool allowNull = false); List<ValidationResult> GetErrors(
object validatingObject,
string name = null,
bool allowNull = false
);
} }
} }

20
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidationContext.cs

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
namespace Volo.Abp.Validation
{
public class ObjectValidationContext
{
[NotNull]
public object ValidatingObject { get; }
public List<ValidationResult> Errors { get; }
public ObjectValidationContext([NotNull] object validatingObject)
{
ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject));
Errors = new List<ValidationResult>();
}
}
}

92
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs

@ -1,25 +1,21 @@
using System.Collections; using Microsoft.Extensions.Options;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Reflection;
namespace Volo.Abp.Validation namespace Volo.Abp.Validation
{ {
public class ObjectValidator : IObjectValidator, ITransientDependency public class ObjectValidator : IObjectValidator, ITransientDependency
{ {
private const int MaxRecursiveParameterValidationDepth = 8; protected IHybridServiceScopeFactory ServiceScopeFactory { get; }
protected AbpValidationOptions Options { get; }
private readonly AbpValidationOptions _options; public ObjectValidator(IOptions<AbpValidationOptions> options, IHybridServiceScopeFactory serviceScopeFactory)
private readonly IDataAnnotationValidator _dataAnnotationValidator;
public ObjectValidator(IOptions<AbpValidationOptions> options, IDataAnnotationValidator dataAnnotationValidator)
{ {
_dataAnnotationValidator = dataAnnotationValidator; ServiceScopeFactory = serviceScopeFactory;
_options = options.Value; Options = options.Value;
} }
public virtual void Validate(object validatingObject, string name = null, bool allowNull = false) public virtual void Validate(object validatingObject, string name = null, bool allowNull = false)
@ -37,75 +33,35 @@ namespace Volo.Abp.Validation
public virtual List<ValidationResult> GetErrors(object validatingObject, string name = null, bool allowNull = false) public virtual List<ValidationResult> GetErrors(object validatingObject, string name = null, bool allowNull = false)
{ {
var errors = new List<ValidationResult>();
if (validatingObject == null && !allowNull)
{
errors.Add(
name == null
? new ValidationResult("Given object is null!")
: new ValidationResult(name + " is null!", new[] { name })
);
return errors;
}
ValidateObjectRecursively(errors, validatingObject, currentDepth: 1);
return errors;
}
protected virtual void ValidateObjectRecursively(List<ValidationResult> errors, object validatingObject, int currentDepth)
{
if (currentDepth > MaxRecursiveParameterValidationDepth)
{
return;
}
if (validatingObject == null) if (validatingObject == null)
{ {
return; if (allowNull)
}
errors.AddRange(_dataAnnotationValidator.GetErrors(validatingObject));
//Validate items of enumerable
if (validatingObject is IEnumerable)
{
if (!(validatingObject is IQueryable))
{ {
foreach (var item in (validatingObject as IEnumerable)) return new List<ValidationResult>(); //TODO: Returning an array would be more performent
}
else
{
return new List<ValidationResult>
{ {
ValidateObjectRecursively(errors, item, currentDepth + 1); name == null
} ? new ValidationResult("Given object is null!")
: new ValidationResult(name + " is null!", new[] {name})
};
} }
return;
} }
var validatingObjectType = validatingObject.GetType(); var context = new ObjectValidationContext(validatingObject);
//Do not recursively validate for primitive objects using (var scope = ServiceScopeFactory.CreateScope())
if (TypeHelper.IsPrimitiveExtended(validatingObjectType))
{ {
return; foreach (var contributorType in Options.ObjectValidationContributors)
}
if (_options.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject)))
{
return;
}
var properties = TypeDescriptor.GetProperties(validatingObject).Cast<PropertyDescriptor>();
foreach (var property in properties)
{
if (property.Attributes.OfType<DisableValidationAttribute>().Any())
{ {
continue; var contributor = (IObjectValidationContributor) scope.ServiceProvider.GetRequiredService(contributorType);
contributor.AddErrors(context);
} }
ValidateObjectRecursively(errors, property.GetValue(validatingObject), currentDepth + 1);
} }
return context.Errors;
} }
} }
} }

33
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs

@ -1,7 +1,7 @@
using System; using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Volo.Abp.Aspects; using Volo.Abp.Aspects;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy; using Volo.Abp.DynamicProxy;
@ -10,13 +10,11 @@ namespace Volo.Abp.Validation
{ {
public class ValidationInterceptor : AbpInterceptor, ITransientDependency public class ValidationInterceptor : AbpInterceptor, ITransientDependency
{ {
private readonly AbpValidationOptions _abpValidationOptions; private readonly IMethodInvocationValidator _methodInvocationValidator;
private readonly IServiceProvider _serviceProvider;
public ValidationInterceptor(IServiceProvider serviceProvider, IOptions<AbpValidationOptions> abpValidationOptions) public ValidationInterceptor(IMethodInvocationValidator methodInvocationValidator)
{ {
_serviceProvider = serviceProvider; _methodInvocationValidator = methodInvocationValidator;
_abpValidationOptions = abpValidationOptions.Value;
} }
public override void Intercept(IAbpMethodInvocation invocation) public override void Intercept(IAbpMethodInvocation invocation)
@ -47,18 +45,13 @@ namespace Volo.Abp.Validation
protected virtual void Validate(IAbpMethodInvocation invocation) protected virtual void Validate(IAbpMethodInvocation invocation)
{ {
foreach (var validationContributor in _abpValidationOptions.MethodValidationContributors) _methodInvocationValidator.Validate(
{ new MethodInvocationValidationContext(
var validator = (IMethodInvocationValidator) _serviceProvider.GetRequiredService(validationContributor); invocation.TargetObject,
invocation.Method,
validator.Validate( invocation.Arguments
new MethodInvocationValidationContext( )
invocation.TargetObject, );
invocation.Method,
invocation.Arguments
)
);
}
} }
} }
} }

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

@ -101,8 +101,7 @@ namespace Volo.Abp.FluentValidation
output.ShouldBe("444"); output.ShouldBe("444");
} }
[DependsOn(typeof(AbpAutofacModule))] [DependsOn(typeof(AbpAutofacModule))]
[DependsOn(typeof(AbpFluentValidationModule))] [DependsOn(typeof(AbpFluentValidationModule))]
public class TestModule : AbpModule public class TestModule : AbpModule

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

@ -16,7 +16,7 @@ namespace Volo.Abp.Validation
public ApplicationService_Validation_Tests() public ApplicationService_Validation_Tests()
{ {
_myAppService = ServiceProvider.GetRequiredService<IMyAppService>(); _myAppService = GetRequiredService<IMyAppService>();
} }
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)

12
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs

@ -35,8 +35,7 @@ namespace Volo.Blogging.EntityFrameworkCore
{ {
b.ToTable(options.TablePrefix + "Blogs", options.Schema); b.ToTable(options.TablePrefix + "Blogs", options.Schema);
b.ConfigureFullAudited(); b.ConfigureFullAuditedAggregateRoot();
b.ConfigureExtraProperties();
b.Property(x => x.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength).HasColumnName(nameof(Blog.Name)); b.Property(x => x.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength).HasColumnName(nameof(Blog.Name));
b.Property(x => x.ShortName).IsRequired().HasMaxLength(BlogConsts.MaxShortNameLength).HasColumnName(nameof(Blog.ShortName)); b.Property(x => x.ShortName).IsRequired().HasMaxLength(BlogConsts.MaxShortNameLength).HasColumnName(nameof(Blog.ShortName));
@ -47,8 +46,7 @@ namespace Volo.Blogging.EntityFrameworkCore
{ {
b.ToTable(options.TablePrefix + "Posts", options.Schema); b.ToTable(options.TablePrefix + "Posts", options.Schema);
b.ConfigureFullAudited(); b.ConfigureFullAuditedAggregateRoot();
b.ConfigureExtraProperties();
b.Property(x => x.BlogId).HasColumnName(nameof(Post.BlogId)); b.Property(x => x.BlogId).HasColumnName(nameof(Post.BlogId));
b.Property(x => x.Title).IsRequired().HasMaxLength(PostConsts.MaxTitleLength).HasColumnName(nameof(Post.Title)); b.Property(x => x.Title).IsRequired().HasMaxLength(PostConsts.MaxTitleLength).HasColumnName(nameof(Post.Title));
@ -65,8 +63,7 @@ namespace Volo.Blogging.EntityFrameworkCore
{ {
b.ToTable(options.TablePrefix + "Comments", options.Schema); b.ToTable(options.TablePrefix + "Comments", options.Schema);
b.ConfigureFullAudited(); b.ConfigureFullAuditedAggregateRoot();
b.ConfigureExtraProperties();
b.Property(x => x.Text).IsRequired().HasMaxLength(CommentConsts.MaxTextLength).HasColumnName(nameof(Comment.Text)); b.Property(x => x.Text).IsRequired().HasMaxLength(CommentConsts.MaxTextLength).HasColumnName(nameof(Comment.Text));
b.Property(x => x.RepliedCommentId).HasColumnName(nameof(Comment.RepliedCommentId)); b.Property(x => x.RepliedCommentId).HasColumnName(nameof(Comment.RepliedCommentId));
@ -80,8 +77,7 @@ namespace Volo.Blogging.EntityFrameworkCore
{ {
b.ToTable(options.TablePrefix + "Tags", options.Schema); b.ToTable(options.TablePrefix + "Tags", options.Schema);
b.ConfigureFullAudited(); b.ConfigureFullAuditedAggregateRoot();
b.ConfigureExtraProperties();
b.Property(x => x.Name).IsRequired().HasMaxLength(TagConsts.MaxNameLength).HasColumnName(nameof(Tag.Name)); b.Property(x => x.Name).IsRequired().HasMaxLength(TagConsts.MaxNameLength).HasColumnName(nameof(Tag.Name));
b.Property(x => x.Description).HasMaxLength(TagConsts.MaxDescriptionLength).HasColumnName(nameof(Tag.Description)); b.Property(x => x.Description).HasMaxLength(TagConsts.MaxDescriptionLength).HasColumnName(nameof(Tag.Description));

8
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js

@ -21,9 +21,7 @@
[ [
{ {
text: l('Edit'), text: l('Edit'),
visible: function () { visible: abp.auth.isGranted('Blogging.Blog.Update'),
return true; //TODO: Check permission
},
action: function (data) { action: function (data) {
_editModal.open({ _editModal.open({
blogId: data.record.id blogId: data.record.id
@ -32,9 +30,7 @@
}, },
{ {
text: l('Delete'), text: l('Delete'),
visible: function () { visible: abp.auth.isGranted('Blogging.Blog.Delete'),
return true; //TODO: Check permission
},
confirmMessage: function (data) { return l('BlogDeletionWarningMessage') }, confirmMessage: function (data) { return l('BlogDeletionWarningMessage') },
action: function (data) { action: function (data) {
volo.blogging.blogs volo.blogging.blogs

8
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/index.js

@ -29,9 +29,7 @@
[ [
{ {
text: l('Edit'), text: l('Edit'),
visible: function () { visible: abp.auth.isGranted('Docs.Admin.Projects.Update'),
return true; //TODO: Check permission
},
action: function (data) { action: function (data) {
_editModal.open({ _editModal.open({
Id: data.record.id Id: data.record.id
@ -40,9 +38,7 @@
}, },
{ {
text: l('Delete'), text: l('Delete'),
visible: function () { visible: abp.auth.isGranted('Docs.Admin.Projects.Delete'),
return true; //TODO: Check permission
},
confirmMessage: function (data) { return l('ProjectDeletionWarningMessage') }, confirmMessage: function (data) { return l('ProjectDeletionWarningMessage') },
action: function (data) { action: function (data) {
volo.docs.admin.projectsAdmin volo.docs.admin.projectsAdmin

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Localization/Resources/FeatureManagement/en.json

@ -1,6 +1,6 @@
{ {
"culture": "en", "culture": "en",
"texts": { "texts": {
"Features": "Features"
} }
} }

4
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Localization/Resources/FeatureManagement/tr.json

@ -1,6 +1,6 @@
{ {
"culture": "tr", "culture": "tr",
"texts": { "texts": {
"Features": "Özellikler"
} }
} }

4
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs

@ -22,9 +22,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
{ {
b.ToTable(options.TablePrefix + "Users", options.Schema); b.ToTable(options.TablePrefix + "Users", options.Schema);
b.ConfigureFullAudited(); b.ConfigureFullAuditedAggregateRoot();
b.ConfigureExtraProperties();
b.ConfigureConcurrencyStamp();
b.ConfigureAbpUser(); b.ConfigureAbpUser();
b.Property(u => u.NormalizedUserName).IsRequired().HasMaxLength(IdentityUserConsts.MaxNormalizedUserNameLength).HasColumnName(nameof(IdentityUser.NormalizedUserName)); b.Property(u => u.NormalizedUserName).IsRequired().HasMaxLength(IdentityUserConsts.MaxNormalizedUserNameLength).HasColumnName(nameof(IdentityUser.NormalizedUserName));

10
modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/index.js

@ -22,9 +22,7 @@
[ [
{ {
text: l('Edit'), text: l('Edit'),
visible: function () { visible: abp.auth.isGranted('AbpIdentity.Roles.Update'),
return true; //TODO: Check permission
},
action: function (data) { action: function (data) {
_editModal.open({ _editModal.open({
id: data.record.id id: data.record.id
@ -33,9 +31,7 @@
}, },
{ {
text: l('Permissions'), text: l('Permissions'),
visible: function () { visible: abp.auth.isGranted('AbpIdentity.Roles.ManagePermissions'),
return true; //TODO: Check permission
},
action: function (data) { action: function (data) {
_permissionsModal.open({ _permissionsModal.open({
providerName: 'Role', providerName: 'Role',
@ -46,7 +42,7 @@
{ {
text: l('Delete'), text: l('Delete'),
visible: function (data) { visible: function (data) {
return !data.isStatic; //TODO: Check permission return !data.isStatic && abp.auth.isGranted('AbpIdentity.Roles.Delete'); //TODO: Check permission
}, },
confirmMessage: function (data) { return l('RoleDeletionConfirmationMessage', data.record.name)}, confirmMessage: function (data) { return l('RoleDeletionConfirmationMessage', data.record.name)},
action: function (data) { action: function (data) {

12
modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/index.js

@ -24,9 +24,7 @@
[ [
{ {
text: l('Edit'), text: l('Edit'),
visible: function () { visible: abp.auth.isGranted('AbpIdentity.Users.Update'),
return true; //TODO: Check permission
},
action: function (data) { action: function (data) {
_editModal.open({ _editModal.open({
id: data.record.id id: data.record.id
@ -35,9 +33,7 @@
}, },
{ {
text: l('Permissions'), text: l('Permissions'),
visible: function () { visible: abp.auth.isGranted('AbpIdentity.Users.ManagePermissions'),
return true; //TODO: Check permission
},
action: function (data) { action: function (data) {
_permissionsModal.open({ _permissionsModal.open({
providerName: 'User', providerName: 'User',
@ -47,9 +43,7 @@
}, },
{ {
text: l('Delete'), text: l('Delete'),
visible: function () { visible: abp.auth.isGranted('AbpIdentity.Users.Delete'),
return true; //TODO: Check permission
},
confirmMessage: function (data) { return l('UserDeletionConfirmationMessage', data.record.userName); }, confirmMessage: function (data) { return l('UserDeletionConfirmationMessage', data.record.userName); },
action: function (data) { action: function (data) {
_identityUserAppService _identityUserAppService

9
modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs

@ -26,8 +26,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore
{ {
client.ToTable(tablePrefix + "Clients", schema); client.ToTable(tablePrefix + "Clients", schema);
client.ConfigureFullAudited(); client.ConfigureFullAuditedAggregateRoot();
client.ConfigureExtraProperties();
client.Property(x => x.ClientId).HasMaxLength(ClientConsts.ClientIdMaxLength).IsRequired(); client.Property(x => x.ClientId).HasMaxLength(ClientConsts.ClientIdMaxLength).IsRequired();
client.Property(x => x.ProtocolType).HasMaxLength(ClientConsts.ProtocolTypeMaxLength).IsRequired(); client.Property(x => x.ProtocolType).HasMaxLength(ClientConsts.ProtocolTypeMaxLength).IsRequired();
@ -160,8 +159,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore
{ {
identityResource.ToTable(tablePrefix + "IdentityResources", schema); identityResource.ToTable(tablePrefix + "IdentityResources", schema);
identityResource.ConfigureFullAudited(); identityResource.ConfigureFullAuditedAggregateRoot();
identityResource.ConfigureExtraProperties();
identityResource.Property(x => x.Name).HasMaxLength(IdentityResourceConsts.NameMaxLength).IsRequired(); identityResource.Property(x => x.Name).HasMaxLength(IdentityResourceConsts.NameMaxLength).IsRequired();
identityResource.Property(x => x.DisplayName).HasMaxLength(IdentityResourceConsts.DisplayNameMaxLength); identityResource.Property(x => x.DisplayName).HasMaxLength(IdentityResourceConsts.DisplayNameMaxLength);
@ -183,8 +181,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore
{ {
apiResource.ToTable(tablePrefix + "ApiResources", schema); apiResource.ToTable(tablePrefix + "ApiResources", schema);
apiResource.ConfigureFullAudited(); apiResource.ConfigureFullAuditedAggregateRoot();
apiResource.ConfigureExtraProperties();
apiResource.Property(x => x.Name).HasMaxLength(ApiResourceConsts.NameMaxLength).IsRequired(); apiResource.Property(x => x.Name).HasMaxLength(ApiResourceConsts.NameMaxLength).IsRequired();
apiResource.Property(x => x.DisplayName).HasMaxLength(ApiResourceConsts.DisplayNameMaxLength); apiResource.Property(x => x.DisplayName).HasMaxLength(ApiResourceConsts.DisplayNameMaxLength);

1
modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementPermissionDefinitionProvider.cs

@ -16,6 +16,7 @@ namespace Volo.Abp.TenantManagement
tenantsPermission.AddChild(TenantManagementPermissions.Tenants.Update, L("Permission:Edit"), multiTenancySide: MultiTenancySides.Host); tenantsPermission.AddChild(TenantManagementPermissions.Tenants.Update, L("Permission:Edit"), multiTenancySide: MultiTenancySides.Host);
tenantsPermission.AddChild(TenantManagementPermissions.Tenants.Delete, L("Permission:Delete"), multiTenancySide: MultiTenancySides.Host); tenantsPermission.AddChild(TenantManagementPermissions.Tenants.Delete, L("Permission:Delete"), multiTenancySide: MultiTenancySides.Host);
tenantsPermission.AddChild(TenantManagementPermissions.Tenants.ManageFeatures, L("Permission:ManageFeatures"), multiTenancySide: MultiTenancySides.Host); tenantsPermission.AddChild(TenantManagementPermissions.Tenants.ManageFeatures, L("Permission:ManageFeatures"), multiTenancySide: MultiTenancySides.Host);
tenantsPermission.AddChild(TenantManagementPermissions.Tenants.ManageConnectionStrings, L("Permission:ManageConnectionStrings"), multiTenancySide: MultiTenancySides.Host);
} }
private static LocalizableString L(string name) private static LocalizableString L(string name)

1
modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json

@ -5,6 +5,7 @@
"Permission:Create": "Create", "Permission:Create": "Create",
"Permission:Edit": "Edit", "Permission:Edit": "Edit",
"Permission:Delete": "Delete", "Permission:Delete": "Delete",
"Permission:ManageConnectionStrings": "Manage connection strings",
"Permission:ManageFeatures": "Manage features" "Permission:ManageFeatures": "Manage features"
} }
} }

1
modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/tr.json

@ -5,6 +5,7 @@
"Permission:Create": "Oluşturma", "Permission:Create": "Oluşturma",
"Permission:Edit": "Düzenleme", "Permission:Edit": "Düzenleme",
"Permission:Delete": "Silme", "Permission:Delete": "Silme",
"Permission:ManageConnectionStrings": "Bağlantı cümlelerini yönet",
"Permission:ManageFeatures": "Özellikleri yönet" "Permission:ManageFeatures": "Özellikleri yönet"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantManagementPermissions.cs

@ -11,6 +11,7 @@
public const string Update = Default + ".Update"; public const string Update = Default + ".Update";
public const string Delete = Default + ".Delete"; public const string Delete = Default + ".Delete";
public const string ManageFeatures = Default + ".ManageFeatures"; public const string ManageFeatures = Default + ".ManageFeatures";
public const string ManageConnectionStrings = Default + ".ManageConnectionStrings";
} }
public static string[] GetAll() public static string[] GetAll()
@ -22,7 +23,8 @@
Tenants.Create, Tenants.Create,
Tenants.Update, Tenants.Update,
Tenants.Delete, Tenants.Delete,
Tenants.ManageFeatures Tenants.ManageFeatures,
Tenants.ManageConnectionStrings
}; };
} }
} }

1
modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/AbpTenantManagementApplicationModule.cs

@ -5,6 +5,7 @@ namespace Volo.Abp.TenantManagement
{ {
[DependsOn(typeof(AbpTenantManagementDomainModule))] [DependsOn(typeof(AbpTenantManagementDomainModule))]
[DependsOn(typeof(AbpTenantManagementApplicationContractsModule))] [DependsOn(typeof(AbpTenantManagementApplicationContractsModule))]
[DependsOn(typeof(AbpAutoMapperModule))]
public class AbpTenantManagementApplicationModule : AbpModule public class AbpTenantManagementApplicationModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)

3
modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs

@ -80,12 +80,14 @@ namespace Volo.Abp.TenantManagement
await TenantRepository.DeleteAsync(tenant); await TenantRepository.DeleteAsync(tenant);
} }
[Authorize(TenantManagementPermissions.Tenants.ManageConnectionStrings)]
public async Task<string> GetDefaultConnectionStringAsync(Guid id) public async Task<string> GetDefaultConnectionStringAsync(Guid id)
{ {
var tenant = await TenantRepository.GetAsync(id); var tenant = await TenantRepository.GetAsync(id);
return tenant?.FindDefaultConnectionString(); return tenant?.FindDefaultConnectionString();
} }
[Authorize(TenantManagementPermissions.Tenants.ManageConnectionStrings)]
public async Task UpdateDefaultConnectionStringAsync(Guid id, string defaultConnectionString) public async Task UpdateDefaultConnectionStringAsync(Guid id, string defaultConnectionString)
{ {
var tenant = await TenantRepository.GetAsync(id); var tenant = await TenantRepository.GetAsync(id);
@ -93,6 +95,7 @@ namespace Volo.Abp.TenantManagement
await TenantRepository.UpdateAsync(tenant); await TenantRepository.UpdateAsync(tenant);
} }
[Authorize(TenantManagementPermissions.Tenants.ManageConnectionStrings)]
public async Task DeleteDefaultConnectionStringAsync(Guid id) public async Task DeleteDefaultConnectionStringAsync(Guid id)
{ {
var tenant = await TenantRepository.GetAsync(id); var tenant = await TenantRepository.GetAsync(id);

4
modules/tenant-management/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementDbContextModelCreatingExtensions.cs

@ -22,9 +22,7 @@ namespace Volo.Abp.TenantManagement.EntityFrameworkCore
{ {
b.ToTable(tablePrefix + "Tenants", schema); b.ToTable(tablePrefix + "Tenants", schema);
b.ConfigureFullAudited(); b.ConfigureFullAuditedAggregateRoot();
b.ConfigureExtraProperties();
b.ConfigureConcurrencyStamp();
b.Property(t => t.Name).IsRequired().HasMaxLength(TenantConsts.MaxNameLength); b.Property(t => t.Name).IsRequired().HasMaxLength(TenantConsts.MaxNameLength);

1
modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs

@ -61,6 +61,7 @@ namespace Volo.Abp.TenantManagement.Web
options.Conventions.AuthorizePage("/TenantManagement/Tenants/Index", TenantManagementPermissions.Tenants.Default); options.Conventions.AuthorizePage("/TenantManagement/Tenants/Index", TenantManagementPermissions.Tenants.Default);
options.Conventions.AuthorizePage("/TenantManagement/Tenants/CreateModal", TenantManagementPermissions.Tenants.Create); options.Conventions.AuthorizePage("/TenantManagement/Tenants/CreateModal", TenantManagementPermissions.Tenants.Create);
options.Conventions.AuthorizePage("/TenantManagement/Tenants/EditModal", TenantManagementPermissions.Tenants.Update); options.Conventions.AuthorizePage("/TenantManagement/Tenants/EditModal", TenantManagementPermissions.Tenants.Update);
options.Conventions.AuthorizePage("/TenantManagement/Tenants/ConnectionStrings", TenantManagementPermissions.Tenants.ManageConnectionStrings);
}); });
} }
} }

12
modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js

@ -25,7 +25,7 @@
[ [
{ {
text: l('Edit'), text: l('Edit'),
visible: function () { return true; }, //TODO: Check permission visible: abp.auth.isGranted('AbpTenantManagement.Tenants.Update'),
action: function (data) { action: function (data) {
_editModal.open({ _editModal.open({
id: data.record.id id: data.record.id
@ -34,9 +34,7 @@
}, },
{ {
text: l('ConnectionStrings'), text: l('ConnectionStrings'),
visible: function () { visible: abp.auth.isGranted('AbpTenantManagement.Tenants.ManageConnectionStrings'),
return true; //TODO: Check permission
},
action: function (data) { action: function (data) {
_connectionStringsModal.open({ _connectionStringsModal.open({
id: data.record.id id: data.record.id
@ -45,9 +43,7 @@
}, },
{ {
text: l('Features'), text: l('Features'),
visible: function () { visible: abp.auth.isGranted('AbpTenantManagement.Tenants.ManageFeatures'),
return true; //TODO: Check permission
},
action: function (data) { action: function (data) {
_featuresModal.open({ _featuresModal.open({
providerName: 'Tenant', providerName: 'Tenant',
@ -57,7 +53,7 @@
}, },
{ {
text: l('Delete'), text: l('Delete'),
visible: function () { return true; }, //TODO: Check permission visible: abp.auth.isGranted('AbpTenantManagement.Tenants.Delete'),
confirmMessage: function (data) { return l('TenantDeletionConfirmationMessage', data.record.name)}, confirmMessage: function (data) { return l('TenantDeletionConfirmationMessage', data.record.name)},
action: function (data) { action: function (data) {
_tenantAppService _tenantAppService

Loading…
Cancel
Save