diff --git a/README.md b/README.md
index dbf8bce537..1d7b7e51a7 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,10 @@ ABP is a community-driven open source project. See [the contribution guide](http
Love ABP Framework? **Please give a star** to this repository :star:
+## Discord Channel
+
+You can use this link to join the ABP Community Discord Server: https://discord.gg/uVGt6hyhcm
+
## ABP Commercial
See also [ABP Commercial](https://commercial.abp.io/) if you are looking for pre-built application modules, professional themes, code generation tooling and premium support for the ABP Framework.
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
index d1890e1786..023a4b18ed 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
@@ -337,18 +337,6 @@
"Expired": "Expired",
"TrialLicenseDeletionWarningMessage": "Are you sure you want to delete the trial license? Trial license, organization, support accounts will be deleted!",
"LicenseCategoryFilter": "License category",
- "Volo.AbpIo.Commercial:030000": "You already used your trial period.",
- "Volo.AbpIo.Commercial:030001": "This organization name already exists.",
- "Volo.AbpIo.Commercial:030002": "Once activated, trial license cannot be set to requested!",
- "Volo.AbpIo.Commercial:030003": "There is no such status!",
- "Volo.AbpIo.Commercial:030004": "Status could not be changed due to an unexpected error!",
- "Volo.AbpIo.Commercial:030005": "Start and end date can be updated when the trial license is in the -activated- status!",
- "Volo.AbpIo.Commercial:030006": "End date must always be greater than start date!",
- "Volo.AbpIo.Commercial:030007": "This trial license has already been activated once!",
- "Volo.AbpIo.Commercial:030008": "Purchase date can be set only when status is Purchased!",
- "Volo.AbpIo.Commercial:030009": "User not found!",
- "Volo.AbpIo.Commercial:030010": "To purchase the trial license, first you need to activate your trial license!",
- "Volo.AbpIo.Commercial:030011": "You cannot delete a trial license when it is purchased!",
"Permission:SendWelcomeEmail": "Send Welcome Email",
"SendWelcomeEmail": "Send Welcome Email",
"SendWelcomeEmailWarningMessage": "Are you sure you want to send welcome email to the organization members?",
@@ -385,6 +373,7 @@
"Menu:Speakers": "Speakers",
"ChooseSpeakerImage": "Choose a speaker image...",
"SpeakerImage": "Speaker image",
+ "AddSpeaker": "Add Speaker",
"ShowPurchaseItemsOfOrganizations": "Purchase Items",
"Enum:OrganizationPurchaseState:0": "Not delivered",
"Enum:OrganizationPurchaseState:1": "Delivered",
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
index fe9085e427..f772006632 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
@@ -14,6 +14,18 @@
"Volo.AbpIo.Domain:020002": "Could not delete this NPM Package because \"{Modules}\" Modules are using this package.",
"Volo.AbpIo.Domain:020003": "Could not delete this NPM Package because \"{Modules}\" Modules are using this package and \"{NugetPackages}\" Nuget Packages are dependent to this package.",
"Volo.AbpIo.Domain:020004": "Could not delete this Nuget Package because \"{Modules}\" Modules are using this package.",
+ "Volo.AbpIo.Domain:030000": "You have already completed your trial period.",
+ "Volo.AbpIo.Domain:030001": "This organization name already exists.",
+ "Volo.AbpIo.Domain:030002": "Once activated, you cannot switch the trial license to -requested- status!",
+ "Volo.AbpIo.Domain:030003": "There is no such status!",
+ "Volo.AbpIo.Domain:030004": "Status could not be changed due to an unexpected error!",
+ "Volo.AbpIo.Domain:030005": "Start and end date can be updated when the trial license is in the -activated- status!",
+ "Volo.AbpIo.Domain:030006": "The end date must be greater than the start date!",
+ "Volo.AbpIo.Domain:030007": "This trial license has already been activated!",
+ "Volo.AbpIo.Domain:030008": "The purchase date can be set only when the status is -purchased-!",
+ "Volo.AbpIo.Domain:030009": "User not found!",
+ "Volo.AbpIo.Domain:030010": "To purchase the trial license, you first need to activate your trial license!",
+ "Volo.AbpIo.Domain:030011": "You cannot delete a trial license when it is purchased!",
"WantToLearn?": "Want to learn?",
"ReadyToGetStarted?": "Ready to get started?",
"JoinOurCommunity": "Join our community",
@@ -72,7 +84,7 @@
"WouldLikeToReceiveMarketingMaterials": "I would like to receive marketing materials like product deals & special offers.",
"JoinOurMarketingNewsletter": "Join our marketing newsletter",
"CommunityPrivacyPolicyConfirmation": "I agree to the Terms & Conditions and Privacy Policy.",
- "ABPIO-Common": "ABPIO-Common",
+ "WouldLikeToReceiveNotification": "I would like to receive the latest news from abp.io websites.",
"CommercialNewsletterConfirmationMessage": "I agree to the Terms & Conditions and Privacy Policy.",
"FreeDDDEBook": "Free DDD E-Book",
"AdditionalServices": "Additional Services",
@@ -104,7 +116,9 @@
"WatchTheEvent": "Watch the Event",
"RegisterNow": "Register Now",
"ThereIsNoEvent": "There is no event.",
+ "Events": "Events",
"Volo.AbpIo.Domain:080000": "There is already a purchase item named \"{Name}\"",
- "MasteringAbpFrameworkBook": "Book: Mastering ABP Framework"
+ "MasteringAbpFrameworkBook": "Book: Mastering ABP Framework",
+ "ABPIO-CommonPreferenceDefinition": "Get latest news about ABP Platform like new posts, events and more."
}
}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
index 60215b5fec..550374b48c 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
@@ -318,6 +318,7 @@
"InstallingTheABPCLI": "Installing the ABP CLI",
"CreateYourProjectNow": "Create Your Project Now",
"OrderOn": "Order on {0}",
- "DownloadFreeDDDBook": "Download Free DDD Book"
+ "DownloadFreeDDDBook": "Download Free DDD Book",
+ "WhatIsABPFramework": "What is the ABP Framework?"
}
}
diff --git a/common.props b/common.props
index f9f1a35ba1..facc92f0bc 100644
--- a/common.props
+++ b/common.props
@@ -1,7 +1,7 @@
latest
- 5.2.0-rc.2
+ 5.3.0
$(NoWarn);CS1591;CS0436
https://abp.io/assets/abp_nupkg.png
https://abp.io/
diff --git a/docs/zh-Hans/Caching.md b/docs/zh-Hans/Caching.md
index a75199ce47..491e60eb29 100644
--- a/docs/zh-Hans/Caching.md
+++ b/docs/zh-Hans/Caching.md
@@ -1,164 +1,180 @@
# 缓存
-ABP框架扩展了ASP.NET Core的分布式缓存系统.
+ABP框架扩展了 [ASP.NET Core的分布式缓存系统](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed).
-## Volo.Abp.Caching Package
+## 安装
-> 默认情况下启动模板已经安装了这个包,所以大部分情况下你不需要手动安装.
+> 默认情况下 [启动模板](Startup-Templates/Application.md) 已经安装了这个包. 所以大部分情况下你不需要手动安装.
-Volo.Abp.Caching是缓存系统的核心包.使用包管理控制台(PMC)安装到项目:
+[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching)是缓存系统的核心包. 可以使用 [ABP CLI](CLI.md) 的add-package命令将其安装到项目中:
```
-Install-Package Volo.Abp.Caching
+abp add-package Volo.Abp.Caching
```
+你需要在包含 `csproj` 文件的文件夹中的命令行终端上运行此命令(请参阅 [其他选项](https://abp.io/package-detail/Volo.Abp.Caching) 安装).
-然后将 **AbpCachingModule** 依赖添加到你的模块:
+## 使用方式
-```c#
-using Volo.Abp.Modularity;
-using Volo.Abp.Caching;
-
-namespace MyCompany.MyProject
-{
- [DependsOn(typeof(AbpCachingModule))]
- public class MyModule : AbpModule
- {
- //...
- }
-}
-```
-
-## `IDistributedCache` 接口
+### `IDistributedCache` 接口
-ASP.NET Core 定义了 `IDistributedCache` 接口用于 get/set 缓存值 . 但是会有以下问题:
+ASP.NET Core 定义了 `IDistributedCache` 接口用于 get/set 缓存值. 但是会有以下问题:
* 它适用于 **byte 数组** 而不是 .NET 对象. 因此你需要对缓存的对象进行**序列化/反序列化**.
-* 它为所有的缓存项提供了 **单个 key 池** , 因此 ;
+* 它为所有的缓存项提供了 **单个 key 池** , 因此;
* 你需要注意键区分 **不同类型的对象**.
* 你需要注意**不同租户**(参见[多租户](Multi-Tenancy.md))的缓存项.
> `IDistributedCache` 定义在 `Microsoft.Extensions.Caching.Abstractions` 包中. 这使它不仅适用于ASP.NET Core应用程序, 也可用于**任何类型的程序**.
-> `IDistributedCache` 接口的默认实现是 `MemoryDistributedCache` 它使用**内存**工作. 参见 [ASP.NET Core文档](https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed) 了解如何切换到 **Redis** 或其他缓存提供程序.
+> `IDistributedCache` 接口的默认实现是 `MemoryDistributedCache` 它使用**内存**工作. 参见 [ASP.NET Core文档](https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed) 了解如何切换到 **Redis** 或其他缓存提供程序. 此外, 如果要将Redis用作分布式缓存服务器, [Redis缓存](Redis-Cache.md) 文档.
有关更多信息, 参见 [ASP.NET Core 分布式缓存文档](https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed).
-## `IDistributedCache` 接口
+### `IDistributedCache` 接口
ABP框架在[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching/)包定义了通用的泛型 `IDistributedCache` 接口. `TCacheItem` 是存储在缓存中的对象类型.
-`IDistributedCache` 接口了上述中的问题;
+`IDistributedCache` 接口解决了上述中的问题;
* 它在内部 **序列化/反序列化** 缓存对象. 默认使用 **JSON** 序列化, 但可以替换[依赖注入](Dependency-Injection.md)系统中 `IDistributedCacheSerializer` 服务的实现来覆盖默认的处理.
-* 它根据缓存中对象类型自动向缓存key添加 **缓存名称** 前缀. 默认缓存名是缓存对象类的全名(如果你的类名以`CacheItem` 结尾, 那么`CacheItem` 会被忽略,不应用到缓存名称上). 你也可以在缓存类上使用 `CacheName` 设置换缓存的名称.
-* 它自动将当前的**租户id**添加到缓存键中, 以区分不同租户的缓存项 (只有在你的应用程序是[多租户](Multi-Tenancy.md)的情况下生效). 在缓存类上应用 `IgnoreMultiTenancy` attribute, 可以在所有的租户间共享缓存.
-* 允许为每个应用程序定义 **全局缓存键前缀** ,不同的应用程序可以在共享的分布式缓存中拥有自己的隔离池.
+* 它根据缓存中对象类型自动向缓存key添加 **缓存名称** 前缀. 默认缓存名是缓存对象类的全名(如果你的类名以`CacheItem` 结尾, 那么`CacheItem` 会被忽略,不应用到缓存名称上). 你也可以在缓存类上使用 **`CacheName` 特性** 设置缓存的名称.
+* 它自动将**当前的租户id**添加到缓存键中, 以区分不同租户的缓存项 (只有在你的应用程序是[多租户](Multi-Tenancy.md)的情况下生效). 如果要在多租户应用程序中的所有租户之间共享缓存对象, 请在缓存项类上定义`IgnoreMultiTenancy`特性以禁用此选项.
+* 允许为每个应用程序定义 **全局缓存键前缀** , 不同的应用程序可以在共享的分布式缓存中拥有自己的隔离池.
+* 它可以在任何可能绕过缓存的情况下 **容忍错误** 的发生. 这在缓存服务器出现临时问题时非常有用.
+* 它有 `GetManyAsync` 和 `SetManyAsync` 等方法, 可以显著提高**批处理**的性能.
-### 使用方式
-
-缓存中存储项的示例类:
+**示例: 在缓存中存储图书名称和价格**
````csharp
-public class BookCacheItem
+namespace MyProject
{
- public string Name { get; set; }
+ public class BookCacheItem
+ {
+ public string Name { get; set; }
- public float Price { get; set; }
+ public float Price { get; set; }
+ }
}
````
你可以注入 `IDistributedCache` 服务用于 get/set `BookCacheItem` 对象.
-使用示例:
-
````csharp
-public class BookService : ITransientDependency
-{
- private readonly IDistributedCache _cache;
-
- public BookService(IDistributedCache cache)
- {
- _cache = cache;
- }
-
- public async Task GetAsync(Guid bookId)
- {
- return await _cache.GetOrAddAsync(
- bookId.ToString(), //Cache key
- async () => await GetBookFromDatabaseAsync(bookId),
- () => new DistributedCacheEntryOptions
- {
- AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
- }
- );
- }
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Distributed;
+using Volo.Abp.Caching;
+using Volo.Abp.DependencyInjection;
- private Task GetBookFromDatabaseAsync(Guid bookId)
+namespace MyProject
+{
+ public class BookService : ITransientDependency
{
- //TODO: get from database
+ private readonly IDistributedCache _cache;
+
+ public BookService(IDistributedCache cache)
+ {
+ _cache = cache;
+ }
+
+ public async Task GetAsync(Guid bookId)
+ {
+ return await _cache.GetOrAddAsync(
+ bookId.ToString(), //缓存键
+ async () => await GetBookFromDatabaseAsync(bookId),
+ () => new DistributedCacheEntryOptions
+ {
+ AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
+ }
+ );
+ }
+
+ private Task GetBookFromDatabaseAsync(Guid bookId)
+ {
+ //TODO: 从数据库获取数据
+ }
}
}
````
-* 示例服务代码中的 `GetOrAddAsync()` 方法从缓存中获取图书项.
+* 示例服务代码中的 `GetOrAddAsync()` 方法从缓存中获取图书项. `GetOrAddAsync`是ABP框架在 ASP.NET Core 分布式缓存方法中添增的附加方法.
* 如果没有在缓存中找到图书,它会调用工厂方法 (本示例中是 `GetBookFromDatabaseAsync`)从原始数据源中获取图书项.
* `GetOrAddAsync` 有一个可选参数 `DistributedCacheEntryOptions` , 可用于设置缓存的生命周期.
-`IDistributedCache` 的其他方法与ASP.NET Core的`IDistributedCache` 接口相同, 你可以参考 [ASP.NET Core文档](https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed).
+`IDistributedCache` 与ASP.NET Core的`IDistributedCache` 接口拥有相同的方法, 你可以参考 [ASP.NET Core文档](https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed).
-## `IDistributedCache` 接口
+### `IDistributedCache` 接口
-`IDistributedCache` 接口默认了键是 `string` 类型 (如果你的键不是string类型需要进行手动类型转换). `IDistributedCache` 将键的类型泛型化试图简化手动转换的操作.
+`IDistributedCache` 接口默认了**缓存键**是 `string` 类型 (如果你的键不是string类型需要进行手动类型转换). 但当缓存键的类型不是`string`时, 可以使用`IDistributedCache`.
-### 使用示例
+**示例: 在缓存中存储图书名称和价格**
示例缓存项
````csharp
-public class BookCacheItem
+using Volo.Abp.Caching;
+
+namespace MyProject
{
- public string Name { get; set; }
+ [CacheName("Books")]
+ public class BookCacheItem
+ {
+ public string Name { get; set; }
- public float Price { get; set; }
+ public float Price { get; set; }
+ }
}
````
-用法示例 (这里假设你的键类型是 `Guid`):
+* 在本例中使用`CacheName`特性给`BookCacheItem`类设置缓存名称.
-````csharp
-public class BookService : ITransientDependency
-{
- private readonly IDistributedCache _cache;
+你可以注入 `IDistributedCache` 服务用于 get/set `BookCacheItem` 对象.
- public BookService(IDistributedCache cache)
- {
- _cache = cache;
- }
+````csharp
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Distributed;
+using Volo.Abp.Caching;
+using Volo.Abp.DependencyInjection;
- public async Task GetAsync(Guid bookId)
- {
- return await _cache.GetOrAddAsync(
- bookId, //Guid type used as the cache key
- async () => await GetBookFromDatabaseAsync(bookId),
- () => new DistributedCacheEntryOptions
- {
- AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
- }
- );
- }
- private Task GetBookFromDatabaseAsync(Guid bookId)
+namespace MyProject
+{
+ public class BookService : ITransientDependency
{
- //TODO: get from database
+ private readonly IDistributedCache _cache;
+
+ public BookService(IDistributedCache cache)
+ {
+ _cache = cache;
+ }
+
+ public async Task GetAsync(Guid bookId)
+ {
+ return await _cache.GetOrAddAsync(
+ bookId, //Guid类型作为缓存键
+ async () => await GetBookFromDatabaseAsync(bookId),
+ () => new DistributedCacheEntryOptions
+ {
+ AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
+ }
+ );
+ }
+ private Task GetBookFromDatabaseAsync(Guid bookId)
+ {
+ //TODO: 从数据库获取数据
+ }
}
}
````
* 示例服务中 `GetOrAddAsync()` 方法获取缓存的图书项.
-* 我们采用了 `Guid` 做为键,在 `_cache_GetOrAddAsync()` 方法中传入 `Guid` 类型的bookid.
+* 我们采用了 `Guid` 做为键, 在 `_cache_GetOrAddAsync()` 方法中传入 `Guid` 类型的bookid.
+
+#### 复杂类型的缓存键
-`IDistributedCache` 在内部使用键对象的 `ToString()` 方法转换类型为string. 如果你的将复杂对象做为键,那么需要重写类的 `ToString` 方法.
+`IDistributedCache` 在内部使用键对象的 `ToString()` 方法转换类型为string. 如果你的将复杂对象做为缓存键,那么需要重写类的 `ToString` 方法.
-示例:
+举例一个作为缓存键的类:
````csharp
public class UserInOrganizationCacheKey
@@ -187,23 +203,72 @@ public class BookService : ITransientDependency
{
_cache = cache;
}
-
+
...
}
````
+## 配置
+
+### AbpDistributedCacheOptions
+`AbpDistributedCacheOptions` 是配置缓存的主要[Option类](Options.md).
+
+**示例:为应用程序设置缓存键前缀**
+
+```csharp
+Configure(options =>
+{
+ options.KeyPrefix = "MyApp1";
+});
+```
+> 在[模块类](Module-Development-Basics.md)的`ConfigureServices`方法中添加代码.
+
+#### 可用选项
+
+* `HideErrors` (`bool`, 默认: `true`): 启用/禁用隐藏从缓存服务器写入/读取值时的错误.
+* `KeyPrefix` (`string`, 默认: `null`): 如果你的缓存服务器由多个应用程序共同使用, 则可以为应用程序的缓存键设置一个前缀. 在这种情况下, 不同的应用程序不能覆盖彼此的缓存内容.
+* `GlobalCacheEntryOptions` (`DistributedCacheEntryOptions`): 用于设置保存缓内容却没有指定选项时, 默认的分布式缓存选项 (例如 `AbsoluteExpiration` 和 `SlidingExpiration`). `SlidingExpiration`的默认值设置为20分钟.
+
+## 错误处理
+
+当为你的对象设计缓存时, 通常会首先尝试从缓存中获取值. 如果在缓存中找不到该值, 则从**来源**查询对象. 它可能在**数据库**中, 或者可能需要通过HTTP调用远程服务器.
+
+在大多数情况下, 你希望**容忍缓存错误**; 如果缓存服务器出现错误, 也不希望取消该操作. 相反, 你可以默默地隐藏(并记录)错误并**从来源查询**. 这就是ABP框架默认的功能.
+
+ABP的分布式缓存 [异常处理](Exception-Handling.md), 默认记录并隐藏错误. 有一个全局修改该功能的选项(参见下面的选项内容).
+
+所有的`IDistributedCache` (和 `IDistributedCache`)方法都有一个可选的参数`hideErrors`, 默认值为`null`. 如果此参数设置为`null`, 则全局生效, 否则你可以选择单个方法调用时隐藏或者抛出异常.
+
## 批量操作
ABP的分布式缓存接口定义了以下批量操作方法,当你需要在一个方法中调用多次缓存操作时,这些方法可以提高性能
-* `SetManyAsync` 和 `SetMany` 方法可以用来设置多个值.
+* `SetManyAsync` 和 `SetMany` 方法可以用来向缓存中设置多个值.
* `GetManyAsync` 和 `GetMany` 方法可以用来从缓存中获取多个值.
* `GetOrAddManyAsync` 和 `GetOrAddMany` 方法可以用来从缓存中获取并添加缺少的值.
* `RefreshManyAsync` 和 `RefreshMany` 方法可以来用重置多个值的滚动过期时间.
-* `RemoveManyAsync` 和 `RemoveMany` 方法呆以用来删除多个值.
+* `RemoveManyAsync` 和 `RemoveMany` 方法可以用来从缓存中删除多个值.
> 这些不是标准的ASP.NET Core缓存方法, 所以某些提供程序可能不支持. [ABP Redis集成包](Redis-Cache.md)实现了它们. 如果提供程序不支持,会回退到 `SetAsync` 和 `GetAsync` ... 方法(循环调用).
-### DistributedCacheOptions
+## 高级主题
+
+### 工作单元级别的缓存
+
+分布式缓存服务提供了一个有趣的功能. 假设你已经更新了数据库中某本书的价格, 然后将新价格设置到缓存中, 以便以后使用缓存的值. 如果设置缓存后出现异常, 并且更新图书价格的**事务被回滚了**, 该怎么办?在这种情况下, 缓存值是错误的.
+
+`IDistributedCache<..>`方法提供一个可选参数, `considerOuw`, 默认为`false`. 如果将其设置为`true`, 则你对缓存所做的更改不会应用于真正的缓存存储, 而是与当前的[工作单元](Unit-Of-Work.md)关联. 你将获得在同一工作单元中设置的缓存值, 但**仅当前工作单元成功时**更改才会生效.
+
+### IDistributedCacheSerializer
+
+`IDistributedCacheSerializer`服务用于序列化和反序列化缓存内容. 默认实现是`Utf8JsonDistributedCacheSerializer`类, 它使用`IJsonSerializer`服务将对象转换为[JSON](Json-Serialization.md), 反之亦然. 然后, 它使用UTC8编码将JSON字符串转换为分布式缓存接受的字节数组.
+
+如果你想实现自己的序列化逻辑, 可以自己实现并[替换](Dependency-Injection.md) 此服务.
+
+### IDistributedCacheKeyNormalizer
+
+默认情况下, `IDistributedCacheKeyNormalizer`是由`DistributedCacheKeyNormalizer`类实现的. 它将缓存名称、应用程序缓存前缀和当前租户id添加到缓存键中. 如果需要更高级的键规范化, 可以自己实现并[替换](Dependency-Injection.md)此服务.
+
+## 另请参阅
-TODO
\ No newline at end of file
+* [Redis 缓存](Redis-Cache.md)
diff --git a/docs/zh-Hans/Distributed-Event-Bus-Kafka-Integration.md b/docs/zh-Hans/Distributed-Event-Bus-Kafka-Integration.md
index f621a95fae..171fe3c23b 100644
--- a/docs/zh-Hans/Distributed-Event-Bus-Kafka-Integration.md
+++ b/docs/zh-Hans/Distributed-Event-Bus-Kafka-Integration.md
@@ -1,10 +1,10 @@
# 分布式事件总线Kafka集成
-> 本文解释了**如何配置[Kafka](https://kafka.apache.org/)**做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统.
+> 本文解释了 **如何配置[Kafka](https://kafka.apache.org/)** 做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统.
## 安装
-使用ABP CLI添加[Volo.Abp.EventBus.Kafka[Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka)NuGet包到你的项目:
+使用ABP CLI添加[Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka)NuGet包到你的项目:
* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI),如果你还没有安装.
* 在你想要安装 `Volo.Abp.EventBus.Kafka` 包的 `.csproj` 文件目录打开命令行(终端).
@@ -18,7 +18,7 @@
### `appsettings.json` 文件配置
-这是配置Kafka设置最简单的方法. 它也非常强大,因为你可以使用[由AspNet Core支持的](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)的任何其他配置源(如环境变量).
+这是配置Kafka设置最简单的方法. 它也非常强大,因为你可以使用[由AspNet Core支持](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)的任何其他配置源(如环境变量).
**示例:最小化配置与默认配置连接到本地的Kafka服务器**
@@ -160,4 +160,4 @@ Configure(options =>
});
````
-使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值.
\ No newline at end of file
+使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值.
diff --git a/docs/zh-Hans/Distributed-Event-Bus-RabbitMQ-Integration.md b/docs/zh-Hans/Distributed-Event-Bus-RabbitMQ-Integration.md
index ab7cb68bd7..1cd6b5b9dd 100644
--- a/docs/zh-Hans/Distributed-Event-Bus-RabbitMQ-Integration.md
+++ b/docs/zh-Hans/Distributed-Event-Bus-RabbitMQ-Integration.md
@@ -1,10 +1,10 @@
# 分布式事件总线RabbitMQ集成
-> 本文解释了**如何配置[RabbitMQ](https://www.rabbitmq.com/)**做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统.
+> 本文解释了 **如何配置[RabbitMQ](https://www.rabbitmq.com/)** 做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统.
## 安装
-使用ABP CLI添加[Volo.Abp.EventBus.RabbitMQ[Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ)NuGet包到你的项目:
+使用ABP CLI添加[Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ)NuGet包到你的项目:
* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI),如果你还没有安装.
* 在你想要安装 `Volo.Abp.EventBus.RabbitMQ` 包的 `.csproj` 文件目录打开命令行(终端).
@@ -18,7 +18,7 @@
### `appsettings.json` 文件配置
-这是配置RabbitMQ设置最简单的方法. 它也非常强大,因为你可以使用[由AspNet Core支持的](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)的任何其他配置源(如环境变量).
+这是配置RabbitMQ设置最简单的方法. 它也非常强大,因为你可以使用[由AspNet Core支持](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)的任何其他配置源(如环境变量).
**示例:最小化配置与默认配置连接到本地的RabbitMQ服务器**
@@ -151,4 +151,4 @@ Configure(options =>
});
````
-使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值.
\ No newline at end of file
+使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值.
diff --git a/docs/zh-Hans/Distributed-Event-Bus-Rebus-Integration.md b/docs/zh-Hans/Distributed-Event-Bus-Rebus-Integration.md
index 409b935ace..64fce06d1b 100644
--- a/docs/zh-Hans/Distributed-Event-Bus-Rebus-Integration.md
+++ b/docs/zh-Hans/Distributed-Event-Bus-Rebus-Integration.md
@@ -1,10 +1,10 @@
# 分布式事件总线Rebus集成
-> 本文解释了**如何配置[Rebus](http://mookid.dk/category/rebus/)**做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统.
+> 本文解释了 **如何配置[Rebus](http://mookid.dk/category/rebus/)** 做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统.
## 安装
-使用ABP CLI添加[Volo.Abp.EventBus.Rebus[Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus)NuGet包到你的项目:
+使用ABP CLI添加[Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus)NuGet包到你的项目:
* 安装[ABP CLI](https://docs.abp.io/en/abp/latest/CLI),如果你还没有安装.
* 在你想要安装 `Volo.Abp.EventBus.Rebus` 包的 `.csproj` 文件目录打开命令行(终端).
diff --git a/docs/zh-Hans/Distributed-Event-Bus.md b/docs/zh-Hans/Distributed-Event-Bus.md
index f16aa0610f..e59fa5e726 100644
--- a/docs/zh-Hans/Distributed-Event-Bus.md
+++ b/docs/zh-Hans/Distributed-Event-Bus.md
@@ -264,7 +264,7 @@ Configure(options =>
因此可以实现 `IDistributedEventHandler>` 订阅事件. 但是订阅这样的通用事件不是一个好方法,你可以为实体类型定义对应的ETO.
-**示例: 为 `Product` 声明使用 `ProductDto`**
+**示例: 为 `Product` 声明使用 `ProductEto`**
````csharp
Configure(options =>
diff --git a/docs/zh-Hans/Modules/Background-Jobs.md b/docs/zh-Hans/Modules/Background-Jobs.md
index b183febd80..fe021f1a56 100644
--- a/docs/zh-Hans/Modules/Background-Jobs.md
+++ b/docs/zh-Hans/Modules/Background-Jobs.md
@@ -1,3 +1,55 @@
-# Background Jobs Module
+# 后台作业模块
-待添加
\ No newline at end of file
+后台作业模块实现了 `IBackgroundJobStore` 接口,并且可以使用ABP框架的默认后台作业管理.如果你不想使用这个模块,那么你需要自己实现 `IBackgroundJobStore` 接口.
+
+> 本文档仅介绍后台作业模块,该模块将后台作业持久化到数据库.有关后台作业系统的更多信息,请参阅[后台作业](../Background-Jobs.md)文档.
+
+## 如何使用
+
+当你使用ABP框架[创建一个新的解决方案](https://abp.io/get-started)时,这个模块是(作为NuGet/NPM包)预先安装的.你可以继续将其作为软件包使用并轻松获取更新,也可以将其源代码包含到解决方案中(请参阅 `get-source` [CLI](../CLI.md)命令)以开发自定义模块.
+
+### 源代码
+
+此模块的源代码可在[此处](https://github.com/abpframework/abp/tree/dev/modules/background-jobs)访问.源代码是由[MIT](https://choosealicense.com/licenses/mit/)授权的,所以你可以自由使用和定制它.
+
+## 内部结构
+
+### 领域层
+
+#### 聚合
+
+- `BackgroundJobRecord` (聚合根): 表示后台工作记录.
+
+#### 仓储
+
+为该模块定义了以下自定义仓储:
+
+- `IBackgroundJobRepository`
+
+### 数据库提供程序
+
+#### 通用
+
+##### 表/集合的前缀与架构
+
+默认情况下,所有表/集合都使用 `Abp` 前缀.如果需要更改表前缀或设置架构名称(如果数据库提供程序支持),请在 `BackgroundJobsDbProperties` 类上设置静态属性.
+
+##### 连接字符串
+
+此模块使用 `AbpBackgroundJobs` 作为连接字符串名称.如果不使用此名称定义连接字符串,它将返回 `Default` 连接字符串.有关详细信息,请参阅[连接字符串](https://docs.abp.io/en/abp/latest/Connection-Strings)文档.
+
+#### Entity Framework Core
+
+##### 表
+
+- **AbpBackgroundJobs**
+
+#### MongoDB
+
+##### 集合
+
+- **AbpBackgroundJobs**
+
+## 另请参阅
+
+* [后台作业系统](../Background-Jobs.md)
diff --git a/docs/zh-Hans/Specifications.md b/docs/zh-Hans/Specifications.md
index f927c80255..40a473dbc0 100644
--- a/docs/zh-Hans/Specifications.md
+++ b/docs/zh-Hans/Specifications.md
@@ -1,3 +1,257 @@
## 规约
-TODO..
\ No newline at end of file
+规约模式用于为实体和其他业务对象定义 **命名、可复用、可组合和可测试的过滤器** .
+
+> 规约是领域层的一部分.
+
+## 安装
+
+> 这个包 **已经安装** 在启动模板中.所以,大多数时候你不需要手动去安装.
+
+添加 [Volo.Abp.Specifications](https://abp.io/package-detail/Volo.Abp.Specifications) 包到你的项目. 如果当前文件夹是你的项目的根目录(`.csproj`)时,你可以在命令行终端中使用 [ABP CLI](CLI.md) *add package* 命令:
+
+````bash
+abp add-package Volo.Abp.Specifications
+````
+
+## 定义规约
+
+假设你定义了如下的顾客实体:
+
+````csharp
+using System;
+using Volo.Abp.Domain.Entities;
+
+namespace MyProject
+{
+ public class Customer : AggregateRoot
+ {
+ public string Name { get; set; }
+
+ public byte Age { get; set; }
+
+ public long Balance { get; set; }
+
+ public string Location { get; set; }
+ }
+}
+````
+
+你可以创建一个由 `Specification` 派生的新规约类.
+
+**例如:规定选择一个18岁以上的顾客**
+
+````csharp
+using System;
+using System.Linq.Expressions;
+using Volo.Abp.Specifications;
+
+namespace MyProject
+{
+ public class Age18PlusCustomerSpecification : Specification
+ {
+ public override Expression> ToExpression()
+ {
+ return c => c.Age >= 18;
+ }
+ }
+}
+````
+
+你只需通过定义一个lambda[表达式](https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/lambda-expressions)来定义规约.
+
+> 你也可以直接实现`ISpecification`接口,但是基类`Specification`做了大量简化.
+
+## 使用规约
+
+这里有两种常见的规约用例.
+
+### IsSatisfiedBy
+
+`IsSatisfiedBy` 方法可以用于检查单个对象是否满足规约.
+
+**例如:如果顾客不满足年龄规定,则抛出异常**
+
+````csharp
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace MyProject
+{
+ public class CustomerService : ITransientDependency
+ {
+ public async Task BuyAlcohol(Customer customer)
+ {
+ if (!new Age18PlusCustomerSpecification().IsSatisfiedBy(customer))
+ {
+ throw new Exception(
+ "这位顾客不满足年龄规定!"
+ );
+ }
+
+ //TODO...
+ }
+ }
+}
+````
+
+### ToExpression & Repositories
+
+`ToExpression()` 方法可用于将规约转化为表达式.通过这种方式,你可以使用规约在**数据库查询时过滤实体**.
+
+````csharp
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Repositories;
+using Volo.Abp.Domain.Services;
+
+namespace MyProject
+{
+ public class CustomerManager : DomainService, ITransientDependency
+ {
+ private readonly IRepository _customerRepository;
+
+ public CustomerManager(IRepository customerRepository)
+ {
+ _customerRepository = customerRepository;
+ }
+
+ public async Task> GetCustomersCanBuyAlcohol()
+ {
+ var queryable = await _customerRepository.GetQueryableAsync();
+ var query = queryable.Where(
+ new Age18PlusCustomerSpecification().ToExpression()
+ );
+
+ return await AsyncExecuter.ToListAsync(query);
+ }
+ }
+}
+````
+
+> 规约被正确地转换为SQL/数据库查询语句,并且在DBMS端高效执行.虽然它与规约无关,但如果你想了解有关 `AsyncExecuter` 的更多信息,请参阅[仓储](Repositories.md)文档.
+
+实际上,没有必要使用 `ToExpression()` 方法,因为规约会自动转换为表达式.这也会起作用:
+
+````csharp
+var queryable = await _customerRepository.GetQueryableAsync();
+var query = queryable.Where(
+ new Age18PlusCustomerSpecification()
+);
+````
+
+## 编写规约
+
+规约有一个强大的功能是,它们可以与`And`、`Or`、`Not`以及`AndNot`扩展方法组合使用.
+
+假设你有另一个规约,定义如下:
+
+```csharp
+using System;
+using System.Linq.Expressions;
+using Volo.Abp.Specifications;
+
+namespace MyProject
+{
+ public class PremiumCustomerSpecification : Specification
+ {
+ public override Expression> ToExpression()
+ {
+ return (customer) => (customer.Balance >= 100000);
+ }
+ }
+}
+```
+
+你可以将 `PremiumCustomerSpecification` 和 `Age18PlusCustomerSpecification` 结合起来,查询优质成人顾客的数量,如下所示:
+
+````csharp
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Repositories;
+using Volo.Abp.Domain.Services;
+using Volo.Abp.Specifications;
+
+namespace MyProject
+{
+ public class CustomerManager : DomainService, ITransientDependency
+ {
+ private readonly IRepository _customerRepository;
+
+ public CustomerManager(IRepository customerRepository)
+ {
+ _customerRepository = customerRepository;
+ }
+
+ public async Task GetAdultPremiumCustomerCountAsync()
+ {
+ return await _customerRepository.CountAsync(
+ new Age18PlusCustomerSpecification()
+ .And(new PremiumCustomerSpecification()).ToExpression()
+ );
+ }
+ }
+}
+````
+
+如果你想让这个组合成为一个可复用的规约,你可以创建这样一个组合的规约类,它派生自`AndSpecification`:
+
+````csharp
+using Volo.Abp.Specifications;
+
+namespace MyProject
+{
+ public class AdultPremiumCustomerSpecification : AndSpecification
+ {
+ public AdultPremiumCustomerSpecification()
+ : base(new Age18PlusCustomerSpecification(),
+ new PremiumCustomerSpecification())
+ {
+ }
+ }
+}
+````
+
+现在,你就可以向下面一样重新编写 `GetAdultPremiumCustomerCountAsync` 方法:
+
+````csharp
+public async Task GetAdultPremiumCustomerCountAsync()
+{
+ return await _customerRepository.CountAsync(
+ new AdultPremiumCustomerSpecification()
+ );
+}
+````
+
+> 你可以从这些例子中看到规约的强大之处.如果你之后想要更改 `PremiumCustomerSpecification` ,比如将余额从 `100.000` 修改为 `200.000` ,所有查询语句和合并的规约都将受到本次更改的影响.这是减少代码重复的好方法!
+
+## 讨论
+
+虽然规约模式通常与C#的lambda表达式相比较,算是一种更老的方式.一些开发人员可能认为不再需要它,我们可以直接将表达式传入到仓储或领域服务中,如下所示:
+
+````csharp
+var count = await _customerRepository.CountAsync(c => c.Balance > 100000 && c.Age => 18);
+````
+
+自从ABP的[仓储](Repositories.md)支持表达式,这是一个完全有效的用法.你不必在应用程序中定义或使用任何规约,可以直接使用表达式.
+
+所以,规约的意义是什么?为什么或者应该在什么时候考虑去使用它?
+
+### 何时使用?
+
+使用规约的一些好处:
+
+- **可复用**:假设你在代码库的许多地方都需要用到优质顾客过滤器.如果使用表达式而不创建规约,那么如果以后更改“优质顾客”的定义会发生什么?假设你想将最低余额从100000美元更改为250000美元,并添加另一个条件,成为顾客超过3年.如果使用了规约,只需修改一个类.如果在任何其他地方重复(复制/粘贴)相同的表达式,则需要更改所有的表达式.
+- **可组合**:可以组合多个规约来创建新规约.这是另一种可复用性.
+- **命名**:`PremiumCustomerSpecification` 更好地解释了为什么使用规约,而不是复杂的表达式.因此,如果在你的业务中使用了一个有意义的表达式,请考虑使用规约.
+- **可测试**:规约是一个单独(且易于)测试的对象.
+
+### 什么时侯不要使用?
+
+- **没有业务含义的表达式**:不要对与业务无关的表达式和操作使用规约.
+- **报表**:如果只是创建报表,不要创建规约,而是直接使用 `IQueryable` 和LINQ表达式.你甚至可以使用普通SQL、视图或其他工具生成报表.DDD不关心报表,因此从性能角度来看,查询底层数据存储的方式可能很重要.
diff --git a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs
index 5f9d9eacfd..d616e1264f 100644
--- a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs
+++ b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs
@@ -47,7 +47,7 @@ public class ConnectionPool : IConnectionPool, ISingletonDependency
connectionName, new Lazy(() =>
{
var config = _options.Connections.GetOrDefault(connectionName);
- return new ServiceBusAdministrationClient(config.ConnectionString);
+ return new ServiceBusAdministrationClient(config.ConnectionString, config.Admin);
})
).Value;
}
diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpAzureEventBusOptions.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpAzureEventBusOptions.cs
index a6aa2f9857..57fa4b7f48 100644
--- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpAzureEventBusOptions.cs
+++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpAzureEventBusOptions.cs
@@ -7,4 +7,6 @@ public class AbpAzureEventBusOptions
public string SubscriberName { get; set; }
public string TopicName { get; set; }
+
+ public bool IsServiceBusDisabled { get; set; }
}
diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpEventBusAzureModule.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpEventBusAzureModule.cs
index 64c8b2fcd3..129004c871 100644
--- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpEventBusAzureModule.cs
+++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpEventBusAzureModule.cs
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using Volo.Abp.AzureServiceBus;
using Volo.Abp.Modularity;
@@ -19,9 +20,15 @@ public class AbpEventBusAzureModule : AbpModule
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
- context
- .ServiceProvider
- .GetRequiredService()
- .Initialize();
+ var options = context.ServiceProvider.GetRequiredService>().Value;
+
+ if (!options.IsServiceBusDisabled)
+ {
+ context
+ .ServiceProvider
+ .GetRequiredService()
+ .Initialize();
+ }
+
}
}
diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs
index 457656291f..15506ecf70 100644
--- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs
+++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs
@@ -231,11 +231,16 @@ namespace Volo.Blogging.Posts
return url;
}
- private async Task SaveTags(ICollection newTags, Post post)
+ private async Task SaveTags(ICollection tags, Post post)
{
- await RemoveOldTags(newTags, post);
-
- await AddNewTags(newTags, post);
+ tags = tags
+ .Select(t => t.ToLowerInvariant())
+ .Distinct()
+ .ToList();
+
+ await RemoveOldTags(tags, post);
+
+ await AddNewTags(tags, post);
}
private async Task RemoveOldTags(ICollection newTags, Post post)
diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/abp.resourcemapping.js b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/abp.resourcemapping.js
index 82b2d4b649..26cdc6aad9 100644
--- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/abp.resourcemapping.js
+++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/abp.resourcemapping.js
@@ -7,6 +7,5 @@ module.exports = {
"@libs"
],
mappings: {
-
}
}
\ No newline at end of file
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/Index.cshtml b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/Index.cshtml
index b67a147baa..615ef31477 100644
--- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/Index.cshtml
+++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/Index.cshtml
@@ -2,6 +2,7 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Layout
+@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Codemirror
@using Volo.CmsKit.Admin.Web.Pages.CmsKit.GlobalResources
@using Volo.CmsKit.Admin.Web.Menus
@using Volo.CmsKit.Localization
@@ -17,8 +18,15 @@
PageLayout.Content.MenuItemName = CmsKitAdminMenus.GlobalResources.GlobalResourcesMenu;
}
+@section styles{
+
+}
+
@section scripts {
+
+
+
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/index.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/index.js
index 72fb11e8dc..c58447b22d 100644
--- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/index.js
+++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/index.js
@@ -3,11 +3,26 @@ $(function (){
var service = volo.cmsKit.admin.globalResources.globalResourceAdmin;
+ var scriptEditor = CodeMirror.fromTextArea(document.getElementById("ScriptContent"),{
+ mode:"javascript",
+ lineNumbers:true
+ });
+
+ var styleEditor = CodeMirror.fromTextArea(document.getElementById("StyleContent"),{
+ mode:"css",
+ lineNumbers:true
+ });
+
+ $('.nav-tabs a').on('shown.bs.tab', function() {
+ scriptEditor.refresh();
+ styleEditor.refresh();
+ });
+
$('#SaveResourcesButton').on('click','',function(){
service.setGlobalResources(
{
- style: $('#StyleContent').val(),
- script: $('#ScriptContent').val()
+ style: styleEditor.getValue(),
+ script: scriptEditor.getValue()
}
).then(function () {
abp.message.success(l("SavedSuccessfully"));
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Create.cshtml b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Create.cshtml
index 841576e761..4671cb2ca2 100644
--- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Create.cshtml
+++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Create.cshtml
@@ -1,6 +1,7 @@
@page
@using System.Globalization
+@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Codemirror
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Uppy
@using Volo.CmsKit.Admin.Web.Pages
@@ -22,6 +23,9 @@
+
+
+
@@ -31,6 +35,7 @@
@section styles {
+
}
@@ -56,11 +61,11 @@
-
+
-
+
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Update.cshtml b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Update.cshtml
index 1271502946..499ad1b633 100644
--- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Update.cshtml
+++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Update.cshtml
@@ -1,6 +1,7 @@
@page "{Id}"
@using System.Globalization
+@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Codemirror
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Uppy
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Slugify
@@ -23,6 +24,9 @@
+
+
+
@@ -32,6 +36,7 @@
@section styles {
+
}
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/create.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/create.js
index ffe37fe5a8..1de6021a7d 100644
--- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/create.js
+++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/create.js
@@ -6,22 +6,40 @@ $(function () {
var $slug = $('#ViewModel_Slug');
var $buttonSubmit = $('#button-page-create');
+ var scriptEditor = CodeMirror.fromTextArea(document.getElementById("ViewModel_Script"), {
+ mode: "javascript",
+ lineNumbers: true
+ });
+
+ var styleEditor = CodeMirror.fromTextArea(document.getElementById("ViewModel_Style"), {
+ mode: "css",
+ lineNumbers: true
+ });
+
+ $('.nav-tabs a').on('shown.bs.tab', function () {
+ scriptEditor.refresh();
+ styleEditor.refresh();
+ });
+
$createForm.data('validator').settings.ignore = ":hidden, [contenteditable='true']:not([name]), .tui-popup-wrapper";
$createForm.on('submit', function (e) {
e.preventDefault();
-
+
if ($createForm.valid()) {
abp.ui.setBusy();
+ $("#ViewModel_Style").val(styleEditor.getValue());
+ $("#ViewModel_Script").val(scriptEditor.getValue());
+
$createForm.ajaxSubmit({
success: function (result) {
abp.notify.success(l('SuccessfullySaved'));
abp.ui.clearBusy();
location.href = "../Pages";
},
- error: function(result){
+ error: function (result) {
abp.ui.clearBusy();
abp.notify.error(result.responseJSON.error.message);
}
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/update.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/update.js
index 99af38b70c..6bd90dd934 100644
--- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/update.js
+++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/update.js
@@ -7,6 +7,21 @@ $(function () {
$formUpdate.data('validator').settings.ignore = ":hidden, [contenteditable='true']:not([name]), .tui-popup-wrapper";
+ var scriptEditor = CodeMirror.fromTextArea(document.getElementById("ViewModel_Script"), {
+ mode: "javascript",
+ lineNumbers: true
+ });
+
+ var styleEditor = CodeMirror.fromTextArea(document.getElementById("ViewModel_Style"), {
+ mode: "css",
+ lineNumbers: true
+ });
+
+ $('.nav-tabs a').on('shown.bs.tab', function () {
+ scriptEditor.refresh();
+ styleEditor.refresh();
+ });
+
$formUpdate.on('submit', function (e) {
e.preventDefault();
@@ -14,6 +29,9 @@ $(function () {
abp.ui.setBusy();
+ $("#ViewModel_Style").val(styleEditor.getValue());
+ $("#ViewModel_Script").val(scriptEditor.getValue());
+
$formUpdate.ajaxSubmit({
success: function (result) {
abp.notify.success(l('SuccessfullySaved'));
diff --git a/npm/packs/cms-kit.admin/package.json b/npm/packs/cms-kit.admin/package.json
index 528bdb3ac0..524583a187 100644
--- a/npm/packs/cms-kit.admin/package.json
+++ b/npm/packs/cms-kit.admin/package.json
@@ -8,7 +8,8 @@
"@abp/jstree": "~5.2.0-rc.1",
"@abp/slugify": "~5.2.0-rc.1",
"@abp/tui-editor": "~5.2.0-rc.1",
- "@abp/uppy": "~5.2.0-rc.1"
+ "@abp/uppy": "~5.2.0-rc.1",
+ "@abp/codemirror": "~5.2.0-rc.1"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431"
}
diff --git a/npm/packs/codemirror/abp.resourcemapping.js b/npm/packs/codemirror/abp.resourcemapping.js
index 71731c389b..d7f349b54a 100644
--- a/npm/packs/codemirror/abp.resourcemapping.js
+++ b/npm/packs/codemirror/abp.resourcemapping.js
@@ -1,5 +1,8 @@
module.exports = {
mappings: {
- "@node_modules/codemirror/lib/*.*": "@libs/codemirror/"
+ "@node_modules/codemirror/lib/*.*": "@libs/codemirror/",
+ "@node_modules/codemirror/mode/**/*.*": "@libs/codemirror/mode/",
+ "@node_modules/codemirror/theme/**/*.*": "@libs/codemirror/theme/",
+ "@node_modules/codemirror/addon/**/*.*": "@libs/codemirror/addon/"
}
}
\ No newline at end of file
diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor
new file mode 100644
index 0000000000..fe27745143
--- /dev/null
+++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor
@@ -0,0 +1,13 @@
+@using System.Net.Http
+@using Microsoft.AspNetCore.Authorization
+@using Microsoft.AspNetCore.Components.Authorization
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.JSInterop
+@using MyCompanyName.MyProjectName.Blazor.Server
+@using Blazorise
+@using Blazorise.DataGrid
+@using Volo.Abp.BlazoriseUI
+@using Volo.Abp.BlazoriseUI.Components
\ No newline at end of file