Browse Source

Merge branch 'dev' of github.com:abpframework/abp into feat/4881

pull/5137/head
Arman Ozak 6 years ago
parent
commit
7422c469ab
  1. 1
      .github/workflows/angular.yml
  2. 26
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  3. 64
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json
  4. 38
      docs/en/Background-Jobs-Quartz.md
  5. 247
      docs/en/Blog-Posts/2020-08-20 v3_1_Release/Post.md
  6. BIN
      docs/en/Blog-Posts/2020-08-20 v3_1_Release/email-phone-verification.png
  7. BIN
      docs/en/Blog-Posts/2020-08-20 v3_1_Release/forgot-password.png
  8. BIN
      docs/en/Blog-Posts/2020-08-20 v3_1_Release/ldap-settings-ui.png
  9. BIN
      docs/en/Blog-Posts/2020-08-20 v3_1_Release/my-security-logs.png
  10. BIN
      docs/en/Blog-Posts/2020-08-20 v3_1_Release/reset-password.png
  11. BIN
      docs/en/Blog-Posts/2020-08-20 v3_1_Release/security-logs-ui.png
  12. BIN
      docs/en/Blog-Posts/2020-08-20 v3_1_Release/user-lock.png
  13. 36
      docs/en/CLI.md
  14. 270
      docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/Using-DevExtreme-In-ABP-Based-Application.md
  15. BIN
      docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/devexp-result.gif
  16. BIN
      docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/devextreme-js.png
  17. BIN
      docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/gulp.png
  18. BIN
      docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/initial-project.png
  19. BIN
      docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/wwwroot-lib.png
  20. 5
      docs/en/Customizing-Application-Modules-Extending-Entities.md
  21. 251
      docs/en/Emailing.md
  22. 7
      docs/en/Entity-Framework-Core-Migrations.md
  23. 7
      docs/en/Entity-Framework-Core.md
  24. 11
      docs/en/Exception-Handling.md
  25. 2
      docs/en/Getting-Started-React-Native.md
  26. 9
      docs/en/Getting-Started.md
  27. 3
      docs/en/Global-Features.md
  28. 48
      docs/en/MailKit.md
  29. 2
      docs/en/Module-Development-Basics.md
  30. 3
      docs/en/Module-Entity-Extensions.md
  31. 32
      docs/en/Nightly-Builds.md
  32. 40
      docs/en/Previews.md
  33. 6
      docs/en/Repositories.md
  34. 19
      docs/en/Startup-Templates/Application.md
  35. 106
      docs/en/Text-Templating.md
  36. 4
      docs/en/Timing.md
  37. 4
      docs/en/UI/Angular/Multi-Tenancy.md
  38. 25
      docs/en/UI/Angular/Permission-Management.md
  39. 2
      docs/en/UI/AspNetCore/Customization-User-Interface.md
  40. 133
      docs/en/Virtual-File-System.md
  41. 17
      docs/en/docs-nav.json
  42. BIN
      docs/en/images/replace-email-layout.png
  43. BIN
      docs/en/images/solution-files-non-mvc.png
  44. 38
      docs/zh-Hans/Background-Jobs-Quartz.md
  45. 4
      docs/zh-Hans/CLI.md
  46. 11
      docs/zh-Hans/Exception-Handling.md
  47. 4
      docs/zh-Hans/Getting-Started-React-Native.md
  48. 9
      docs/zh-Hans/Getting-Started.md
  49. 16
      docs/zh-Hans/Startup-Templates/Application.md
  50. 14
      framework/Volo.Abp.sln
  51. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.csproj
  52. 15
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/AbpMvcUiOptions.cs
  53. 1
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
  54. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  55. 5
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs
  56. 74
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  57. 47
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/GlobalFeatureActionFilter.cs
  58. 52
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/GlobalFeaturePageFilter.cs
  59. 51
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs
  60. 7
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingOptions.cs
  61. 9
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs
  62. 3
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs
  63. 0
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogScope.cs
  64. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  65. 13
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs
  66. 14
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationAdder.cs
  67. 31
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
  68. 269
      framework/src/Volo.Abp.Core/System/Linq/PredicateOperator.cs
  69. 18
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs
  70. 6
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJobArgs.cs
  71. 22
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs
  72. 40
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/IEmailSender.cs
  73. 1
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Smtp/SmtpEmailSender.cs
  74. 2
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs
  75. 2
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs
  76. 3
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs
  77. 17
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs
  78. 3
      framework/src/Volo.Abp.GlobalFeatures/FodyWeavers.xml
  79. 30
      framework/src/Volo.Abp.GlobalFeatures/FodyWeavers.xsd
  80. 3
      framework/src/Volo.Abp.GlobalFeatures/Properties/AssemblyInfo.cs
  81. 21
      framework/src/Volo.Abp.GlobalFeatures/Volo.Abp.GlobalFeatures.csproj
  82. 9
      framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/AbpGlobalFeaturesModule.cs
  83. 51
      framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeature.cs
  84. 9
      framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureDictionary.cs
  85. 59
      framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureManager.cs
  86. 44
      framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureNameAttribute.cs
  87. 98
      framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalModuleFeatures.cs
  88. 16
      framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalModuleFeaturesDictionary.cs
  89. 28
      framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/RequiresGlobalFeatureAttribute.cs
  90. 4
      framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/tr.json
  91. 8
      framework/src/Volo.Abp.MailKit/Volo/Abp/MailKit/MailKitSmtpEmailSender.cs
  92. 4
      framework/src/Volo.Abp.Quartz/Volo.Abp.Quartz.csproj
  93. 41
      framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzJobFactory.cs
  94. 72
      framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs
  95. 4
      framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzOptions.cs
  96. 8
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj
  97. 13
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs
  98. 30
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/AbpAspNetCoreMvcTestFeatures.cs
  99. 18
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/DisabledGlobalFeatureTestController.cs
  100. 19
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/DisabledGlobalFeatureTestPage.cshtml

1
.github/workflows/angular.yml

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
paths: paths:
- 'npm/ng-packs/**' - 'npm/ng-packs/**'
- '!npm/ng-packs/scripts/**'
jobs: jobs:
build-test-lint: build-test-lint:
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04

26
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -23,7 +23,7 @@
"UrlContentNotFound": "Url content not found.", "UrlContentNotFound": "Url content not found.",
"Summary": "Summary", "Summary": "Summary",
"MostRead": "Most Read", "MostRead": "Most Read",
"LatestArticles": "Latest Articles", "Latest": "Latest",
"ContributeAbpCommunity": "Contribute to the ABP Community", "ContributeAbpCommunity": "Contribute to the ABP Community",
"SubmitYourArticle": "Submit Your Article", "SubmitYourArticle": "Submit Your Article",
"ContributionGuide": "Contribution Guide", "ContributionGuide": "Contribution Guide",
@ -36,6 +36,28 @@
"FeatureRequest": "Feature Request", "FeatureRequest": "Feature Request",
"CreateArticleTitleInfo": "Title of the article to be shown on the article list.", "CreateArticleTitleInfo": "Title of the article to be shown on the article list.",
"CreateArticleUrlInfo": "Original GitHub/External URL of the article.", "CreateArticleUrlInfo": "Original GitHub/External URL of the article.",
"CreateArticleSummaryInfo": "A short summary of the article to be shown on the article list." "CreateArticleSummaryInfo": "A short summary of the article to be shown on the article list.",
"CreateArticleCoverInfo": "For creating an effective article, add a cover photo. Only 16:9 aspect ratio pictures will be accepted!",
"ThisExtensionIsNotAllowed": "This extension is not allowed.",
"TheFileIsTooLarge": "The file is too large.",
"GoToTheArticle": "Go to the Article",
"Contribute": "Contribute",
"OverallProgress": "Overall Progress",
"Done": "Done",
"Open": "Open",
"Closed": "Closed",
"LatestQuestionOnThe": "Latest Question On The",
"Stackoverflow": "Stackoverflow",
"Votes": "votes",
"Answer": "Answer",
"Views": "views",
"Answered": "Answered",
"WaitingForYourAnswer": "Waiting for your answer",
"Asked": "asked",
"AllQuestions": "All Questions",
"NextVersion": "Next Version",
"MilestoneErrorMessage": "Could not get the current milestone details from Github.",
"QuestionItemErrorMessage": "Could not get the latest question details from Stackoverflow.",
"Oops": "Oops!"
} }
} }

64
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json

@ -0,0 +1,64 @@
{
"culture": "zh-Hans",
"texts": {
"Permission:CommunityArticle": "社区文章",
"Permission:Edit": "修改",
"Waiting": "等待中",
"Approved": "已批准",
"Rejected": "已拒绝",
"Wait": "等待",
"Approve": "批准",
"Reject": "拒绝",
"ReadArticle": "阅读文章",
"Status": "状态",
"ContentSource": "内容来源",
"Details": "详情",
"Url": "url",
"Title": "标题",
"CreationTime": "创建时间",
"Save": "保存",
"SameUrlAlreadyExist": "url已存在,如果你想要添加这篇文章,你需要更改url!",
"UrlIsNotValid": "Url无效.",
"UrlNotFound" : "Url未找到.",
"UrlContentNotFound": "Url内容未找到.",
"Summary": "摘要",
"MostRead": "阅读最多",
"Latest": "最新",
"ContributeAbpCommunity": "为ABP社区做贡献",
"SubmitYourArticle": "提交你的文章",
"ContributionGuide": "贡献指南",
"BugReport": "Bug报告",
"SeeAllArticles": "查看所有的文章",
"WelcomeToABPCommunity!": "欢迎来到ABP社区!",
"MyProfile": "我的资料",
"MyOrganizations": "我的组织",
"EmailNotValid": "请输入有效的电子邮箱地址.",
"FeatureRequest": "功能请求",
"CreateArticleTitleInfo": "文章标题显示在文章列表中.",
"CreateArticleUrlInfo": "文章的原始GitHub/外部URL.",
"CreateArticleSummaryInfo": "文章的简短摘要将显示在文章列表中.",
"CreateArticleCoverInfo": "为了创建有效的文章,请添加封面图. 仅支持16:9的图片!",
"ThisExtensionIsNotAllowed": "不允许此扩展名.",
"TheFileIsTooLarge": "文件过大.",
"GoToTheArticle": "转到文章",
"Contribute": "贡献",
"OverallProgress": "总体流程",
"Done": "完成",
"Open": "打开",
"Closed": "关闭",
"LatestQuestionOnThe": "有关的最新问题",
"Stackoverflow": "Stackoverflow",
"Votes": "票数",
"Answer": "回答",
"Views": "观看次数",
"Answered": "已回答",
"WaitingForYourAnswer": "等待你的回答",
"Asked": "提问",
"AllQuestions": "所有的问题",
"NextVersion": "下一个版本",
"MilestoneErrorMessage": "无法从Github获取当前的里程碑详细信息.",
"QuestionItemErrorMessage": "无法从Stackoverflow获取最新的问题详细信息.",
"Oops": "哎呀!"
}
}

38
docs/en/Background-Jobs-Quartz.md

@ -70,7 +70,43 @@ public class YourModule : AbpModule
} }
```` ````
Quartz stores job and scheduling information **in memory by default**. In the example, we use the pre-configuration of [options pattern](Options.md) to change it to the database. For more configuration of Quartz, please refer to the Quartz's [documentation](https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html). Starting from ABP 3.1 version, we have added `Configurator` to `AbpQuartzOptions` to configure Quartz. For example:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
PreConfigure<AbpQuartzOptions>(options =>
{
options.Configurator = configure =>
{
configure.UsePersistentStore(storeOptions =>
{
storeOptions.UseProperties = true;
storeOptions.UseJsonSerializer();
storeOptions.UseSqlServer(configuration.GetConnectionString("Quartz"));
storeOptions.UseClustering(c =>
{
c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20);
c.CheckinInterval = TimeSpan.FromSeconds(10);
});
});
};
});
}
}
````
> You can choose the way you favorite to configure Quaratz.
Quartz stores job and scheduling information **in memory by default**. In the example, we use the pre-configuration of [options pattern](Options.md) to change it to the database. For more configuration of Quartz, please refer to the Quartz's [documentation](https://www.quartz-scheduler.net/).
## Exception handling ## Exception handling

247
docs/en/Blog-Posts/2020-08-20 v3_1_Release/Post.md

