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: + + ![custom-error-handler-toaster-message](images/custom-error-handler-toaster-message.jpg) + + - 404 not found errors is handled. When a 404 error occurs, `Error404Component` will be appended to the `` as shown below: + +![custom-error-handler-404-component](images/custom-error-handler-404-component.jpg) + + - 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 Formatter { get; set; } + + public string Message => Formatter(State, Exception); + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLogger.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLogger.cs new file mode 100644 index 0000000000..2cc0963282 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLogger.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.Logging +{ + public class DefaultInitLogger : IInitLogger + { + public List Entries { get; } + + public DefaultInitLogger() + { + Entries = new List(); + } + + public virtual void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + Entries.Add(new AbpInitLogEntry + { + LogLevel = logLevel, + EventId = eventId, + State = state, + Exception = exception, + Formatter = (s, e) => formatter((TState)s, e), + }); + } + + public virtual bool IsEnabled(LogLevel logLevel) + { + return logLevel != LogLevel.None; + } + + public virtual IDisposable BeginScope(TState state) + { + return NullDisposable.Instance; + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs new file mode 100644 index 0000000000..cb2e5d4060 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Logging +{ + public class DefaultInitLoggerFactory : IInitLoggerFactory + { + private readonly Dictionary _cache = new Dictionary(); + + public virtual IInitLogger Create() + { + return (IInitLogger)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger());; + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLogger.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLogger.cs new file mode 100644 index 0000000000..bcb578394f --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLogger.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.Logging +{ + public interface IInitLogger : ILogger + { + public List Entries { get; } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLoggerFactory.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLoggerFactory.cs new file mode 100644 index 0000000000..91300f7967 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLoggerFactory.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Logging +{ + public interface IInitLoggerFactory + { + IInitLogger Create(); + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs index d58483811b..e8bccb8e18 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs @@ -2,15 +2,17 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.Extensions.Logging; namespace Volo.Abp.Modularity { internal static class AbpModuleHelper { - public static List FindAllModuleTypes(Type startupModuleType) + public static List FindAllModuleTypes(Type startupModuleType, ILogger logger) { var moduleTypes = new List(); - AddModuleAndDependenciesResursively(moduleTypes, startupModuleType); + logger.Log(LogLevel.Information, "Loaded ABP modules:"); + AddModuleAndDependenciesResursively(moduleTypes, startupModuleType, logger); return moduleTypes; } @@ -35,7 +37,11 @@ namespace Volo.Abp.Modularity return dependencies; } - private static void AddModuleAndDependenciesResursively(List moduleTypes, Type moduleType) + private static void AddModuleAndDependenciesResursively( + List moduleTypes, + Type moduleType, + ILogger logger, + int depth = 0) { AbpModule.CheckAbpModuleType(moduleType); @@ -45,10 +51,11 @@ namespace Volo.Abp.Modularity } moduleTypes.Add(moduleType); + logger.Log(LogLevel.Information, $"{new string(' ', depth * 2)}- {moduleType.FullName}"); foreach (var dependedModuleType in FindDependedModuleTypes(moduleType)) { - AddModuleAndDependenciesResursively(moduleTypes, dependedModuleType); + AddModuleAndDependenciesResursively(moduleTypes, dependedModuleType, logger, depth + 1); } } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs index 7e7e69d457..3c5fb9aa56 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs @@ -25,7 +25,7 @@ namespace Volo.Abp.Modularity } private List GetDescriptors( - IServiceCollection services, + IServiceCollection services, Type startupModuleType, PlugInSourceList plugInSources) { @@ -43,14 +43,16 @@ namespace Volo.Abp.Modularity Type startupModuleType, PlugInSourceList plugInSources) { + var logger = services.GetInitLogger(); + //All modules starting from the startup module - foreach (var moduleType in AbpModuleHelper.FindAllModuleTypes(startupModuleType)) + foreach (var moduleType in AbpModuleHelper.FindAllModuleTypes(startupModuleType, logger)) { modules.Add(CreateModuleDescriptor(services, moduleType)); } //Plugin modules - foreach (var moduleType in plugInSources.GetAllModules()) + foreach (var moduleType in plugInSources.GetAllModules(logger)) { if (modules.Any(m => m.Type == moduleType)) { @@ -102,4 +104,4 @@ namespace Volo.Abp.Modularity } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs index 70b9036150..764acf1289 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs @@ -32,8 +32,6 @@ namespace Volo.Abp.Modularity public void InitializeModules(ApplicationInitializationContext context) { - LogListOfModules(); - foreach (var contributor in _lifecycleContributors) { foreach (var module in _moduleContainer.Modules) @@ -52,16 +50,6 @@ namespace Volo.Abp.Modularity _logger.LogInformation("Initialized all ABP modules."); } - private void LogListOfModules() - { - _logger.LogInformation("Loaded ABP modules:"); - - foreach (var module in _moduleContainer.Modules) - { - _logger.LogInformation("- " + module.Type.FullName); - } - } - public void ShutdownModules(ApplicationShutdownContext context) { var modules = _moduleContainer.Modules.Reverse().ToList(); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceExtensions.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceExtensions.cs index bf44f94605..92be156b69 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceExtensions.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceExtensions.cs @@ -1,21 +1,23 @@ using System; using System.Linq; using JetBrains.Annotations; +using Microsoft.Extensions.Logging; +using Volo.Abp.Logging; namespace Volo.Abp.Modularity.PlugIns { public static class PlugInSourceExtensions { [NotNull] - public static Type[] GetModulesWithAllDependencies([NotNull] this IPlugInSource plugInSource) + public static Type[] GetModulesWithAllDependencies([NotNull] this IPlugInSource plugInSource, ILogger logger) { Check.NotNull(plugInSource, nameof(plugInSource)); return plugInSource .GetModules() - .SelectMany(AbpModuleHelper.FindAllModuleTypes) + .SelectMany(type => AbpModuleHelper.FindAllModuleTypes(type, logger)) .Distinct() .ToArray(); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceList.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceList.cs index 1e96e26e14..06c0187a32 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceList.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceList.cs @@ -2,18 +2,19 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using Microsoft.Extensions.Logging; namespace Volo.Abp.Modularity.PlugIns { public class PlugInSourceList : List { [NotNull] - internal Type[] GetAllModules() + internal Type[] GetAllModules(ILogger logger) { return this - .SelectMany(pluginSource => pluginSource.GetModulesWithAllDependencies()) + .SelectMany(pluginSource => pluginSource.GetModulesWithAllDependencies(logger)) .Distinct() .ToArray(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs index 3541f9b78e..4b7ed95e89 100644 --- a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Shouldly; +using Volo.Abp.Logging; using Volo.Abp.Modularity.PlugIns; using Xunit; @@ -11,7 +12,12 @@ namespace Volo.Abp.Modularity public void Should_Load_Modules_By_Dependency_Order() { var moduleLoader = new ModuleLoader(); - var modules = moduleLoader.LoadModules(new ServiceCollection(), typeof(MyStartupModule), new PlugInSourceList()); + var modules = moduleLoader.LoadModules( + new ServiceCollection() + .AddSingleton(new DefaultInitLoggerFactory()), + typeof(MyStartupModule), + new PlugInSourceList() + ); modules.Length.ShouldBe(2); modules[0].Type.ShouldBe(typeof(IndependentEmptyModule)); modules[1].Type.ShouldBe(typeof(MyStartupModule)); @@ -22,7 +28,7 @@ namespace Volo.Abp.Modularity { public override void ConfigureServices(ServiceConfigurationContext context) { - + } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo.CmsKit.HttpApi.csproj b/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo.CmsKit.HttpApi.csproj index 64a3dcef21..b23ef3ba0e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo.CmsKit.HttpApi.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo.CmsKit.HttpApi.csproj @@ -9,9 +9,9 @@ - + - + diff --git a/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs b/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs index c4a45bb8cf..219f2d9442 100644 --- a/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs @@ -5,8 +5,8 @@ using Volo.CmsKit.Public; namespace Volo.CmsKit { [DependsOn( - typeof(CmsKitAdminApplicationModule), - typeof(CmsKitPublicApplicationModule), + typeof(CmsKitAdminHttpApiModule), + typeof(CmsKitPublicHttpApiModule), typeof(CmsKitApplicationContractsModule) )] public class CmsKitHttpApiModule : AbpModule diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts index 1835b80eef..1001c98085 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts @@ -12,13 +12,14 @@ import { } from '@angular/core'; import { NavigationError, ResolveEnd } from '@angular/router'; import { Actions, ofActionSuccessful } from '@ngxs/store'; -import { Observable, Subject } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; +import { Observable, of, Subject, throwError } from 'rxjs'; +import { catchError, filter, map, switchMap } from 'rxjs/operators'; import snq from 'snq'; import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; import { ErrorScreenErrorCodes, HttpErrorConfig } from '../models/common'; import { Confirmation } from '../models/confirmation'; import { ConfirmationService } from '../services/confirmation.service'; +import { HTTP_ERROR_HANDLER } from '../tokens/http-error.token'; export const DEFAULT_ERROR_MESSAGES = { defaultError: { @@ -70,28 +71,32 @@ export const DEFAULT_ERROR_LOCALIZATIONS = { export class ErrorHandler { componentRef: ComponentRef; + protected httpErrorHandler = this.injector.get(HTTP_ERROR_HANDLER, (_, err: HttpErrorResponse) => + throwError(err), + ); + constructor( - private actions: Actions, - private routerEvents: RouterEvents, - private confirmationService: ConfirmationService, - private cfRes: ComponentFactoryResolver, - private rendererFactory: RendererFactory2, - private injector: Injector, - @Inject('HTTP_ERROR_CONFIG') private httpErrorConfig: HttpErrorConfig, + protected actions: Actions, + protected routerEvents: RouterEvents, + protected confirmationService: ConfirmationService, + protected cfRes: ComponentFactoryResolver, + protected rendererFactory: RendererFactory2, + protected injector: Injector, + @Inject('HTTP_ERROR_CONFIG') protected httpErrorConfig: HttpErrorConfig, ) { this.listenToRestError(); this.listenToRouterError(); this.listenToRouterDataResolved(); } - private listenToRouterError() { + protected listenToRouterError() { this.routerEvents .getNavigationEvents('Error') .pipe(filter(this.filterRouteErrors)) .subscribe(() => this.show404Page()); } - private listenToRouterDataResolved() { + protected listenToRouterDataResolved() { this.routerEvents .getEvents(ResolveEnd) .pipe(filter(() => !!this.componentRef)) @@ -101,113 +106,127 @@ export class ErrorHandler { }); } - private listenToRestError() { + protected listenToRestError() { this.actions .pipe( ofActionSuccessful(RestOccurError), map(action => action.payload), filter(this.filterRestErrors), + switchMap(this.executeErrorHandler), ) - .subscribe(err => { - const body = snq(() => err.error.error, { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, - }); + .subscribe(); + } - if (err instanceof HttpErrorResponse && err.headers.get('_AbpErrorFormat')) { - const confirmation$ = this.showError(null, null, body); + private executeErrorHandler = error => { + const returnValue = this.httpErrorHandler(this.injector, error); - if (err.status === 401) { - confirmation$.subscribe(() => { - this.navigateToLogin(); - }); - } - } else { - switch (err.status) { - case 401: - this.canCreateCustomError(401) - ? this.show401Page() - : this.showError( - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title, - }, - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.details, - }, - ).subscribe(() => this.navigateToLogin()); - break; - case 403: - this.createErrorComponent({ - title: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.title, - }, - details: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.details, - }, - status: 403, - }); - break; - case 404: - this.canCreateCustomError(404) - ? this.show404Page() - : this.showError( - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.details, - }, - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title, - }, - ); - break; - case 500: - this.createErrorComponent({ - title: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.title, + return (returnValue instanceof Observable ? returnValue : of(null)).pipe( + catchError(err => { + this.handleError(err); + return of(null); + }), + ); + }; + + private handleError(err: any) { + const body = snq(() => err.error.error, { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, + }); + + if (err instanceof HttpErrorResponse && err.headers.get('_AbpErrorFormat')) { + const confirmation$ = this.showError(null, null, body); + + if (err.status === 401) { + confirmation$.subscribe(() => { + this.navigateToLogin(); + }); + } + } else { + switch (err.status) { + case 401: + this.canCreateCustomError(401) + ? this.show401Page() + : this.showError( + { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title, }, - details: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.details, + { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.details, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.details, }, - status: 500, - }); - break; - case 0: - if (err.statusText === 'Unknown Error') { - this.createErrorComponent({ - title: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, - }, - details: err.message, - isHomeShow: false, - }); - } - break; - default: - this.showError( + ).subscribe(() => this.navigateToLogin()); + break; + case 403: + this.createErrorComponent({ + title: { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.title, + }, + details: { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.details, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.details, + }, + status: 403, + }); + break; + case 404: + this.canCreateCustomError(404) + ? this.show404Page() + : this.showError( { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.details, + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.details, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.details, }, { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title, }, ); - break; + break; + case 500: + this.createErrorComponent({ + title: { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.title, + }, + details: { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.details, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.details, + }, + status: 500, + }); + break; + case 0: + if (err.statusText === 'Unknown Error') { + this.createErrorComponent({ + title: { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, + }, + details: err.message, + isHomeShow: false, + }); } - } - }); + break; + default: + this.showError( + { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.details, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.details, + }, + { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, + }, + ); + break; + } + } } - private show401Page() { + protected show401Page() { this.createErrorComponent({ title: { key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title, @@ -217,7 +236,7 @@ export class ErrorHandler { }); } - private show404Page() { + protected show404Page() { this.createErrorComponent({ title: { key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title, @@ -227,7 +246,7 @@ export class ErrorHandler { }); } - private showError( + protected showError( message?: LocalizationParam, title?: LocalizationParam, body?: any, @@ -256,7 +275,7 @@ export class ErrorHandler { }); } - private navigateToLogin() { + protected navigateToLogin() { this.injector.get(AuthService).initLogin(); } @@ -304,13 +323,13 @@ export class ErrorHandler { ); } - private filterRestErrors = ({ status }: HttpErrorResponse): boolean => { + protected filterRestErrors = ({ status }: HttpErrorResponse): boolean => { if (typeof status !== 'number') return false; return this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === status) < 0; }; - private filterRouteErrors = (navigationError: NavigationError): boolean => { + protected filterRouteErrors = (navigationError: NavigationError): boolean => { return ( snq(() => navigationError.error.message.indexOf('Cannot match') > -1) && this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === 404) < 0 diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts index 64047e0db7..0e8dd9e592 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts @@ -1,5 +1,7 @@ -import { Type } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { Injector, Type } from '@angular/core'; import { Validation } from '@ngx-validate/core'; +import { Observable } from 'rxjs'; export interface RootParams { httpErrorConfig: HttpErrorConfig; @@ -17,4 +19,9 @@ export interface HttpErrorConfig { }; } +export type HttpErrorHandler = ( + injector: Injector, + httpError: HttpErrorResponse, +) => Observable; + export type LocaleDirection = 'ltr' | 'rtl'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts b/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts index 62f2d2d593..39cb43a350 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts @@ -1,5 +1,5 @@ import { InjectionToken } from '@angular/core'; -import { HttpErrorConfig } from '../models/common'; +import { HttpErrorConfig, HttpErrorHandler } from '../models/common'; export function httpErrorConfigFactory(config = {} as HttpErrorConfig) { if (config.errorScreen && config.errorScreen.component && !config.errorScreen.forWhichErrors) { @@ -13,4 +13,6 @@ export function httpErrorConfigFactory(config = {} as HttpErrorConfig) { } as HttpErrorConfig; } -export const HTTP_ERROR_CONFIG = new InjectionToken('HTTP_ERROR_CONFIG'); +export const HTTP_ERROR_CONFIG = new InjectionToken('HTTP_ERROR_CONFIG'); + +export const HTTP_ERROR_HANDLER = new InjectionToken('HTTP_ERROR_HANDLER');