diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
index 51d818aad7..2548ab8176 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
@@ -215,8 +215,25 @@
"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"
}
}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json
index 34ee392140..c193a0b621 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json
@@ -166,6 +166,22 @@
"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",
+ "ClearFilter": "Filtreyi temizle"
}
}
\ 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..7351fde5cb 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,27 @@
"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": "成功索引所有的帖子"
}
}
\ 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/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/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.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.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/RemoveProjectFromTyeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromTyeStep.cs
new file mode 100644
index 0000000000..1c51e22bc9
--- /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.Equals($"{nameLine} {_name}"))
+ {
+ isOneOfTargetLines = true;
+ continue;
+ }
+
+ if (line.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/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..1efbe09ff1 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,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 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 +50,12 @@ 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 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 +63,7 @@ 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"));
break;
case UiFramework.Mvc:
@@ -63,6 +71,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