@ -0,0 +1,247 @@
# ABP Framework v3.1 RC.1 Has Been Released
Today, we are releasing the **ABP Framework version 3.1 Release Candidate 1** (RC.1). The development cycle for this version was **~7 weeks**. It was the longest development cycle for a feature version release ever. We have completed **~150 issues**, merged **~150 PRs** and made **~1,000 commits** only in the main [abp repository](https://github.com/abpframework/abp). See the related [milestone](https://github.com/abpframework/abp/milestone/38?closed=1) on GitHub.
There were two main reasons of this long development cycle;
* We've switched to **4-weeks** release cycle (was discussed in [this issue](https://github.com/abpframework/abp/issues/4692)).
* We've [re-written](https://github.com/abpframework/abp/issues/4881) the Angular service proxy generation system using the Angular schematics to make it more stable. There were some problems with the previous implementation.
This long development cycle brings a lot of new features, improvements and bug fixes. I will highlight the fundamental features and changes in this blog post.
## About the Preview/Stable Version Cycle
As mentioned above, it is planned to release a new stable feature version (like 3.1, 3.2, 3.3...) in every 4-weeks.
In addition, we are starting to deploy a **preview version** 2-weeks before the stable versions for every feature/major releases.
Today, we've released `3.1.0-rc.1` as the first preview version. We may release more previews if it is needed until the stable 3.1.0 version.
**The stable `3.1.0` version will be released on September 3, 2020.** Next RC version, `3.2.0-rc.1`, is planned for September 17, 2020 (2 weeks after the stable 3.1.0 and 2 weeks before the stable 3.2.0).
We **won't add new features** to a version after publishing the preview version. We only will make **bug fixes** until the stable version. The new features being developed in this period will be available in the next version.
> We will use `-rc.x` suffix (like `3.1.0-rc.1` and `3.1.0-rc.2`) for preview releases. However, we may also publish with `-preview.x` suffix before RC (Release Candidate) releases, especially for major versions (like 4.x, 5.x...).
### About the Nightly Builds
Don't confuse preview versions vs nightly builds. When we say preview, we are mentioning the preview system explained above.
We will continue to publish **nightly builds** for all the [ABP Framework packages](https://abp.io/packages). Nightly pages are built from the development branch. You can refer to [this document](https://docs.abp.io/en/abp/latest/Nightly-Builds) to learn how to use the nightly packages.
## Get Started with the RC Versions
Please try the preview versions and provide feedback to us to release more stable versions. Please open an issue on the [GitHub repository](https://github.com/abpframework/abp/issues/new) if you find a bug or want to give feedback.
### Update the ABP CLI to the 3.1.0-rc.1
Since this is the first preview version, you need to upgrade the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) to the `3.1.0-rc.1` to be able to use the preview features:
````bash
dotnet tool update Volo.Abp.Cli -g --version 3.1.0-rc.1
````
### New Solutions
The [ABP.IO](https://abp.io/) platform and the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) are compatible with the RC system. You can select the "preview" option on the [download page](https://abp.io/get-started) or use the "**--preview**" parameter with the ABP CLI [new](https://docs.abp.io/en/abp/latest/CLI?_ga=2.106435654.411298747.1597771169-1910388957.1594128976#new) command:
````bash
abp new Acme.BookStore --preview
````
This command will create a new project with the latest RC/Preview version. Whenever the stable version is released, you can switch to the stable version for your solution using the `abp switch-to-stable` command in the root folder of your solution.
### Existing Solutions
If you already have a solution and want to use/test the latest RC/Preview version, use the `abp switch-to-preview` command in the root folder of your solution. You can return back to the latest stable using the `abp switch-to-stable ` command later.
> Note that the `abp switch-to-preview` command was being used to switch to nightly builds before the v3.1. Now, you should use the `abp switch-to-nightly` for [nightly builds](https://docs.abp.io/en/abp/latest/Nightly-Builds).
## What's New with the ABP Framework 3.1 RC.1
### Angular Service Proxies
ABP provides a system to generate Angular service proxies (with TypeScript) to consume the HTTP APIs of your application. Service proxy generation system **has been completely re-written** with the ABP Framework 3.1. The main goal was to build more stable and feature rich system that is better aligned with other ABP Framework features (like [modularity](https://docs.abp.io/en/abp/latest/Module-Development-Basics)).
[See the documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies) to learn more about the service proxy generation for Angular applications.
### Authorization Code Flow for the Angular UI
We were using the **resource owner password authentication** flow for the Angular UI login page. We've implemented **Authorization Code Flow** for the Angular account module and made it **default for new projects**. With this change, the Angular application now redirects to the login page of the MVC UI which was implemented using the Identity Server 4. We also removed the client secret from the Angular side with this change.
Old behavior remains exist. If you want to switch to the new flow (which is recommended), follow the steps below:
1) Add `authorization_code` to the `IdentityServerClientGrantTypes` table in the database, for the client used by the Angular UI (the `ClientId` is `YourProjectName_App` by default, in the `IdentityServerClients` table).
2) Add `http://localhost:4200` to `IdentityServerClientRedirectUris` and `IdentityServerClientPostLogoutRedirectUris` tables for the same client.
3) Set `RequireClientSecret` to `false` in the `IdentityServerClients` table for the same client.
> [ABP Commercial](https://commercial.abp.io/) users can make these changes on the [Identity Server Management UI](https://commercial.abp.io/modules/Volo.Identityserver.Ui).
4) Change the `oAuthConfig` section in the `src/environments/environment.ts` file of the Angular application.
You can take [this new configuration](https://gist.github.com/hikalkan/e7f6ae7f507b201783682dccaeadc5e3) as a reference. Main changes are;
* Added `responseType` as `code`.
* Added `redirectUri`
* Added `offline_access` to the `scope`.
* Removed `oidc: false` option.
* Removed the client secret option.
### Global Feature System
The new "Global Features" system allows to **enable/disable features of an application or a module** in a central point. It is especially useful if you want to use a module but don't want to bring all its features into your application. If the module was so designed, you can enable only the features you need.
When you disable a feature;
* The **database tables** related to that feature should not be created in the database.
* The **HTTP APIs** related to that feature should not be exposed. They returns 404 if they are directly requested.
So, the goal is that; when you disable a feature, it should behave like that feature doesn't exists in your system at all.
There is **no way to enable/disable a global feature on runtime**. You should decide it in the development time (remember, even database tables are not being created for disabled global features, so you can't enable it on runtime).
> "Global Features" system is different than [SaaS/multi-tenancy features](https://docs.abp.io/en/abp/latest/Features), where you can enable/disable features for your tenants on runtime.
Assume that you are using the [CMS Kit module](https://github.com/abpframework/abp/tree/dev/modules/cms-kit) (this module is in a very early stage) where you only want to enable the comment feature:
````csharp
GlobalFeatureManager.Instance.Modules.CmsKit().Comments.Enable();
````
You can check if a feature was enabled:
```csharp
GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>();
```
Or you can add `[RequiresGlobalFeature(...)]` attribute to a controller/page to disable it if the related feature was disabled:
```csharp
//...
[RequiresGlobalFeature(typeof(CommentsFeature))]
public class CommentController : AbpController
{
//...
}
```
See the issue [#5061](https://github.com/abpframework/abp/issues/5061) until this is fully documented.
### Social/External Logins
Implemented the infrastructure for social/external logins in the account module. So, now you can easily configure your application to support social/external logins by [following the documentation](https://github.com/abpframework/abp/blob/dev/docs/en/Authentication/Social-External-Logins.md). Once you configure a provider, a button will appear on the login page to use this provider.
The social logins will work as expected even if you are using the Angular UI, since the Angular UI uses the MVC login using the authorization code flow implemented with this new version (as explained above).
### Forgot/Reset Password
Implemented forgot password / password reset for the account module.
You can now enter your email address to get an email containing a **password reset link**:
![forgot-password](forgot-password.png)
When you click to the link, you are redirected to a password reset page to determine your new password:
![reset-password](reset-password.png)
### External Login System
The standard Social/External Login system (like Facebook login) works via OpenID Connect. That means the user is redirected to the login provider, logins there and redirected to your application.
While this is pretty nice for most scenarios, sometimes you want a simpler external login mechanism: User enters username & password in your own application's login form and you check the username & password from another source, not from your own database.
ABP v3.1 introduces an External Login System to check username & password from any source (from an external database, a REST service or from an LDAP / Active Directory server).
You can check the [issue #4977](https://github.com/abpframework/abp/issues/4977#issuecomment-670006297) until it is fully documented.
We've implemented LDAP authentication for the ABP Commercial, using this new login extension system (see the ABP Commercial section below).
### User Security Logs
The new [Security Log System](https://github.com/abpframework/abp/issues/4492) (of the Identity module) automatically logs all authentication related operations (login, logout, change password...) to a `AbpSecurityLogs` table in the database.
### New BLOB Storage Providers
Implemented [AWS](https://github.com/abpframework/abp/blob/dev/docs/en/Blob-Storing-Aws.md) and [Aliyun](https://github.com/abpframework/abp/blob/dev/docs/en/Blob-Storing-Aliyun.md) providers for the [BLOB storing](https://docs.abp.io/en/abp/latest/Blob-Storing) system with this version.
### Other Features / Highlights
Here, some other highlights from this release;
* UOW level caching system [#4796](https://github.com/abpframework/abp/issues/4796)
* Refactored the console application template to better integrate to the host builder [#5006](https://github.com/abpframework/abp/issues/5006)
* [Volo.Abp.Ldap](https://www.nuget.org/packages/Volo.Abp.Ldap) package now supports multi-tenancy.
* Introduce `BasicAggregateRoot` base class [#4808](https://github.com/abpframework/abp/issues/4808)
* Sets GUID Id in the `InsertAsync` method of the EF Core repository if it was not set by the developer [#4634](https://github.com/abpframework/abp/pull/4634)
* Added `GetPagedListAsync` methods to the repository to simplify paging [#4617](https://github.com/abpframework/abp/pull/4617)
* Configured [Prettier](https://prettier.io/) for the startup template [#4318](https://github.com/abpframework/abp/issues/4318)
* Defined new layout hooks for the MVC UI: before page content & after page content [#4008](https://github.com/abpframework/abp/issues/4008)
* Allow to put static resources (js, css... files) under the Components folder for ASP.NET Core MVC UI.
* Upgraded to AutoMapper 10 and Quartz 3.1 for the related integration packages.
## What's New with the ABP Commercial v3.1 RC.1
### Security Logs UI
We've created a UI to report user security logs for authentication related operations, under the Identity Management menu:
![security-logs-ui](security-logs-ui.png)
Also, every user can see his/her own security logs by selecting the "My security logs" under the user menu:
![my-security-logs](my-security-logs.png)
### LDAP Authentication
We've implemented LDAP authentication using the new external login system explained above. Also, created a UI to configure the server settings:
![ldap-settings-ui](ldap-settings-ui.png)
In this way, you can simply check passwords of the users from LDAP in the login page. If given username / password doesn't exists on LDAP, then it fallbacks to the local database, just like before.
Since it supports **multi-tenancy**, you can enable, disable and configure it for your tenants.
### Email / Phone Number Verification
User profile management page now supports to Email & Phone Number verification flow:
![email-phone-verification](email-phone-verification.png)
When user clicks to the **verify** button, a verification email/SMS (that has a verification code) sent to the user and the UI waits to submit this code.
### User Lock
Implemented to **lock a user** for a given period of time. Locked users can not login to the application for the given period of time:
![user-lock](user-lock.png)
### ABP Suite: Angular UI Code Generation Revisited
Angular UI code generation has been re-written using the Angular Schematics for the ABP Suite. It is now more stable and produces a better application code.
ABP Suite also supports code generation on module development.
### Others
* **Social logins** and **authorization code flow** are also implemented for the ABP Commercial, just as described above.
* Added breadcrumb and file icons for the **file management module**.
## The ABP Community
We've lunched the [community.abp.io](https://community.abp.io/) ~two weeks ago with its initial version. It only has "Article submission" system for now. We are developing new exciting features. There will be an update in a few days and we'll publish a new blog post for it.
## Conclusion
The main goals of the 3.1 version were;
* Complete the missing **authentication features** (like social logins, LDAP authentication, authorization code flow for the Angular UI...) for the ABP Framework & ABP Commercial.
* Re-write a stable and feature complete **Angular service proxy generation** system for the ABP Framework and CRUD UI generation system for the ABP Commercial.
* Develop a system to lunch **preview versions** of the platform. `3.1.0-rc.1` is the first preview version that has been published with this new system.
* Complete the fundamental **documentation & tutorials** (we've even created a [video tutorial series](https://www.youtube.com/watch?v=cJzyIFfAlp8&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ)).
ABP.IO platform will be more mature & stable with the v3.1. Enjoy Coding!

BIN
docs/en/Blog-Posts/2020-08-20 v3_1_Release/email-phone-verification.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/en/Blog-Posts/2020-08-20 v3_1_Release/forgot-password.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
docs/en/Blog-Posts/2020-08-20 v3_1_Release/ldap-settings-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
docs/en/Blog-Posts/2020-08-20 v3_1_Release/my-security-logs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/Blog-Posts/2020-08-20 v3_1_Release/reset-password.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Blog-Posts/2020-08-20 v3_1_Release/security-logs-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

BIN
docs/en/Blog-Posts/2020-08-20 v3_1_Release/user-lock.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

36
docs/en/CLI.md

@ -26,7 +26,8 @@ Here, the list of all available commands before explaining their details:
* **`add-package`**: Adds an ABP package to a project. * **`add-package`**: Adds an ABP package to a project.
* **`add-module`**: Adds a [multi-package application module](https://docs.abp.io/en/abp/latest/Modules/Index) to a solution. * **`add-module`**: Adds a [multi-package application module](https://docs.abp.io/en/abp/latest/Modules/Index) to a solution.
* **`generate-proxy`**: Generates client side proxies to use HTTP API endpoints on the server. * **`generate-proxy`**: Generates client side proxies to use HTTP API endpoints on the server.
* **`switch-to-preview`**: Switches to the latest [nightly builds](Nightly-Builds.md) of the ABP related packages on a solution. * **`switch-to-preview`**: Switches to the latest preview version of the ABP Framework.
* **`switch-to-nightly`**: Switches to the latest [nightly builds](Nightly-Builds.md) of the ABP related packages on a solution.
* **`switch-to-stable`**: Switches to the latest stable versions of the ABP related packages on a solution. * **`switch-to-stable`**: Switches to the latest stable versions of the ABP related packages on a solution.
* **`translate`**: Simplifies to translate localization files when you have multiple JSON [localization](Localization.md) files in a source control repository. * **`translate`**: Simplifies to translate localization files when you have multiple JSON [localization](Localization.md) files in a source control repository.
* **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password. * **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password.
@ -79,8 +80,7 @@ abp new Acme.BookStore
* `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side. * `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
* `none`: Without UI. There are some additional options for this template: * `none`: Without UI. There are some additional options for this template:
* `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side. * `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
* `--mobile` or `-m`: Specifies the mobile application framework. Default framework is `react-native`. Available frameworks: * `--mobile` or `-m`: Specifies the mobile application framework. If not specified, no mobile application will be created. Available options:
* `none`: no mobile application.
* `react-native`: React Native. * `react-native`: React Native.
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core. * `ef`: Entity Framework Core.
@ -90,7 +90,7 @@ abp new Acme.BookStore
* **`console`**: [Console template](Startup-Templates/Console.md). * **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. * `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--preview`: Use latest pre-release version (Only if `--version ` is not specified and there is at least one pre-release after latest stable version). * `--preview`: Use latest preview version.
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D:\local-template` or `https://.../my-template-file.zip`). * `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D:\local-template` or `https://.../my-template-file.zip`).
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. * `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](Entity-Framework-Core-Other-DBMS.md) (after creating the solution). * `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](Entity-Framework-Core-Other-DBMS.md) (after creating the solution).
@ -111,7 +111,6 @@ abp update [options]
#### Options #### Options
* `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions.
* `--npm`: Only updates NPM packages. * `--npm`: Only updates NPM packages.
* `--nuget`: Only updates NuGet packages. * `--nuget`: Only updates NuGet packages.
* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default * `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default
@ -197,7 +196,22 @@ abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all
### switch-to-preview ### switch-to-preview
You can use this command to switch your project to latest **nightly** preview version of the ABP framework packages. You can use this command to switch your project to latest preview version of the ABP framework.
Usage:
````bash
abp switch-to-preview [options]
````
#### Options
`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### switch-to-nightly
You can use this command to switch your project to latest [nightly](Nightly-Builds.md) preview version of the ABP framework packages.
Usage: Usage:
@ -211,7 +225,7 @@ abp switch-to-nightly [options]
### switch-to-stable ### switch-to-stable
If you're using the ABP Framework preview packages, you can switch back to latest stable version using this command. If you're using the ABP Framework preview packages (including nightly previews), you can switch back to latest stable version using this command.
Usage: Usage:
@ -274,11 +288,13 @@ Then review changes on your source control system to be sure that it has changed
Some features of the CLI requires to be logged in to abp.io platform. To login with your username write: Some features of the CLI requires to be logged in to abp.io platform. To login with your username write:
```bash ```bash
abp login <username> # Asks password separately abp login <username> # Allows you to enter your password hidden
abp login <username> -p <password> # Specify the password as a parameter abp login <username> -p <password> # Specify the password as a parameter (password is visible)
abp login <username> --organization <organization> # If you have multiple organizations, you need set your active organization
abp login <username> -p <password> -o <organization> # You can enter both your password and organization in the same command
``` ```
> Using `-p` parameter might not be safe if someone is watching your screen :) It can be useful for automation purposes. > When using the -p parameter, be careful as your password will be visible. It's useful for CI/CD automation pipelines.
A new login with an already active session overwrites the previous session. A new login with an already active session overwrites the previous session.

270
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/Using-DevExtreme-In-ABP-Based-Application.md

@ -0,0 +1,270 @@
## Using DevExtreme with ABP Based Applications
Hi, in this step by step article, I will show you how to integrate DevExtreme components into ABP Framework based applications.
## Install DevExtreme
You can follow [this documentation](https://js.devexpress.com/Documentation/17_1/Guide/ASP.NET_MVC_Controls/Prerequisites_and_Installation/) to install devexpress packages.
## Preparing the Project
### Startup template and the initial run
ABP Framework offers startup templates to get into the business faster. We can download a new startup template using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI):
````bash
abp new DevExtremeSample
````
After the download is finished, open the solution in the Visual Studio (or your favorite IDE):
![initial-project](initial-project.png)
Run the `DevExtremeSample.DbMigrator` application to create the database and seed initial data (which creates the admin user, admin role, related permissions, etc). Then we can run the `DevExtremeSample.Web` project to see our application working.
> _Default admin username is **admin** and password is **1q2w3E\***_
### Adding DevExtreme NuGet Packages
Add the `DevExtreme.AspNet.Core` NuGet package to the `DevExtremeSample.Application.Contracts` project.
```
Install-Package DevExtreme.AspNet.Core
```
Add the `DevExtreme.AspNet.Data` package to your `DevExtremeSample.Web` project.
```
Install-Package DevExtreme.AspNet.Data
```
> Please remember that, you must add _"DevExpress NuGet Feed"_ to your **Nuget Package Sources**. Check [this documentation](https://js.devexpress.com/Documentation/17_1/Guide/ASP.NET_MVC_Controls/Prerequisites_and_Installation/) for more information.
### Adding DevExtreme NPM Depencies
Open your `DevExtremeSample.Web` project folder with a command line and add `devextreme` NPM package:
````bash
npm install devextreme
````
### Adding Resource Mappings
The `devextreme` NPM package is saved under `node_modules` folder. We need to move the needed files in our `wwwroot/libs` folder to use them in our web project. We can do it using the ABP [client side resource mapping](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Client-Side-Package-Management) system.
Open the `abp.resourcemapping.js` file in your `DevExtremeSample.Web` project and add the following definition to inside `mappings` object.
````json
"@node_modules/devextreme/dist/**/*": "@libs/devextreme/"
````
The final `abp.resourcemapping.js` file should look like below:
```
module.exports = {
aliases: {},
mappings: {
"@node_modules/devextreme/dist/**/*": "@libs/devextreme/",
},
};
```
Open your `DevExtremeSample.Web` project folder with a command line and run the `gulp` command. This command will copy the needed library files into the ``/wwwroot/libs/devextreme/` folder.
![gulp](gulp.png)
You can see `devextreme` folder inside the `wwwroot/libs`:
![wwwroot-lib](wwwroot-lib.png)
### Adding DevExtremeStyleContributor
We will add DevExtreme CSS files to the global bundle by creating a [bundle contributor](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification).
Create a `Bundling` folder in the `DevExtremeSample.Web` project and a `DevExtremeStyleContributor.cs` file with the following content:
```csharp
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace DevExtremeSample.Web.Bundling
{
public class DevExtremeStyleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/devextreme/css/dx.common.css");
context.Files.AddIfNotContains("/libs/devextreme/css/dx.light.css");
}
}
}
```
> You can choose another theme than the light theme. Check the `/libs/devextreme/css/` folder and the DevExtreme documentation for other themes.
Open your `DevExtremeSampleWebModule.cs` file in your `DevExtremeSample.Web` project and add following code into the `ConfigureServices` method:
```csharp
Configure<AbpBundlingOptions>(options =>
{
options
.StyleBundles
.Get(StandardBundles.Styles.Global)
.AddContributors(typeof(DevExtremeStyleContributor));
});
```
### Adding DevExtremeScriptContributor
We cannot add DevExtreme js packages to `Global Script Bundles`, because DevExtreme is using some inline javascript codes and js packages requires to located in the `<head>` section.
But we can create `ViewComponent` and render it at `<head>` section with `AbpLayoutHookOptions`.
First, create `DevExtremeScriptContributor.cs` file at your `Bundling` folder and copy following code block to inside it.
```csharp
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.Modularity;
namespace DevExtremeSample.Web.Bundling
{
[DependsOn(
typeof(JQueryScriptContributor)
)]
public class DevExtremeScriptContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/devextreme/js/dx.all.js");
context.Files.AddIfNotContains("/libs/devextreme/js/dx.aspnet.mvc.js");
context.Files.AddIfNotContains("/libs/devextreme/js/dx.aspnet.data.js");
}
}
}
```
As you see, the `DevExtremeScriptContributor` is depends on `JQueryScriptContributor`. All packages are `JQueryScriptContributor` will added before the `DevExtremeScriptContributor` packages.
#### Create DevExtremeJsViewComponent
Create `Components` folder in your `DevExtremeSample.Web` project. Then create `DevExtremeJs` folder under `Components` folder.
Create `Default.cshtml` file in your `DevExtremeJs` folder and paste following codes to your file.
```csharp
@using DevExtremeSample.Web.Bundling
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
<!-- Devextreme -->
<abp-script type="typeof(DevExtremeScriptContributor)" />
```
Create `DevExtremeJsViewComponent.cs` file in your `DevExtremeJs` folder and paste following codes to your file.
```csharp
namespace DevExtremeSample.Web.Components.DevExtremeJs
{
public class DevExtremeJsViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View("/Components/DevExtremeJs/Default.cshtml");
}
}
}
```
After that, your `*.Web` project should be like as following.
![devextreme-js](devextreme-js.png)
Then we can add this view component to `<head>` section by using **hooks**.
Open your `DevExtremeSampleWebModule.cs` file in your `DevExtremeSample.Web` project and add following code to `ConfigureServices` method.
```csharp
Configure<AbpLayoutHookOptions>(options =>
{
options.Add(
LayoutHooks.Head.Last, //The hook name
typeof(DevExtremeJsViewComponent) //The component to add
);
});
```
#### Known Issue: Uncaught TypeError: MutationObserver.observe: Argument 1 is not an object.
> If you are using ABP 3.1 version or more, you can skip this section.
When you run your `*.Web` project, you will see an exception (`Uncaught TypeError: MutationObserver.observe: Argument 1 is not an object.`) at your console.
This article is written in ABP Version 3.1 development cycle. We have fixed this issue [(ref)](https://github.com/abpframework/abp/issues/4566) in this development cycle.
To fix that issue, download this file [abp.jquery.js](https://github.com/abpframework/abp/blob/dev/npm/packs/jquery/src/abp.jquery.js) and replace with `wwwroot / libs / abp / jquery / abp.jquery.js` file of your `*.Web` project.
### Result
After following this step-by-step article you can use all DevExtreme features in your project.
![devexp-result](devexp-result.gif)
> The result example is created by following [this documentation](https://js.devexpress.com/Demos/WidgetsGallery/Demo/ProgressBar/Overview/NetCore/Light/).
---
### Sample Application
We have created a sample application with [Tree List](https://demos.devexpress.com/ASPNetCore/Demo/TreeList/Overview/) and [Data Grid](https://demos.devexpress.com/ASPNetCore/Demo/DataGrid/Overview/) examples.
You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Mvc).
We have some notes about this sample and general usages of DevExtreme at ABP based application.
### Data Storage
We will use an in-memory list for using data storage for this sample.
There is a `SampleDataService.cs` file in `Data` folder at `*.Application.Contracts` project. We store all sample data here.
We did not create `Entities` etc. Because we want to show "How to use DevExtreme?", because of that, in this sample we focused to application and UI layer.
### JSON Serialization
You can see some `[JsonProperty(Name = "OrderId")]` attributes at DTO's. In this sample, we use that attribute on DTO's properties because DevExtreme official resource is suggesting to _disable the conversion in the JSON serializer_ [(ref)](https://js.devexpress.com/Documentation/19_1/Guide/Angular_Components/Visual_Studio_Integration/Add_DevExtreme_to_an_ASP.NET_Core_Angular_Application/#Troubleshooting). **DO NOT DO THAT!**
If you change **the conversion in the JSON serializer**, some pre-build abp modules may occur a problem.
### MVC
You can use some DevExtreme functions to create UI. The following code blocks show you how you can use it with ABP Applicaion Services.
```csharp
Html.DevExtreme().DataGrid<Order>()
.DataSource(d => d.Mvc()
.Controller("Order") // Application Service Name 'without **AppService**'
.LoadAction("GetOrders") // Method Name 'without **Async**'
.InsertAction("InsertOrder")
.UpdateAction("UpdateOrder")
.DeleteAction("DeleteOrder")
.Key("OrderID")
)
```
```csharp
public class OrderAppService : DevExtremeSampleAppService, IOrderAppService
{
public async Task<LoadResult> GetOrdersAsync(DataSourceLoadOptions loadOptions)
{
...
}
public async Task<Order> InsertOrder(string values)
{
...
}
...
}
```

BIN
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/devexp-result.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/devextreme-js.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/gulp.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/initial-project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/en/Community-Articles/2020-08-18-DevExtreme-With-ABP/wwwroot-lib.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

5
docs/en/Customizing-Application-Modules-Extending-Entities.md

@ -43,7 +43,10 @@ Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entit
ObjectExtensionManager.Instance ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>( .MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber", "SocialSecurityNumber",
b => { b.HasMaxLength(32); } (entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(32);
}
); );
```` ````

251
docs/en/Emailing.md

@ -1,3 +1,250 @@
# Emailing # Email Sending
TODO! ABP Framework provides various services, settings and integrations for sending emails;
* Provides `IEmailSender` service that is used to send emails.
* Defines [settings](Settings.md) to configure email sending.
* Integrates to the [background job system](Background-Jobs.md) to send emails via background jobs.
* Provides [MailKit integration](MailKit.md) package.
## Installation
> This package is already installed if you are using the [application startup template](Startup-Templates/Application.md).
It is suggested to use the [ABP CLI](CLI.md) to install this package. Open a command line window in the folder of the project (.csproj file) and type the following command:
````bash
abp add-package Volo.Abp.Emailing
````
If you haven't done it yet, you first need to install the ABP CLI. For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Emailing).
## Sending Emails
### IEmailSender
[Inject](Dependency-Injection.md) the `IEmailSender` into any service and use the `SendAsync` method to send emails.
**Example**
````csharp
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing;
namespace MyProject
{
public class MyService : ITransientDependency
{
private readonly IEmailSender _emailSender;
public MyService(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public async Task DoItAsync()
{
await _emailSender.SendAsync(
"target@domain.com", // target email address
"Email subject", // subject
"This is email body..." // email body
);
}
}
}
````
`SendAsync` method has overloads to supply more parameters like;
* **from**: You can set this as the first argument to set a sender email address. If not provided, the default sender address is used (see the email settings below).
* **isBodyHtml**: Indicates whether the email body may contain HTML tags. **Default: true**.
> `IEmailSender` is the suggested way to send emails, since it makes your code provider independent.
#### MailMessage
In addition to primitive parameters, you can pass a **standard `MailMessage` object** ([see](https://docs.microsoft.com/en-us/dotnet/api/system.net.mail.mailmessage)) to the `SendAsync` method to set more options, like adding attachments.
### ISmtpEmailSender
Sending emails is implemented by the standard `SmtpClient` class ([see](https://docs.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient)) by default. The implementation class is the `SmtpEmailSender`. This class also expose the `ISmtpEmailSender` service (in addition to the `IEmailSender`).
Most of the time you want to directly use the `IEmailSender` to make your code provider independent. However, if you want to create an `SmtpClient` object with the same email settings, you can inject the `ISmtpEmailSender` and use its `BuildClientAsync` method to obtain a `SmtpClient` object and send the email yourself.
## Queueing Emails / Background Jobs
`IEmailSender` has a `QueueAsync` method that can be used to add emails to the background job queue to send them in a background thread. In this way, you don't take time of the user by waiting to send the email. `QueueAsync` method gets the same arguments with the `SendAsync` method.
Queueing emails tolerates errors since the background job system has re-try mechanism to overcome temporary network/server problems.
See the [background jobs document](Background-Jobs.md) for more about the background job system.
## Email Settings
Email sending uses the [setting system](Settings.md) to define settings and get the values of these settings on the runtime. `Volo.Abp.Emailing.EmailSettingNames` defines constants for the setting names, just listed below:
* **Abp.Mailing.DefaultFromAddress**: Used as the sender's email address when you don't specify a sender when sending emails (just like in the example above).
* **Abp.Mailing.DefaultFromDisplayName**: Used as the sender's display name when you don't specify a sender when sending emails (just like in the example above).
* **Abp.Mailing.Smtp.Host**: The IP/Domain of the SMTP server (default: 127.0.0.1).
* **Abp.Mailing.Smtp.Port**: The Port of the SMTP server (default: 25).
* **Abp.Mailing.Smtp.UserName**: Username, if the SMTP server requires authentication.
* **Abp.Mailing.Smtp.Password**: Password, if the SMTP server requires authentication. **This value is encrypted **(see the section below).
* **Abp.Mailing.Smtp.Domain**: Domain for the username, if the SMTP server requires authentication.
* **Abp.Mailing.Smtp.EnableSsl**: A value that indicates if the SMTP server uses SSL or not ("true" or "false". Default: "false").
* **Abp.Mailing.Smtp.UseDefaultCredentials**: If true, uses default credentials instead of the provided username and password ("true" or "false". Default: "true").
The easiest way to define these settings it to add them to the `appsettings.json` file. The [application startup template](Startup-Templates/Application.md) already has these settings in the `appsettings.json`:
````json
"Settings": {
"Abp.Mailing.Smtp.Host": "127.0.0.1",
"Abp.Mailing.Smtp.Port": "25",
"Abp.Mailing.Smtp.UserName": "",
"Abp.Mailing.Smtp.Password": "",
"Abp.Mailing.Smtp.Domain": "",
"Abp.Mailing.Smtp.EnableSsl": "false",
"Abp.Mailing.Smtp.UseDefaultCredentials": "true",
"Abp.Mailing.DefaultFromAddress": "noreply@abp.io",
"Abp.Mailing.DefaultFromDisplayName": "ABP application"
}
````
You can set/change these settings using the `ISettingManager` and store values in a database. See the [setting system document](Settings.md) to understand the setting system better.
### Encrypt the SMTP Password
*Abp.Mailing.Smtp.Password* must be an **encrypted** value. If you use the `ISettingManager` to set the password, you don't have to worry. It internally encrypts the values on set and decrypts on get.
If you use the `appsettings.json` to store the password, you should manually inject the `ISettingEncryptionService` and use its `Encrypt` method to obtain an encrypted value. This can be done by creating a simple code in your application. Then you can delete the code. As better, you can create a UI in your application to configure the email settings. In this case, you can directly use the `ISettingManager` without worrying the encryption.
### ISmtpEmailSenderConfiguration
If you don't want to use the setting system to store the email sending configuration, you can replace the `ISmtpEmailSenderConfiguration` service with your own implementation to get the configuration from any other source. `ISmtpEmailSenderConfiguration` is implemented by the `SmtpEmailSenderConfiguration` by default, which gets the configuration from the setting system as explained above.
## Text Template Integration
ABP Framework provides a strong and flexible [text templating system](Text-Templating.md). You can use the text templating system to create dynamic email contents. Inject the `ITemplateRenderer` and use the `RenderAsync` to render a template. Then use the result as the email body.
While you can define and use your own text templates, email sending system provides two simple built-in text templates.
**Example: Use the standard and simple message template to send emails**
````csharp
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing;
using Volo.Abp.Emailing.Templates;
using Volo.Abp.TextTemplating;
namespace Acme.BookStore.Web
{
public class MyService : ITransientDependency
{
private readonly IEmailSender _emailSender;
private readonly ITemplateRenderer _templateRenderer;
public MyService(
IEmailSender emailSender,
ITemplateRenderer templateRenderer)
{
_emailSender = emailSender;
_templateRenderer = templateRenderer;
}
public async Task DoItAsync()
{
var body = await _templateRenderer.RenderAsync(
StandardEmailTemplates.Message,
new
{
message = "This is email body..."
}
);
await _emailSender.SendAsync(
"target-address@domain.com",
"Email subject",
body
);
}
}
}
````
The resulting email body will be shown below:
````html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
This is email body...
</body>
</html>
````
Emailing system defines the built-in text templates with the given names:
"**Abp.StandardEmailTemplates.Message**" is simplest template that has a text message:
````html
{%{{{model.message}}}%}
````
This template uses the "Abp.StandardEmailTemplates.Layout" as its layout.
"**Abp.StandardEmailTemplates.Layout**" is a simple template to provide an HTML document layout:
````html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
{%{{{content}}}%}
</body>
</html>
````
The final rendered message was shown above.
> These template names are contants defined in the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` class.
### Overriding/Replacing the Standard Templates
You typically want to replace the standard templates with your own ones, so you can prepare a branded email messages. To do that, you can use the power of the [virtual file system](Virtual-File-System.md) (VFS) or replace them in your own template definition provider.
Pathes of the templates in the virtual file system are shown below:
* `/Volo/Abp/Emailing/Templates/Layout.tpl`
* `/Volo/Abp/Emailing/Templates/Message.tpl`
If you add files to the same localization in the virtual file system, your files will override them.
Templates are inline localized, that means you can take the power of the [localization system](Localization.md) to make your templates multi-cultural.
See the [text templating system](Text-Templating.md) document for details.
> Notice that you can define and use your own templates for your application, rather than using the standard simple templates. These standard templates are mostly for reusable modules where they don't define their own templates but rely on the built-in ones. This makes easy to customize emails sent by the used modules, by just overriding the standard email layout template.
## NullEmailSender
`NullEmailSender` is a built-in class that implements the `IEmailSender`, but writes email contents to the [standard log system](Logging.md), rathen than actually sending the emails.
This class can be useful especially in development time where you generally don't want to send real emails. The [application startup template](Startup-Templates/Application.md) already uses this class in the **DEBUG mode** with the following configuration in the domain layer:
````csharp
#if DEBUG
context.Services.Replace(ServiceDescriptor.Singleton<IEmailSender, NullEmailSender>());
#endif
````
So, don't confuse if you don't receive emails on DEBUG mode. Emails will be sent as expected on production (RELEASE mode). Remove these lines if you want to send real emails on DEBUG too.
## See Also
* [MailKit integration for sending emails](MailKit.md)

7
docs/en/Entity-Framework-Core-Migrations.md

@ -398,7 +398,7 @@ builder.Entity<AppRole>(b =>
You've configured the custom property for your `DbContext` that is used by your application on the runtime. We also need to configure the `MigrationsDbContext`. You've configured the custom property for your `DbContext` that is used by your application on the runtime. We also need to configure the `MigrationsDbContext`.
Instead of directly changing the `MigrationsDbContext`, we should use the entity extension system of the ABP Framework. Find the `YourProjectNameEntityExtensions` class in the `.EntityFrameworkCore` project of your solution (`BookStoreEntityExtensions` for this example) and change it as shown below: Instead of directly changing the `MigrationsDbContext`, we **should** use the entity extension system of the ABP Framework. Find the `YourProjectNameEfCoreEntityExtensionMappings` class in the `.EntityFrameworkCore` project of your solution (`BookStoreEfCoreEntityExtensionMappings` for this example) and change it as shown below:
````csharp ````csharp
public static class MyProjectNameEntityExtensions public static class MyProjectNameEntityExtensions
@ -412,7 +412,10 @@ public static class MyProjectNameEntityExtensions
ObjectExtensionManager.Instance ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>( .MapEfCoreProperty<IdentityRole, string>(
"Title", "Title",
builder => { builder.HasMaxLength(64); } (entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(128);
}
); );
}); });
} }

7
docs/en/Entity-Framework-Core.md

@ -325,13 +325,16 @@ This section only explains the EF Core related usage of the `ObjectExtensionMana
ObjectExtensionManager.Instance ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>( .MapEfCoreProperty<IdentityRole, string>(
"Title", "Title",
builder => { builder.HasMaxLength(64); } (entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(64);
}
); );
```` ````
If the related module has implemented this feature (by using the `ConfigureEfCoreEntity` explained below), then the new property is added to the model. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field. If the related module has implemented this feature (by using the `ConfigureEfCoreEntity` explained below), then the new property is added to the model. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field.
>`MapEfCoreProperty` method must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEntityExtensions` class that is safe to use this method inside. >`MapEfCoreProperty` method must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEfCoreEntityExtensionMappings` class that is safe to use this method inside.
### ConfigureEfCoreEntity ### ConfigureEfCoreEntity

11
docs/en/Exception-Handling.md

@ -320,3 +320,14 @@ Some exception types are automatically thrown by the framework:
- `EntityNotFoundException` is thrown if the requested entity is not available. This is mostly thrown by [repositories](Repositories.md). - `EntityNotFoundException` is thrown if the requested entity is not available. This is mostly thrown by [repositories](Repositories.md).
You can also throw these type of exceptions in your code (although it's rarely needed). You can also throw these type of exceptions in your code (although it's rarely needed).
## Send exception details to the client
You can send exceptions to the client via the `SendExceptionsDetailsToClients` property of the `AbpExceptionHandlingOptions` class:
````csharp
services.Configure<AbpExceptionHandlingOptions>(options =>
{
options.SendExceptionsDetailsToClients = true;
});
````

2
docs/en/Getting-Started-React-Native.md

@ -2,7 +2,7 @@
ABP platform provide basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated to your ABP based backends**. ABP platform provide basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated to your ABP based backends**.
When you **create a new application** as described in the [getting started document](Getting-Started.md), the solution includes the React Native application in the `react-native` folder as default. When you **create a new application** as described in the [getting started document](Getting-Started.md), you have to add `-m react-native` option to include `react-native` project in your solution.
## Configure Your Local IP Address ## Configure Your Local IP Address

9
docs/en/Getting-Started.md

@ -65,8 +65,6 @@ Use the `new` command of the ABP CLI to create a new project:
abp new Acme.BookStore{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}} abp new Acme.BookStore{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}}
```` ````
> This command also creates a React Native mobile application inside the solution folder. If you don't want it, you can safely delete it or specify the `-m none` option to the `abp new` command to not include it in the solution at all.
{{ if UI == "NG" }} {{ if UI == "NG" }}
* `-u` argument specifies the UI framework, `angular` in this case. * `-u` argument specifies the UI framework, `angular` in this case.
@ -126,7 +124,6 @@ There are three folders in the created solution:
* `angular` folder contains the Angular UI application. * `angular` folder contains the Angular UI application.
* `aspnet-core` folder contains the backend solution. * `aspnet-core` folder contains the backend solution.
* `react-native` folder contains the React Native UI application.
Open the `.sln` (Visual Studio solution) file under the `aspnet-core` folder: Open the `.sln` (Visual Studio solution) file under the `aspnet-core` folder:
@ -339,11 +336,7 @@ Enter **admin** as the username and **1q2w3E*** as the password to login to the
## Mobile Development ## Mobile Development
When you create a new application, the solution includes `react-native` folder by default. This is a basic [React Native](https://reactnative.dev/) startup template to develop mobile applications integrated to your ABP based backends. If you want to include a [React Native](https://reactnative.dev/) project in your solution, add `-m react-native` (or `--mobile react-native`) argument to project creation command. This is a basic React Native startup template to develop mobile applications integrated to your ABP based backends.
If you don't plan to develop a mobile application with React Native, you can safely delete the `react-native` folder.
> You can specifying the `-m none` option to the ABP CLI to not create the `react-native` folder in the beginning.
See the "[Getting Started with the React Native](Getting-Started-React-Native.md)" document to learn how to configure and run the React Native application. See the "[Getting Started with the React Native](Getting-Started-React-Native.md)" document to learn how to configure and run the React Native application.

3
docs/en/Global-Features.md

@ -0,0 +1,3 @@
# Global Features
TODO (see [#5061](https://github.com/abpframework/abp/issues/5061) until this is documented).

48
docs/en/MailKit.md

@ -0,0 +1,48 @@
# MailKit Integration
[MailKit](http://www.mimekit.net/) is a cross-platform, popular open source mail client library for .net. ABP Framework provides an integration package to use the MailKit as the [email sender](Emailing.md).
## Installation
It is suggested to use the [ABP CLI](CLI.md) to install this package. Open a command line window in the folder of the project (.csproj file) and type the following command:
````bash
abp add-package Volo.Abp.MailKit
````
If you haven't done it yet, you first need to install the ABP CLI. For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.MailKit).
## Sending Emails
### IEmailSender
[Inject](Dependency-Injection.md) the standard `IEmailSender` into any service and use the `SendAsync` method to send emails. See the [email sending document](Emailing.md) for details.
> `IEmailSender` is the suggested way to send emails even if you use MailKit, since it makes your code provider independent.
### IMailKitSmtpEmailSender
MailKit package also exposes the `IMailKitSmtpEmailSender` service that extends the `IEmailSender` by adding the `BuildClientAsync()` method. This method can be used to obtain a `MailKit.Net.Smtp.SmtpClient` object that can be used to perform MailKit specific operations.
## Configuration
MailKit integration package uses the same settings defined by the email sending system. So, refer to the [email sending document](Emailing.md) for the settings.
In addition to the standard settings, this package defines `AbpMailKitOptions` as a simple [options](Options.md) class. This class defines only one options:
* **SecureSocketOption**: Used to set one of the `SecureSocketOptions`. Default: `null` (uses the defaults).
**Example: Use *SecureSocketOptions.SslOnConnect***
````csharp
Configure<AbpMailKitOptions>(options =>
{
options.SecureSocketOption = SecureSocketOptions.SslOnConnect;
});
````
Refer to the [MailKit documentation](http://www.mimekit.net/) to learn more about this option.
## See Also
* [Email sending](Emailing.md)

2
docs/en/Module-Development-Basics.md

@ -1,4 +1,4 @@
# Module Development # Modularity
## Introduction ## Introduction

3
docs/en/Module-Entity-Extensions.md

@ -0,0 +1,3 @@
# Module Entity Extensions
See https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions (it will be moved here soon).

32
docs/en/Nightly-Builds.md

@ -2,40 +2,18 @@
All framework & module packages are deployed to MyGet every night in weekdays. So, you can use or test the latest code without waiting the next release. All framework & module packages are deployed to MyGet every night in weekdays. So, you can use or test the latest code without waiting the next release.
## Configure Visual Studio ## Install & Uninstall Nightly Preview Packages
> Requires Visual Studio 2017+ The latest version of nightly preview packages can be installed by the running below command in the root folder of application:
1. Go to `Tools > Options > NuGet Package Manager > Package Source`.
2. Click the green `+` icon.
3. Set `ABP Nightly` as *Name* and `https://www.myget.org/F/abp-nightly/api/v3/index.json` as the *Source* as shown below:
![night-build-add-nuget-source](images/night-build-add-nuget-source.png)
4. Click the `Update` button.
5. Click the `OK` button to save changes.
## Install Package
Now, you can install preview / nightly packages to your project from Nuget Browser or Package Manager Console.
![night-build-add-nuget-package](images/night-build-add-nuget-package.png)
1. In the nuget browser, select "Include prereleases".
2. Change package source to "All".
3. Search a package. You will see prereleases of the package formatted as `(VERSION)-preview(DATE)` (like *v0.16.0-preview20190401* in this sample).
4. You can click to the `Install` button to add package to your project.
## Install & Uninstall Preview NPM Packages
The latest version of preview NPM packages can be installed by the running below command in the root folder of application:
```bash ```bash
abp switch-to-preview --npm abp switch-to-nightly
``` ```
If you're using the ABP Framework preview packages, you can switch back to stable version using this command: If you're using the ABP Framework nightly preview packages, you can switch back to stable version using this command:
```bash ```bash
abp switch-to-stable --npm abp switch-to-stable
``` ```
See the [ABP CLI documentation](./CLI.md) for more information. See the [ABP CLI documentation](./CLI.md) for more information.

40
docs/en/Previews.md

@ -0,0 +1,40 @@
# Preview Releases
The preview versions are released **~2 weeks before** releasing a major or feature version of the ABP Framework. They are released for developers to try and provide feedback to have more stable versions.
Versioning of a preview release is like that:
* 3.1.0-rc.1
* 4.0.0-rc.1
More than one preview releases (like 3.1.0-rc.2 and 3.1.0-rc.3) might be published until the stable version (like 3.1.0).
## Using the Preview Versions
### New Solutions
To create a project for testing the preview version, you can select the "**preview**" option on the [download page](https://abp.io/get-started) or use the "**--preview**" parameter with the [ABP CLI](CLI.md) new command:
````bash
abp new Acme.BookStore --preview
````
This command will create a new project using the latest preview NuGet packages, NPM packages and the solution template. Whenever the stable version is released, you can switch to the stable version for your solution using the `abp switch-to-stable` command in the root folder of your solution.
### Existing Solutions
If you already have a solution and want to use/test the latest preview version, use the following [ABP CLI](CLI.md) command in the root folder of your solution.
````bash
abp switch-to-preview
````
You can return back to the latest stable using the `abp switch-to-stable ` command later.
````bash
abp switch-to-stable
````
## Providing Feedback
You can open an issue on the [GitHub repository](https://github.com/abpframework/abp/issues/new), if you find a bug or want to provide any kind of feedback.

6
docs/en/Repositories.md

@ -6,7 +6,11 @@ Repositories, in practice, are used to perform database operations for domain ob
## Generic Repositories ## Generic Repositories
ABP can provide a **default generic repository** for each aggregate root or entity. You can [inject](Dependency-Injection.md) `IRepository<TEntity, TKey>` into your service and perform standard **CRUD** operations. Example usage: ABP can provide a **default generic repository** for each aggregate root or entity. You can [inject](Dependency-Injection.md) `IRepository<TEntity, TKey>` into your service and perform standard **CRUD** operations.
> Database provider layer should be properly configured to be able to use the default generic repositories. It is **already done** if you've created your project using the startup templates. If not, refer to the database provider documents ([EF Core](Entity-Framework-Core.md) / [MongoDB](MongoDB.md)) to configure it.
**Example usage of a default generic repository:**
````C# ````C#
public class PersonAppService : ApplicationService public class PersonAppService : ApplicationService

19
docs/en/Startup-Templates/Application.md

@ -54,6 +54,20 @@ Use `-d` (or `--database-provider`) option to specify the database provider:
abp new Acme.BookStore -d mongodb abp new Acme.BookStore -d mongodb
```` ````
### Specify the Mobile Application Framework
This template supports the following mobile application frameworks:
- `react-native`: React Native
Use `-m` (or `--mobile`) option to specify the mobile application framework:
````bash
abp new Acme.BookStore -m react-native
````
If not specified, no mobile application will be created.
## Solution Structure ## Solution Structure
Based on the options you've specified, you will get a slightly different solution structure. Based on the options you've specified, you will get a slightly different solution structure.
@ -261,11 +275,10 @@ You should run the application with the given order:
### Angular UI ### Angular UI
If you choose `Angular` as the UI framework (using the `-u angular` option), the solution is being separated into three folders: If you choose `Angular` as the UI framework (using the `-u angular` option), the solution is being separated into two folders:
* `angular` folder contains the Angular UI application, the client-side code. * `angular` folder contains the Angular UI application, the client-side code.
* `aspnet-core` folder contains the ASP.NET Core solution, the server-side code. * `aspnet-core` folder contains the ASP.NET Core solution, the server-side code.
* `react-native` folder contains the React Native UI application, the client-side code for mobile.
The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the `Angular` application consumes it. The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the `Angular` application consumes it.
@ -356,7 +369,7 @@ See the [testing document](https://angular.io/guide/testing).
### React Native ### React Native
The solution includes the [React Native](https://reactnative.dev/) application in the `react-native` folder as default. if `-m react-native` option is spesified in new project command, the solution includes the [React Native](https://reactnative.dev/) application in the `react-native` folder.
The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the React Native application consumes it. The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the React Native application consumes it.

106
docs/en/Text-Templating.md

@ -380,6 +380,112 @@ The rendering result will be:
A global object value: TEST VALUE A global object value: TEST VALUE
```` ````
## Replacing the Existing Templates
It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code.
### Option-1: Using the Virtual File System
The [Virtual File System](Virtual-File-System.md) allows you to override any file by placing the same file into the same path in your project.
#### Example: Replace the Standard Email Layout Template
ABP Framework provides an [email sending system](Emailing.md) that internally uses the text templating to render the email content. It defines a standard email layout template in the `/Volo/Abp/Emailing/Templates/Layout.tpl` path. The unique name of the template is `Abp.StandardEmailTemplates.Layout` and this string is defined as a constant on the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` static class.
Do the following steps to replace the template file with your own;
**1)** Add a new file into the same location (`/Volo/Abp/Emailing/Templates/Layout.tpl`) in your project:
![replace-email-layout](images/replace-email-layout.png)
**2)** Prepare your email layout template:
````html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>This my header</h1>
{%{{{content}}}%}
<footer>
This is my footer...
</footer>
</body>
</html>
````
This example simply adds a header and footer to the template and renders the content between them (see the *Layout Templates* section above to understand it).
**3)** Configure the embedded resources in the `.csproj` file
* Add [Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet package to the project.
* Add `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` into the `<PropertyConfig>...</PropertyConfig>` section of your `.csproj` file.
* Add the following code into your `.csproj` file:
````xml
<ItemGroup>
<None Remove="Volo\Abp\Emailing\Templates\*.tpl" />
<EmbeddedResource Include="Volo\Abp\Emailing\Templates\*.tpl" />
</ItemGroup>
````
This makes the template files "embedded resource".
**4)** Configure the virtual file system
Configure the `AbpVirtualFileSystemOptions` in the `ConfigureServices` method of your [module](Module-Development-Basics.md) to add the embedded files into the virtual file system:
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BookStoreDomainModule>();
});
```
`BookStoreDomainModule` should be your module name, in this example code.
> Be sure that your module (directly or indirectly) [depends on](Module-Development-Basics.md) the `AbpEmailingModule`. Because the VFS can override files based on the dependency order.
Now, your template will be used when you want to render the email layout template.
### Option-2: Using the Template Definition Provider
You can create a template definition provider class that gets the email layout template and changes the virtual file path for the template.
**Example: Use the `/MyTemplates/EmailLayout.tpl` file instead of the standard template**
```csharp
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing.Templates;
using Volo.Abp.TextTemplating;
namespace MyProject
{
public class MyTemplateDefinitionProvider
: TemplateDefinitionProvider, ITransientDependency
{
public override void Define(ITemplateDefinitionContext context)
{
var emailLayoutTemplate = context.GetOrNull(StandardEmailTemplates.Layout);
emailLayoutTemplate
.WithVirtualFilePath(
"/MyTemplates/EmailLayout.tpl",
isInlineLocalized: true
);
}
}
}
```
You should still add the file `/MyTemplates/EmailLayout.tpl` to the virtual file system as explained before. This approach allows you to locate templates in any folder instead of the folder defined by the depended module.
Beside the template content, you can manipulate the template definition properties, like `DisplayName`, `Layout` or `LocalizationSource`.
## Advanced Features ## Advanced Features
This section covers some internals and more advanced usages of the text templating system. This section covers some internals and more advanced usages of the text templating system.

4
docs/en/Timing.md

@ -10,7 +10,7 @@ ABP provides a basic infrastructure to make it easy and handle automatically whe
`DateTime.Now` returns a `DateTime` object with the **local date & time of the server**. A `DateTime` object **doesn't store the time zone information**. So, you can not know the **absolute date & time** stored in this object. You can only make **assumptions**, like assuming that it was created in UTC+05 time zone. The things especially gets complicated when you save this value to a database and read later, or send it to a client in a **different time zone**. `DateTime.Now` returns a `DateTime` object with the **local date & time of the server**. A `DateTime` object **doesn't store the time zone information**. So, you can not know the **absolute date & time** stored in this object. You can only make **assumptions**, like assuming that it was created in UTC+05 time zone. The things especially gets complicated when you save this value to a database and read later, or send it to a client in a **different time zone**.
One solution to this problem is always use `DateTime.UtcNow` and assume all `DateTime` objects as UTC time. In this was, you can convert it to the time zone of the target client when needed. One solution to this problem is always use `DateTime.UtcNow` and assume all `DateTime` objects as UTC time. In this way, you can convert it to the time zone of the target client when needed.
`IClock` provides an abstraction while getting the current time, so you can control the kind of the date time (UTC or local) in a single point in your application. `IClock` provides an abstraction while getting the current time, so you can control the kind of the date time (UTC or local) in a single point in your application.
@ -110,4 +110,4 @@ See the [setting documentation](Settings.md) to learn more about the setting sys
`ITimezoneProvider` is a service to simple convert [Windows Time Zone Id](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values) values to [Iana Time Zone Name](https://www.iana.org/time-zones) values and vice verse. It also provides methods to get list of these time zones and get a `TimeZoneInfo` with a given name. `ITimezoneProvider` is a service to simple convert [Windows Time Zone Id](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values) values to [Iana Time Zone Name](https://www.iana.org/time-zones) values and vice verse. It also provides methods to get list of these time zones and get a `TimeZoneInfo` with a given name.
It has been implemented using the [TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter) library. It has been implemented using the [TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter) library.

4
docs/en/UI/Angular/Multi-Tenancy.md

@ -1,4 +1,4 @@
# Multi Tenancy in Angular UI # Multi Tenancy in Angular UI
ABP Angular UI supports the multi-tenancy. The following features related to multi-tenancy are available in the startup templates. ABP Angular UI supports the multi-tenancy. The following features related to multi-tenancy are available in the startup templates.
@ -17,7 +17,7 @@ On the page above, you can;
![Tenant Switching Component](./images/tenant-switching-box.png) ![Tenant Switching Component](./images/tenant-switching-box.png)
<p style="font-size:small;text-align:center;">Tenant Switching Component</p> <p style="font-size:small;text-align:center;">Tenant Switching Component</p>
You can switch between existing tenants by using the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page). Angular UI sends the selected tenant id sends to the backend as `__tenant` header on each request. You can switch between existing tenants by using the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page). Angular UI sends the selected tenant id to the backend as `__tenant` header on each request.
## Domain Tenant Resolver ## Domain Tenant Resolver

25
docs/en/UI/Angular/Permission-Management.md

@ -4,36 +4,17 @@ A permission is a simple policy that is granted or prohibited for a particular u
You can get permission of authenticated user using `getGrantedPolicy` selector of `ConfigState`. You can get permission of authenticated user using `getGrantedPolicy` selector of `ConfigState`.
You can get permission as boolean value from store: You can get permission as boolean value:
```js
import { Store } from '@ngxs/store';
import { ConfigState } from '@abp/ng.core';
export class YourComponent {
constructor(private store: Store) {}
ngOnInit(): void {
const canCreate = this.store.selectSnapshot(ConfigState.getGrantedPolicy('AbpIdentity.Roles.Create'));
}
// ...
}
```
Or you can get it via `ConfigStateService`:
```js ```js
import { ConfigStateService } from '@abp/ng.core'; import { ConfigStateService } from '@abp/ng.core';
export class YourComponent { export class YourComponent {
constructor(private configStateService: ConfigStateService) {} constructor(private config: ConfigStateService) {}
ngOnInit(): void { ngOnInit(): void {
const canCreate = this.configStateService.getGrantedPolicy('AbpIdentity.Roles.Create'); const canCreate = this.config.getGrantedPolicy('AbpIdentity.Roles.Create');
} }
// ...
} }
``` ```

2
docs/en/UI/AspNetCore/Customization-User-Interface.md

@ -158,7 +158,7 @@ Just as explained above, you can replace any component, layout or c# class of th
## Overriding Static Resources ## Overriding Static Resources
Overriding a static embedded resource (like JavaScript, Css or image files) of a module is pretty easy. Just place a file in the same path in your solution and let the Virtual File System to handle it. Overriding a static embedded resource (like JavaScript, Css or image files) of a module is pretty easy. Just place a file in the same path in your solution and let the [Virtual File System](../../Virtual-File-System.md) to handle it.
## Manipulating the Bundles ## Manipulating the Bundles

133
docs/en/Virtual-File-System.md

@ -6,7 +6,7 @@ The Virtual File System makes it possible to manage files that do not physically
> Most of the times you don't need to manually install this package since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). > Most of the times you don't need to manually install this package since it comes pre-installed with the [application startup template](Startup-Templates/Application.md).
[Volo.Abp.VirtualFileSystem](https://www.nuget.org/packages/Volo.Abp.VirtualFileSystem) is the main page of the Virtual File System. [Volo.Abp.VirtualFileSystem](https://www.nuget.org/packages/Volo.Abp.VirtualFileSystem) is the main package of the Virtual File System.
Use the ABP CLI to add this package to your project: Use the ABP CLI to add this package to your project:
@ -18,9 +18,9 @@ If you want to do it manually, install the [Volo.Abp.VirtualFileSystem](https://
## Working with the Embedded Files ## Working with the Embedded Files
### Embed the Files ### Embedding the Files
A file should be first marked as an **embedded resource** to embed the file into the assembly. The easiest way to do it is to select the file from the **Solution Explorer** and set **Build Action** to **Embedded Resource** from the **Properties** window. Example: A file should be first marked as **embedded resource** to embed the file into the assembly. The easiest way to do it is to select the file from the **Solution Explorer** and set **Build Action** to **Embedded Resource** from the **Properties** window. Example:
![build-action-embedded-resource-sample](images/build-action-embedded-resource-sample.png) ![build-action-embedded-resource-sample](images/build-action-embedded-resource-sample.png)
@ -55,21 +55,21 @@ Configure<AbpVirtualFileSystemOptions>(options =>
}); });
```` ````
The `AddEmbedded` extension method takes a class, finds all embedded files from the **assembly of the given class** and registers them to the virtual file system. It is common to pass the module class as the generic argument. The `AddEmbedded` extension method takes a class, finds all embedded files from the **assembly of the given class** and registers them to the virtual file system.
`AddEmbedded` can get two optional parameters; `AddEmbedded` can get two optional parameters;
* `baseNamespace`: This may only needed if you didn't configure the `GenerateEmbeddedFilesManifest` step explained above and your root namespace is not empty. In this case, set your root namespace here. * `baseNamespace`: This may only needed if you didn't configure the `GenerateEmbeddedFilesManifest` step explained above and your root namespace is not empty. In this case, set your root namespace here.
* `baseFolder`: If you don't want to expose all embedded files in the project, but only want to expose a specific folder (and sub folders/files), then you can set the base folder relative to your project root page. * `baseFolder`: If you don't want to expose all embedded files in the project, but only want to expose a specific folder (and sub folders/files), then you can set the base folder relative to your project root folder.
**Example: Add files under the `MyFiles` folder in the project** **Example: Add files under the `MyResources` folder in the project**
````csharp ````csharp
Configure<AbpVirtualFileSystemOptions>(options => Configure<AbpVirtualFileSystemOptions>(options =>
{ {
options.FileSets.AddEmbedded<MyModule>( options.FileSets.AddEmbedded<MyModule>(
baseNamespace: "Acme.BookStore.MyFiles", baseNamespace: "Acme.BookStore",
baseFolder: "/MyFiles" baseFolder: "/MyResources"
); );
}); });
```` ````
@ -77,56 +77,15 @@ Configure<AbpVirtualFileSystemOptions>(options =>
This example assumes; This example assumes;
* Your project root (default) namespace is `Acme.BookStore`. * Your project root (default) namespace is `Acme.BookStore`.
* Your project has a folder, named `MyFiles` * Your project has a folder, named `MyResources`
* You only want to add `MyFiles` folder to the virtual file system. * You only want to add `MyResources` folder to the virtual file system.
### Dealing With Embedded Files During Development ### IVirtualFileProvider
Embedding a file into an assembly and being able to use it from another project just by referencing the assembly (or adding a NuGet package) is invaluable for creating a re-usable module. However, it makes it a little bit harder to develop the module itself. After embedding a file into an assembly and registering it to the virtual file system, the `IVirtualFileProvider` interface can be used to get the file or directory contents:
Let's assume that you're developing a module that contains an embedded JavaScript file. Whenever you change this file you must re-compile the project, re-start the application and refresh the browser page to take the change. Obviously, this is very time consuming and tedious.
What is needed is the ability for the application to directly use the physical file at development time and a have a browser refresh reflect any change made in the JavaScript file. The `ReplaceEmbeddedByPhysical` method makes all this possible.
The example below shows an application that depends on a module (`MyModule`) that itself contains embedded files. The application can reach the source code of the module at development time.
````C# ````C#
[DependsOn(typeof(MyModule))] public class MyService : ITransientDependency
public class MyWebAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (hostingEnvironment.IsDevelopment()) //only for development time
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.ReplaceEmbeddedByPhysical<MyModule>(
Path.Combine(
hostingEnvironment.ContentRootPath,
string.Format(
"..{0}MyModuleProject",
Path.DirectorySeparatorChar
)
)
);
});
}
}
}
````
The code above assumes that `MyWebAppModule` and `MyModule` are two different projects in a Visual Studio solution and `MyWebAppModule` depends on the `MyModule`.
> The [application startup template](Startup-Templates/Application.md) already uses this technique for the localization files. So, when you change a localization file it automatically detects the change.
## IVirtualFileProvider
After embedding a file into an assembly and registering it to the virtual file system, the `IVirtualFileProvider` interface can be used to get files or directory contents:
````C#
public class MyService
{ {
private readonly IVirtualFileProvider _virtualFileProvider; private readonly IVirtualFileProvider _virtualFileProvider;
@ -135,7 +94,7 @@ public class MyService
_virtualFileProvider = virtualFileProvider; _virtualFileProvider = virtualFileProvider;
} }
public void Foo() public void Test()
{ {
//Getting a single file //Getting a single file
var file = _virtualFileProvider var file = _virtualFileProvider
@ -160,14 +119,14 @@ The Virtual File System is well integrated to ASP.NET Core:
### UseVirtualFiles Middleware ### UseVirtualFiles Middleware
The Virtual Files Middleware is used to serve embedded (js, css, image...) files to clients/browsers just like physical files in the **wwwroot** folder. Add it just after the static file middleware as shown below: The Virtual Files Middleware is used to serve embedded (js, css, image...) files to clients/browsers just like physical files in the **wwwroot** folder. It also covers the physical files.
Replace the `app.UseStaticFiles()` with the `app.UseVirtualFiles()` in your ASP.NET Core middleware configuration:
````C# ````C#
app.UseVirtualFiles(); app.UseVirtualFiles();
```` ````
Adding virtual files middleware after the static files middleware makes it possible to override a virtual file with a real physical file simply by placing it in the same location as the virtual file.
> `UseVirtualFiles()` is already configured for the [application startup template](Startup-Templates/Application.md). > `UseVirtualFiles()` is already configured for the [application startup template](Startup-Templates/Application.md).
#### Static Virtual File Folders #### Static Virtual File Folders
@ -176,6 +135,62 @@ By default, ASP.NET Core only allows the `wwwroot` folder to contain the static
* Pages * Pages
* Views * Views
* Components
* Themes * Themes
This allows to add `.js`, `.css`... files near to your `.cshtml` file that is easier to develop and maintain your project. This allows to add `.js`, `.css`... files near to your `.cshtml` file that is easier to develop and maintain your project.
## Dealing With Embedded Files During Development
Embedding a file into an assembly and being able to use it from another project just by referencing the assembly (or adding a NuGet package) is invaluable for creating a re-usable module. However, it makes it a little bit harder to develop the module itself.
Let's assume that you're developing a module that contains an embedded JavaScript file. Whenever you change this file you must re-compile the project, re-start the application and refresh the browser page to take the change. Obviously, this is very time consuming and tedious.
What is needed is the ability for the application to directly use the physical file at development time and a browser refresh reflects any change made in the JavaScript file. The `ReplaceEmbeddedByPhysical` method makes all this possible.
The example below shows an application that depends on a module (`MyModule`) that contains embedded files. The application can access to the source code of the module at development time.
````C#
[DependsOn(typeof(MyModule))]
public class MyWebAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (hostingEnvironment.IsDevelopment()) //only for development time
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.ReplaceEmbeddedByPhysical<MyModule>(
Path.Combine(
hostingEnvironment.ContentRootPath,
string.Format(
"..{0}MyModuleProject",
Path.DirectorySeparatorChar
)
)
);
});
}
}
}
````
The code above assumes that `MyWebAppModule` and `MyModule` are two different projects in a Visual Studio solution and `MyWebAppModule` depends on the `MyModule`.
> The [application startup template](Startup-Templates/Application.md) already uses this technique for the localization files. So, when you change a localization file it automatically detects the change.
## Replacing/Overriding Virtual Files
Virtual File System creates a unified file system on runtime, where the actual files are distributed into different modules in the development time.
If two modules adds a file to the same virtual path (like `my-path/my-file.css`), the one added later overrides/replaces the previous one ([module dependency](Module-Development-Basics.md) order determines the order of the files being added).
This feature allows your application to override/replace any virtual file defined a module that is used by your application. This is one of the fundamental extensibility features of the ABP Framework.
So, if you need to replace a file of a module, just create the file in the exactly same path in your module/application
### Physical Files
Physical files always override the virtual files. That means if you put a file under the `/wwwroot/my-folder/my-file.css`, it will override the file in the same location of the virtual file system. So, you need to know the file paths defined in the modules to override them.

17
docs/en/docs-nav.json

@ -214,6 +214,19 @@
"text": "Object to object mapping", "text": "Object to object mapping",
"path": "Object-To-Object-Mapping.md" "path": "Object-To-Object-Mapping.md"
}, },
{
"text": "Email Sending",
"items": [
{
"text": "Email Sending System",
"path": "Emailing.md",
},
{
"text": "MailKit Integration",
"path": "MailKit.md",
}
]
},
{ {
"text": "BLOB Storing", "text": "BLOB Storing",
"items": [ "items": [
@ -651,6 +664,10 @@
"text": "Microservice Architecture", "text": "Microservice Architecture",
"path": "Microservice-Architecture.md" "path": "Microservice-Architecture.md"
}, },
{
"text": "Preview Releases",
"path": "Previews.md"
},
{ {
"text": "Nightly Builds", "text": "Nightly Builds",
"path": "Nightly-Builds.md" "path": "Nightly-Builds.md"

BIN
docs/en/images/replace-email-layout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/en/images/solution-files-non-mvc.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

38
docs/zh-Hans/Background-Jobs-Quartz.md

@ -70,7 +70,43 @@ public class YourModule : AbpModule
} }
```` ````
Quartz**默认**将作业与调度信息存储在**内存**中,示例中我们使用[选项模式](Options.md)的预配置将其更改为存储到数据库中. 有关Quartz的更多配置请参阅[Quartz文档](https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html). 从ABP3.1版本开始,我们在 `AbpQuartzOptions` 添加了 `Configurator` 用于配置Quartz. 例:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
PreConfigure<AbpQuartzOptions>(options =>
{
options.Configurator = configure =>
{
configure.UsePersistentStore(storeOptions =>
{
storeOptions.UseProperties = true;
storeOptions.UseJsonSerializer();
storeOptions.UseSqlServer(configuration.GetConnectionString("Quartz"));
storeOptions.UseClustering(c =>
{
c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20);
c.CheckinInterval = TimeSpan.FromSeconds(10);
});
});
};
});
}
}
````
> 你可以选择你喜爱的方式来配置Quaratz.
Quartz**默认**将作业与调度信息存储在**内存**中,示例中我们使用[选项模式](Options.md)的预配置将其更改为存储到数据库中. 有关Quartz的更多配置请参阅[Quartz文档](https://www.quartz-scheduler.net/).
## 异常处理 ## 异常处理

4
docs/zh-Hans/CLI.md

@ -79,7 +79,7 @@ abp new Acme.BookStore
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点. * `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `none`: 无UI. 这个模板还有一些额外的选项: * `none`: 无UI. 这个模板还有一些额外的选项:
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点. * `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `--mobile` 或者 `-m`: 指定移动应用程序框架. 默认框架是 `react-native`. 其他选项: * `--mobile` 或者 `-m`: 指定移动应用程序框架. 如果未指定,则不会创建任何移动应用程序,其他选项:
* `none`: 不包含移动应用程序. * `none`: 不包含移动应用程序.
* `react-native`: React Native. * `react-native`: React Native.
* `--database-provider` 或者 `-d`: 指定数据库提供程序.默认是 `ef`.其他选项: * `--database-provider` 或者 `-d`: 指定数据库提供程序.默认是 `ef`.其他选项:
@ -251,4 +251,4 @@ abp help [命令名]
````bash ````bash
abp help # 显示常规帮助. abp help # 显示常规帮助.
abp help new # 显示有关 "New" 命令的帮助. abp help new # 显示有关 "New" 命令的帮助.
```` ````

11
docs/zh-Hans/Exception-Handling.md

@ -300,3 +300,14 @@ services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
- 如果请求的实体不存在,则抛出`EntityNotFoundException` 异常. 此异常大多数由 [repositories](Repositories.md) 抛出. - 如果请求的实体不存在,则抛出`EntityNotFoundException` 异常. 此异常大多数由 [repositories](Repositories.md) 抛出.
你同样可以在代码中抛出这些类型的异常(虽然很少需要这样做) 你同样可以在代码中抛出这些类型的异常(虽然很少需要这样做)
## 发送异常详情到客户端
你可以通过 `AbpExceptionHandlingOptions` 类的 `SendExceptionsDetailsToClients` 属性异常发送到客户端:
````csharp
services.Configure<AbpExceptionHandlingOptions>(options =>
{
options.SendExceptionsDetailsToClients = true;
});
````

4
docs/zh-Hans/Getting-Started-React-Native.md

@ -2,7 +2,7 @@
ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序. ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序.
当你按照[入门文档](Getting-Started.md)中所述**创建新应用程序**时,解决方案默认将React Native应用程序包含在 `react-native` 文件夹中. 当你按照[入门文档](Getting-Started.md)中所述**创建新应用程序**时, 你应该使用`-m react-native`选项以在解决方案中包含`react-native`项目.
## 配置你的本地IP地址 ## 配置你的本地IP地址
@ -67,4 +67,4 @@ yarn start
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. 输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序.
应用程序已经启动并执行,你可以基于该启动模板开发应用程序. 应用程序已经启动并执行,你可以基于该启动模板开发应用程序.

9
docs/zh-Hans/Getting-Started.md

@ -62,10 +62,10 @@ dotnet tool update -g Volo.Abp.Cli
使用ABP CLI的 `new` 命令创建新项目: 使用ABP CLI的 `new` 命令创建新项目:
````shell ````shell
abp new Acme.BookStore{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}} abp new Acme.BookStore{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}} --mobile react-native
```` ````
* 此命令还会在解决方案文件夹内创建一个React Native移动应用程序. 如果你不想要它,可以安全地删除它,或者在 `abp new` 命令中指定 `-m none` 选项,以使其完全不包含在解决方案中. * 此命令还会在解决方案文件夹内创建一个React Native移动应用程序. 如果你不想要它,可以安全地删除它或从`abp new`命令中删除`--mobile react-native`选项, 以使其完全不包含在解决方案中.
{{ if UI == "NG" }} {{ if UI == "NG" }}
@ -351,11 +351,8 @@ yarn start
#### 移动开发 #### 移动开发
当你创建一个新的应用程序时.该解决方案默认包含 `react-native`文件夹. 这是一个基础的[React Native](https://reactnative.dev/)启动模板,用于开发与基于ABP的后端集成的移动应用程序. 当你创建一个新的应用程序时. 可以添加`-m react-native`选项以在解决方案中包含 `react-native`项目. 这是一个基础的[React Native](https://reactnative.dev/)启动模板,用于开发与基于ABP的后端集成的移动应用程序.
如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹.
> 你可以在ABP CLI中指定 `-m none` 选项,以使 `react-native` 目录完全不包含在解决方案中
请参阅"[React Native入门](Getting-Started-React-Native.md)"文档了解如何配置和运行React Native应用程序. 请参阅"[React Native入门](Getting-Started-React-Native.md)"文档了解如何配置和运行React Native应用程序.

16
docs/zh-Hans/Startup-Templates/Application.md

@ -57,6 +57,20 @@ abp new Acme.BookStore -u angular
abp new Acme.BookStore -d mongodb abp new Acme.BookStore -d mongodb
```` ````
### 指定移动应用程序框架
该模板支持以下移动应用程序框架:
- `react-native`: React Native
使用 `-m` (or `--mobile`) 选项来指定移动应用程序框架:
````bash
abp new Acme.BookStore -m react-native
````
如果未指定, 则不会创建任何移动应用程序.
## 解决方案结构 ## 解决方案结构
根据命令的选项,会创建略有不同的解决方案结构. 根据命令的选项,会创建略有不同的解决方案结构.
@ -342,7 +356,7 @@ Home模块是一个可延迟加载的模块, 它加载应用程序的根地址.
### React Native ### React Native
解决方案将[React Native](https://reactnative.dev/)应用程序作为默认值包含在 `react-native` 文件夹中. 如果使用 `-m react-native` 选项解决方案将[React Native](https://reactnative.dev/)应用程序作为默认值包含在 `react-native` 文件夹中.
服务器端类似于上面描述的解决方案. `*.HttpApi.Host` 的项目提供 API, 所以 React 本机应用程序使用它. 服务器端类似于上面描述的解决方案. `*.HttpApi.Host` 的项目提供 API, 所以 React 本机应用程序使用它.

14
framework/Volo.Abp.sln

@ -323,6 +323,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws",
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws.Tests", "test\Volo.Abp.BlobStoring.Aws.Tests\Volo.Abp.BlobStoring.Aws.Tests.csproj", "{2CD3B26A-CA81-4279-8D5D-6A594517BB3F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws.Tests", "test\Volo.Abp.BlobStoring.Aws.Tests\Volo.Abp.BlobStoring.Aws.Tests.csproj", "{2CD3B26A-CA81-4279-8D5D-6A594517BB3F}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.GlobalFeatures", "src\Volo.Abp.GlobalFeatures\Volo.Abp.GlobalFeatures.csproj", "{04F44063-C952-403A-815F-EFB778BDA125}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.GlobalFeatures.Tests", "test\Volo.Abp.GlobalFeatures.Tests\Volo.Abp.GlobalFeatures.Tests.csproj", "{231F1581-AA21-44C3-BF27-51EB3AD5355C}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -961,6 +965,14 @@ Global
{2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Debug|Any CPU.Build.0 = Debug|Any CPU {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Release|Any CPU.ActiveCfg = Release|Any CPU {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Release|Any CPU.Build.0 = Release|Any CPU {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Release|Any CPU.Build.0 = Release|Any CPU
{04F44063-C952-403A-815F-EFB778BDA125}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04F44063-C952-403A-815F-EFB778BDA125}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04F44063-C952-403A-815F-EFB778BDA125}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04F44063-C952-403A-815F-EFB778BDA125}.Release|Any CPU.Build.0 = Release|Any CPU
{231F1581-AA21-44C3-BF27-51EB3AD5355C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{231F1581-AA21-44C3-BF27-51EB3AD5355C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{231F1581-AA21-44C3-BF27-51EB3AD5355C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{231F1581-AA21-44C3-BF27-51EB3AD5355C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1124,6 +1136,8 @@ Global
{8E49687A-E69F-49F2-8DB0-428D0883A937} = {447C8A77-E5F0-4538-8687-7383196D04EA} {8E49687A-E69F-49F2-8DB0-428D0883A937} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{50968CDE-1029-4051-B2E5-B69D0ECF2A18} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {50968CDE-1029-4051-B2E5-B69D0ECF2A18} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{2CD3B26A-CA81-4279-8D5D-6A594517BB3F} = {447C8A77-E5F0-4538-8687-7383196D04EA} {2CD3B26A-CA81-4279-8D5D-6A594517BB3F} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{04F44063-C952-403A-815F-EFB778BDA125} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{231F1581-AA21-44C3-BF27-51EB3AD5355C} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.csproj

@ -20,8 +20,12 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Pages\**\*.js" /> <EmbeddedResource Include="Pages\**\*.js" />
<EmbeddedResource Include="Volo\Abp\AspNetCore\Mvc\UI\MultiTenancy\Localization\*.json" /> <EmbeddedResource Include="Volo\Abp\AspNetCore\Mvc\UI\MultiTenancy\Localization\*.json" />
<EmbeddedResource Include="Components\**\*.js" />
<EmbeddedResource Include="Components\**\*.css" />
<Content Remove="Pages\**\*.js" /> <Content Remove="Pages\**\*.js" />
<Content Remove="Volo\Abp\AspNetCore\Mvc\UI\MultiTenancy\Localization\*.json" /> <Content Remove="Volo\Abp\AspNetCore\Mvc\UI\MultiTenancy\Localization\*.json" />
<Content Remove="Components\**\*.js" />
<Content Remove="Components\**\*.css" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

15
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/AbpMvcUiOptions.cs

@ -0,0 +1,15 @@
namespace Volo.Abp.AspNetCore.Mvc.UI
{
public class AbpMvcUiOptions
{
/// <summary>
/// Default value: "/Account/Login".
/// </summary>
public string LoginUrl { get; set; } = "/Account/Login";
/// <summary>
/// Default value: "/Account/Logout".
/// </summary>
public string LogoutUrl { get; set; } = "/Account/Logout";
}
}

1
framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj

@ -21,6 +21,7 @@
<ProjectReference Include="..\Volo.Abp.ApiVersioning.Abstractions\Volo.Abp.ApiVersioning.Abstractions.csproj" /> <ProjectReference Include="..\Volo.Abp.ApiVersioning.Abstractions\Volo.Abp.ApiVersioning.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj" /> <ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore\Volo.Abp.AspNetCore.csproj" /> <ProjectReference Include="..\Volo.Abp.AspNetCore\Volo.Abp.AspNetCore.csproj" />
<ProjectReference Include="..\Volo.Abp.GlobalFeatures\Volo.Abp.GlobalFeatures.csproj" />
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" /> <ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
<ProjectReference Include="..\Volo.Abp.UI\Volo.Abp.UI.csproj" /> <ProjectReference Include="..\Volo.Abp.UI\Volo.Abp.UI.csproj" />
</ItemGroup> </ItemGroup>

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

@ -10,14 +10,12 @@ using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@ -34,6 +32,7 @@ using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Http; using Volo.Abp.Http;
using Volo.Abp.DynamicProxy; using Volo.Abp.DynamicProxy;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Http.Modeling; using Volo.Abp.Http.Modeling;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -46,7 +45,8 @@ namespace Volo.Abp.AspNetCore.Mvc
typeof(AbpLocalizationModule), typeof(AbpLocalizationModule),
typeof(AbpApiVersioningAbstractionsModule), typeof(AbpApiVersioningAbstractionsModule),
typeof(AbpAspNetCoreMvcContractsModule), typeof(AbpAspNetCoreMvcContractsModule),
typeof(AbpUiModule) typeof(AbpUiModule),
typeof(AbpGlobalFeaturesModule)
)] )]
public class AbpAspNetCoreMvcModule : AbpModule public class AbpAspNetCoreMvcModule : AbpModule
{ {
@ -168,7 +168,7 @@ namespace Volo.Abp.AspNetCore.Mvc
context.Services.Replace(ServiceDescriptor.Singleton<IValidationAttributeAdapterProvider, AbpValidationAttributeAdapterProvider>()); context.Services.Replace(ServiceDescriptor.Singleton<IValidationAttributeAdapterProvider, AbpValidationAttributeAdapterProvider>());
context.Services.AddSingleton<ValidationAttributeAdapterProvider>(); context.Services.AddSingleton<ValidationAttributeAdapterProvider>();
Configure<MvcOptions>(mvcOptions => Configure<MvcOptions>(mvcOptions =>
{ {
mvcOptions.AddAbp(context.Services); mvcOptions.AddAbp(context.Services);

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

@ -4,6 +4,7 @@ using Volo.Abp.AspNetCore.Mvc.Auditing;
using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Features; using Volo.Abp.AspNetCore.Mvc.Features;
using Volo.Abp.AspNetCore.Mvc.GlobalFeatures;
using Volo.Abp.AspNetCore.Mvc.ModelBinding; using Volo.Abp.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.AspNetCore.Mvc.Response; using Volo.Abp.AspNetCore.Mvc.Response;
using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Uow;
@ -29,6 +30,7 @@ namespace Volo.Abp.AspNetCore.Mvc
private static void AddActionFilters(MvcOptions options) private static void AddActionFilters(MvcOptions options)
{ {
options.Filters.AddService(typeof(GlobalFeatureActionFilter));
options.Filters.AddService(typeof(AbpAuditActionFilter)); options.Filters.AddService(typeof(AbpAuditActionFilter));
options.Filters.AddService(typeof(AbpNoContentActionFilter)); options.Filters.AddService(typeof(AbpNoContentActionFilter));
options.Filters.AddService(typeof(AbpFeatureActionFilter)); options.Filters.AddService(typeof(AbpFeatureActionFilter));
@ -39,6 +41,7 @@ namespace Volo.Abp.AspNetCore.Mvc
private static void AddPageFilters(MvcOptions options) private static void AddPageFilters(MvcOptions options)
{ {
options.Filters.AddService(typeof(GlobalFeaturePageFilter));
options.Filters.AddService(typeof(AbpExceptionPageFilter)); options.Filters.AddService(typeof(AbpExceptionPageFilter));
options.Filters.AddService(typeof(AbpAuditPageFilter)); options.Filters.AddService(typeof(AbpAuditPageFilter));
options.Filters.AddService(typeof(AbpFeaturePageFilter)); options.Filters.AddService(typeof(AbpFeaturePageFilter));
@ -58,4 +61,4 @@ namespace Volo.Abp.AspNetCore.Mvc
); );
} }
} }
} }

74
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Http; using Volo.Abp.Http;
using Volo.Abp.Http.Modeling; using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Http.ProxyScripting.Generators;
@ -142,18 +143,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
if (controller.ApiExplorer.IsVisible == null) if (controller.ApiExplorer.IsVisible == null)
{ {
var controllerType = controller.ControllerType.AsType(); controller.ApiExplorer.IsVisible = IsVisibleRemoteService(controller.ControllerType);
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType.GetTypeInfo());
if (remoteServiceAtt != null)
{
controller.ApiExplorer.IsVisible =
remoteServiceAtt.IsEnabledFor(controllerType) &&
remoteServiceAtt.IsMetadataEnabledFor(controllerType);
}
else
{
controller.ApiExplorer.IsVisible = true;
}
} }
foreach (var action in controller.Actions) foreach (var action in controller.Actions)
@ -164,16 +154,18 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual void ConfigureApiExplorer(ActionModel action) protected virtual void ConfigureApiExplorer(ActionModel action)
{ {
if (action.ApiExplorer.IsVisible == null) if (action.ApiExplorer.IsVisible != null)
{ {
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod); return;
if (remoteServiceAtt != null)
{
action.ApiExplorer.IsVisible =
remoteServiceAtt.IsEnabledFor(action.ActionMethod) &&
remoteServiceAtt.IsMetadataEnabledFor(action.ActionMethod);
}
} }
var visible = IsVisibleRemoteServiceMethod(action.ActionMethod);
if (visible == null)
{
return;
}
action.ApiExplorer.IsVisible = visible;
} }
protected virtual void ConfigureSelector(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration) protected virtual void ConfigureSelector(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration)
@ -397,5 +389,45 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
{ {
return typeof(IRemoteService).GetTypeInfo().IsAssignableFrom(controllerType); return typeof(IRemoteService).GetTypeInfo().IsAssignableFrom(controllerType);
} }
protected virtual bool IsVisibleRemoteService(Type controllerType)
{
if (!IsGlobalFeatureEnabled(controllerType))
{
return false;
}
var attribute = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType);
if (attribute == null)
{
return true;
}
return attribute.IsEnabledFor(controllerType) &&
attribute.IsMetadataEnabledFor(controllerType);
}
protected virtual bool? IsVisibleRemoteServiceMethod(MethodInfo method)
{
var attribute = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(method);
if (attribute == null)
{
return null;
}
return attribute.IsEnabledFor(method) &&
attribute.IsMetadataEnabledFor(method);
}
protected virtual bool IsGlobalFeatureEnabled(Type controllerType)
{
var attribute = ReflectionHelper.GetSingleAttributeOrDefault<RequiresGlobalFeatureAttribute>(controllerType);
if (attribute == null)
{
return true;
}
return GlobalFeatureManager.Instance.IsEnabled(attribute.GetFeatureName());
}
} }
} }

47
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/GlobalFeatureActionFilter.cs

@ -0,0 +1,47 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
public class GlobalFeatureActionFilter : IAsyncActionFilter, ITransientDependency
{
public ILogger<GlobalFeatureActionFilter> Logger { get; set; }
public GlobalFeatureActionFilter()
{
Logger = NullLogger<GlobalFeatureActionFilter>.Instance;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ActionDescriptor.IsControllerAction())
{
await next();
return;
}
if (!IsGlobalFeatureEnabled(context.Controller.GetType(), out var attribute))
{
Logger.LogWarning($"The '{context.Controller.GetType().FullName}' controller needs to enable '{attribute.Name}' feature.");
context.Result = new NotFoundResult();
return;
}
await next();
}
protected virtual bool IsGlobalFeatureEnabled(Type controllerType, out RequiresGlobalFeatureAttribute attribute)
{
attribute = ReflectionHelper.GetSingleAttributeOrDefault<RequiresGlobalFeatureAttribute>(controllerType);
return attribute == null || GlobalFeatureManager.Instance.IsEnabled(attribute.GetFeatureName());
}
}
}

52
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/GlobalFeaturePageFilter.cs

@ -0,0 +1,52 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
public class GlobalFeaturePageFilter: IAsyncPageFilter, ITransientDependency
{
public ILogger<GlobalFeaturePageFilter> Logger { get; set; }
public GlobalFeaturePageFilter()
{
Logger = NullLogger<GlobalFeaturePageFilter>.Instance;
}
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
return Task.CompletedTask;
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context.HandlerInstance == null || !context.ActionDescriptor.IsPageAction())
{
await next();
return;
}
if (!IsGlobalFeatureEnabled(context.HandlerInstance.GetType(), out var attribute))
{
Logger.LogWarning($"The '{context.HandlerInstance.GetType().FullName}' page needs to enable '{attribute.Name}' feature.");
context.Result = new NotFoundResult();
return;
}
await next();
}
protected virtual bool IsGlobalFeatureEnabled(Type controllerType, out RequiresGlobalFeatureAttribute attribute)
{
attribute = ReflectionHelper.GetSingleAttributeOrDefault<RequiresGlobalFeatureAttribute>(controllerType);
return attribute == null || GlobalFeatureManager.Instance.IsEnabled(attribute.GetFeatureName());
}
}
}

51
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs

@ -50,35 +50,38 @@ namespace Microsoft.AspNetCore.RequestLocalization
{ {
using (await _syncSemaphore.LockAsync()) using (await _syncSemaphore.LockAsync())
{ {
using (var serviceScope = _serviceProviderFactory.CreateScope()) if (_requestLocalizationOptions == null)
{ {
var languageProvider = serviceScope.ServiceProvider.GetRequiredService<ILanguageProvider>(); using (var serviceScope = _serviceProviderFactory.CreateScope())
var settingProvider = serviceScope.ServiceProvider.GetRequiredService<ISettingProvider>(); {
var languageProvider = serviceScope.ServiceProvider.GetRequiredService<ILanguageProvider>();
var settingProvider = serviceScope.ServiceProvider.GetRequiredService<ISettingProvider>();
var languages = await languageProvider.GetLanguagesAsync(); var languages = await languageProvider.GetLanguagesAsync();
var defaultLanguage = await settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage); var defaultLanguage = await settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage);
var options = !languages.Any() var options = !languages.Any()
? new RequestLocalizationOptions() ? new RequestLocalizationOptions()
: new RequestLocalizationOptions : new RequestLocalizationOptions
{ {
DefaultRequestCulture = DefaultGetRequestCulture(defaultLanguage, languages), DefaultRequestCulture = DefaultGetRequestCulture(defaultLanguage, languages),
SupportedCultures = languages SupportedCultures = languages
.Select(l => l.CultureName) .Select(l => l.CultureName)
.Distinct() .Distinct()
.Select(c => new CultureInfo(c)) .Select(c => new CultureInfo(c))
.ToArray(), .ToArray(),
SupportedUICultures = languages SupportedUICultures = languages
.Select(l => l.UiCultureName) .Select(l => l.UiCultureName)
.Distinct() .Distinct()
.Select(c => new CultureInfo(c)) .Select(c => new CultureInfo(c))
.ToArray() .ToArray()
}; };
_optionsAction?.Invoke(options); _optionsAction?.Invoke(options);
_requestLocalizationOptions = options; _requestLocalizationOptions = options;
}
} }
} }
} }
@ -98,4 +101,4 @@ namespace Microsoft.AspNetCore.RequestLocalization
return new RequestCulture(cultureName, uiCultureName); return new RequestCulture(cultureName, uiCultureName);
} }
} }
} }

7
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingOptions.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.AspNetCore.ExceptionHandling
{
public class AbpExceptionHandlingOptions
{
public bool SendExceptionsDetailsToClients { get; set; } = false;
}
}

9
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs

@ -19,20 +19,21 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
{ {
public class DefaultExceptionToErrorInfoConverter : IExceptionToErrorInfoConverter, ITransientDependency public class DefaultExceptionToErrorInfoConverter : IExceptionToErrorInfoConverter, ITransientDependency
{ {
public bool SendAllExceptionsToClients { get; set; } = false;
protected AbpExceptionLocalizationOptions LocalizationOptions { get; } protected AbpExceptionLocalizationOptions LocalizationOptions { get; }
protected AbpExceptionHandlingOptions ExceptionHandlingOptions { get; }
protected IStringLocalizerFactory StringLocalizerFactory { get; } protected IStringLocalizerFactory StringLocalizerFactory { get; }
protected IStringLocalizer<AbpUiResource> L { get; } protected IStringLocalizer<AbpUiResource> L { get; }
protected IServiceProvider ServiceProvider { get; } protected IServiceProvider ServiceProvider { get; }
public DefaultExceptionToErrorInfoConverter( public DefaultExceptionToErrorInfoConverter(
IOptions<AbpExceptionLocalizationOptions> localizationOptions, IOptions<AbpExceptionLocalizationOptions> localizationOptions,
IOptions<AbpExceptionHandlingOptions> exceptionHandlingOptions,
IStringLocalizerFactory stringLocalizerFactory, IStringLocalizerFactory stringLocalizerFactory,
IStringLocalizer<AbpUiResource> abpUiStringLocalizer, IStringLocalizer<AbpUiResource> abpUiStringLocalizer,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
{ {
ServiceProvider = serviceProvider; ServiceProvider = serviceProvider;
ExceptionHandlingOptions = exceptionHandlingOptions.Value;
StringLocalizerFactory = stringLocalizerFactory; StringLocalizerFactory = stringLocalizerFactory;
L = abpUiStringLocalizer; L = abpUiStringLocalizer;
LocalizationOptions = localizationOptions.Value; LocalizationOptions = localizationOptions.Value;
@ -52,7 +53,7 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
protected virtual RemoteServiceErrorInfo CreateErrorInfoWithoutCode(Exception exception) protected virtual RemoteServiceErrorInfo CreateErrorInfoWithoutCode(Exception exception)
{ {
if (SendAllExceptionsToClients) if (ExceptionHandlingOptions.SendExceptionsDetailsToClients)
{ {
return CreateDetailedErrorInfoFromException(exception); return CreateDetailedErrorInfoFromException(exception);
} }
@ -293,4 +294,4 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
return detailBuilder.ToString(); return detailBuilder.ToString();
} }
} }
} }

3
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs

@ -400,7 +400,8 @@ namespace Volo.Abp.AspNetCore.VirtualFileSystem
{ {
"/Pages", "/Pages",
"/Views", "/Views",
"/Themes" "/Themes",
"/Components"
}; };
AllowedExtraWebContentFileExtensions = ContentTypeMaps.Select(x => x.Key).ToList(); AllowedExtraWebContentFileExtensions = ContentTypeMaps.Select(x => x.Key).ToList();

0
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditScope.cs → framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogScope.cs

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -294,7 +294,7 @@ namespace Volo.Abp.Cli.Commands
protected virtual MobileApp GetMobilePreference(CommandLineArgs commandLineArgs) protected virtual MobileApp GetMobilePreference(CommandLineArgs commandLineArgs)
{ {
var optionValue = commandLineArgs.Options.GetOrNull(Options.Mobile.Short, Options.Mobile.Long); var optionValue = commandLineArgs.Options.GetOrNull(Options.Mobile.Short, Options.Mobile.Long);
var template = commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long);
switch (optionValue) switch (optionValue)
{ {
case "none": case "none":
@ -302,7 +302,7 @@ namespace Volo.Abp.Cli.Commands
case "react-native": case "react-native":
return MobileApp.ReactNative; return MobileApp.ReactNative;
default: default:
return ConsoleTemplate.TemplateName == template ? MobileApp.None : MobileApp.ReactNative; return MobileApp.None;
} }
} }

13
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs

@ -53,9 +53,6 @@ namespace Volo.Abp.Cli.Commands
private async Task UpdateNugetPackages(CommandLineArgs commandLineArgs, string directory) private async Task UpdateNugetPackages(CommandLineArgs commandLineArgs, string directory)
{ {
var includePreviews = commandLineArgs
.Options
.GetOrNull(Options.IncludePreviews.Short, Options.IncludePreviews.Long) != null;
var solution = commandLineArgs.Options.GetOrNull(Options.SolutionName.Short, Options.SolutionName.Long); var solution = commandLineArgs.Options.GetOrNull(Options.SolutionName.Short, Options.SolutionName.Long);
if (solution.IsNullOrWhiteSpace()) if (solution.IsNullOrWhiteSpace())
@ -69,7 +66,7 @@ namespace Volo.Abp.Cli.Commands
{ {
var solutionName = Path.GetFileName(solution).RemovePostFix(".sln"); var solutionName = Path.GetFileName(solution).RemovePostFix(".sln");
await _nugetPackagesVersionUpdater.UpdateSolutionAsync(solution, includePreviews, checkAll: checkAll); await _nugetPackagesVersionUpdater.UpdateSolutionAsync(solution, checkAll: checkAll);
Logger.LogInformation($"Volo packages are updated in {solutionName} solution."); Logger.LogInformation($"Volo packages are updated in {solutionName} solution.");
return; return;
@ -81,7 +78,7 @@ namespace Volo.Abp.Cli.Commands
{ {
var projectName = Path.GetFileName(project).RemovePostFix(".csproj"); var projectName = Path.GetFileName(project).RemovePostFix(".csproj");
await _nugetPackagesVersionUpdater.UpdateProjectAsync(project, includePreviews, checkAll: checkAll); await _nugetPackagesVersionUpdater.UpdateProjectAsync(project, checkAll: checkAll);
Logger.LogInformation($"Volo packages are updated in {projectName} project."); Logger.LogInformation($"Volo packages are updated in {projectName} project.");
return; return;
@ -141,12 +138,6 @@ namespace Volo.Abp.Cli.Commands
public const string Long = "solution-name"; public const string Long = "solution-name";
} }
public static class IncludePreviews
{
public const string Short = "p";
public const string Long = "include-previews";
}
public static class Packages public static class Packages
{ {
public const string Npm = "npm"; public const string Npm = "npm";

14
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationAdder.cs

@ -7,22 +7,12 @@ namespace Volo.Abp.Cli.ProjectModification
{ {
public class EfCoreMigrationAdder : ITransientDependency public class EfCoreMigrationAdder : ITransientDependency
{ {
public void AddMigration(string csprojFile, string module, string startupProject, bool updateDatabase = true) public void AddMigration(string dbMigrationsCsprojFile, string module, string startupProject)
{ {
var moduleName = ParseModuleName(module); var moduleName = ParseModuleName(module);
var migrationName = "Added_" + moduleName + "_Module" + GetUniquePostFix(); var migrationName = "Added_" + moduleName + "_Module" + GetUniquePostFix();
CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(csprojFile) + "\" && dotnet ef migrations add " + migrationName + GetStartupProjectOption(startupProject)); CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(dbMigrationsCsprojFile) + "\" && dotnet ef migrations add " + migrationName + GetStartupProjectOption(startupProject));
if (updateDatabase)
{
UpdateDatabase(csprojFile, startupProject);
}
}
protected void UpdateDatabase(string csprojFile, string startupProject)
{
CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(csprojFile) + "\" && dotnet ef database update" + GetStartupProjectOption(startupProject));
} }
protected virtual string ParseModuleName(string fullModuleName) protected virtual string ParseModuleName(string fullModuleName)

31
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using Volo.Abp.Cli.Commands.Services; using Volo.Abp.Cli.Commands.Services;
using Volo.Abp.Cli.Http; using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.ProjectBuilding; using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Json; using Volo.Abp.Json;
@ -214,6 +215,11 @@ namespace Volo.Abp.Cli.ProjectModification
{ {
if (string.IsNullOrWhiteSpace(module.EfCoreConfigureMethodName)) if (string.IsNullOrWhiteSpace(module.EfCoreConfigureMethodName))
{ {
if (!skipDbMigrations)
{
RunMigrator(projectFiles);
}
return; return;
} }
@ -227,6 +233,12 @@ namespace Volo.Abp.Cli.ProjectModification
if (dbMigrationsProject == null) if (dbMigrationsProject == null)
{ {
Logger.LogDebug("Solution doesn't have a \".DbMigrations\" project."); Logger.LogDebug("Solution doesn't have a \".DbMigrations\" project.");
if (!skipDbMigrations)
{
RunMigrator(projectFiles);
}
return; return;
} }
@ -240,9 +252,24 @@ namespace Volo.Abp.Cli.ProjectModification
var addedNewBuilder = DbContextFileBuilderConfigureAdder.Add(dbContextFile, module.EfCoreConfigureMethodName); var addedNewBuilder = DbContextFileBuilderConfigureAdder.Add(dbContextFile, module.EfCoreConfigureMethodName);
if (addedNewBuilder && !skipDbMigrations) if (!skipDbMigrations)
{
if (addedNewBuilder)
{
EfCoreMigrationAdder.AddMigration(dbMigrationsProject, module.Name, startupProject);
}
RunMigrator(projectFiles);
}
}
protected virtual async Task RunMigrator(string[] projectFiles)
{
var dbMigratorProject = projectFiles.FirstOrDefault(p => p.EndsWith(".DbMigrator.csproj"));
if (!string.IsNullOrEmpty(dbMigratorProject))
{ {
EfCoreMigrationAdder.AddMigration(dbMigrationsProject, module.Name, startupProject); CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(dbMigratorProject) + "\" && dotnet run");
} }
} }

269
framework/src/Volo.Abp.Core/System/Linq/PredicateOperator.cs

@ -0,0 +1,269 @@
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using JetBrains.Annotations;
namespace System.Linq
{
// Codes below are taken from https://github.com/scottksmith95/LINQKit project.
/// <summary> The Predicate Operator </summary>
public enum PredicateOperator
{
/// <summary> The "Or" </summary>
Or,
/// <summary> The "And" </summary>
And
}
/// <summary>
/// See http://www.albahari.com/expressions for information and examples.
/// </summary>
public static class PredicateBuilder
{
private class RebindParameterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldParameter;
private readonly ParameterExpression _newParameter;
public RebindParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == _oldParameter ? _newParameter : base.VisitParameter(node);
}
}
/// <summary> Start an expression </summary>
public static ExpressionStarter<T> New<T>(Expression<Func<T, bool>> expr = null)
{
return new ExpressionStarter<T>(expr);
}
/// <summary> Create an expression with a stub expression true or false to use when the expression is not yet started. </summary>
public static ExpressionStarter<T> New<T>(bool defaultExpression)
{
return new ExpressionStarter<T>(defaultExpression);
}
/// <summary> OR </summary>
public static Expression<Func<T, bool>> Or<T>([NotNull] this Expression<Func<T, bool>> expr1,
[NotNull] Expression<Func<T, bool>> expr2)
{
var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, expr2Body), expr1.Parameters);
}
/// <summary> AND </summary>
public static Expression<Func<T, bool>> And<T>([NotNull] this Expression<Func<T, bool>> expr1,
[NotNull] Expression<Func<T, bool>> expr2)
{
var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2Body), expr1.Parameters);
}
/// <summary>
/// Extends the specified source Predicate with another Predicate and the specified PredicateOperator.
/// </summary>
/// <typeparam name="T">The type</typeparam>
/// <param name="first">The source Predicate.</param>
/// <param name="second">The second Predicate.</param>
/// <param name="operator">The Operator (can be "And" or "Or").</param>
/// <returns>Expression{Func{T, bool}}</returns>
public static Expression<Func<T, bool>> Extend<T>([NotNull] this Expression<Func<T, bool>> first,
[NotNull] Expression<Func<T, bool>> second, PredicateOperator @operator = PredicateOperator.Or)
{
return @operator == PredicateOperator.Or ? first.Or(second) : first.And(second);
}
/// <summary>
/// Extends the specified source Predicate with another Predicate and the specified PredicateOperator.
/// </summary>
/// <typeparam name="T">The type</typeparam>
/// <param name="first">The source Predicate.</param>
/// <param name="second">The second Predicate.</param>
/// <param name="operator">The Operator (can be "And" or "Or").</param>
/// <returns>Expression{Func{T, bool}}</returns>
public static Expression<Func<T, bool>> Extend<T>([NotNull] this ExpressionStarter<T> first,
[NotNull] Expression<Func<T, bool>> second, PredicateOperator @operator = PredicateOperator.Or)
{
return @operator == PredicateOperator.Or ? first.Or(second) : first.And(second);
}
}
/// <summary>
/// ExpressionStarter{T} which eliminates the default 1=0 or 1=1 stub expressions
/// </summary>
/// <typeparam name="T">The type</typeparam>
public class ExpressionStarter<T>
{
public ExpressionStarter() : this(false)
{
}
public ExpressionStarter(bool defaultExpression)
{
if (defaultExpression)
{
DefaultExpression = f => true;
}
else
{
DefaultExpression = f => false;
}
}
public ExpressionStarter(Expression<Func<T, bool>> exp) : this(false)
{
_predicate = exp;
}
/// <summary>The actual Predicate. It can only be set by calling Start.</summary>
private Expression<Func<T, bool>> Predicate =>
(IsStarted || !UseDefaultExpression) ? _predicate : DefaultExpression;
private Expression<Func<T, bool>> _predicate;
/// <summary>Determines if the predicate is started.</summary>
public bool IsStarted => _predicate != null;
/// <summary> A default expression to use only when the expression is null </summary>
public bool UseDefaultExpression => DefaultExpression != null;
/// <summary>The default expression</summary>
public Expression<Func<T, bool>> DefaultExpression { get; set; }
/// <summary>Set the Expression predicate</summary>
/// <param name="exp">The first expression</param>
public Expression<Func<T, bool>> Start(Expression<Func<T, bool>> exp)
{
if (IsStarted)
{
throw new Exception("Predicate cannot be started again.");
}
return _predicate = exp;
}
/// <summary>Or</summary>
public Expression<Func<T, bool>> Or([NotNull] Expression<Func<T, bool>> expr2)
{
return (IsStarted) ? _predicate = Predicate.Or(expr2) : Start(expr2);
}
/// <summary>And</summary>
public Expression<Func<T, bool>> And([NotNull] Expression<Func<T, bool>> expr2)
{
return (IsStarted) ? _predicate = Predicate.And(expr2) : Start(expr2);
}
/// <summary> Show predicate string </summary>
public override string ToString()
{
return Predicate?.ToString();
}
#region Implicit Operators
/// <summary>
/// Allows this object to be implicitely converted to an Expression{Func{T, bool}}.
/// </summary>
/// <param name="right"></param>
public static implicit operator Expression<Func<T, bool>>(ExpressionStarter<T> right)
{
return right?.Predicate;
}
/// <summary>
/// Allows this object to be implicitely converted to an Expression{Func{T, bool}}.
/// </summary>
/// <param name="right"></param>
public static implicit operator Func<T, bool>(ExpressionStarter<T> right)
{
return right == null ? null :
(right.IsStarted || right.UseDefaultExpression) ? right.Predicate.Compile() : null;
}
/// <summary>
/// Allows this object to be implicitely converted to an Expression{Func{T, bool}}.
/// </summary>
/// <param name="right"></param>
public static implicit operator ExpressionStarter<T>(Expression<Func<T, bool>> right)
{
return right == null ? null : new ExpressionStarter<T>(right);
}
#endregion
#region Implement Expression<TDelagate> methods and properties
#if !(NET35)
/// <summary></summary>
public Func<T, bool> Compile()
{
return Predicate.Compile();
}
#endif
#if !(NET35 || WINDOWS_APP || NETSTANDARD || PORTABLE || PORTABLE40 || UAP)
/// <summary></summary>
public Func<T, bool> Compile(DebugInfoGenerator debugInfoGenerator) { return Predicate.Compile(debugInfoGenerator); }
/// <summary></summary>
public Expression<Func<T, bool>> Update(Expression body, IEnumerable<ParameterExpression> parameters) { return Predicate.Update(body, parameters); }
#endif
#endregion
#region Implement LamdaExpression methods and properties
/// <summary></summary>
public Expression Body => Predicate.Body;
/// <summary></summary>
public ExpressionType NodeType => Predicate.NodeType;
/// <summary></summary>
public ReadOnlyCollection<ParameterExpression> Parameters => Predicate.Parameters;
/// <summary></summary>
public Type Type => Predicate.Type;
#if !(NET35)
/// <summary></summary>
public string Name => Predicate.Name;
/// <summary></summary>
public Type ReturnType => Predicate.ReturnType;
/// <summary></summary>
public bool TailCall => Predicate.TailCall;
#endif
#if !(NET35 || WINDOWS_APP || NETSTANDARD || PORTABLE || PORTABLE40 || UAP)
/// <summary></summary>
public void CompileToMethod(MethodBuilder method) { Predicate.CompileToMethod(method); }
/// <summary></summary>
public void CompileToMethod(MethodBuilder method, DebugInfoGenerator debugInfoGenerator) { Predicate.CompileToMethod(method, debugInfoGenerator); }
#endif
#endregion
#region Implement Expression methods and properties
#if !(NET35)
/// <summary></summary>
public virtual bool CanReduce => Predicate.CanReduce;
#endif
#endregion
}
}

18
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs

@ -1,10 +1,11 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs; using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.Emailing namespace Volo.Abp.Emailing
{ {
public class BackgroundEmailSendingJob : BackgroundJob<BackgroundEmailSendingJobArgs>, ITransientDependency public class BackgroundEmailSendingJob : AsyncBackgroundJob<BackgroundEmailSendingJobArgs>, ITransientDependency
{ {
protected IEmailSender EmailSender { get; } protected IEmailSender EmailSender { get; }
@ -13,9 +14,16 @@ namespace Volo.Abp.Emailing
EmailSender = emailSender; EmailSender = emailSender;
} }
public override void Execute(BackgroundEmailSendingJobArgs args) public override async Task ExecuteAsync(BackgroundEmailSendingJobArgs args)
{ {
AsyncHelper.RunSync(() => EmailSender.SendAsync(args.To, args.Subject, args.Body, args.IsBodyHtml)); if (args.From.IsNullOrWhiteSpace())
{
await EmailSender.SendAsync(args.To, args.Subject, args.Body, args.IsBodyHtml);
}
else
{
await EmailSender.SendAsync(args.From, args.To, args.Subject, args.Body, args.IsBodyHtml);
}
} }
} }
} }

6
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJobArgs.cs

@ -5,12 +5,14 @@ namespace Volo.Abp.Emailing
[Serializable] [Serializable]
public class BackgroundEmailSendingJobArgs public class BackgroundEmailSendingJobArgs
{ {
public string From { get; set; }
public string To { get; set; } public string To { get; set; }
public string Subject { get; set; } public string Subject { get; set; }
public string Body { get; set; } public string Body { get; set; }
/// <summary> /// <summary>
/// Default: true. /// Default: true.
/// </summary> /// </summary>
@ -18,4 +20,4 @@ namespace Volo.Abp.Emailing
//TODO: Add other properties and attachments //TODO: Add other properties and attachments
} }
} }

22
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs

@ -69,6 +69,26 @@ namespace Volo.Abp.Emailing
); );
} }
public async Task QueueAsync(string from, string to, string subject, string body, bool isBodyHtml = true)
{
if (!BackgroundJobManager.IsAvailable())
{
await SendAsync(from, to, subject, body, isBodyHtml);
return;
}
await BackgroundJobManager.EnqueueAsync(
new BackgroundEmailSendingJobArgs
{
From = from,
To = to,
Subject = subject,
Body = body,
IsBodyHtml = isBodyHtml
}
);
}
/// <summary> /// <summary>
/// Should implement this method to send email in derived classes. /// Should implement this method to send email in derived classes.
/// </summary> /// </summary>
@ -108,4 +128,4 @@ namespace Volo.Abp.Emailing
} }
} }
} }
} }

