diff --git a/.github/workflows/angular.yml b/.github/workflows/angular.yml
index dfca28338b..a4036f8474 100644
--- a/.github/workflows/angular.yml
+++ b/.github/workflows/angular.yml
@@ -2,8 +2,14 @@ name: 'Angular'
on:
pull_request:
paths:
- - 'npm/ng-packs/**'
+ - 'npm/ng-packs/**/*.ts'
+ - 'npm/ng-packs/**/*.html'
+ - 'npm/ng-packs/*.json'
- '!npm/ng-packs/scripts/**'
+ - '!npm/ng-packs/packages/schematics/**'
+ branches:
+ - 'rel-*'
+ - 'dev'
jobs:
build-test-lint:
runs-on: ubuntu-18.04
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 6ba3ad7a6d..a5f00bc93d 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -2,14 +2,18 @@ name: "build and test"
on:
pull_request:
paths:
- - "framework/**"
- - "modules/**"
- - "templates/**"
- push:
- paths:
- - "framework/**"
- - "modules/**"
- - "templates/**"
+ - 'framework/**/*.cs'
+ - 'framework/**/*.cshtml'
+ - 'framework/**/*.csproj'
+ - 'framework/**/*.razor'
+ - 'modules/**/*.cs'
+ - 'modules/**/*.cshtml'
+ - 'modules/**/*.csproj'
+ - 'modules/**/*.razor'
+ - 'templates/**/*.cs'
+ - 'templates/**/*.cshtml'
+ - 'templates/**/*.csproj'
+ - 'templates/**/*.razor'
jobs:
build-test:
runs-on: windows-latest
@@ -17,7 +21,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@master
with:
- dotnet-version: 5.0.100
+ dotnet-version: 5.0.201
- name: Build All
run: .\build-all.ps1
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
index 51d818aad7..1523a97c6d 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
@@ -207,7 +207,7 @@
"DeleteCoverImageConfirmationMessage": "Are you sure you want to delete the cover image for \"{0}\"?",
"DeleteCoverImageSuccessMessage": "Cover image successfully deleted",
"PaymentsOf": "Payments of",
- "ShowPaymentsOfOrganization": "Show payments",
+ "ShowPaymentsOfOrganization": "Payments",
"Date": "Date",
"Products": "Products",
"TotalAmount": "Total amount",
@@ -215,8 +215,42 @@
"Gateway": "Gateway",
"State": "State",
"FailReason": "Fail reason",
- "ReIndexAllPosts": "ReIndex All Posts",
- "ReIndexAllPostsConfirmationMessage": "Are you sure to reindex all posts?",
- "SuccessfullyReIndexAllPosts": "Successfully reindex for all posts"
+ "ReIndexAllPosts": "Reindex All Posts",
+ "ReIndexAllPostsConfirmationMessage": "Are you sure you want to reindex all posts?",
+ "SuccessfullyReIndexAllPosts": "All posts have been successfully reindexed.",
+ "Permission:FullSearch": "Full text search",
+ "Menu:CliAnalytics": "Cli Analytics",
+ "TemplateName": "Template name",
+ "TemplateVersion": "Template version",
+ "DatabaseProvider": "Database provider",
+ "IsTiered": "Is Tiered",
+ "ProjectName": "Project name",
+ "Username": "User name",
+ "Tool": "Tool",
+ "Command": "Command",
+ "UiFramework": "Ui framework",
+ "Options": "Options",
+ "CliAnalytics": "Cli Analytics",
+ "Permission:CliAnalyticses": "Cli Analyticses",
+ "Permission:CliAnalytics": "Cli Analytics",
+ "Search": "Search",
+ "ClearFilter": "Clear filter",
+ "LicensePrivateKey": "License private key",
+ "LicensePublicKey": "License public key",
+ "ApiKey": "NuGet API key",
+ "ShowInvoiceRequestsOfOrganization": "Invoice requests",
+ "ShowQuestionsOfOrganization": "Questions",
+ "Question": "Question",
+ "Open": "Open",
+ "Questions": "Questions",
+ "InvoiceRequests": "Invoice Requests",
+ "Address": "Address",
+ "TaxNo": "Tax No",
+ "Permission:InvoiceRequest": "Invoice Request",
+ "Permission:Question": "Question",
+ "AddNoteSuccessMessage": "Note successfully added",
+ "NameSurname": "Name Surname",
+ "Note": "Note",
+ "Add": "Add"
}
}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json
index 34ee392140..750df02dc4 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json
@@ -166,6 +166,40 @@
"DeleteCoverImageSuccessMessage": "Kapak fotoğrafı başarılı bir şekilde silinmiştir",
"ReIndexAllPosts": "Yeniden indeksle",
"ReIndexAllPostsConfirmationMessage": "Tüm gönderileri yeniden indeksleyeceğinizden emin misiniz?",
- "SuccessfullyReIndexAllPosts": "Tüm gönderiler başarıyla yeniden indekslendi"
+ "SuccessfullyReIndexAllPosts": "Tüm gönderiler başarıyla yeniden indekslendi",
+ "Menu:CliAnalytics": "Cli Analitik",
+ "TemplateName": "Şablon ismi",
+ "TemplateVersion": "Şablon versiyon",
+ "DatabaseProvider": "Veritabanı sağlayıcı",
+ "IsTiered": "Katmanlı mı",
+ "ProjectName": "Proje ismi",
+ "Username": "Kullanıcı adı",
+ "Tool": "Araç",
+ "Command": "Komut",
+ "UiFramework": "Ui çerçevesi",
+ "Options": "Seçenekler",
+ "CliAnalytics": "Cli Analitik",
+ "Permission:CliAnalyticses": "Cli Analitikleri",
+ "Permission:CliAnalytics": "Cli Analitik",
+ "Search": "Ara",
+ "ShowPaymentsOfOrganization": "Ödemeler",
+ "ClearFilter": "Filtreyi temizle",
+ "LicensePrivateKey": "Lisans özel anahtar",
+ "LicensePublicKey": "Lisans genel key",
+ "ApiKey": "NuGet API anahatar",
+ "ShowInvoiceRequestsOfOrganization": "Fatura talepleri",
+ "ShowQuestionsOfOrganization": "Sorular",
+ "Question": "Soru",
+ "Open": "Aç",
+ "Questions": "Sorular",
+ "InvoiceRequests": "Fatura talepleri",
+ "Address": "Adres",
+ "TaxNo": "Vergi no",
+ "Permission:InvoiceRequest": "Fatura Talebi",
+ "Permission:Question": "Soru",
+ "AddNoteSuccessMessage": "Not başarıyla eklendi",
+ "NameSurname": "Adı Soyadı",
+ "Note": "Not",
+ "Add": "Ekle"
}
}
\ No newline at end of file
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json
index f5cbeb0213..9a269da0d4 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json
@@ -35,6 +35,7 @@
"NameFilter": "名称",
"CreationTime": "创建时间",
"IsPro": "是否为专业版",
+ "IsFreeToActiveLicenseOwners": "许可证所有者免费",
"ShowOnModuleList": "展示模块列表",
"EfCoreConfigureMethodName": "配置方法法",
"IsProFilter": "是否为专业版",
@@ -90,6 +91,7 @@
"UserNotFound": "用户不存在",
"{0}WillBeRemovedFromDevelopers": "{0} 将从开发者中移除, 你确定吗?",
"{0}WillBeRemovedFromOwners": "{0} 将从所有者中移除, 你确定吗?",
+ "{0}WillBeRemovedFromMembers": "{0} 将从成员中删除, 你确定吗?",
"Computers": "计算机",
"UniqueComputerId": "计算机唯一ID",
"LastSeenDate": "上次查看日期",
@@ -194,6 +196,46 @@
"CoverImage": "封面图片",
"RemoveCacheConfirmationMessage": "你确定要删除\"{0}\" 文章缓存?",
"SuccessfullyRemoved": "清除成功",
- "RemoveCache": "删除缓存"
+ "RemoveCache": "删除缓存",
+ "Language": "语言",
+ "Optional": "可选的",
+ "CreateArticleLanguageInfo": "发帖所用的语言",
+ "Enum:ContentSource:2": "视频发布",
+ "VideoPreview": "视频预览",
+ "VideoPreviewErrorMessage": "无法从YouTube获取给定的视频网址. 可能是由于视频是私有视频,或者给定的URL不可用.",
+ "DeleteCoverImage": "删除封面图片",
+ "DeleteCoverImageConfirmationMessage": "你确定要删除封面图片吗",
+ "DeleteCoverImageSuccessMessage": "成功删除封面图片",
+ "PaymentsOf": "付款的",
+ "ShowPaymentsOfOrganization": "显示付款",
+ "Date": "日期",
+ "Products": "商品",
+ "TotalAmount": "总金额",
+ "Currency": "货币",
+ "Gateway": "网关",
+ "State": "状态",
+ "FailReason": "失败原因",
+ "ReIndexAllPosts": "重新索引所有帖子",
+ "ReIndexAllPostsConfirmationMessage": "你确定要重新索引所有的帖子吗",
+ "SuccessfullyReIndexAllPosts": "成功索引所有的帖子",
+ "ClearFilter": "清除过滤",
+ "LicensePrivateKey": "许可私有密钥",
+ "LicensePublicKey": "许可公有密钥",
+ "ApiKey": "NuGet API密钥",
+ "ShowInvoiceRequestsOfOrganization": "发票请求",
+ "ShowQuestionsOfOrganization": "问题",
+ "Question": "问题",
+ "Open": "打开",
+ "Questions": "问题",
+ "InvoiceRequests": "发票请求",
+ "Address": "地址",
+ "TaxNo": "税号",
+ "Permission:InvoiceRequest": "发票请求",
+ "Permission:Question": "问题",
+ "AddNoteSuccessMessage": "注释添加成功",
+ "NameSurname": "姓",
+ "Note": "注释",
+ "Add": "添加"
+
}
}
\ No newline at end of file
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
index 9c5ae26f89..82033e5849 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
@@ -138,6 +138,7 @@
"TwitterUserNameValidationMessage": "Your Twitter username can not include whitespace, please be sure your Twitter username is correct.",
"LinkedinUrlValidationMessage": "Your Linkedin URL can not include whitespace, please be sure your Linkedin URL is correct.",
"NoPostsFound": "No posts found!",
- "SearchInPosts": "Search in posts..."
+ "SearchInPosts": "Search in posts...",
+ "MinimumSearchContent": "You must enter at least 3 characters!"
}
}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json
index 49b819227f..9d7b56f0f3 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json
@@ -110,7 +110,34 @@
"VideoContentForm": "视频内容来源",
"GithubPostForm": "Github文章来源",
"ExternalPostForm": "外部文章来源",
- "PostSourceTypeChooses": "我们接受文章的三种来源类型;",
- "Posts": "文章"
+ "HowToPost": "如何发布",
+ "Posts": "文章",
+ "VideoUrl": "视频Url",
+ "GithubArticleUrl": "Github文章Url",
+ "ExternalArticleUrl": "外部文章Url",
+ "CreatePostCoverInfo": "要创建一个有效的帖子,需要添加封面图片. 上传16:9的图片获取最佳视觉效果. 文件最大支持: 1MB",
+ "ThankYouForContribution": "感谢你对ABP社区的贡献",
+ "GithubArticle": "Github文章",
+ "GithubArticleSubmitStepOne": "1. 用Markdown格式在GitHub的任何公共存储库上写一篇文章. 示例",
+ "GithubArticleSubmitStepTwo": "2. 使用表单提交你的文章URL.",
+ "GithubArticleSubmitStepThree": "3. 你的文章将在这个网站中呈现.",
+ "YoutubeVideo": "Youtube视频",
+ "YoutubeVideoSubmitStepOne": "1. 在YouTube上发布你的视频.",
+ "YoutubeVideoSubmitStepTwo": "2. 使用表单提交视频URL.",
+ "YoutubeVideoSubmitStepThree": "3. 访客将能够在这个网站上直接观看你的视频内容.",
+ "ExternalContent": "外部内容",
+ "ExternalContentSubmitStepOne": "1. 在任何公开的平台创建内容(medium, 你自己的博客或者任何你喜欢的).",
+ "ExternalContentSubmitStepTwo": "2. 使用表单提交内容URL.",
+ "ExternalContentSubmitStepThree": "3. 访客被重定向到原网站的内容.",
+ "ChooseYourContentType": "请选择你想要添加内容的方式",
+ "PostContentViaGithub": "我想要使用 GitHub提交markdown格式的文章",
+ "PostContentViaYoutube": "我想要分享我在 Youtube的可用视频",
+ "PostContentViaExternalSource": "我想要添加我在其他平台发布的内容",
+ "GitHubUserNameValidationMessage": "您的Github用户名不能包含空格, 请确认你的Github用户名是正确的.",
+ "PersonalSiteUrlValidationMessage": "你的个人网站URL不能包含空格, 请确定你的个人网站URL是正确的.",
+ "TwitterUserNameValidationMessage": "你的Twitter用户名不能包含空格, 请确认你的Twitter用户名是正确的.",
+ "LinkedinUrlValidationMessage": "你的领英URL不能包含空格, 请确认你的领英URL是正确的",
+ "NoPostsFound": "没有发现帖子",
+ "SearchInPosts": "搜索帖子…"
}
}
\ No newline at end of file
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json
index e187a628f2..8185ead8cd 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json
@@ -8,6 +8,7 @@
"ProjectName": "项目名称",
"ProjectType": "项目类型",
"DatabaseProvider": "数据库提供者",
+ "DatabaseManagementSystem": "数据库管理系统",
"NTier": "N层",
"IncludeUserInterface": "包含用户界面",
"CreateNow": "现在创建",
@@ -194,7 +195,7 @@
"MultipleUIOptionsExplanation": "核心框架设计为独立与UI,可以和任何类型的UI系统一起使用. 同时提供了多个开箱即用的预构建集成选项.",
"MultipleDBOptionsExplanation": "该框架可以使用任何数据源,并且以下提供程序已得到正式开发和支持;",
"SelectLanguage": "选择语言",
- "LatestArticleOnCommunity": "关于ABP社区的最新文章",
- "Register": "寄存器"
+ "LatestArticleOnCommunity": "关于ABP社区的最新文章",
+ "Register": "寄存器"
}
-}
+}
\ No newline at end of file
diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md
index 3e8a3e9da2..86f9774c7f 100644
--- a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md
+++ b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md
@@ -78,7 +78,7 @@ public static class CustomizeUserDemoEfCoreEntityExtensionMappings
nameof(AppUser.Title),
(entityBuilder, propertyBuilder) =>
{
- propertyBuilder.IsRequired();
+ propertyBuilder.HasDefaultValue();
propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength);
}
).MapEfCoreProperty(
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/POST.md b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/POST.md
new file mode 100644
index 0000000000..519a09fa42
--- /dev/null
+++ b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/POST.md
@@ -0,0 +1,98 @@
+# Send Real-time Notifications via SignalR in ABP Project
+
+SignalR is an open source library that adds real-time operation functionality to applications. Real-time web functionality enables server-side code to instantly send content to clients without refreshing the page. I'll show you how to add SignalR and use it to send notifications from backend. I'll implement this functionality in MVC template of ABP Framework.
+
+
+
+## Implement Backend
+
+### Create Notification Hub
+
+Create a new folder named `SignalR` in your root directory of your Web project.
+
+
+
+Then add the following classes to the folder:
+
+1. [INotificationClient.cs](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-inotificationclient-cs)
+2. [UiNotificationClient.cs](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-uinotificationclient-cs)
+3. [UiNotificationHub.cs](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-uinotificationhub-cs)
+
+### Configure Module
+
+These 3 steps will be done in your web module class.
+
+#### 1- Add SignalR
+
+Open `YourProjectWebModule.cs` class and add the following line to the `PreConfigureServices` method:
+
+```csharp
+ context.Services.AddSignalR();
+```
+
+
+
+
+
+
+
+#### 2- Add Client Scripts
+
+2- In the `ConfigureServices` method of your web module add the following code to add the `signalr.js` and `notification-hub.js`. We'll add these packages in the next steps.
+
+
+
+#### 3- Add Hub Endpoint
+
+Add the following code to add the notification hub endpoint in `OnApplicationInitialization` method:
+
+```csharp
+app.UseEndpoints(endpoints =>
+{
+ endpoints.MapHub("/notification-hub");
+});
+```
+
+
+
+### Implement Frontend
+
+We'll write the client-side code to be able to handle the SignalR response.
+
+#### 1- Add Notification Hub
+
+Add the following JavaScript class into your `Pages` folder in your Web project. We already added this script to our global scripts.
+
+[notification-hub.js](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-notification-hub-js)
+
+
+
+#### 2- Add SignalR NPM package
+
+Add [Microsoft.SignalR](https://www.npmjs.com/package/@microsoft/signalr) JavaScript package to the `package.json` which is located in your root folder of the Web project. After you add it, run `yarn` command in your Web directory to be able to install this package.
+
+
+
+#### 3- Add resource Mapping
+
+We added SignalR to the `package.json` but it comes into your `node_modules` folder. We need to copy the related files to `wwwroot/libs` folder. To do this copy the content of the following file to your `abp.resourcemappings.js` file. It's in your root directory of Web folder. After you do this, go to your web directory and run `gulp` command. By doing this, it'll copy the related files into your `wwwroot/libs` folder.
+
+[abp.resourcemappings.js](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-abp-resourcemapping-js)
+
+
+
+#### 4- Usage
+
+We have completed the implementation part. Let's check if it's running...
+
+To do this easily, open your `Index.cshtml` which is in the Pages folder of your Web project. And replace the content with the following. Also replace the `Index.cshtml.cs` as well.
+
+[Index.cshtml](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-index-cshtml)
+
+[Index.cshtml.cs](https://gist.github.com/ebicoglu/f7dc22cca2d353f8bf7f68a03e3395b8#file-index-cshtml-cs)
+
+#### 5- See it in action
+
+Run your web project and in the Index page you'll see a button named as "Get Notification". Click the button and see the notification that comes from SignalR. This is a basic usage of SignalR notification system. You can implement it according to your own requirements.
+
+
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-endpoint.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-endpoint.jpg
new file mode 100644
index 0000000000..706a860dd0
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-endpoint.jpg differ
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-script-bundles.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-script-bundles.jpg
new file mode 100644
index 0000000000..0523d73e37
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/add-script-bundles.jpg differ
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/article-signalr-banner.png b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/article-signalr-banner.png
new file mode 100644
index 0000000000..450d4e2339
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/article-signalr-banner.png differ
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/notification-hub.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/notification-hub.jpg
new file mode 100644
index 0000000000..e762b2cc8d
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/notification-hub.jpg differ
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/preconfigureservices.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/preconfigureservices.jpg
new file mode 100644
index 0000000000..4421143aa3
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/preconfigureservices.jpg differ
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/resource-mappings.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/resource-mappings.jpg
new file mode 100644
index 0000000000..7d6d0a3de8
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/resource-mappings.jpg differ
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/result.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/result.jpg
new file mode 100644
index 0000000000..4c9ca0663b
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/result.jpg differ
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-architecture.png b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-architecture.png
new file mode 100644
index 0000000000..6f39568239
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-architecture.png differ
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-folder.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-folder.jpg
new file mode 100644
index 0000000000..5c1dd351fb
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-folder.jpg differ
diff --git a/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-package.jpg b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-package.jpg
new file mode 100644
index 0000000000..be1e87d1b2
Binary files /dev/null and b/docs/en/Community-Articles/2021-03-12-Simple-SignalR-Notification/signalr-package.jpg differ
diff --git a/docs/en/Migration-Guides/Abp-4_3-Angular.md b/docs/en/Migration-Guides/Abp-4_3-Angular.md
new file mode 100644
index 0000000000..0b4a90fc72
--- /dev/null
+++ b/docs/en/Migration-Guides/Abp-4_3-Angular.md
@@ -0,0 +1,90 @@
+# Angular UI v4.3 Migration Guide
+
+## Breaking Changes
+
+### Manage Profile Page
+
+Before v4.3, the "Manage Your Profile" link in the current user dropdown on the top bar redirected the user to MVC's profile management page. As of v4.3, the same link will land on a page in the Angular UI account module instead. So you have to install and implement the account module to your Angular project when you update the ABP to v4.3.
+
+#### Account Module Implementation
+
+Install the `@abp/ng.account` NPM package by running the below command:
+
+```bash
+npm install @abp/ng.account@next
+```
+
+> Make sure v4.3-rc or higher version is installed.
+
+Open the `app.module.ts` and add `AccountConfigModule.forRoot()` to the imports array as shown below:
+
+```js
+// app.module.ts
+
+import { AccountConfigModule } from '@abp/ng.account/config';
+//...
+
+@NgModule({
+ imports: [
+ //...
+ AccountConfigModule.forRoot()
+ ],
+ //...
+})
+export class AppModule {}
+```
+
+Open the `app-routing.module.ts` and add the `account` route to `routes` array as follows:
+
+```js
+// app-routing.module.ts
+const routes: Routes = [
+ //...
+ {
+ path: 'account',
+ loadChildren: () => import('@abp/ng.account').then(m => m.AccountModule.forLazy()),
+ },
+ //...
+export class AppRoutingModule {}
+```
+
+#### Account Module Implementation for Commercial Templates
+
+The pro startup template comes with `@volo/abp.ng.account` package. You should update the package version to v4.3-rc or higher version. The package can be updated by running the following command:
+
+```bash
+npm install @volo/abp.ng.account@next
+```
+> Make sure v4.3-rc or higher version is installed.
+
+`AccountConfigModule` is already imported to `app.module.ts` in the startup template. So no need to import the module to the `AppModule`. If you removed the `AccountConfigModule` from the `AppModule`, you can import it as shown below:
+
+```js
+// app.module.ts
+
+import { AccountConfigModule } from '@volo/abp.ng.account/config';
+//...
+
+@NgModule({
+ imports: [
+ //...
+ AccountConfigModule.forRoot()
+ ],
+ //...
+})
+export class AppModule {}
+```
+
+Open the `app-routing.module.ts` and add the `account` route to `routes` array as follows:
+
+```js
+// app-routing.module.ts
+const routes: Routes = [
+ //...
+ {
+ path: 'account',
+ loadChildren: () => import('@volo/abp.ng.account').then(m => m.AccountPublicModule.forLazy()),
+ },
+ //...
+export class AppRoutingModule {}
+```
\ No newline at end of file
diff --git a/docs/en/Migration-Guides/Abp-4_3.md b/docs/en/Migration-Guides/Abp-4_3.md
new file mode 100644
index 0000000000..491143b0b0
--- /dev/null
+++ b/docs/en/Migration-Guides/Abp-4_3.md
@@ -0,0 +1,5 @@
+# ABP v4.3 Migration Guide
+
+## Angular UI
+
+See the [Angular UI Migration Guide](Abp-4_3-Angular.md).
\ No newline at end of file
diff --git a/docs/en/Migration-Guides/Index.md b/docs/en/Migration-Guides/Index.md
index 26fe825f50..c7bee5a82a 100644
--- a/docs/en/Migration-Guides/Index.md
+++ b/docs/en/Migration-Guides/Index.md
@@ -1,5 +1,6 @@
# ABP Framework Migration Guides
+* [4.2 to 4.3](Abp-4_3.md)
* [4.x to 4.2](Abp-4_2.md)
* [3.3.x to 4.0](Abp-4_0.md)
* [2.9.x to 3.0](../UI/Angular/Migration-Guide-v3.md)
diff --git a/docs/en/UI/Angular/HTTP-Requests.md b/docs/en/UI/Angular/HTTP-Requests.md
index ab1ac04097..aeb55578b8 100644
--- a/docs/en/UI/Angular/HTTP-Requests.md
+++ b/docs/en/UI/Angular/HTTP-Requests.md
@@ -27,13 +27,13 @@ An `HttpInterceptor` is able to catch `HttpErrorResponse` and can be used for
## RestService
-ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by deafult.
+ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by default.
### Getting Started with RestService
-In order to use the `RestService`, you must inject it in your class as a dependency.
+In order to use the `RestService`, you must inject it in your class as a dependency.
```js
import { RestService } from '@abp/ng.core';
@@ -200,6 +200,101 @@ getSomeCustomHeaderValue() {
}
```
+You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10).
-You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10).
+## HTTP Error Handling
+
+When the `RestService` is used, all HTTP errors are automatically handled by `ErrorHandler` which is a service that exposed by the `@abp/ng.theme.shared` package.
+
+### Custom HTTP Error Handler
+
+A custom HTTP error handler can be registered to an injection token named `HTTP_ERROR_HANDLER`. If a custom handler function is registered, the `ErrorHandler` executes that function.
+
+See an example:
+
+```js
+// http-error-handler.ts
+import { ContentProjectionService, PROJECTION_STRATEGY } from '@abp/ng.core';
+import { ToasterService } from '@abp/ng.theme.shared';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Injector } from '@angular/core';
+import { throwError } from 'rxjs';
+import { Error404Component } from './error404/error404.component';
+
+export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
+ if (httpError.status === 400) {
+ const toaster = injector.get(ToasterService);
+ toaster.error(httpError.error?.error?.message || 'Bad request!', '400');
+ return;
+ }
+
+ if (httpError.status === 404) {
+ const contentProjection = injector.get(ContentProjectionService);
+ contentProjection.projectContent(PROJECTION_STRATEGY.AppendComponentToBody(Error404Component));
+ return;
+ }
+
+ return throwError(httpError);
+}
+
+// app.module.ts
+import { Error404Component } from './error404/error404.component';
+import { handleHttpErrors } from './http-error-handling';
+import { HTTP_ERROR_HANDLER, ... } from '@abp/ng.theme.shared';
+
+@NgModule({
+ // ...
+ providers: [
+ // ...
+ { provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors }
+ ],
+ declarations: [
+ //...
+ Error404Component],
+})
+export class AppModule {}
+```
+
+In the example above:
+
+ - Created a function named `handleHttpErrors` and defined as value of the `HTTP_ERROR_HANDLER` provider in app.module. After this, the function executes when an HTTP error occurs.
+ - 400 bad request errors is handled. When a 400 error occurs, backend error message will be displayed as shown below:
+
+ 
+
+ - 404 not found errors is handled. When a 404 error occurs, `Error404Component` will be appended to the `` as shown below:
+
+
+
+ - Since `throwError(httpError)` is returned at bottom of the `handleHttpErrors`, the `ErrorHandler` will handle the HTTP errors except 400 and 404 errors.
+
+
+**Note 1:** If you put `return` to next line of handling an error, default error handling will not work for that error.
+
+```js
+export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
+ if (httpError.status === 403) {
+ // handle 403 errors here
+ return; // put return to skip default error handling
+ }
+}
+```
+
+**Note 2:** If you put `return throwError(httpError)`, default error handling will work.
+ - `throwError` is a function. It can be imported from `rxjs`.
+ - `httpError` is the second parameter of the error handler function which is registered to the `HTTP_ERROR_HANDLER` provider. Type of the `httpError` is `HttpErrorResponse`.
+
+```js
+import { throwError } from 'rxjs';
+
+export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
+ if (httpError.status === 500) {
+ // handle 500 errors here
+ return;
+ }
+
+ // you can return the throwError(httpError) at bottom of the function to run the default handler of ABP for HTTP errors that you didn't handle above.
+ return throwError(httpError)
+}
+```
diff --git a/docs/en/UI/Angular/Router-Events.md b/docs/en/UI/Angular/Router-Events.md
index 011d405b22..1d36c3a211 100644
--- a/docs/en/UI/Angular/Router-Events.md
+++ b/docs/en/UI/Angular/Router-Events.md
@@ -1,6 +1,6 @@
# Router Events Simplified
-`RouterEvents` is a utility service to provide an easy implementation for one of the most frequent needs in Angular templates: `TrackByFunction`. Please see [this page in Angular docs](https://angular.io/guide/template-syntax#ngfor-with-trackby) for its purpose.
+`RouterEvents` is a utility service for filtering specific router events and reacting to them. Please see [this page in Angular docs](https://angular.io/api/router/Event) for available router events.
diff --git a/docs/en/UI/Angular/images/custom-error-handler-404-component.jpg b/docs/en/UI/Angular/images/custom-error-handler-404-component.jpg
new file mode 100644
index 0000000000..e4b2f15c50
Binary files /dev/null and b/docs/en/UI/Angular/images/custom-error-handler-404-component.jpg differ
diff --git a/docs/en/UI/Angular/images/custom-error-handler-toaster-message.jpg b/docs/en/UI/Angular/images/custom-error-handler-toaster-message.jpg
new file mode 100644
index 0000000000..c1cced8237
Binary files /dev/null and b/docs/en/UI/Angular/images/custom-error-handler-toaster-message.jpg differ
diff --git a/docs/en/UI/AspNetCore/Navigation-Menu.md b/docs/en/UI/AspNetCore/Navigation-Menu.md
index 7a8fe31e3e..8480590f50 100644
--- a/docs/en/UI/AspNetCore/Navigation-Menu.md
+++ b/docs/en/UI/AspNetCore/Navigation-Menu.md
@@ -104,6 +104,7 @@ There are more options of a menu item (the constructor of the `ApplicationMenuIt
* `target` (`string`): Target of the menu item. Can be `null` (default), "\_*blank*", "\_*self*", "\_*parent*", "\_*top*" or a frame name for web applications.
* `elementId` (`string`): Can be used to render the element with a specific HTML `id` attribute.
* `cssClass` (`string`): Additional string classes for the menu item.
+* `RequiredPermissionName` (`string`): The required permission name, this menu item will be removed if this permission is not granted.
### Authorization
@@ -120,6 +121,25 @@ if (await context.IsGrantedAsync("MyPermissionName"))
}
````
+For the authorization, you can use `RequiredPermissionName` as a shortcut. It is also more performant, ABP optimizes the permission check for all the items.
+
+````csharp
+context.Menu.AddItem(
+ new ApplicationMenuItem("MyProject.Crm", l["Menu:CRM"])
+ .AddItem(new ApplicationMenuItem(
+ name: "MyProject.Crm.Customers",
+ displayName: l["Menu:Customers"],
+ url: "/crm/customers",
+ requiredPermissionName: "MyProject.Crm.Customers")
+ ).AddItem(new ApplicationMenuItem(
+ name: "MyProject.Crm.Orders",
+ displayName: l["Menu:Orders"],
+ url: "/crm/orders",
+ requiredPermissionName: "MyProject.Crm.Orders")
+ )
+);
+````
+
> You can use `context.AuthorizationService` to directly access to the `IAuthorizationService`.
### Resolving Dependencies
diff --git a/docs/en/UI/AspNetCore/Toolbars.md b/docs/en/UI/AspNetCore/Toolbars.md
index 3f1185f61b..66ec7519b8 100644
--- a/docs/en/UI/AspNetCore/Toolbars.md
+++ b/docs/en/UI/AspNetCore/Toolbars.md
@@ -52,6 +52,21 @@ public class MyToolbarContributor : IToolbarContributor
}
````
+You can use the [authorization](../../Authorization.md) to decide whether to add a `ToolbarItem`.
+
+````csharp
+if (await context.IsGrantedAsync("MyPermissionName"))
+{
+ //...add Toolbar items
+}
+````
+
+You can use `RequiredPermissionName` as a shortcut. It is also more performant, ABP optimizes the permission check for all the items.
+
+````csharp
+context.Toolbar.Items.Insert(0, new ToolbarItem(typeof(NotificationViewComponent), requiredPermissionName: "MyPermissionName"));
+````
+
This class adds the `NotificationViewComponent` as the first item in the `Main` toolbar.
Finally, you need to add this contributor to the `AbpToolbarOptions`, in the `ConfigureServices` of your [module](../../Module-Development-Basics.md):
@@ -71,4 +86,4 @@ That's all, you will see the notification icon on the toolbar when you run the a
## IToolbarManager
-`IToolbarManager` is used to render the toolbar. It returns the toolbar items by a toolbar name. This is generally used by the [themes](Theming.md) to render the toolbar on the layout.
\ No newline at end of file
+`IToolbarManager` is used to render the toolbar. It returns the toolbar items by a toolbar name. This is generally used by the [themes](Theming.md) to render the toolbar on the layout.
diff --git a/docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md b/docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md
index 47cab8c02e..61fde91bfc 100644
--- a/docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md
+++ b/docs/zh-Hans/Domain-Driven-Design-Implementation-Guide.md
@@ -781,7 +781,7 @@ namespace IssueTracking.Issues
- 创建时间大于30天
- 最近30天没有评论
-这个业务逻辑就被实现再了仓储内部,当我们需要重用这个业务规则时就会出现问题.
+这个业务逻辑就被实现在了仓储内部,当我们需要重用这个业务规则时就会出现问题.
例如:我们需要再实体`Issue`上添加一个方法来判断是否非活动`bool IsInActive()`,以方便我们在`Issue`实例上获取.
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs
index ca3a2be9c5..87af9b92b7 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ExceptionHandling/UserExceptionInformer.cs
@@ -1,6 +1,7 @@
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Components.Messages;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.DependencyInjection;
@@ -15,12 +16,16 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly.ExceptionHandling
protected IUiMessageService MessageService { get; }
protected IExceptionToErrorInfoConverter ExceptionToErrorInfoConverter { get; }
+ protected AbpExceptionHandlingOptions Options { get; }
+
public UserExceptionInformer(
IUiMessageService messageService,
- IExceptionToErrorInfoConverter exceptionToErrorInfoConverter)
+ IExceptionToErrorInfoConverter exceptionToErrorInfoConverter,
+ IOptions options)
{
MessageService = messageService;
ExceptionToErrorInfoConverter = exceptionToErrorInfoConverter;
+ Options = options.Value;
Logger = NullLogger.Instance;
}
@@ -41,12 +46,7 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly.ExceptionHandling
protected virtual RemoteServiceErrorInfo GetErrorInfo(UserExceptionInformerContext context)
{
- if (context.Exception is AbpRemoteCallException remoteCallException)
- {
- return remoteCallException.Error;
- }
-
- return ExceptionToErrorInfoConverter.Convert(context.Exception, false);
+ return ExceptionToErrorInfoConverter.Convert(context.Exception, Options.SendExceptionsDetailsToClients);
}
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs
index 85822414cf..dee3b3c492 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs
@@ -108,12 +108,12 @@ namespace Volo.Abp.AspNetCore.Mvc
return localizer;
}
- protected RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
+ protected virtual RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
- private string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
+ protected virtual string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
@@ -125,7 +125,7 @@ namespace Volo.Abp.AspNetCore.Mvc
return returnUrl;
}
- private string NormalizeReturnUrl(string returnUrl)
+ protected virtual string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs
index d01bac2e81..944b9ef0ee 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs
@@ -23,20 +23,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Authentication
{
return RedirectSafely(returnUrl, returnUrlHash);
}
- else
- {
- return Challenge(
- new AuthenticationProperties
- {
- Parameters =
- {
- {"returnUrl", returnUrl},
- {"returnUrlHash", returnUrlHash}
- }
- },
- ChallengeAuthenticationSchemas
- );
- }
+
+ return Challenge(new AuthenticationProperties {RedirectUri = GetRedirectUrl(returnUrl, returnUrlHash)}, ChallengeAuthenticationSchemas);
}
[HttpGet]
@@ -44,20 +32,20 @@ namespace Volo.Abp.AspNetCore.Mvc.Authentication
{
await HttpContext.SignOutAsync();
- if (HttpContext.User.Identity.AuthenticationType == AuthenticationType)
+ if (HttpContext.User.Identity?.AuthenticationType == AuthenticationType)
{
return RedirectSafely(returnUrl, returnUrlHash);
}
- return new SignOutResult(ChallengeAuthenticationSchemas);
+ return SignOut(new AuthenticationProperties {RedirectUri = GetRedirectUrl(returnUrl, returnUrlHash)}, ChallengeAuthenticationSchemas);
}
[HttpGet]
public async Task FrontChannelLogout(string sid)
{
- if (User.Identity.IsAuthenticated)
+ if (User.Identity != null && User.Identity.IsAuthenticated)
{
- var currentSid = User.FindFirst("sid").Value ?? string.Empty;
+ var currentSid = User.FindFirst("sid")?.Value ?? string.Empty;
if (string.Equals(currentSid, sid, StringComparison.Ordinal))
{
await Logout();
diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/zh-Hant.json b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/zh-Hant.json
new file mode 100644
index 0000000000..937299656c
--- /dev/null
+++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/zh-Hant.json
@@ -0,0 +1,10 @@
+{
+ "culture": "zh-Hant",
+ "texts": {
+ "Volo.Authorization:010001": "認證失敗! Given policy has not granted.",
+ "Volo.Authorization:010002": "認證失敗! Given policy has not granted: {PolicyName}",
+ "Volo.Authorization:010003": "認證失敗! Given policy has not granted for given resource: {ResourceName}",
+ "Volo.Authorization:010004": "認證失敗! Given requirement has not granted for given resource: {ResourceName}",
+ "Volo.Authorization:010005": "認證失敗! Given requirements has not granted for given resource: {ResourceName}"
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/MicroserviceServiceRandomPortStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/MicroserviceServiceRandomPortStep.cs
new file mode 100644
index 0000000000..88a19b43af
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/MicroserviceServiceRandomPortStep.cs
@@ -0,0 +1,78 @@
+using System;
+using System.IO;
+using System.Linq;
+using Volo.Abp.Cli.Commands;
+
+namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
+{
+ public class MicroserviceServiceRandomPortStep : ProjectBuildPipelineStep
+ {
+ private readonly string _defaultPort = string.Empty;
+ private string _tyeFileContent = null;
+
+ public MicroserviceServiceRandomPortStep(string defaultPort)
+ {
+ _defaultPort = defaultPort;
+ }
+
+ public override void Execute(ProjectBuildContext context)
+ {
+ var newPort = GetNewRandomPort(context);
+
+ var targetFiles = context.Files.Where(f=> f.Name.EndsWith("launchSettings.json") || f.Name.EndsWith("appsettings.json")).ToList();
+
+ foreach (var file in targetFiles)
+ {
+ file.SetContent(file.Content.Replace(_defaultPort, newPort));
+ }
+ }
+
+ private string GetNewRandomPort(ProjectBuildContext context)
+ {
+ string newPort;
+ var rnd = new Random();
+ var tryCount = 0;
+
+ do
+ {
+ newPort = rnd.Next(44350, 45350).ToString();
+
+ if (tryCount++ > 2000)
+ {
+ break;
+ }
+
+ } while (PortExistsForAnotherService(context, newPort));
+
+ return newPort;
+ }
+
+ private bool PortExistsForAnotherService(ProjectBuildContext context, string newPort)
+ {
+ return ReadTyeFileContent(context).SplitToLines().Any(l => l.Contains("port") && l.Contains(newPort));
+ }
+
+ private string ReadTyeFileContent(ProjectBuildContext context)
+ {
+ if (_tyeFileContent != null)
+ {
+ return _tyeFileContent;
+ }
+
+ var solutionFolderPath = context.BuildArgs.ExtraProperties[NewCommand.Options.OutputFolder.Short] ??
+ context.BuildArgs.ExtraProperties[NewCommand.Options.OutputFolder.Long] ??
+ Directory.GetCurrentDirectory();
+
+ var tyeFilePath = Path.Combine(solutionFolderPath, "tye.yaml");
+
+ if (!File.Exists(tyeFilePath))
+ {
+ return String.Empty;
+ }
+
+ _tyeFileContent = File.ReadAllText(tyeFilePath);
+
+ return _tyeFileContent;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreDependencyFromPublicStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreRelatedCodeStep.cs
similarity index 86%
rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreDependencyFromPublicStep.cs
rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreRelatedCodeStep.cs
index 1f7b8f4e6f..df8f18128e 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreDependencyFromPublicStep.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreRelatedCodeStep.cs
@@ -3,7 +3,7 @@ using Volo.Abp.Cli.ProjectBuilding.Files;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
- public class RemoveEfCoreDependencyFromPublicStep : ProjectBuildPipelineStep
+ public class RemoveEfCoreRelatedCodeStep : ProjectBuildPipelineStep
{
public override void Execute(ProjectBuildContext context)
{
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromPrometheusStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromPrometheusStep.cs
new file mode 100644
index 0000000000..d2523fbc01
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromPrometheusStep.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
+{
+ public class RemoveProjectFromPrometheusStep : ProjectBuildPipelineStep
+ {
+ private readonly string _name;
+
+ public RemoveProjectFromPrometheusStep(string name)
+ {
+ _name = name;
+ }
+
+ public override void Execute(ProjectBuildContext context)
+ {
+ var tyeFile = context.Files.FirstOrDefault(f => f.Name == "/etc/prometheus/prometheus.yml");
+
+ if (tyeFile == null)
+ {
+ return;
+ }
+
+ var lines = tyeFile.GetLines();
+ var newLines = new List();
+
+ var nameLine = $"- job_name:";
+ var isOneOfTargetLines = false;
+
+ foreach (var line in lines)
+ {
+ if (line.Trim().Equals($"{nameLine} '{_name}'"))
+ {
+ isOneOfTargetLines = true;
+ continue;
+ }
+
+ if (line.Trim().StartsWith(nameLine))
+ {
+ isOneOfTargetLines = false;
+ }
+
+ if (!isOneOfTargetLines)
+ {
+ newLines.Add(line);
+ }
+ }
+
+ tyeFile.SetContent(String.Join(Environment.NewLine, newLines));
+ }
+
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromTyeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromTyeStep.cs
new file mode 100644
index 0000000000..8c84acf9e9
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromTyeStep.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
+{
+ public class RemoveProjectFromTyeStep : ProjectBuildPipelineStep
+ {
+ private readonly string _name;
+
+ public RemoveProjectFromTyeStep(string name)
+ {
+ _name = name;
+ }
+
+ public override void Execute(ProjectBuildContext context)
+ {
+ var tyeFile = context.Files.FirstOrDefault(f => f.Name == "/tye.yaml");
+
+ if (tyeFile == null)
+ {
+ return;
+ }
+
+ var lines = tyeFile.GetLines();
+ var newLines = new List();
+
+ var nameLine = $"- name:";
+ var isOneOfTargetLines = false;
+
+ foreach (var line in lines)
+ {
+ if (line.Trim().Equals($"{nameLine} {_name}"))
+ {
+ isOneOfTargetLines = true;
+ continue;
+ }
+
+ if (line.Trim().StartsWith(nameLine))
+ {
+ isOneOfTargetLines = false;
+ }
+
+ if (!isOneOfTargetLines)
+ {
+ newLines.Add(line);
+ }
+ }
+
+ tyeFile.SetContent(String.Join(Environment.NewLine, newLines));
+ }
+
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
index 7541894181..260d996484 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
@@ -64,6 +64,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests"));
+ steps.Add(new RemoveEfCoreRelatedCodeStep());
}
if (context.BuildArgs.DatabaseProvider != DatabaseProvider.MongoDb)
@@ -154,11 +155,6 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
steps.Add(new ChangePublicAuthPortStep());
}
- if (context.BuildArgs.DatabaseProvider != DatabaseProvider.NotSpecified && context.BuildArgs.DatabaseProvider != DatabaseProvider.EntityFrameworkCore)
- {
- steps.Add(new RemoveEfCoreDependencyFromPublicStep());
- }
-
// We disabled cms-kit for v4.2 release.
if (true || context.BuildArgs.ExtraProperties.ContainsKey("without-cms-kit"))
{
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs
index de589336b0..54ec4894b9 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs
@@ -31,6 +31,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
var steps = new List();
DeleteUnrelatedUiProject(context, steps);
+ SetRandomPortForHostProject(context, steps);
RandomizeStringEncryption(context, steps);
return steps;
@@ -54,6 +55,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
}
}
+ private static void SetRandomPortForHostProject(ProjectBuildContext context, List steps)
+ {
+ steps.Add(new MicroserviceServiceRandomPortStep("44371"));
+ }
+
private static void RandomizeStringEncryption(ProjectBuildContext context, List steps)
{
steps.Add(new RandomizeStringEncryptionStep());
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
index 0a56f5272a..909c9841d9 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
@@ -36,9 +36,14 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web",null,
"/applications/web/src/MyCompanyName.MyProjectName.Web"));
steps.Add(new RemoveFolderStep("/applications/web"));
+ steps.Add(new RemoveProjectFromTyeStep("web"));
+ steps.Add(new RemoveProjectFromPrometheusStep("web"));
+
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor",null,
"/applications/blazor/src/MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RemoveFolderStep("/applications/blazor"));
+ steps.Add(new RemoveProjectFromTyeStep("blazor"));
+
steps.Add(new RemoveFolderStep("/angular"));
break;
@@ -46,9 +51,13 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web",null,
"/applications/web/src/MyCompanyName.MyProjectName.Web"));
steps.Add(new RemoveFolderStep("/applications/web"));
+ steps.Add(new RemoveProjectFromTyeStep("web"));
+ steps.Add(new RemoveProjectFromPrometheusStep("web"));
+
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor",null,
"/applications/blazor/src/MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RemoveFolderStep("/applications/blazor"));
+ steps.Add(new RemoveProjectFromTyeStep("blazor"));
break;
case UiFramework.Blazor:
@@ -56,6 +65,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
"/applications/web/src/MyCompanyName.MyProjectName.Web"));
steps.Add(new RemoveFolderStep("/applications/web"));
steps.Add(new RemoveFolderStep("/angular"));
+ steps.Add(new RemoveProjectFromTyeStep("web"));
+ steps.Add(new RemoveProjectFromPrometheusStep("web"));
break;
case UiFramework.Mvc:
@@ -63,6 +74,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor",null,
"/applications/blazor/src/MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RemoveFolderStep("/applications/blazor"));
+ steps.Add(new RemoveProjectFromTyeStep("blazor"));
+
steps.Add(new RemoveFolderStep("/angular"));
break;
}
diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionLoggingExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionLoggingExtensions.cs
new file mode 100644
index 0000000000..e31e7cbf51
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionLoggingExtensions.cs
@@ -0,0 +1,13 @@
+using Microsoft.Extensions.Logging;
+using Volo.Abp.Logging;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class ServiceCollectionLoggingExtensions
+ {
+ public static ILogger GetInitLogger(this IServiceCollection services)
+ {
+ return services.GetSingletonInstance().Create();
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
index f272484611..5fe47112ba 100644
--- a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
+++ b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
@@ -260,8 +260,9 @@ namespace System
///
/// String to convert
/// set true to use current culture. Otherwise, invariant culture will be used.
+ /// set true to if you want to convert 'XYZ' to 'xyz'.
/// camelCase of the string
- public static string ToCamelCase(this string str, bool useCurrentCulture = false)
+ public static string ToCamelCase(this string str, bool useCurrentCulture = false, bool handleAbbreviations = false)
{
if (string.IsNullOrWhiteSpace(str))
{
@@ -273,6 +274,11 @@ namespace System
return useCurrentCulture ? str.ToLower() : str.ToLowerInvariant();
}
+ if (handleAbbreviations && IsAllUpperCase(str))
+ {
+ return useCurrentCulture ? str.ToLower() : str.ToLowerInvariant();
+ }
+
return (useCurrentCulture ? char.ToLower(str[0]) : char.ToLowerInvariant(str[0])) + str.Substring(1);
}
@@ -545,5 +551,18 @@ namespace System
return encoding.GetBytes(str);
}
+
+ private static bool IsAllUpperCase(string input)
+ {
+ for (int i = 0; i < input.Length; i++)
+ {
+ if (Char.IsLetter(input[i]) && !Char.IsUpper(input[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
index 1e5b251ca0..61cee2ce90 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Internal;
+using Volo.Abp.Logging;
using Volo.Abp.Modularity;
namespace Volo.Abp
@@ -60,7 +62,7 @@ namespace Volo.Abp
{
//TODO: Shutdown if not done before?
}
-
+
protected virtual void SetServiceProvider(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
@@ -71,12 +73,31 @@ namespace Volo.Abp
{
using (var scope = ServiceProvider.CreateScope())
{
+ WriteInitLogs(scope.ServiceProvider);
scope.ServiceProvider
.GetRequiredService()
.InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider));
}
}
+ protected virtual void WriteInitLogs(IServiceProvider serviceProvider)
+ {
+ var logger = serviceProvider.GetService>();
+ if (logger == null)
+ {
+ return;
+ }
+
+ var initLogger = serviceProvider.GetRequiredService().Create();
+
+ foreach (var entry in initLogger.Entries)
+ {
+ logger.Log(entry.LogLevel, entry.EventId, entry.State, entry.Exception, entry.Formatter);
+ }
+
+ initLogger.Entries.Clear();
+ }
+
protected virtual IReadOnlyList LoadModules(IServiceCollection services, AbpApplicationCreationOptions options)
{
return services
@@ -87,7 +108,7 @@ namespace Volo.Abp
options.PlugInSources
);
}
-
+
//TODO: We can extract a new class for this
protected virtual void ConfigureServices()
{
@@ -158,4 +179,4 @@ namespace Volo.Abp
}
}
}
-}
\ No newline at end of file
+}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs
index 83a7ec163e..ee06003204 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Volo.Abp.Logging;
using Volo.Abp.Modularity;
using Volo.Abp.Reflection;
@@ -16,7 +17,7 @@ namespace Volo.Abp.Internal
}
internal static void AddCoreAbpServices(this IServiceCollection services,
- IAbpApplication abpApplication,
+ IAbpApplication abpApplication,
AbpApplicationCreationOptions applicationCreationOptions)
{
var moduleLoader = new ModuleLoader();
@@ -35,6 +36,7 @@ namespace Volo.Abp.Internal
services.TryAddSingleton(moduleLoader);
services.TryAddSingleton(assemblyFinder);
services.TryAddSingleton(typeFinder);
+ services.TryAddSingleton(new DefaultInitLoggerFactory());
services.AddAssemblyOf();
@@ -47,4 +49,4 @@ namespace Volo.Abp.Internal
});
}
}
-}
\ No newline at end of file
+}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Logging/AbpInitLogEntry.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/AbpInitLogEntry.cs
new file mode 100644
index 0000000000..da44f2be2b
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/AbpInitLogEntry.cs
@@ -0,0 +1,20 @@
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace Volo.Abp.Logging
+{
+ public class AbpInitLogEntry
+ {
+ public LogLevel LogLevel { get; set; }
+
+ public EventId EventId { get; set; }
+
+ public object State { get; set; }
+
+ public Exception Exception { get; set; }
+
+ public Func