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/en/Background-Workers-Hangfire.md b/docs/en/Background-Workers-Hangfire.md index 7f832f2c71..4cbaf8cc24 100644 --- a/docs/en/Background-Workers-Hangfire.md +++ b/docs/en/Background-Workers-Hangfire.md @@ -126,4 +126,4 @@ context.ServiceProvider So, it resolves the given background worker and adds to the `IBackgroundWorkerManager`. -While we generally add workers in OnApplicationInitialization, there are no restrictions on that. You can inject IBackgroundWorkerManager anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down. \ No newline at end of file +While we generally add workers in OnApplicationInitialization, there are no restrictions on that. You can inject IBackgroundWorkerManager anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down. diff --git a/docs/en/Caching.md b/docs/en/Caching.md index 13129bbc95..074d6d951f 100644 --- a/docs/en/Caching.md +++ b/docs/en/Caching.md @@ -258,7 +258,7 @@ ABP's distributed cache interfaces provide methods to perform batch methods thos Distributed cache service provides an interesting feature. Assume that you've updated the price of a book in the database, then set the new price to the cache, so you can use the cached value later. What if you have an exception after setting the cache and you **rollback the transaction** that updates the price of the book? In this case, cache value will be incorrect. -`IDistributedCache<..>` methods gets an optional parameter, named `considerOuw`, which is `false` by default. If you set it to `true`, then the changes you made for the cache are not actually applied to the real cache store, but associated with the current [unit of work](Unit-Of-Work.md). You get the value you set in the same unit of work, but the changes are applied **only if the current unit of work succeed**. +`IDistributedCache<..>` methods gets an optional parameter, named `considerUow`, which is `false` by default. If you set it to `true`, then the changes you made for the cache are not actually applied to the real cache store, but associated with the current [unit of work](Unit-Of-Work.md). You get the value you set in the same unit of work, but the changes are applied **only if the current unit of work succeed**. ### IDistributedCacheSerializer diff --git a/docs/zh-Hans/Caching.md b/docs/zh-Hans/Caching.md index a75199ce47..d8f064a24e 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<..>`方法提供一个可选参数, `considerUow`, 默认为`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/Domain-Services.md b/docs/zh-Hans/Domain-Services.md index 9c9bb7dfd2..a617aea645 100644 --- a/docs/zh-Hans/Domain-Services.md +++ b/docs/zh-Hans/Domain-Services.md @@ -1,3 +1,128 @@ -# ABP Documentation +# 领域服务 -待添加 +## 介绍 + +在 [领域驱动设计](Domain-Driven-Design.md) (DDD) 解决方案中,核心业务逻辑通常在聚合 ([实体](Entities.md)) 和领域服务中实现. 在以下情况下特别需要创建领域服务 + +* 你实现了依赖于某些服务(如存储库或其他外部服务)的核心域逻辑. +* 你需要实现的逻辑与多个聚合/实体相关,因此它不适合任何聚合. + +## ABP 领域服务基础设施 + +领域服务是简单的无状态类. 虽然你不必从任何服务或接口派生,但 ABP 框架提供了一些有用的基类和约定. + +### DomainService 和 IDomainService + +从 `DomainService` 基类派生领域服务或直接实现 `IDomainService` 接口. + +**示例: 创建从 `DomainService` 基类派生的领域服务.** + +````csharp +using Volo.Abp.Domain.Services; +namespace MyProject.Issues +{ + public class IssueManager : DomainService + { + + } +} +```` + +当你这样做时: + +* ABP 框架自动将类注册为瞬态生命周期到依赖注入系统. +* 你可以直接使用一些常用服务作为基础属性,而无需手动注入 (例如 [ILogger](Logging.md) and [IGuidGenerator](Guid-Generation.md)). + +> 建议使用 `Manager` 或 `Service` 后缀命名领域服务. 我们通常使用如上面示例中的 `Manager` 后缀. +**示例: 实现将问题分配给用户的领域逻辑** + +````csharp +public class IssueManager : DomainService +{ + private readonly IRepository _issueRepository; + public IssueManager(IRepository issueRepository) + { + _issueRepository = issueRepository; + } + + public async Task AssignAsync(Issue issue, AppUser user) + { + var currentIssueCount = await _issueRepository + .CountAsync(i => i.AssignedUserId == user.Id); + + //Implementing a core business validation + if (currentIssueCount >= 3) + { + throw new IssueAssignmentException(user.UserName); + } + issue.AssignedUserId = user.Id; + } +} +```` + +问题是定义如下所示的 [聚合根](Entities.md): + +````csharp +public class Issue : AggregateRoot +{ + public Guid? AssignedUserId { get; internal set; } + + //... +} +```` + +* 使用 `internal` 的 set 确保外层调用者不能直接在调用 set ,并强制始终使用 `IssueManager` 为 `User` 分配 `Issue`. + +### 使用领域服务 + +领域服务通常用于 [应用程序服务](Application-Services.md). + +**示例: 使用 `IssueManager` 将问题分配给用户** + +````csharp +using System; +using System.Threading.Tasks; +using MyProject.Users; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +namespace MyProject.Issues +{ + public class IssueAppService : ApplicationService, IIssueAppService + { + private readonly IssueManager _issueManager; + private readonly IRepository _userRepository; + private readonly IRepository _issueRepository; + public IssueAppService( + IssueManager issueManager, + IRepository userRepository, + IRepository issueRepository) + { + _issueManager = issueManager; + _userRepository = userRepository; + _issueRepository = issueRepository; + } + public async Task AssignAsync(Guid id, Guid userId) + { + var issue = await _issueRepository.GetAsync(id); + var user = await _userRepository.GetAsync(userId); + await _issueManager.AssignAsync(issue, user); + await _issueRepository.UpdateAsync(issue); + } + } +} +```` + +由于 `IssueAppService` 在应用层, 它不能直接将问题分配给用户.因此,它使用 `IssueManager`. + +## 应用程序服务与领域服务 + +虽然应用服务和领域服务都实现了业务规则,但存在根本的逻辑和形式差异; +虽然 [应用服务](Application-Services.md) 和领域服务都实现了业务规则,但存在根本的逻辑和形式差异: + +* 应用程序服务实现应用程序的 **用例** (典型 Web 应用程序中的用户交互), 而领域服务实现 **核心的、用例独立的领域逻辑**. +* 应用程序服务获取/返回 [数据传输对象](Data-Transfer-Objects.md), 领域服务方法通常获取和返回 **领域对象** ([实体](Entities.md), [值对象](Value-Objects.md)). +* 领域服务通常由应用程序服务或其他领域服务使用,而应用程序服务由表示层或客户端应用程序使用. + +## 生命周期 + +领域服务的生命周期是 [瞬态](https://docs.abp.io/en/abp/latest/Dependency-Injection) 的,它们会自动注册到依赖注入服务. 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.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj b/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj index 822189c677..a78b131962 100644 --- a/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj @@ -3,7 +3,7 @@ - netstandard2.0 + net6.0 Volo.Abp.BlobStoring.Minio Volo.Abp.BlobStoring.Minio $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; @@ -15,7 +15,7 @@ - + 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 95233c7c82..01ead7bc32 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.2", "@abp/slugify": "~5.2.0-rc.2", "@abp/tui-editor": "~5.2.0-rc.2", - "@abp/uppy": "~5.2.0-rc.2" + "@abp/uppy": "~5.2.0-rc.2", + "@abp/codemirror": "~5.2.0-rc.2" }, "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