40
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/IEmailSender.cs

@ -11,12 +11,23 @@ namespace Volo.Abp.Emailing
/// <summary> /// <summary>
/// Sends an email. /// Sends an email.
/// </summary> /// </summary>
Task SendAsync(string to, string subject, string body, bool isBodyHtml = true); Task SendAsync(
string to,
string subject,
string body,
bool isBodyHtml = true
);
/// <summary> /// <summary>
/// Sends an email. /// Sends an email.
/// </summary> /// </summary>
Task SendAsync(string from, string to, string subject, string body, bool isBodyHtml = true); Task SendAsync(
string from,
string to,
string subject,
string body,
bool isBodyHtml = true
);
/// <summary> /// <summary>
/// Sends an email. /// Sends an email.
@ -24,14 +35,33 @@ namespace Volo.Abp.Emailing
/// <param name="mail">Mail to be sent</param> /// <param name="mail">Mail to be sent</param>
/// <param name="normalize"> /// <param name="normalize">
/// Should normalize email? /// Should normalize email?
/// If true, it sets sender address/name if it's not set before and makes mail encoding UTF-8. /// If true, it sets sender address/name if it's not set before and makes mail encoding UTF-8.
/// </param> /// </param>
Task SendAsync(MailMessage mail, bool normalize = true); Task SendAsync(
MailMessage mail,
bool normalize = true
);
/// <summary> /// <summary>
/// Adds an email to queue to send via background jobs. /// Adds an email to queue to send via background jobs.
/// </summary> /// </summary>
Task QueueAsync(string to, string subject, string body, bool isBodyHtml = true); Task QueueAsync(
string to,
string subject,
string body,
bool isBodyHtml = true
);
/// <summary>
/// Adds an email to queue to send via background jobs.
/// </summary>
Task QueueAsync(
string from,
string to,
string subject,
string body,
bool isBodyHtml = true
);
//TODO: Add other Queue methods too. Problem: MailMessage is not serializable so can not be used in background jobs. //TODO: Add other Queue methods too. Problem: MailMessage is not serializable so can not be used in background jobs.
} }

