Browse Source

Merge branch 'dev' into auto-merge/rel-5-2/939

pull/12006/head
albert 4 years ago
committed by GitHub
parent
commit
4b4cc08bc7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      README.md
  2. 13
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  3. 18
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  4. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  5. 2
      common.props
  6. 259
      docs/zh-Hans/Caching.md
  7. 8
      docs/zh-Hans/Distributed-Event-Bus-Kafka-Integration.md
  8. 8
      docs/zh-Hans/Distributed-Event-Bus-RabbitMQ-Integration.md
  9. 4
      docs/zh-Hans/Distributed-Event-Bus-Rebus-Integration.md
  10. 2
      docs/zh-Hans/Distributed-Event-Bus.md
  11. 56
      docs/zh-Hans/Modules/Background-Jobs.md
  12. 256
      docs/zh-Hans/Specifications.md
  13. 2
      framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs
  14. 2
      framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpAzureEventBusOptions.cs
  15. 15
      framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AbpEventBusAzureModule.cs
  16. 13
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs
  17. 1
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/abp.resourcemapping.js
  18. 8
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/Index.cshtml
  19. 19
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/GlobalResources/index.js
  20. 9
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Create.cshtml
  21. 5
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Update.cshtml
  22. 22
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/create.js
  23. 18
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/update.js
  24. 3
      npm/packs/cms-kit.admin/package.json
  25. 5
      npm/packs/codemirror/abp.resourcemapping.js
  26. 13
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor

4
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.

13
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",

18
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 <a class=\"text-white fw-6 text-decoration-underline opacity-50\" href=\"https://commercial.abp.io/Privacy\">Privacy Policy</a>.",
"ABPIO-Common": "ABPIO-Common",
"WouldLikeToReceiveNotification": "I would like to receive the latest news from abp.io websites.",
"CommercialNewsletterConfirmationMessage": "I agree to the <a class=\"text-white fw-6 text-decoration-underline opacity-50\" href=\"https://commercial.abp.io/TermsConditions\">Terms & Conditions</a> and <a class=\"text-white fw-6 text-decoration-underline opacity-50\" href=\"https://commercial.abp.io/Privacy\">Privacy Policy</a>.",
"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."
}
}

3
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?"
}
}

2
common.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>5.2.0-rc.2</Version>
<Version>5.3.0</Version>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>

