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/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/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