1
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Smtp/SmtpEmailSender.cs

@ -4,7 +4,6 @@ using System.Net.Mail;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs; using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.Emailing.Smtp namespace Volo.Abp.Emailing.Smtp
{ {

2
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs

@ -6,6 +6,7 @@ namespace Volo.Abp.ObjectExtending
{ {
public static class EfCoreObjectExtensionInfoExtensions public static class EfCoreObjectExtensionInfoExtensions
{ {
[Obsolete("Use MapEfCoreProperty with EntityTypeAndPropertyBuildAction parameters.")]
public static ObjectExtensionInfo MapEfCoreProperty<TProperty>( public static ObjectExtensionInfo MapEfCoreProperty<TProperty>(
[NotNull] this ObjectExtensionInfo objectExtensionInfo, [NotNull] this ObjectExtensionInfo objectExtensionInfo,
[NotNull] string propertyName, [NotNull] string propertyName,
@ -18,6 +19,7 @@ namespace Volo.Abp.ObjectExtending
); );
} }
[Obsolete("Use MapEfCoreProperty with EntityTypeAndPropertyBuildAction parameters.")]
public static ObjectExtensionInfo MapEfCoreProperty( public static ObjectExtensionInfo MapEfCoreProperty(
[NotNull] this ObjectExtensionInfo objectExtensionInfo, [NotNull] this ObjectExtensionInfo objectExtensionInfo,
[NotNull] Type propertyType, [NotNull] Type propertyType,

2
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs

@ -39,6 +39,7 @@ namespace Volo.Abp.ObjectExtending
); );
} }
[Obsolete("Use MapEfCoreProperty with EntityTypeAndPropertyBuildAction parameters.")]
public static ObjectExtensionManager MapEfCoreProperty<TEntity, TProperty>( public static ObjectExtensionManager MapEfCoreProperty<TEntity, TProperty>(
[NotNull] this ObjectExtensionManager objectExtensionManager, [NotNull] this ObjectExtensionManager objectExtensionManager,
[NotNull] string propertyName, [NotNull] string propertyName,
@ -53,6 +54,7 @@ namespace Volo.Abp.ObjectExtending
); );
} }
[Obsolete("Use MapEfCoreProperty with EntityTypeAndPropertyBuildAction parameters.")]
public static ObjectExtensionManager MapEfCoreProperty( public static ObjectExtensionManager MapEfCoreProperty(
[NotNull] this ObjectExtensionManager objectExtensionManager, [NotNull] this ObjectExtensionManager objectExtensionManager,
[NotNull] Type entityType, [NotNull] Type entityType,

3
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs

@ -23,6 +23,7 @@ namespace Volo.Abp.ObjectExtending
return propertyExtension; return propertyExtension;
} }
[Obsolete("Use MapEfCore with EntityTypeAndPropertyBuildAction parameters.")]
[NotNull] [NotNull]
public static ObjectExtensionPropertyInfo MapEfCore( public static ObjectExtensionPropertyInfo MapEfCore(
[NotNull] this ObjectExtensionPropertyInfo propertyExtension, [NotNull] this ObjectExtensionPropertyInfo propertyExtension,
@ -49,7 +50,7 @@ namespace Volo.Abp.ObjectExtending
propertyExtension.Configuration[EfCorePropertyConfigurationName] = propertyExtension.Configuration[EfCorePropertyConfigurationName] =
new ObjectExtensionPropertyInfoEfCoreMappingOptions( new ObjectExtensionPropertyInfoEfCoreMappingOptions(
propertyExtension, propertyExtension,
entityTypeAndPropertyBuildAction: entityTypeAndPropertyBuildAction entityTypeAndPropertyBuildAction
); );
return propertyExtension; return propertyExtension;

17
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs

@ -12,12 +12,14 @@ namespace Volo.Abp.ObjectExtending
[NotNull] [NotNull]
public ObjectExtensionInfo ObjectExtension => ExtensionProperty.ObjectExtension; public ObjectExtensionInfo ObjectExtension => ExtensionProperty.ObjectExtension;
[Obsolete("Use EntityTypeAndPropertyBuildAction property.")]
[CanBeNull] [CanBeNull]
public Action<PropertyBuilder> PropertyBuildAction { get; set; } public Action<PropertyBuilder> PropertyBuildAction { get; set; }
[CanBeNull] [CanBeNull]
public Action<EntityTypeBuilder, PropertyBuilder> EntityTypeAndPropertyBuildAction { get; set; } public Action<EntityTypeBuilder, PropertyBuilder> EntityTypeAndPropertyBuildAction { get; set; }
[Obsolete("Use other constructors.")]
public ObjectExtensionPropertyInfoEfCoreMappingOptions( public ObjectExtensionPropertyInfoEfCoreMappingOptions(
[NotNull] ObjectExtensionPropertyInfo extensionProperty, [NotNull] ObjectExtensionPropertyInfo extensionProperty,
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null, [CanBeNull] Action<PropertyBuilder> propertyBuildAction = null,
@ -28,5 +30,20 @@ namespace Volo.Abp.ObjectExtending
PropertyBuildAction = propertyBuildAction; PropertyBuildAction = propertyBuildAction;
EntityTypeAndPropertyBuildAction = entityTypeAndPropertyBuildAction; EntityTypeAndPropertyBuildAction = entityTypeAndPropertyBuildAction;
} }
public ObjectExtensionPropertyInfoEfCoreMappingOptions(
[NotNull] ObjectExtensionPropertyInfo extensionProperty)
{
ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty));
}
public ObjectExtensionPropertyInfoEfCoreMappingOptions(
[NotNull] ObjectExtensionPropertyInfo extensionProperty,
[CanBeNull] Action<EntityTypeBuilder, PropertyBuilder> entityTypeAndPropertyBuildAction)
{
ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty));
EntityTypeAndPropertyBuildAction = entityTypeAndPropertyBuildAction;
}
} }
} }