259
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<TCacheItem>` 接口
### `IDistributedCache<TCacheItem>` 接口
ABP框架在[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching/)包定义了通用的泛型 `IDistributedCache<TCacheItem>` 接口. `TCacheItem` 是存储在缓存中的对象类型.
`IDistributedCache<TCacheItem>` 接口了上述中的问题;
`IDistributedCache<TCacheItem>` 接口解决了上述中的问题;
* 它在内部 **序列化/反序列化** 缓存对象. 默认使用 **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<BookCacheItem>` 服务用于 get/set `BookCacheItem` 对象.
使用示例:
````csharp
public class BookService : ITransientDependency
{
private readonly IDistributedCache<BookCacheItem> _cache;
public BookService(IDistributedCache<BookCacheItem> cache)
{
_cache = cache;
}
public async Task<BookCacheItem> 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<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
namespace MyProject
{
public class BookService : ITransientDependency
{
//TODO: get from database
private readonly IDistributedCache<BookCacheItem> _cache;
public BookService(IDistributedCache<BookCacheItem> cache)
{
_cache = cache;
}
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId.ToString(), //缓存键
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
{
//TODO: 从数据库获取数据
}
}
}
````
* 示例服务代码中的 `GetOrAddAsync()` 方法从缓存中获取图书项.
* 示例服务代码中的 `GetOrAddAsync()` 方法从缓存中获取图书项. `GetOrAddAsync`是ABP框架在 ASP.NET Core 分布式缓存方法中添增的附加方法.
* 如果没有在缓存中找到图书,它会调用工厂方法 (本示例中是 `GetBookFromDatabaseAsync`)从原始数据源中获取图书项.
* `GetOrAddAsync` 有一个可选参数 `DistributedCacheEntryOptions` , 可用于设置缓存的生命周期.
`IDistributedCache<BookCacheItem>` 的其他方法与ASP.NET Core的`IDistributedCache` 接口相同, 你可以参考 [ASP.NET Core文档](https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed).
`IDistributedCache<BookCacheItem>` 与ASP.NET Core的`IDistributedCache` 接口拥有相同的方法, 你可以参考 [ASP.NET Core文档](https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed).
## `IDistributedCache<TCacheItem, TCacheKey>` 接口
### `IDistributedCache<TCacheItem, TCacheKey>` 接口
`IDistributedCache<TCacheItem>` 接口默认了键是 `string` 类型 (如果你的键不是string类型需要进行手动类型转换). `IDistributedCache<TCacheItem, TCacheKey>` 将键的类型泛型化试图简化手动转换的操作.
`IDistributedCache<TCacheItem>` 接口默认了**缓存**`string` 类型 (如果你的键不是string类型需要进行手动类型转换). 但当缓存键的类型不是`string`时, 可以使用`IDistributedCache<TCacheItem, TCacheKey>`.
### 使用示例
**示例: 在缓存中存储图书名称和价格**
示例缓存项
````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<BookCacheItem, Guid> _cache;
你可以注入 `IDistributedCache<BookCacheItem, Guid>` 服务用于 get/set `BookCacheItem` 对象.
public BookService(IDistributedCache<BookCacheItem, Guid> 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<BookCacheItem> 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<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
namespace MyProject
{
public class BookService : ITransientDependency
{
//TODO: get from database
private readonly IDistributedCache<BookCacheItem, Guid> _cache;
public BookService(IDistributedCache<BookCacheItem, Guid> cache)
{
_cache = cache;
}
public async Task<BookCacheItem> GetAsync(Guid bookId)
{
return await _cache.GetOrAddAsync(
bookId, //Guid类型作为缓存键
async () => await GetBookFromDatabaseAsync(bookId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
{
//TODO: 从数据库获取数据
}
}
}
````
* 示例服务中 `GetOrAddAsync()` 方法获取缓存的图书项.
* 我们采用了 `Guid` 做为键,在 `_cache_GetOrAddAsync()` 方法中传入 `Guid` 类型的bookid.
* 我们采用了 `Guid` 做为键, 在 `_cache_GetOrAddAsync()` 方法中传入 `Guid` 类型的bookid.
#### 复杂类型的缓存键
`IDistributedCache<TCacheItem, TCacheKey>` 在内部使用键对象的 `ToString()` 方法转换类型为string. 如果你的将复杂对象做为键,那么需要重写类的 `ToString` 方法.
`IDistributedCache<TCacheItem, TCacheKey>` 在内部使用键对象的 `ToString()` 方法转换类型为string. 如果你的将复杂对象做为缓存键,那么需要重写类的 `ToString` 方法.
示例:
举例一个作为缓存键的类:
````csharp
public class UserInOrganizationCacheKey
@ -187,23 +203,72 @@ public class BookService : ITransientDependency
{
_cache = cache;
}
...
}
````
## 配置
### AbpDistributedCacheOptions
`AbpDistributedCacheOptions` 是配置缓存的主要[Option类](Options.md).
**示例:为应用程序设置缓存键前缀**
```csharp
Configure<AbpDistributedCacheOptions>(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<TCacheItem>` (和 `IDistributedCache<TCacheItem, TCacheKey>`)方法都有一个可选的参数`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
* [Redis 缓存](Redis-Cache.md)

8
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<AbpKafkaOptions>(options =>
});
````
使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值.
使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值.

8
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<AbpRabbitMqEventBusOptions>(options =>
});
````
使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值.
使用这些选项类可以与 `appsettings.json` 组合在一起. 在代码中配置选项属性会覆盖配置文件中的值.

4
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` 文件目录打开命令行(终端).

2
docs/zh-Hans/Distributed-Event-Bus.md

@ -264,7 +264,7 @@ Configure<AbpDistributedEntityEventOptions>(options =>
因此可以实现 `IDistributedEventHandler<EntityUpdatedEto<EntityEto>>` 订阅事件. 但是订阅这样的通用事件不是一个好方法,你可以为实体类型定义对应的ETO.
**示例: 为 `Product` 声明使用 `ProductDto`**
**示例: 为 `Product` 声明使用 `ProductEto`**
````csharp
Configure<AbpDistributedEntityEventOptions>(options =>

56
docs/zh-Hans/Modules/Background-Jobs.md

@ -1,3 +1,55 @@
# Background Jobs Module
# 后台作业模块
待添加
后台作业模块实现了 `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)

256
docs/zh-Hans/Specifications.md

@ -1,3 +1,257 @@
## 规约
TODO..
规约模式用于为实体和其他业务对象定义 **命名、可复用、可组合和可测试的过滤器** .
> 规约是领域层的一部分.
## 安装
> 这个包 **已经安装** 在启动模板中.所以,大多数时候你不需要手动去安装.
添加 [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<Guid>
{
public string Name { get; set; }
public byte Age { get; set; }
public long Balance { get; set; }
public string Location { get; set; }
}
}
````
你可以创建一个由 `Specification<Customer>` 派生的新规约类.
**例如:规定选择一个18岁以上的顾客**
````csharp
using System;
using System.Linq.Expressions;
using Volo.Abp.Specifications;
namespace MyProject
{
public class Age18PlusCustomerSpecification : Specification<Customer>
{
public override Expression<Func<Customer, bool>> ToExpression()
{
return c => c.Age >= 18;
}
}
}
````
你只需通过定义一个lambda[表达式](https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/lambda-expressions)来定义规约.
> 你也可以直接实现`ISpecification<T>`接口,但是基类`Specification<T>`做了大量简化.
## 使用规约
这里有两种常见的规约用例.
### 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<Customer, Guid> _customerRepository;
public CustomerManager(IRepository<Customer, Guid> customerRepository)
{
_customerRepository = customerRepository;
}
public async Task<List<Customer>> 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<Customer>
{
public override Expression<Func<Customer, bool>> 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<Customer, Guid> _customerRepository;
public CustomerManager(IRepository<Customer, Guid> customerRepository)
{
_customerRepository = customerRepository;
}
public async Task<int> GetAdultPremiumCustomerCountAsync()
{
return await _customerRepository.CountAsync(
new Age18PlusCustomerSpecification()
.And(new PremiumCustomerSpecification()).ToExpression()
);
}
}
}
````
如果你想让这个组合成为一个可复用的规约,你可以创建这样一个组合的规约类,它派生自`AndSpecification`:
````csharp
using Volo.Abp.Specifications;
namespace MyProject
{
public class AdultPremiumCustomerSpecification : AndSpecification<Customer>
{
public AdultPremiumCustomerSpecification()
: base(new Age18PlusCustomerSpecification(),
new PremiumCustomerSpecification())
{
}
}
}
````
现在,你就可以向下面一样重新编写 `GetAdultPremiumCustomerCountAsync` 方法:
````csharp
public async Task<int> 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不关心报表,因此从性能角度来看,查询底层数据存储的方式可能很重要.

2
framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs

@ -47,7 +47,7 @@ public class ConnectionPool : IConnectionPool, ISingletonDependency
connectionName, new Lazy<ServiceBusAdministrationClient>(() =>
{
var config = _options.Connections.GetOrDefault(connectionName);
return new ServiceBusAdministrationClient(config.ConnectionString);
return new ServiceBusAdministrationClient(config.ConnectionString, config.Admin);
})
).Value;
}

2
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; }
}

15
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<AzureDistributedEventBus>()
.Initialize();
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpAzureEventBusOptions>>().Value;
if (!options.IsServiceBusDisabled)
{
context
.ServiceProvider
.GetRequiredService<AzureDistributedEventBus>()
.Initialize();
}
}
}

13
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<string> newTags, Post post)
private async Task SaveTags(ICollection<string> 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<string> newTags, Post post)

1
modules/cms-kit/host/Volo.CmsKit.Web.Unified/abp.resourcemapping.js

@ -7,6 +7,5 @@ module.exports = {
"@libs"
],
mappings: {
}
}

8
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{
<abp-style type="typeof(CodemirrorStyleContributor)" />
}
@section scripts {
<abp-script-bundle>
<abp-script type="typeof(CodemirrorScriptContributor )"/>
<abp-script src="/libs/codemirror/mode/css/css.js"/>
<abp-script src="/libs/codemirror/mode/javascript/javascript.js"/>
<abp-script src="/client-proxies/cms-kit-common-proxy.js"/>
<abp-script src="/client-proxies/cms-kit-admin-proxy.js"/>
<abp-script src="/Pages/CmsKit/GlobalResources/index.js"/>

19
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"));

9
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 @@
<abp-script type="typeof(TuiEditorScriptContributor)" />
<abp-script type="typeof(UppyScriptContributor)" />
<abp-script type="typeof(SlugifyScriptContributor)" />
<abp-script type="typeof(CodemirrorScriptContributor)"/>
<abp-script src="/libs/codemirror/mode/css/css.js"/>
<abp-script src="/libs/codemirror/mode/javascript/javascript.js"/>
<abp-script src="/client-proxies/cms-kit-common-proxy.js"/>
<abp-script src="/client-proxies/cms-kit-admin-proxy.js"/>
<abp-script src="/Pages/CmsKit/Pages/create.js" />
@ -31,6 +35,7 @@
@section styles {
<abp-style-bundle>
<abp-style type="typeof(TuiEditorStyleContributor)" />
<abp-style type="typeof(CodemirrorStyleContributor)" />
<abp-style src="/Pages/CmsKit/Pages/create.css"/>
</abp-style-bundle>
}
@ -56,11 +61,11 @@
</abp-tab>
<abp-tab title="@L["Script"]">
<abp-input asp-for="ViewModel.Script" suppress-label="true" class="cms-kit-editor" />
<abp-input asp-for="ViewModel.Script" suppress-label="true" />
</abp-tab>
<abp-tab title="@L["Style"]">
<abp-input asp-for="ViewModel.Style" suppress-label="true" class="cms-kit-editor"/>
<abp-input asp-for="ViewModel.Style" suppress-label="true"/>
</abp-tab>
</abp-tabs>

5
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 @@
<abp-script type="typeof(TuiEditorScriptContributor)" />
<abp-script type="typeof(UppyScriptContributor)" />
<abp-script type="typeof(SlugifyScriptContributor)" />
<abp-script type="typeof(CodemirrorScriptContributor)"/>
<abp-script src="/libs/codemirror/mode/css/css.js"/>
<abp-script src="/libs/codemirror/mode/javascript/javascript.js"/>
<abp-script src="/client-proxies/cms-kit-common-proxy.js"/>
<abp-script src="/client-proxies/cms-kit-admin-proxy.js"/>
<abp-script src="/Pages/CmsKit/Pages/update.js" />
@ -32,6 +36,7 @@
@section styles {
<abp-style-bundle>
<abp-style type="typeof(TuiEditorStyleContributor)" />
<abp-style type="typeof(CodemirrorStyleContributor)" />
<abp-style src="/Pages/CmsKit/Pages/update.css" />
</abp-style-bundle>
}

22
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);
}

18
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'));

3
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"
}

5
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/"
}
}

13
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
Loading…
Cancel
Save