3
framework/src/Volo.Abp.GlobalFeatures/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.GlobalFeatures/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

3
framework/src/Volo.Abp.GlobalFeatures/Properties/AssemblyInfo.cs

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("Volo.Abp.GlobalFeatures.Tests")]

21
framework/src/Volo.Abp.GlobalFeatures/Volo.Abp.GlobalFeatures.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.GlobalFeatures</AssemblyName>
<PackageId>Volo.Abp.GlobalFeatures</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
</ItemGroup>
</Project>

9
framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/AbpGlobalFeaturesModule.cs

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.GlobalFeatures
{
public class AbpGlobalFeaturesModule : AbpModule
{
}
}

51
framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeature.cs

@ -0,0 +1,51 @@
using JetBrains.Annotations;
namespace Volo.Abp.GlobalFeatures
{
public abstract class GlobalFeature
{
[NotNull]
public GlobalModuleFeatures Module { get; }
[NotNull]
public GlobalFeatureManager FeatureManager { get; }
[NotNull]
public string FeatureName { get; }
public bool IsEnabled
{
get => FeatureManager.IsEnabled(FeatureName);
set => SetEnabled(value);
}
protected GlobalFeature([NotNull] GlobalModuleFeatures module)
{
Module = Check.NotNull(module, nameof(module));
FeatureManager = Module.FeatureManager;
FeatureName = GlobalFeatureNameAttribute.GetName(GetType());
}
public virtual void Enable()
{
FeatureManager.Enable(FeatureName);
}
public virtual void Disable()
{
FeatureManager.Disable(FeatureName);
}
public void SetEnabled(bool isEnabled)
{
if (isEnabled)
{
Enable();
}
else
{
Disable();
}
}
}
}

9
framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureDictionary.cs

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Volo.Abp.GlobalFeatures
{
public class GlobalFeatureDictionary : Dictionary<string, GlobalFeature>
{
}
}

59
framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureManager.cs

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.GlobalFeatures
{
public class GlobalFeatureManager
{
public static GlobalFeatureManager Instance { get; protected set; } = new GlobalFeatureManager();
/// <summary>
/// A common dictionary to store arbitrary configurations.
/// </summary>
[NotNull]
public Dictionary<object, object> Configuration { get; }
public GlobalModuleFeaturesDictionary Modules { get; }
protected HashSet<string> EnabledFeatures { get; }
internal GlobalFeatureManager()
{
EnabledFeatures = new HashSet<string>();
Configuration = new Dictionary<object, object>();
Modules = new GlobalModuleFeaturesDictionary(this);
}
public virtual bool IsEnabled<TFeature>()
where TFeature : GlobalFeature
{
return IsEnabled(GlobalFeatureNameAttribute.GetName<TFeature>());
}
public virtual bool IsEnabled([NotNull] Type featureType)
{
return IsEnabled(GlobalFeatureNameAttribute.GetName(featureType));
}
public virtual bool IsEnabled(string featureName)
{
return EnabledFeatures.Contains(featureName);
}
protected internal void Enable(string featureName)
{
EnabledFeatures.AddIfNotContains(featureName);
}
protected internal void Disable(string featureName)
{
EnabledFeatures.Remove(featureName);
}
public virtual IEnumerable<string> GetEnabledFeatureNames()
{
return EnabledFeatures;
}
}
}

44
framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureNameAttribute.cs

@ -0,0 +1,44 @@
using System;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
namespace Volo.Abp.GlobalFeatures
{
[AttributeUsage(AttributeTargets.Class)]
public class GlobalFeatureNameAttribute : Attribute
{
[NotNull]
public string Name { get; }
public GlobalFeatureNameAttribute([NotNull] string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
}
public static string GetName<TFeature>()
where TFeature : GlobalFeature
{
return GetName(typeof(TFeature));
}
[NotNull]
public static string GetName([NotNull] Type type)
{
Check.NotNull(type, nameof(type));
var attribute = type
.GetCustomAttributes<GlobalFeatureNameAttribute>()
.FirstOrDefault();
if (attribute == null)
{
throw new AbpException($"{type.AssemblyQualifiedName} should define the {typeof(GlobalFeatureNameAttribute).FullName} atttribute!");
}
return attribute
.As<GlobalFeatureNameAttribute>()
.Name;
}
}
}

98
framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalModuleFeatures.cs

@ -0,0 +1,98 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using JetBrains.Annotations;
namespace Volo.Abp.GlobalFeatures
{
public abstract class GlobalModuleFeatures
{
[NotNull]
public GlobalFeatureManager FeatureManager { get; }
[NotNull]
protected GlobalFeatureDictionary AllFeatures { get; }
protected GlobalModuleFeatures(
[NotNull] GlobalFeatureManager featureManager)
{
FeatureManager = Check.NotNull(featureManager, nameof(featureManager));
AllFeatures = new GlobalFeatureDictionary();
}
public virtual void Enable<TFeature>()
where TFeature : GlobalFeature
{
GetFeature<TFeature>().Enable();
}
public virtual void Disable<TFeature>()
where TFeature : GlobalFeature
{
GetFeature<TFeature>().Disable();
}
public virtual void SetEnabled<TFeature>(bool isEnabled)
where TFeature : GlobalFeature
{
GetFeature<TFeature>().SetEnabled(isEnabled);
}
public virtual void Enable(string featureName)
{
GetFeature(featureName).Enable();
}
public virtual void Disable(string featureName)
{
GetFeature(featureName).Disable();
}
public virtual void SetEnabled(string featureName, bool isEnabled)
{
GetFeature(featureName).SetEnabled(isEnabled);
}
public virtual void EnableAll()
{
foreach (var feature in AllFeatures.Values)
{
feature.Enable();
}
}
public virtual void DisableAll()
{
foreach (var feature in AllFeatures.Values)
{
feature.Disable();
}
}
public virtual GlobalFeature GetFeature(string featureName)
{
var feature = AllFeatures.GetOrDefault(featureName);
if (feature == null)
{
throw new AbpException($"There is no feature defined by name '{featureName}'.");
}
return feature;
}
public virtual TFeature GetFeature<TFeature>()
where TFeature : GlobalFeature
{
return (TFeature) GetFeature(GlobalFeatureNameAttribute.GetName<TFeature>());
}
public virtual IReadOnlyList<GlobalFeature> GetFeatures()
{
return AllFeatures.Values.ToImmutableList();
}
protected void AddFeature(GlobalFeature feature)
{
AllFeatures[feature.FeatureName] = feature;
}
}
}

16
framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalModuleFeaturesDictionary.cs

@ -0,0 +1,16 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.GlobalFeatures
{
public class GlobalModuleFeaturesDictionary : Dictionary<string, GlobalModuleFeatures>
{
public GlobalFeatureManager FeatureManager { get; }
public GlobalModuleFeaturesDictionary(
[NotNull] GlobalFeatureManager featureManager)
{
FeatureManager = Check.NotNull(featureManager, nameof(featureManager));
}
}
}

28
framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/RequiresGlobalFeatureAttribute.cs

@ -0,0 +1,28 @@
using System;
using JetBrains.Annotations;
namespace Volo.Abp.GlobalFeatures
{
[AttributeUsage(AttributeTargets.Class)]
public class RequiresGlobalFeatureAttribute : Attribute
{
public Type Type { get; }
public string Name { get; }
public RequiresGlobalFeatureAttribute([NotNull] Type type)
{
Type = Check.NotNull(type, nameof(type));
}
public RequiresGlobalFeatureAttribute([NotNull] string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
}
public virtual string GetFeatureName()
{
return Name ?? GlobalFeatureNameAttribute.GetName(Type);
}
}
}

4
framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/tr.json

@ -13,7 +13,7 @@
"DisplayName:Abp.Ldap.UserName": "Kullanıcı adı", "DisplayName:Abp.Ldap.UserName": "Kullanıcı adı",
"Description:Abp.Ldap.UserName": "Kullanıcı adı", "Description:Abp.Ldap.UserName": "Kullanıcı adı",
"DisplayName:Abp.Ldap.Password": "parola", "DisplayName:Abp.Ldap.Password": "Parola",
"Description:Abp.Ldap.Password": "parola" "Description:Abp.Ldap.Password": "Parola"
} }
} }

8
framework/src/Volo.Abp.MailKit/Volo/Abp/MailKit/MailKitSmtpEmailSender.cs

@ -56,21 +56,21 @@ namespace Volo.Abp.MailKit
protected virtual async Task ConfigureClient(SmtpClient client) protected virtual async Task ConfigureClient(SmtpClient client)
{ {
client.Connect( await client.ConnectAsync(
await SmtpConfiguration.GetHostAsync(), await SmtpConfiguration.GetHostAsync(),
await SmtpConfiguration.GetPortAsync(), await SmtpConfiguration.GetPortAsync(),
await GetSecureSocketOption() await GetSecureSocketOption()
); );
if (await SmtpConfiguration.GetUseDefaultCredentialsAsync()) if (await SmtpConfiguration.GetUseDefaultCredentialsAsync())
{ {
return; return;
} }
client.Authenticate( await client.AuthenticateAsync(
await SmtpConfiguration.GetUserNameAsync(), await SmtpConfiguration.GetUserNameAsync(),
await SmtpConfiguration.GetPasswordAsync() await SmtpConfiguration.GetPasswordAsync()
); );
} }
protected virtual async Task<SecureSocketOptions> GetSecureSocketOption() protected virtual async Task<SecureSocketOptions> GetSecureSocketOption()

4
framework/src/Volo.Abp.Quartz/Volo.Abp.Quartz.csproj

@ -15,7 +15,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Quartz" Version="3.0.7" /> <PackageReference Include="Quartz" Version="3.1.0" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.1.0" />
<PackageReference Include="Quartz.Plugins.TimeZoneConverter" Version="3.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

41
framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzJobFactory.cs

@ -1,41 +0,0 @@
using System;
using System.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Spi;
namespace Volo.Abp.Quartz
{
/// <summary>
/// Get the job from the dependency injection
/// </summary>
public class AbpQuartzJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly ConcurrentDictionary<IJob, IServiceScope> _scopes = new ConcurrentDictionary<IJob, IServiceScope>();
public AbpQuartzJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
var scope = _serviceProvider.CreateScope();
var job = scope.ServiceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
if (job == null)
{
throw new ArgumentException("Given job does not implement IJob");
}
_scopes.TryAdd(job, scope);
return job;
}
public void ReturnJob(IJob job)
{
_scopes.TryRemove(job, out var serviceScope);
serviceScope?.Dispose();
}
}
}

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

@ -2,7 +2,7 @@
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Quartz; using Quartz;
using Quartz.Impl; using Quartz.Impl;
using Quartz.Spi; using Quartz.Impl.AdoJobStore.Common;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -15,8 +15,48 @@ namespace Volo.Abp.Quartz
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
var options = context.Services.ExecutePreConfiguredActions<AbpQuartzOptions>(); var options = context.Services.ExecutePreConfiguredActions<AbpQuartzOptions>();
context.Services.AddSingleton(AsyncHelper.RunSync(() => new StdSchedulerFactory(options.Properties).GetScheduler()));
context.Services.AddSingleton(typeof(IJobFactory), typeof(AbpQuartzJobFactory)); // TODO: Remove this once Pomelo update MySqlConnector to >= 1.0.0 : https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/1103
AdaptMysqlConnector();
context.Services.AddQuartz(options.Properties, build =>
{
// these are the defaults
if (options.Properties[StdSchedulerFactory.PropertySchedulerJobFactoryType] == null)
{
build.UseMicrosoftDependencyInjectionScopedJobFactory();
}
if (options.Properties[StdSchedulerFactory.PropertySchedulerTypeLoadHelperType] == null)
{
build.UseSimpleTypeLoader();
}
if (options.Properties[StdSchedulerFactory.PropertyJobStoreType] == null)
{
build.UseInMemoryStore();
}
if (options.Properties[StdSchedulerFactory.PropertyThreadPoolType] == null)
{
build.UseDefaultThreadPool(tp =>
{
tp.MaxConcurrency = 10;
});
}
if (options.Properties["quartz.plugin.timeZoneConverter.type"] == null)
{
build.UseTimeZoneConverter();
}
options.Configurator?.Invoke(build);
});
context.Services.AddSingleton(serviceProvider =>
{
return AsyncHelper.RunSync(() => serviceProvider.GetService<ISchedulerFactory>().GetScheduler());
});
Configure<AbpQuartzOptions>(quartzOptions => Configure<AbpQuartzOptions>(quartzOptions =>
{ {
@ -30,7 +70,6 @@ namespace Volo.Abp.Quartz
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpQuartzOptions>>().Value; var options = context.ServiceProvider.GetRequiredService<IOptions<AbpQuartzOptions>>().Value;
_scheduler = context.ServiceProvider.GetService<IScheduler>(); _scheduler = context.ServiceProvider.GetService<IScheduler>();
_scheduler.JobFactory = context.ServiceProvider.GetService<IJobFactory>();
AsyncHelper.RunSync(() => options.StartSchedulerFactory.Invoke(_scheduler)); AsyncHelper.RunSync(() => options.StartSchedulerFactory.Invoke(_scheduler));
} }
@ -42,5 +81,28 @@ namespace Volo.Abp.Quartz
AsyncHelper.RunSync(() => _scheduler.Shutdown()); AsyncHelper.RunSync(() => _scheduler.Shutdown());
} }
} }
private void AdaptMysqlConnector()
{
var mySqlAvailable = System.Type.GetType("MySql.Data.MySqlClient.MySqlConnection, MySqlConnector") != null;
if (mySqlAvailable)
{
// Overriding the default 'MySqlConnector' provider to use the old 'MySql.Data' namespace found in MySqlConnector < 1.0.0
DbProvider.RegisterDbMetadata("MySqlConnector", new DbMetadata()
{
ProductName = "MySQL, MySqlConnector provider",
AssemblyName = "MySqlConnector",
ConnectionType = System.Type.GetType("MySql.Data.MySqlClient.MySqlConnection, MySqlConnector"),
CommandType = System.Type.GetType("MySql.Data.MySqlClient.MySqlCommand, MySqlConnector"),
ParameterType = System.Type.GetType("MySql.Data.MySqlClient.MySqlParameter, MySqlConnector"),
ParameterDbType = System.Type.GetType("MySql.Data.MySqlClient.MySqlDbType, MySqlConnector"),
ParameterDbTypePropertyName = "MySqlDbType",
ParameterNamePrefix = "?",
ExceptionType = System.Type.GetType("MySql.Data.MySqlClient.MySqlException, MySqlConnector"),
BindByName = true,
DbBinaryTypeName = "Blob"
});
}
}
} }
} }

4
framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzOptions.cs

@ -13,6 +13,8 @@ namespace Volo.Abp.Quartz
/// </summary> /// </summary>
public NameValueCollection Properties { get; set; } public NameValueCollection Properties { get; set; }
public Action<IServiceCollectionQuartzConfigurator> Configurator { get; set; }
/// <summary> /// <summary>
/// How long Quartz should wait before starting. Default: 0. /// How long Quartz should wait before starting. Default: 0.
/// </summary> /// </summary>
@ -45,4 +47,4 @@ namespace Volo.Abp.Quartz
} }
} }
} }
} }

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

@ -61,6 +61,14 @@
<ExcludeFromSingleFile>true</ExcludeFromSingleFile> <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content> </Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\GlobalFeature\DisabledGlobalFeatureTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\GlobalFeature\EnabledGlobalFeatureTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup> </ItemGroup>
<!-- https://github.com/NuGet/Home/issues/4412. --> <!-- https://github.com/NuGet/Home/issues/4412. -->

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

@ -1,19 +1,23 @@
using System; using System;
using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using Localization.Resources.AbpUi; using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Authorization; using Volo.Abp.AspNetCore.Mvc.Authorization;
using Volo.Abp.AspNetCore.Mvc.GlobalFeatures;
using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.Localization.Resource; using Volo.Abp.AspNetCore.Mvc.Localization.Resource;
using Volo.Abp.AspNetCore.Security.Claims; using Volo.Abp.AspNetCore.Security.Claims;
using Volo.Abp.AspNetCore.TestBase; using Volo.Abp.AspNetCore.TestBase;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.MemoryDb; using Volo.Abp.MemoryDb;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.TestApp; using Volo.Abp.TestApp;
using Volo.Abp.Threading;
using Volo.Abp.Validation.Localization; using Volo.Abp.Validation.Localization;
using Volo.Abp.VirtualFileSystem; using Volo.Abp.VirtualFileSystem;
@ -27,6 +31,8 @@ namespace Volo.Abp.AspNetCore.Mvc
)] )]
public class AbpAspNetCoreMvcTestModule : AbpModule public class AbpAspNetCoreMvcTestModule : AbpModule
{ {
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options => context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
@ -40,6 +46,13 @@ namespace Volo.Abp.AspNetCore.Mvc
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
OneTimeRunner.Run(() =>
{
GlobalFeatureManager.Instance.Modules.GetOrAdd(AbpAspNetCoreMvcTestFeatures.ModuleName,
() => new AbpAspNetCoreMvcTestFeatures(GlobalFeatureManager.Instance))
.EnableAll();
});
context.Services.AddAuthorization(options => context.Services.AddAuthorization(options =>
{ {
options.AddPolicy("MyClaimTestPolicy", policy => options.AddPolicy("MyClaimTestPolicy", policy =>

30
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/AbpAspNetCoreMvcTestFeatures.cs

@ -0,0 +1,30 @@
using JetBrains.Annotations;
using Volo.Abp.GlobalFeatures;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
[GlobalFeatureName(Name)]
public class AbpAspNetCoreMvcTestFeature1 : Abp.GlobalFeatures.GlobalFeature
{
public const string Name = "AbpAspNetCoreMvcTest.Feature1";
internal AbpAspNetCoreMvcTestFeature1([NotNull] AbpAspNetCoreMvcTestFeatures abpAspNetCoreMvcTestFeatures)
: base(abpAspNetCoreMvcTestFeatures)
{
}
}
public class AbpAspNetCoreMvcTestFeatures : GlobalModuleFeatures
{
public const string ModuleName = "AbpAspNetCoreMvcTest";
public AbpAspNetCoreMvcTestFeature1 Reactions => GetFeature<AbpAspNetCoreMvcTestFeature1>();
public AbpAspNetCoreMvcTestFeatures([NotNull] GlobalFeatureManager featureManager)
: base(featureManager)
{
AddFeature(new AbpAspNetCoreMvcTestFeature1(this));
}
}
}

18
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/DisabledGlobalFeatureTestController.cs

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.GlobalFeatures;
namespace Volo.Abp.AspNetCore.Mvc.GlobalFeatures
{
[RequiresGlobalFeature("Not-Enabled-Feature")]
[Route("api/DisabledGlobalFeatureTestController-Test")]
public class DisabledGlobalFeatureTestController : AbpController
{
[HttpGet]
[Route("TestMethod")]
public string TestMethod()
{
return "TestMethod";
}
}
}

19
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/GlobalFeatures/DisabledGlobalFeatureTestPage.cshtml

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

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

Loading…
Cancel
Save