From 58d9f3fb5cabbc2fad36f5c00d93d282b5cffc33 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Fri, 10 Jan 2020 12:25:19 +0300 Subject: [PATCH 01/68] Updated AuditingMiddleware, updated AuditingManager --- .../Auditing/AbpAuditingMiddleware.cs | 13 ++++- .../Volo/Abp/Auditing/AbpAuditingOptions.cs | 8 +++- .../Volo/Abp/Auditing/AuditingManager.cs | 23 +++++---- .../Volo/Abp/Auditing/IAuditLogSaveHandle.cs | 2 +- .../Mvc/Auditing/AuditTestController.cs | 29 ++++++++++++ .../Mvc/Auditing/AuditTestController_Tests.cs | 47 +++++++++++++++++++ 6 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index d56c29709a..b651274ed4 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -39,6 +39,10 @@ namespace Volo.Abp.AspNetCore.Auditing try { await next(context).ConfigureAwait(false); + } + catch (Exception ex) + { + await scope.SaveAsync(ex).ConfigureAwait(false); } finally { @@ -49,6 +53,13 @@ namespace Volo.Abp.AspNetCore.Auditing private bool ShouldWriteAuditLog(HttpContext httpContext) { + // IF selected, save audit logs on exception on GET requests even if audit log is disabled for GET requests. + if (Options.AlwaysLogOnException && + string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (!Options.IsEnabled) { return false; @@ -59,7 +70,7 @@ namespace Volo.Abp.AspNetCore.Auditing return false; } - if (!Options.IsEnabledForGetRequests && + if (!Options.IsEnabledForGetRequests && string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase)) { return false; diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs index da406fa490..5dcae1e097 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs @@ -29,7 +29,13 @@ namespace Volo.Abp.Auditing /// /// Default: true. /// - public bool IsEnabledForAnonymousUsers { get; set; } + public bool IsEnabledForAnonymousUsers { get; set; } = false; + + /// + /// Audit log e xceptions. + /// Default: false. + /// + public bool AlwaysLogOnException { get; set; } public List Contributors { get; } diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs index 25738843c0..1d3b912311 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs @@ -23,8 +23,8 @@ namespace Volo.Abp.Auditing private readonly IAuditingStore _auditingStore; public AuditingManager( - IAmbientScopeProvider ambientScopeProvider, - IAuditingHelper auditingHelper, + IAmbientScopeProvider ambientScopeProvider, + IAuditingHelper auditingHelper, IAuditingStore auditingStore, IServiceProvider serviceProvider, IOptions options) @@ -84,7 +84,7 @@ namespace Volo.Abp.Auditing { var changeGroups = auditLog.EntityChanges .Where(e => e.ChangeType == EntityChangeType.Updated) - .GroupBy(e => new {e.EntityTypeFullName, e.EntityId}) + .GroupBy(e => new { e.EntityTypeFullName, e.EntityId }) .ToList(); foreach (var changeGroup in changeGroups) @@ -141,7 +141,7 @@ namespace Volo.Abp.Auditing public DisposableSaveHandle( AuditingManager auditingManager, IDisposable scope, - AuditLogInfo auditLog, + AuditLogInfo auditLog, Stopwatch stopWatch) { _auditingManager = auditingManager; @@ -150,15 +150,22 @@ namespace Volo.Abp.Auditing StopWatch = stopWatch; } - public async Task SaveAsync() + public async Task SaveAsync(Exception exception = null) { - await _auditingManager.SaveAsync(this).ConfigureAwait(false); + if (exception != null) + { + this.AuditLog.Exceptions.Add(exception); + } + else + { + await _auditingManager.SaveAsync(this).ConfigureAwait(false); + } } - + public void Dispose() { _scope.Dispose(); - } + } } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs index 4709b745d2..6e48f3525c 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs @@ -5,6 +5,6 @@ namespace Volo.Abp.Auditing { public interface IAuditLogSaveHandle : IDisposable { - Task SaveAsync(); + Task SaveAsync(Exception exception = null); } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs new file mode 100644 index 0000000000..967f3269d2 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.Auditing; + +namespace Volo.Abp.AspNetCore.Mvc.Auditing +{ + [Route("api/audit-test")] + public class AuditTestController : AbpController + { + private readonly AbpAuditingOptions _options; + + public AuditTestController(IOptions options) + { + _options = options.Value; + } + + [Route("audit-success")] + public IActionResult AuditSuccessForGetRequests() + { + return Ok(); + } + + [Route("audit-fail")] + public IActionResult AuditFailForGetRequests() + { + throw new UserFriendlyException("Exception occurred!"); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs new file mode 100644 index 0000000000..97b211ba5a --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using NSubstitute; +using System.Threading.Tasks; +using Volo.Abp.Auditing; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Auditing +{ + public class AuditTestController_Tests : AspNetCoreMvcTestBase + { + private readonly AbpAuditingOptions _options; + private IAuditingStore _auditingStore; + + public AuditTestController_Tests() + { + _options = ServiceProvider.GetRequiredService>().Value; + _auditingStore = ServiceProvider.GetRequiredService(); + } + + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + _auditingStore = Substitute.For(); + services.Replace(ServiceDescriptor.Singleton(_auditingStore)); + base.ConfigureServices(context, services); + } + + [Fact] + public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests() + { + _options.IsEnabledForGetRequests = true; + await GetResponseAsync("api/audit-test/audit-success"); + //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope + } + + [Fact] + public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always() + { + _options.IsEnabled = false; + _options.AlwaysLogOnException = true; + await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.BadRequest); + //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope + } + } +} From 73782c1408389289d17123e282f4753cf2183661 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Mon, 13 Jan 2020 12:35:00 +0300 Subject: [PATCH 02/68] Reverted SaveAsync on IAuditLgSaveHandle instead added AddException method. --- .../Auditing/AbpAuditingMiddleware.cs | 2 +- .../Volo/Abp/Auditing/AuditingManager.cs | 17 +++++++---------- .../Volo/Abp/Auditing/IAuditLogSaveHandle.cs | 3 ++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index b651274ed4..f2929e3f5a 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -42,7 +42,7 @@ namespace Volo.Abp.AspNetCore.Auditing } catch (Exception ex) { - await scope.SaveAsync(ex).ConfigureAwait(false); + scope.AddException(ex); } finally { diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs index 1d3b912311..f2980d1cf5 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs @@ -150,16 +150,13 @@ namespace Volo.Abp.Auditing StopWatch = stopWatch; } - public async Task SaveAsync(Exception exception = null) - { - if (exception != null) - { - this.AuditLog.Exceptions.Add(exception); - } - else - { - await _auditingManager.SaveAsync(this).ConfigureAwait(false); - } + public async Task SaveAsync() + { + await _auditingManager.SaveAsync(this).ConfigureAwait(false); + } + public void AddException(Exception exception) + { + this.AuditLog.Exceptions.Add(exception); } public void Dispose() diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs index 6e48f3525c..a549dcb755 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs @@ -5,6 +5,7 @@ namespace Volo.Abp.Auditing { public interface IAuditLogSaveHandle : IDisposable { - Task SaveAsync(Exception exception = null); + Task SaveAsync(); + void AddException(Exception exception); } } \ No newline at end of file From 62127df0eab23d7f4c252a2b88b8dc4730039794 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Mon, 13 Jan 2020 13:25:31 +0300 Subject: [PATCH 03/68] removed empty space --- .../src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs index f2980d1cf5..409084ac6f 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs @@ -152,7 +152,7 @@ namespace Volo.Abp.Auditing public async Task SaveAsync() { - await _auditingManager.SaveAsync(this).ConfigureAwait(false); + await _auditingManager.SaveAsync(this).ConfigureAwait(false); } public void AddException(Exception exception) { From 5c73a61e1c76f6e97cf49133fd2114c8d03de8bb Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Mon, 13 Jan 2020 16:57:09 +0300 Subject: [PATCH 04/68] updated AuditingMiddleware for AlwaysLogOnExceptions --- .../Auditing/AbpAuditingMiddleware.cs | 27 ++++++++++++------- .../Volo/Abp/Auditing/AbpAuditingOptions.cs | 7 ++--- .../Mvc/Auditing/AuditTestController_Tests.cs | 2 +- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index f2929e3f5a..cc71797a5b 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -33,6 +33,23 @@ namespace Volo.Abp.AspNetCore.Auditing await next(context).ConfigureAwait(false); return; } + if (Options.AlwaysLogOnException) + { + using (var scope = _auditingManager.BeginScope()) + { + try + { + await next(context).ConfigureAwait(false); + return; + } + catch (Exception) + { + await scope.SaveAsync().ConfigureAwait(false); + if (!Options.HideErrors) + throw; + } + } + } using (var scope = _auditingManager.BeginScope()) { @@ -40,9 +57,8 @@ namespace Volo.Abp.AspNetCore.Auditing { await next(context).ConfigureAwait(false); } - catch (Exception ex) + catch (Exception) { - scope.AddException(ex); } finally { @@ -53,13 +69,6 @@ namespace Volo.Abp.AspNetCore.Auditing private bool ShouldWriteAuditLog(HttpContext httpContext) { - // IF selected, save audit logs on exception on GET requests even if audit log is disabled for GET requests. - if (Options.AlwaysLogOnException && - string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (!Options.IsEnabled) { return false; diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs index 5dcae1e097..db13bdaf74 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs @@ -29,11 +29,11 @@ namespace Volo.Abp.Auditing /// /// Default: true. /// - public bool IsEnabledForAnonymousUsers { get; set; } = false; + public bool IsEnabledForAnonymousUsers { get; set; } /// - /// Audit log e xceptions. - /// Default: false. + /// Audit log on exceptions. + /// Default: true. /// public bool AlwaysLogOnException { get; set; } @@ -54,6 +54,7 @@ namespace Volo.Abp.Auditing IsEnabled = true; IsEnabledForAnonymousUsers = true; HideErrors = true; + AlwaysLogOnException = true; Contributors = new List(); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs index 97b211ba5a..b65f8eef1f 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs @@ -39,7 +39,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always() { _options.IsEnabled = false; - _options.AlwaysLogOnException = true; + _options.AlwaysLogOnException = false; await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.BadRequest); //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope } From ca64497b1f8a3cba727a8877ae3041dab5e437fd Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Mon, 13 Jan 2020 17:32:18 +0300 Subject: [PATCH 05/68] reverted back --- .../Auditing/AbpAuditingMiddleware.cs | 26 +++++++------------ .../Mvc/Auditing/AuditTestController_Tests.cs | 1 + 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index cc71797a5b..4f0e87f27f 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -33,23 +33,6 @@ namespace Volo.Abp.AspNetCore.Auditing await next(context).ConfigureAwait(false); return; } - if (Options.AlwaysLogOnException) - { - using (var scope = _auditingManager.BeginScope()) - { - try - { - await next(context).ConfigureAwait(false); - return; - } - catch (Exception) - { - await scope.SaveAsync().ConfigureAwait(false); - if (!Options.HideErrors) - throw; - } - } - } using (var scope = _auditingManager.BeginScope()) { @@ -59,6 +42,10 @@ namespace Volo.Abp.AspNetCore.Auditing } catch (Exception) { + if (!Options.HideErrors) + { + throw; + } } finally { @@ -69,6 +56,11 @@ namespace Volo.Abp.AspNetCore.Auditing private bool ShouldWriteAuditLog(HttpContext httpContext) { + if (Options.AlwaysLogOnException) + { + return true; + } + if (!Options.IsEnabled) { return false; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs index b65f8eef1f..1f81118e03 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs @@ -40,6 +40,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing { _options.IsEnabled = false; _options.AlwaysLogOnException = false; + _options.HideErrors = false; await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.BadRequest); //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope } From 1a9fd66be77d9309ee45869c99ccab0e275f5647 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Mon, 13 Jan 2020 19:14:39 +0300 Subject: [PATCH 06/68] updated AbpAuditingMiddleware --- .../Auditing/AbpAuditingMiddleware.cs | 42 ++++++++++--------- .../Mvc/Auditing/AuditTestController_Tests.cs | 6 +-- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index 4f0e87f27f..621e78630e 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -33,7 +33,7 @@ namespace Volo.Abp.AspNetCore.Auditing await next(context).ConfigureAwait(false); return; } - + bool hasError = false; using (var scope = _auditingManager.BeginScope()) { try @@ -42,6 +42,7 @@ namespace Volo.Abp.AspNetCore.Auditing } catch (Exception) { + hasError = true; if (!Options.HideErrors) { throw; @@ -49,34 +50,37 @@ namespace Volo.Abp.AspNetCore.Auditing } finally { - await scope.SaveAsync().ConfigureAwait(false); + if (ShouldWriteAuditLog(context, hasError)) + { + await scope.SaveAsync().ConfigureAwait(false); + } } } } - private bool ShouldWriteAuditLog(HttpContext httpContext) + private bool ShouldWriteAuditLog(HttpContext httpContext, bool hasError = false) { - if (Options.AlwaysLogOnException) + if (!Options.IsEnabled) + { + return false; + } + + if (Options.AlwaysLogOnException || hasError) { return true; + } + + if (!Options.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated) + { + return false; } - if (!Options.IsEnabled) - { - return false; - } - - if (!Options.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated) - { - return false; - } - if (!Options.IsEnabledForGetRequests && - string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - + string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + return true; } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs index 1f81118e03..b411b20ee0 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs @@ -31,6 +31,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests() { _options.IsEnabledForGetRequests = true; + _options.AlwaysLogOnException = false; await GetResponseAsync("api/audit-test/audit-success"); //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope } @@ -38,9 +39,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing [Fact] public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always() { - _options.IsEnabled = false; - _options.AlwaysLogOnException = false; - _options.HideErrors = false; + _options.IsEnabled = true; + _options.AlwaysLogOnException = true; await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.BadRequest); //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope } From cce337439a1db4cf82ac483116b80f7614ed694b Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 16 Jan 2020 10:54:22 +0300 Subject: [PATCH 07/68] fixed logic error on middleware --- .../Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index 621e78630e..eee629e969 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -28,11 +28,6 @@ namespace Volo.Abp.AspNetCore.Auditing public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - if (!ShouldWriteAuditLog(context)) - { - await next(context).ConfigureAwait(false); - return; - } bool hasError = false; using (var scope = _auditingManager.BeginScope()) { @@ -53,7 +48,7 @@ namespace Volo.Abp.AspNetCore.Auditing if (ShouldWriteAuditLog(context, hasError)) { await scope.SaveAsync().ConfigureAwait(false); - } + } } } } @@ -65,10 +60,10 @@ namespace Volo.Abp.AspNetCore.Auditing return false; } - if (Options.AlwaysLogOnException || hasError) + if (Options.AlwaysLogOnException && hasError) { return true; - } + } if (!Options.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated) { From 9a2e84de688fe61cb033f264a2aab63ee8493087 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Mon, 10 Feb 2020 14:26:10 +0300 Subject: [PATCH 08/68] Create DocumentNotFoundPage https://github.com/volosoft/volo/issues/751 --- modules/docs/Volo.Docs.sln | 4 +- .../docs/src/Volo.Docs.Web/DocsWebModule.cs | 8 +- .../Pages/Documents/Project/Index.cshtml | 436 +++++++++--------- .../Pages/Documents/Project/Index.cshtml.cs | 17 + .../DocumentNotFoundComponent/Default.cshtml | 11 + .../DocumentNotFoundPageModel.cs | 15 + .../DocumentNotFoundViewComponent.cs | 26 ++ .../src/Volo.Docs.Web/Volo.Docs.Web.csproj | 1 + 8 files changed, 305 insertions(+), 213 deletions(-) create mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml create mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs create mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs diff --git a/modules/docs/Volo.Docs.sln b/modules/docs/Volo.Docs.sln index 74e748ac9b..1c32767baa 100644 --- a/modules/docs/Volo.Docs.sln +++ b/modules/docs/Volo.Docs.sln @@ -57,9 +57,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoloDocs.Web", "app\VoloDoc EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoloDocs.Migrator", "app\VoloDocs.Migrator\VoloDocs.Migrator.csproj", "{8A5E5001-C017-44A8-ADDA-DC66C102556E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.MongoDB", "src\Volo.Docs.MongoDB\Volo.Docs.MongoDB.csproj", "{DBE846CD-1BED-4F2C-ABF2-94F6240BCB9B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Docs.MongoDB", "src\Volo.Docs.MongoDB\Volo.Docs.MongoDB.csproj", "{DBE846CD-1BED-4F2C-ABF2-94F6240BCB9B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.MongoDB.Tests", "test\Volo.Docs.MongoDB.Tests\Volo.Docs.MongoDB.Tests.csproj", "{C5E2A2A3-D54D-4C2E-97BA-EA50A49ED7AD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Docs.MongoDB.Tests", "test\Volo.Docs.MongoDB.Tests\Volo.Docs.MongoDB.Tests.csproj", "{C5E2A2A3-D54D-4C2E-97BA-EA50A49ED7AD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs b/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs index 13f29184a9..5b7309a310 100644 --- a/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs +++ b/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs @@ -5,7 +5,9 @@ using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Packages; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AutoMapper; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -18,7 +20,11 @@ namespace Volo.Docs { [DependsOn( typeof(DocsHttpApiModule), - typeof(AbpAspNetCoreMvcUiBootstrapModule) + typeof(AbpAutoMapperModule), + typeof(AbpAspNetCoreMvcUiBootstrapModule), + typeof(AbpAspNetCoreMvcUiThemeSharedModule), + typeof(AbpAspNetCoreMvcUiPackagesModule), + typeof(AbpAspNetCoreMvcUiBundlingModule) )] public class DocsWebModule : AbpModule { diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index bebf083381..3af95c420d 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -7,6 +7,7 @@ @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Popper @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs @using Volo.Abp.AspNetCore.Mvc.UI.Theming +@using Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent @using Volo.Docs @using Volo.Docs.Localization @using Volo.Docs.Pages.Documents.Project @@ -39,268 +40,283 @@ } -
-
-
-
+@if (Model.DocumentFound) +{ +
+
+
+
-
- -
- -
+
+ +
-
- @if (Model.VersionSelectItems.Any()) - { -
-
-
-
-
- -
+
- + @if (Model.ShowProjectsCombobox && Model.ProjectSelectItems.Count > 1) + { +
+
+
+
+
+ +
} - @if (Model.LanguageSelectListItems.Count > 1) - { -
-
-
-
-
- +
+ @if (Model.VersionSelectItems.Any()) + { +
+
+
+
+
+ +
+ +
-
-
- } -
+ } -
-
-
-
- + @if (Model.LanguageSelectListItems.Count > 1) + { +
+
+
+
+
+ +
+ +
+
+
- - -
+ }
-
+
+
+
+
+ +
- @if (Model.Navigation == null || Model.Navigation.Content.IsNullOrEmpty()) - { -
- @L["NavigationDocumentNotFound"] + +
+
- } - else - { - - } -
- -
-
- @if (Model.Document != null) - { -
- +
-
-
-
- @if (Model.Document.Contributors != null && Model.Document.Contributors.Count > 0) - { -
- @(L["Contributors"].Value + " :") - @foreach (var contributor in Model.Document.Contributors) - { - - - - } -
- } + @if (Model.Document != null) + { +
+ -
-
+
+
+
+ @if (Model.Document.Contributors != null && Model.Document.Contributors.Count > 0) + { +
+ @(L["Contributors"].Value + " :") + @foreach (var contributor in Model.Document.Contributors) + { + + + + } +
+ } - @if (Model.DocumentLanguageIsDifferent) + @if (Model.DocumentPreferences != null && Model.DocumentPreferences.Parameters != null && Model.DocumentPreferences.Parameters.Any()) { - - @L["DocumentNotFoundInSelectedLanguage"] - +
+ + +

+ + @L["MultipleVersionDocumentInfo"] +

+
+
+ + @foreach (var parameter in Model.DocumentPreferences.Parameters) + { + +
+
+ @(parameter.DisplayName) +
+ +
+
+ } +
+
} - @Html.Raw(Model.Document.Content) -
+
+ +
+
+ + @if (Model.DocumentLanguageIsDifferent) + { + + @L["DocumentNotFoundInSelectedLanguage"] + + } + @Html.Raw(Model.Document.Content) +
-
-
-
+
-
+
-
@L["InThisDocument"]
- +
@L["InThisDocument"]
+ -
-
- } - else - { -
-

@L["DocumentNotFound"]

+ } + else + { +
+

@L["DocumentNotFound"]

- - - @L["BackToWebsite"] - -
- } + + + @L["BackToWebsite"] + +
+ } +
-
- +} +else +{ + @(await Component.InvokeAsync(new + { + model = new DocumentNotFoundPageModel + { + ProjectName = Model.ProjectName, + DocumentName = Model.DocumentName, + LanguageCode = Model.LanguageCode, + Version = Model.Version, + } + })) +} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index 82b58ad117..e3802b2b36 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -33,6 +33,8 @@ namespace Volo.Docs.Pages.Documents.Project [BindProperty(SupportsGet = true)] public string LanguageCode { get; set; } + public bool DocumentFound { get; set; } = true; + public string DefaultLanguageCode { get; set; } public ProjectDto Project { get; set; } @@ -86,6 +88,21 @@ namespace Volo.Docs.Pages.Documents.Project } public async Task OnGetAsync() + { + try + { + return await SetPageAsync(); + } + catch (DocumentNotFoundException exception) + { + Logger.LogWarning(exception.Message); + + DocumentFound = false; + return Page(); + } + } + + private async Task SetPageAsync() { DocumentsUrlPrefix = _uiOptions.RoutePrefix; ShowProjectsCombobox = _uiOptions.ShowProjectsCombobox; diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml new file mode 100644 index 0000000000..ade6e18529 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml @@ -0,0 +1,11 @@ +@using Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent +@model DocumentNotFoundPageModel +@{ +} + +

"@Model.DocumentName" not found in "@Model.ProjectName" documents, with "@Model.Version" version and "@Model.LanguageCode" language

+
+ + click here to return. + + diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs new file mode 100644 index 0000000000..72b2b97584 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs @@ -0,0 +1,15 @@ +namespace Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent +{ + public class DocumentNotFoundPageModel + { + public string ProjectName { get; set; } + + public string LanguageCode { get; set; } + + public string Version { get; set; } + + public string DocumentName { get; set; } + + public string DocumentsUrlPrefix { get; set; } + } +} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs new file mode 100644 index 0000000000..7aeed9beee --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent +{ + public class DocumentNotFoundViewComponent : AbpViewComponent + { + private readonly DocsUiOptions _options; + + public DocumentNotFoundViewComponent(IOptions options) + { + _options = options.Value; + } + public IViewComponentResult Invoke(DocumentNotFoundPageModel model, string defaultErrorMessageKey) + { + model.DocumentsUrlPrefix = _options.RoutePrefix; + + return View("~/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml", model); + } + } +} diff --git a/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj b/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj index 2e89e9b255..ed9cf98af8 100644 --- a/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj +++ b/modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj @@ -18,6 +18,7 @@ + From 52b1940ac50faf197344829079c339b125671087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan=20=C3=9Cnl=C3=BC?= <36102404+armgnunlu@users.noreply.github.com> Date: Mon, 10 Feb 2020 17:33:31 +0300 Subject: [PATCH 09/68] Resolved #2796 --- .../Pages/Documents/Project/Index.cshtml | 1 - .../DocumentNotFoundComponent/Default.cshtml | 52 +++++++++++++++++-- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index 3af95c420d..a6b1e1538f 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -46,7 +46,6 @@
-
- @if (Model.Navigation == null || Model.Navigation.Content.IsNullOrEmpty()) + @if (Model.Navigation == null || !Model.Navigation.HasChildItems) {
@L["NavigationDocumentNotFound"] @@ -153,7 +153,7 @@ } else { -
diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index 82b58ad117..6212619971 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -49,7 +49,7 @@ namespace Volo.Docs.Pages.Documents.Project public List ProjectSelectItems { get; private set; } - public NavigationWithDetailsDto Navigation { get; private set; } + public NavigationNode Navigation { get; private set; } public VersionInfoViewModel LatestVersionInfo { get; private set; } @@ -271,7 +271,7 @@ namespace Volo.Docs.Pages.Documents.Project { try { - var document = await _documentAppService.GetNavigationAsync( + Navigation = await _documentAppService.GetNavigationAsync( new GetNavigationDocumentInput { ProjectId = Project.Id, @@ -279,15 +279,11 @@ namespace Volo.Docs.Pages.Documents.Project Version = Version } ); - - Navigation = ObjectMapper.Map(document); } catch (DocumentNotFoundException) //TODO: What if called on a remote service which may return 404 { return; } - - Navigation.ConvertItems(); } public string CreateVersionLink(VersionInfoViewModel latestVersion, string version, string documentName = null) diff --git a/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentStoreFactory_Tests.cs b/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentStoreFactory_Tests.cs index 73e07ec8e2..23d34748a6 100644 --- a/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentStoreFactory_Tests.cs +++ b/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentStoreFactory_Tests.cs @@ -8,18 +8,18 @@ namespace Volo.Docs { public class DocumentStoreFactory_Tests : DocsDomainTestBase { - private readonly IDocumentStoreFactory _documentStoreFactory; + private readonly IDocumentSourceFactory _documentStoreFactory; public DocumentStoreFactory_Tests() { - _documentStoreFactory = GetRequiredService(); + _documentStoreFactory = GetRequiredService(); } [Fact] public void Create() { - _documentStoreFactory.Create(GithubDocumentStore.Type).GetType().ShouldBe(typeof(GithubDocumentStore)); - _documentStoreFactory.Create(FileSystemDocumentStore.Type).GetType().ShouldBe(typeof(FileSystemDocumentStore)); + _documentStoreFactory.Create(GithubDocumentSource.Type).GetType().ShouldBe(typeof(GithubDocumentSource)); + _documentStoreFactory.Create(FileSystemDocumentSource.Type).GetType().ShouldBe(typeof(FileSystemDocumentSource)); } } } diff --git a/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentStore_Tests.cs b/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentStore_Tests.cs index 76d1f9f68f..6b6bb0565c 100644 --- a/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentStore_Tests.cs +++ b/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentStore_Tests.cs @@ -9,13 +9,13 @@ namespace Volo.Docs { public class GithubDocumentStore_Tests : DocsDomainTestBase { - private readonly IDocumentStoreFactory _documentStoreFactory; + private readonly IDocumentSourceFactory _documentStoreFactory; private readonly IProjectRepository _projectRepository; private readonly DocsTestData _testData; public GithubDocumentStore_Tests() { - _documentStoreFactory = GetRequiredService(); + _documentStoreFactory = GetRequiredService(); _projectRepository = GetRequiredService(); _testData = GetRequiredService(); } @@ -23,15 +23,15 @@ namespace Volo.Docs [Fact] public async Task GetDocumentAsync() { - var store = _documentStoreFactory.Create(GithubDocumentStore.Type); + var source = _documentStoreFactory.Create(GithubDocumentSource.Type); var project = await _projectRepository.FindAsync(_testData.PorjectId); project.ShouldNotBeNull(); - var document = await store.GetDocumentAsync(project, "index2", "en", "0.123.0"); + var document = await source.GetDocumentAsync(project, "index2", "en", "0.123.0"); document.ShouldNotBeNull(); - document.Title.ShouldBe("index2"); + document.Name.ShouldBe("index2"); document.FileName.ShouldBe("index2"); document.Version.ShouldBe("0.123.0"); document.Content.ShouldBe("stringContent"); @@ -40,12 +40,12 @@ namespace Volo.Docs [Fact] public async Task GetVersionsAsync() { - var store = _documentStoreFactory.Create(GithubDocumentStore.Type); + var source = _documentStoreFactory.Create(GithubDocumentSource.Type); var project = await _projectRepository.FindAsync(_testData.PorjectId); project.ShouldNotBeNull(); - var document = await store.GetVersionsAsync(project); + var document = await source.GetVersionsAsync(project); document.ShouldNotBeNull(); document.Count.ShouldBe(1); @@ -55,12 +55,12 @@ namespace Volo.Docs [Fact] public async Task GetResource() { - var store = _documentStoreFactory.Create(GithubDocumentStore.Type); + var source = _documentStoreFactory.Create(GithubDocumentSource.Type); var project = await _projectRepository.FindAsync(_testData.PorjectId); project.ShouldNotBeNull(); - var documentResource = await store.GetResource(project, "index.md", "en", "0.123.0"); + var documentResource = await source.GetResource(project, "index.md", "en", "0.123.0"); documentResource.ShouldNotBeNull(); documentResource.Content.ShouldBe(new byte[] diff --git a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs index 121d18b44a..be5bab81bc 100644 --- a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs +++ b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs @@ -25,7 +25,7 @@ namespace Volo.Docs _testData.PorjectId, "ABP vNext", "ABP", - GithubDocumentStore.Type, + GithubDocumentSource.Type, "md", "index", "docs-nav.json", From bc640ebee5261c92f5c2fdda3ea3507ea3314563 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 12 Feb 2020 17:29:33 +0300 Subject: [PATCH 23/68] ci: fix commander description --- npm/ng-packs/scripts/publish.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/scripts/publish.ts b/npm/ng-packs/scripts/publish.ts index f9357159c7..3a8197522f 100644 --- a/npm/ng-packs/scripts/publish.ts +++ b/npm/ng-packs/scripts/publish.ts @@ -4,7 +4,8 @@ import program from 'commander'; program .option( - "-v, --nextVersion ', 'next semantic version. Available versions: ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease', 'or type a custom version']", + '-v, --nextVersion ', + 'next semantic version. Available versions: ["major", "minor", "patch", "premajor", "preminor", "prepatch", "prerelease", "or type a custom version"]', ) .option('-p, --preview', 'publish with preview tag'); From e055cb8f536130a53ba498969c2713b458318aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 12 Feb 2020 18:08:20 +0300 Subject: [PATCH 24/68] Add repositories section to the migration guide --- docs/en/AspNet-Boilerplate-Migration-Guide.md | 125 +++++++++++++++++- docs/en/Repositories.md | 1 - 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md index 96c7ec3ab5..7e1044ec77 100644 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ b/docs/en/AspNet-Boilerplate-Migration-Guide.md @@ -73,7 +73,7 @@ Most of your domain layer code will remain same, while you need to perform some ### Aggregate Roots & Entities -ABP Framework and the ASP.NET Boilerplate both have the `IEntity` and `IEntity` interfaces and `Entity` and `Entity` base classes to define entities but they have some differences. +The ABP Framework and the ASP.NET Boilerplate both have the `IEntity` and `IEntity` interfaces and `Entity` and `Entity` base classes to define entities but they have some differences. If you have an entity in the ASP.NET Boilerplate application like that: @@ -122,10 +122,131 @@ We suggest & use the GUID as the PK type for all the ABP Framework modules. Howe The challenging part will be the primary keys of the ASP.NET Boilerplate related entities, like Users, Roles, Tenants, Settings... etc. Our suggestion is to copy data from existing database to the new database tables using a tool or in a manual way (be careful about the foreign key values). +#### Documentation + +See the documentation for details on the entities: + +* [ASP.NET Boilerplate - Entity documentation](https://aspnetboilerplate.com/Pages/Documents/Entities) +* [ABP Framework - Entity documentation](https://docs.abp.io/en/abp/latest/Entities) + ### Repositories +> ABP Framework creates default repositories (`IRepository`) **only for the aggregate roots**. It doesn't create for other types derived from the `Entity`. See the "Aggregate Root" section above for more information. + +The ABP Framework and the ASP.NET Boilerplate both have the default generic repository system, but has some differences. + +#### Injecting the Repositories + +In the ASP.NET Boilerplate, there are two default repository interfaces you can directly inject and use: + +* `IRepository` (e.g. `IRepository`) is used for entities with `int` primary key (PK) which is the default PK type. +* `IRepository` (e.g. `IRepository`) is used for entities with other types of PKs. + +ABP Framework doesn't have a default PK type, so you need to **explicitly declare the PK type** of your entity, like `IRepository` or `IRepository`. + +ABP Framework also has the `IRepository` (without PK), but it is mostly used when your entity has a composite PK (because this repository has no methods work with the `Id` property). See [the documentation](https://docs.abp.io/en/abp/latest/Entities#entities-with-composite-keys) to learn more about the **composite PKs**. + +#### Restricted Repositories + +ABP Framework additionally provides a few repository interfaces: + +* `IBasicRepository` has the same methods with the `IRepository` except it doesn't have `IQueryable` support. It can be useful if you don't want to expose complex querying code to the application layer. In this case, you typically want to create custom repositories to encapsulate the querying logic. It is also useful for database providers those don't support `IQueryable`. +* `IReadOnlyRepository` has the methods get data from the database, but doesn't contain any method change the database. +* `IReadOnlyBasicRepository` is similar to the read only repository but also doesn't support `IQueryable`. + +All the interfaces also have versions without `TKey` (like ``IReadOnlyRepository`) those can be used for composite PKs just like explained above. + +#### GetAll() vs IQueryable + +ASP.NET Boilerplate's repository has a `GetAll()` method that is used to obtain an `IQueryable` object to execute LINQ on it. An example application service calls the `GetAll()` method: + +````csharp +public class PersonAppService : ApplicationService, IPersonAppService +{ + private readonly IRepository _personRepository; + + public PersonAppService(IRepository personRepository) + { + _personRepository = personRepository; + } + + public async Task DoIt() + { + var people = await _personRepository + .GetAll() //GetAll() returns IQueryable + .Where(p => p.BirthYear > 2000) //Use LINQ extension methods + .ToListAsync(); + } +} +```` + +ABP Framework's repository doesn't have this method. Instead, it implements the `IQueryable` itself. So, you can directly use LINQ on the repository: + +````csharp +public class PersonAppService : ApplicationService, IPersonAppService +{ + private readonly IRepository _personRepository; + + public PersonAppService(IRepository personRepository) + { + _personRepository = personRepository; + } + + public async Task DoIt() + { + var people = await _personRepository + .Where(p => p.BirthYear > 2000) //Use LINQ extension methods + .ToListAsync(); + } +} +```` + +> Note that in order to use the async LINQ extension methods (like `ToListAsync` here), you may need to depend on the database provider (like EF Core) since these methods are defined in the database provider package, they are not standard LINQ methods. + +#### FirstOrDefault(predicate), Single()... Methods + +ABP Framework repository has not such methods get predicate (expression) since the repository itself is `IQueryable` and all these methods are already standard LINQ extension methods those can be directly used. + +However, it provides the following methods those can be used to query a single entity by its Id: + +* `FindAsync(id)` returns the entity or null if not found. +* `GetAsync(id)` method returns the entity or throws an `EntityNotFoundException` (which causes HTTP 404 status code) if not found. + +#### Sync vs Async + +ABP Framework repository has no sync methods (like `Insert`). All the methods are async (like `InsertAsync`). So, if your application has sync repository method usages, convert them to async versions. + +In general, ABP Framework forces you to completely use async everywhere, because mixing async & sync methods is not a recommended approach. + +#### Documentation + +See the documentation for details on the repositories: + +* [ASP.NET Boilerplate - Repository documentation](https://aspnetboilerplate.com/Pages/Documents/Repositories) +* [ABP Framework - Repository documentation](https://docs.abp.io/en/abp/latest/Repositories) + +### Domain Services + +Your domain service logic mostly remains same on the migration. ABP Framework also defines the base `DomainService` class and the `IDomainService` interface just works like the ASP.NET Boilerplate. + +## The Application Layer + +TODO + +## The Infrastructure Layer + +### IAbpSession vs ICurrentUser and ICurrentTenant + +TODO + +### Unit of Work + TODO ## Missing Features -TODO: Notification... etc. \ No newline at end of file +The following features are not present for the ABP Framework. Here, a list of major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository): + +* [Multi-Lingual Entities](https://aspnetboilerplate.com/Pages/Documents/Multi-Lingual-Entities) ([#1754](https://github.com/abpframework/abp/issues/1754)) + +Most of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework by implementing these yourself. \ No newline at end of file diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md index e6b55cb3de..6a60c58278 100644 --- a/docs/en/Repositories.md +++ b/docs/en/Repositories.md @@ -52,7 +52,6 @@ Generic Repositories provides some standard CRUD features out of the box: * Provides `Update` and `Delete` methods to update or delete an entity by entity object or it's id. * Provides `Delete` method to delete multiple entities by a filter. * Implements `IQueryable`, so you can use LINQ and extension methods like `FirstOrDefault`, `Where`, `OrderBy`, `ToList` and so on... -* Have **sync** and **async** versions for all methods. ### Basic Repositories From 571bed9be9647be314ecd7736f14b6c3751dc176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 12 Feb 2020 19:27:25 +0300 Subject: [PATCH 25/68] Update AspNet-Boilerplate-Migration-Guide.md --- docs/en/AspNet-Boilerplate-Migration-Guide.md | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md index 7e1044ec77..b7453b9aff 100644 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ b/docs/en/AspNet-Boilerplate-Migration-Guide.md @@ -231,18 +231,82 @@ Your domain service logic mostly remains same on the migration. ABP Framework al ## The Application Layer -TODO +Your application service logic remains similar on the migration. ABP Framework also defines the base `ApplicationService` class and the `IApplicationService` interface just works like the ASP.NET Boilerplate, but there are some differences in details. + +### Declarative Authorization + +ASP.NET Boilerplate has `AbpAuthorize` and `AbpMvcAuthorize` attributes for declarative authorization. Example usage: + +````csharp +[AbpAuthorize("MyUserDeletionPermissionName")] +public async Task DeleteUserAsync(...) +{ + ... +} +```` + +ABP Framework doesn't has such a custom attribute. It uses the standard `Authorize` attribute in all layers. + +````csharp +[Authorize("MyUserDeletionPermissionName")] +public async Task DeleteUserAsync(...) +{ + ... +} +```` + +This is possible with the better integration to the Microsoft Authorization Extensions libraries. See the Authorization section below for more information about the authorization system. + +### CrudAppService and AsyncCrudAppService Classes + +ASP.NET Boilerplate has `CrudAppService` (with sync service methods) and `AsyncCrudAppService` (with async service methods) classes. + +ABP Framework only has the `CrudAppService` which actually has only the async methods (instead of sync methods). + +ABP Framework's `CrudAppService` method signatures are slightly different than the old one. For example, old update method signature was ` Task UpdateAsync(TUpdateInput input) ` while the new one is ` Task UpdateAsync(TKey id, TUpdateInput input) `. The main difference is that it gets the Id of the updating entity as a separate parameter instead of including in the input DTO. + +### Data Transfer Objects (DTOs) + +There are similar base DTO classes (like `EntityDto`) in the ABP Framework too. So, you can find the corresponding DTO base class if you need. + +#### Validation + +You can continue to use the data annotation attributes to validate your DTOs just like in the ASP.NET Boilerplate. + +ABP Framework doesn't include the ` ICustomValidate ` that does exists in the ASP.NET Boilerplate. Instead, you should implement the standard `IValidatableObject` interface for your custom validation logic. ## The Infrastructure Layer ### IAbpSession vs ICurrentUser and ICurrentTenant -TODO +ASP.NET Boilerplate's `IAbpSession` service is used to obtain the current user and tenant information, like ` UserId ` and `TenantId`. + +ABP Framework doesn't have the same service. Instead, use `ICurrentUser` and `ICurrentTenant` services. These services are defined as base properties in some common classes (like `ApplicationService` and `AbpController`), so you generally don't need to manually inject them. They also have much properties compared to the `IAbpSession`. + +### Authorization + +ABP Framework extends the [ASP.NET Core Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing the authorization system to be usable in the [application services](Application-Services.md) too. + +#### AbpAutorize vs Autorize + +Use the standard `[Autorize]` and `[AllowAnonymous]` attributes instead of ASP.NET Boilerplate's custom `[AbpAutorize]` and `[AbpAllowAnonymous]` attributes. + +#### IPermissionChecker vs IAuthorizationService + +Use the standard `IAuthorizationService` to check permissions instead of the ASP.NET Boilerplate's `IPermissionChecker` service. While `IPermissionChecker` also exists in the ABP Framework, it is used to explicitly use the permissions. Using `IAuthorizationService` is the recommended way since it covers other type of policy checks too. + +#### AuthorizationProvider vs PermissionDefinitionProvider + +You inherit from the `AuthorizationProvider` in the ASP.NET Boilerplate to define your permissions. ABP Framework replaces it by the `PermissionDefinitionProvider` base class. So, define your permissions by inheriting from the `PermissionDefinitionProvider` class. ### Unit of Work TODO +### Multi-Tenancy + +TODO + ## Missing Features The following features are not present for the ABP Framework. Here, a list of major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository): From 2f65d3a3c548d46503be50aa4cff2166ff283adf Mon Sep 17 00:00:00 2001 From: liangshiw Date: Thu, 13 Feb 2020 10:33:53 +0800 Subject: [PATCH 26/68] Translate Hangfire Background Job Manager doc --- docs/zh-Hans/Background-Jobs-Hangfire.md | 44 ++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/zh-Hans/Background-Jobs-Hangfire.md b/docs/zh-Hans/Background-Jobs-Hangfire.md index 5ef9aad32d..d35d8482ca 100644 --- a/docs/zh-Hans/Background-Jobs-Hangfire.md +++ b/docs/zh-Hans/Background-Jobs-Hangfire.md @@ -1,3 +1,43 @@ -# Hangfire Background Job Manager +# Hangfire后台作业管理 -待添加 \ No newline at end of file +[Hangfire](https://www.hangfire.io/)是一个高级的后台作业管理. 你可以用ABP框架集成Hangfire代替[默认后台作业管理](Background-Jobs.md). 通过这种方式你可以使用相同的后台作业API,将你的代码独立于Hangfire. 如果你喜欢也可以直接使用Hangfire的API. + +> 参阅[后台作业文档](Background-Jobs.md),学习如何使用后台作业系统. 本文只介绍了如何安装和配置Hangfire集成. + +## 安装 + +建议使用[ABP CLI](CLI.md)安装包. + +### 使用ABP CLI + +在项目的文件夹(.csproj文件)中打开命令行窗口输入以下命令: + +````bash +abp add-package Volo.Abp.BackgroundJobs.HangFire +```` + +### 手动安装 + +如果你想手动安装; + +1. 添加 [Volo.Abp.BackgroundJobs.HangFire](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.HangFire) NuGet包添加到你的项目: + + ```` + Install-Package Volo.Abp.BackgroundJobs.HangFire + ```` + +2. 添加 `AbpBackgroundJobsHangfireModule` 到你的模块的依赖列表: + +````csharp +[DependsOn( + //...other dependencies + typeof(AbpBackgroundJobsHangfireModule) //Add the new module dependency + )] +public class YourModule : AbpModule +{ +} +```` + +## 配置 + +TODO... \ No newline at end of file From 3106729f010fa6df3d513a9e73986d392a3750df Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Thu, 13 Feb 2020 10:33:18 +0300 Subject: [PATCH 27/68] ci: add npm install as first step to publish scripts --- npm/preview-publish.ps1 | 2 ++ npm/publish.ps1 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/npm/preview-publish.ps1 b/npm/preview-publish.ps1 index c3a15f356b..f10173226b 100644 --- a/npm/preview-publish.ps1 +++ b/npm/preview-publish.ps1 @@ -2,6 +2,8 @@ param( [string]$Version ) +npm install + $NextVersion = $(node get-version.js) + '-preview' + (Get-Date).tostring(“yyyyMMdd”) $rootFolder = (Get-Item -Path "./" -Verbose).FullName diff --git a/npm/publish.ps1 b/npm/publish.ps1 index 3f0f03bc51..69f9ece982 100644 --- a/npm/publish.ps1 +++ b/npm/publish.ps1 @@ -2,6 +2,8 @@ param( [string]$Version ) +npm install + $NextVersion = $(node get-version.js) $rootFolder = (Get-Item -Path "./" -Verbose).FullName From ef78ff655ba986e949c4d157e452d06afc9ba6b2 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Thu, 13 Feb 2020 11:42:56 +0300 Subject: [PATCH 28/68] ci: remove unnecessary yarn command from publish.ps1 --- npm/preview-publish.ps1 | 1 - npm/publish.ps1 | 1 - 2 files changed, 2 deletions(-) diff --git a/npm/preview-publish.ps1 b/npm/preview-publish.ps1 index f10173226b..da705d07f9 100644 --- a/npm/preview-publish.ps1 +++ b/npm/preview-publish.ps1 @@ -16,7 +16,6 @@ $commands = ( "npm install", "npm run publish-packages -- --nextVersion $Version --preview", "cd ../../", - "yarn", "yarn lerna publish $Version --no-push --yes --no-git-reset --no-commit-hooks --no-git-tag-version --force-publish --dist-tag preview" ) diff --git a/npm/publish.ps1 b/npm/publish.ps1 index 69f9ece982..a510527401 100644 --- a/npm/publish.ps1 +++ b/npm/publish.ps1 @@ -16,7 +16,6 @@ $commands = ( "npm install", "npm run publish-packages -- --nextVersion $Version", "cd ../../", - "yarn", "yarn lerna publish $Version --no-push --yes --no-git-reset --no-commit-hooks --no-git-tag-version --force-publish", "yarn update:templates", "yarn gulp:app", From 67488b9ac24ca00703c70e6f8bbe8f5e4b726145 Mon Sep 17 00:00:00 2001 From: Arkat Erol Date: Thu, 13 Feb 2020 11:48:54 +0300 Subject: [PATCH 29/68] npm publish version test --- npm/preview-publish.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/preview-publish.ps1 b/npm/preview-publish.ps1 index da705d07f9..d448378750 100644 --- a/npm/preview-publish.ps1 +++ b/npm/preview-publish.ps1 @@ -4,7 +4,7 @@ param( npm install -$NextVersion = $(node get-version.js) + '-preview' + (Get-Date).tostring(“yyyyMMdd”) +$NextVersion = $(node get-version.js) + '-preview' + (Get-Date).tostring(“yyyyMMdd”) + '-1' $rootFolder = (Get-Item -Path "./" -Verbose).FullName if(-Not $Version) { From f0d9083aa655d05b72b4ca97e75c2b00f67639a2 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Feb 2020 17:36:51 +0800 Subject: [PATCH 30/68] Added pull document function in UI. --- .../Docs/ApplicationContracts/en.json | 7 +- .../Docs/ApplicationContracts/tr.json | 7 +- .../Docs/ApplicationContracts/zh-Hans.json | 7 +- .../Docs/ApplicationContracts/zh-Hant.json | 7 +- .../DocsAdminWebAutoMapperProfile.cs | 4 + .../Pages/Docs/Admin/Projects/Index.cshtml | 1 + .../Pages/Docs/Admin/Projects/Pull.cshtml | 22 ++++++ .../Pages/Docs/Admin/Projects/Pull.cshtml.cs | 75 +++++++++++++++++++ .../Pages/Docs/Admin/Projects/Pull.js | 23 ++++++ .../Pages/Docs/Admin/Projects/index.js | 14 ++++ .../Volo/Docs/Documents/DocumentAppService.cs | 5 +- .../GitHub/Documents/GithubDocumentSource.cs | 55 +++++--------- 12 files changed, 184 insertions(+), 43 deletions(-) create mode 100644 modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml create mode 100644 modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml.cs create mode 100644 modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.js diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json index 58cff5aa2e..708088b6a6 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json @@ -6,11 +6,13 @@ "Permission:Edit": "Edit", "Permission:Delete": "Delete", "Permission:Create": "Create", + "Permission:Documents": "Documents", "Menu:DocumentManagement": "Documents", "Menu:ProjectManagement": "Projects", "CreateANewProject": "Create new project", "Edit": "Edit", "Create": "Create", + "Pull": "Pull", "Projects": "Projects", "Name": "Name", "ShortName": "ShortName", @@ -27,6 +29,9 @@ "DisplayName:LatestVersionBranchName": "Latest version branch name", "DisplayName:GitHubRootUrl": "GitHub root URL", "DisplayName:GitHubAccessToken": "GitHub access token", - "DisplayName:GitHubUserAgent": "GitHub user agent" + "DisplayName:GitHubUserAgent": "GitHub user agent", + "DisplayName:All": "Pull all", + "DisplayName:LanguageCode": "Language code", + "DisplayName:Version": "Version" } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/tr.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/tr.json index 8c31fb1c5a..c40893e538 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/tr.json +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/tr.json @@ -6,11 +6,13 @@ "Permission:Edit": "Düzenle", "Permission:Delete": "Sil", "Permission:Create": "Oluştur", + "Permission:Documents": "Döküman", "Menu:DocumentManagement": "Dökümanlar", "Menu:ProjectManagement": "Projeler", "CreateANewProject": "Yeni proje oluştur", "Edit": "Düzenle", "Create": "Yeni oluştur", + "Pull": "çekme", "Projects": "Projeler", "Name": "İsim", "ShortName": "Kısa isim", @@ -26,6 +28,9 @@ "DisplayName:MainWebsiteUrl": "Ana web site URL", "DisplayName:LatestVersionBranchName": "Son versiyon Branch adı", "DisplayName:GitHubRootUrl": "GitHub kök adresi", - "DisplayName:GitHubAccessToken": "GitHub erişim token" + "DisplayName:GitHubAccessToken": "GitHub erişim token", + "DisplayName:All": "Çekme bütün", + "DisplayName:LanguageCode": "Dil kodu", + "DisplayName:Version": "versiyon" } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/zh-Hans.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/zh-Hans.json index 3c540e8154..039be20995 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/zh-Hans.json +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/zh-Hans.json @@ -6,11 +6,13 @@ "Permission:Edit": "编辑", "Permission:Delete": "删除", "Permission:Create": "创建", + "Permission:Documents": "文档", "Menu:DocumentManagement": "文档", "Menu:ProjectManagement": "项目", "CreateANewProject": "创建新项目", "Edit": "编辑", "Create": "创建", + "Pull": "拉取", "Projects": "项目", "Name": "名称", "ShortName": "简称", @@ -27,6 +29,9 @@ "DisplayName:LatestVersionBranchName": "最新版本的分支名称", "DisplayName:GitHubRootUrl": "GitHub根网址", "DisplayName:GitHubAccessToken": "GitHub访问令牌", - "DisplayName:GitHubUserAgent": "GitHub用户代理" + "DisplayName:GitHubUserAgent": "GitHub用户代理", + "DisplayName:All": "拉取所有", + "DisplayName:LanguageCode": "语言代码", + "DisplayName:Version": "版本" } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/zh-Hant.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/zh-Hant.json index 0a8415ccad..910194094d 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/zh-Hant.json +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/zh-Hant.json @@ -6,11 +6,13 @@ "Permission:Edit": "編輯", "Permission:Delete": "刪除", "Permission:Create": "建立", + "Permission:Documents": "文件", "Menu:DocumentManagement": "文件管理", "Menu:ProjectManagement": "專案管理", "CreateANewProject": "建立新專案", "Edit": "編輯", "Create": "建立", + "Pull": "拉取", "Projects": "專案", "Name": "名稱", "ShortName": "簡稱", @@ -27,6 +29,9 @@ "DisplayName:LatestVersionBranchName": "最新版本的分支名稱", "DisplayName:GitHubRootUrl": "GitHub根網址", "DisplayName:GitHubAccessToken": "GitHub 存取Token ", - "DisplayName:GitHubUserAgent": "GitHub 使用者代理" + "DisplayName:GitHubUserAgent": "GitHub 使用者代理", + "DisplayName:All": "拉取所有", + "DisplayName:LanguageCode": "語言代碼", + "DisplayName:Version": "版本" } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs b/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs index 9bf81c2140..ca430cdaad 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs @@ -1,5 +1,6 @@ using AutoMapper; using Volo.Abp.AutoMapper; +using Volo.Docs.Admin.Documents; using Volo.Docs.Admin.Pages.Docs.Admin.Projects; using Volo.Docs.Admin.Projects; @@ -15,6 +16,9 @@ namespace Volo.Docs.Admin CreateMap () .Ignore(x => x.GitHubAccessToken).Ignore(x => x.GitHubRootUrl).Ignore(x => x.GitHubUserAgent); + + CreateMap(); + CreateMap(); } } } diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Index.cshtml b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Index.cshtml index a72506d978..e3c0416b6d 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Index.cshtml @@ -20,6 +20,7 @@ + } diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml new file mode 100644 index 0000000000..6257971669 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml @@ -0,0 +1,22 @@ +@page +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Docs.Admin.Pages.Docs.Admin.Projects +@inherits Volo.Docs.Admin.Pages.Docs.Admin.DocsAdminPage +@model Volo.Docs.Admin.Pages.Docs.Admin.Projects.PullModel +@{ + Layout = null; +} + +@if (Model.PullDocument != null) +{ + + + + + + + + + + +} diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml.cs new file mode 100644 index 0000000000..dd1b5c2ac9 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml.cs @@ -0,0 +1,75 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Docs.Admin.Documents; +using Volo.Docs.Admin.Projects; +using Volo.Docs.Documents; + +namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects +{ + public class PullModel : DocsAdminPageModel + { + [BindProperty] + public PullDocumentViewModel PullDocument { get; set; } + + private readonly IProjectAdminAppService _projectAppService; + private readonly IDocumentAdminAppService _documentAppService; + + public PullModel(IProjectAdminAppService projectAppService, + IDocumentAdminAppService documentAppService) + { + _projectAppService = projectAppService; + _documentAppService = documentAppService; + } + + public async Task OnGetAsync(Guid id) + { + var project = await _projectAppService.GetAsync(id); + + PullDocument = new PullDocumentViewModel() + { + ProjectId = project.Id, + All = false + }; + + return Page(); + } + + public async Task OnPostAsync() + { + if (PullDocument.All) + { + await _documentAppService.PullAllAsync( + ObjectMapper.Map(PullDocument)); + } + else + { + await _documentAppService.PullAsync( + ObjectMapper.Map(PullDocument)); + } + + return NoContent(); + } + + public class PullDocumentViewModel + { + [HiddenInput] + public Guid ProjectId { get; set; } + + public bool All { get; set; } + + [Required] + [StringLength(DocumentConsts.MaxNameLength)] + public string Name { get; set; } + + [Required] + [StringLength(DocumentConsts.MaxLanguageCodeNameLength)] + public string LanguageCode { get; set; } + + [Required] + [StringLength(DocumentConsts.MaxVersionNameLength)] + public string Version { get; set; } + } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.js b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.js new file mode 100644 index 0000000000..8c62b5de34 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.js @@ -0,0 +1,23 @@ +var abp = abp || {}; + +$(function () { + abp.modals.projectPull = function () { + var initModal = function (publicApi, args) { + var $form = publicApi.getForm(); + var fg = $form.find("#PullDocument_Name").parent(); + var nameInput = fg.html(); + + $form.find("input:checkbox").change(function() { + if ($(this).prop("checked")) { + fg.html(""); + } else { + fg.html(nameInput); + } + }); + }; + + return { + initModal: initModal + }; + }; +}); \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/index.js b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/index.js index d25d04d671..b843202dd9 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/index.js +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/index.js @@ -12,6 +12,11 @@ modalClass: 'projectEdit' }); + var _pullModal = new abp.ModalManager({ + viewUrl: abp.appPath + 'Docs/Admin/Projects/Pull', + modalClass: 'projectPull' + }); + var _dataTable = $('#ProjectsTable').DataTable(abp.libs.datatables.normalizeConfiguration({ processing: true, @@ -48,6 +53,15 @@ _dataTable.ajax.reload(); }); } + }, + { + text: l('Pull'), + visible: abp.auth.isGranted('Docs.Admin.Documents'), + action: function (data) { + _pullModal.open({ + Id: data.record.id + }); + } } ] } diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index 6037a6739d..1dc43a4e0b 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -172,16 +172,15 @@ namespace Volo.Docs.Documents return CreateDocumentWithDetailsDto(project, sourceDocument); } - /* if (HostEnvironment.IsDevelopment()) { return await GetDocumentAsync(); - }*/ + } var document = await _documentRepository.FindAsync(project.Id, documentName, languageCode, version); //TODO: Configurable cache time? - if (document == null || document.LastCachedTime + TimeSpan.FromHours(12) < DateTime.Now) + if (document == null || document.LastCachedTime + TimeSpan.FromDays(30) < DateTime.Now) { return await GetDocumentAsync(); } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs index ffe6f042be..972d8ecfbb 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs @@ -34,7 +34,7 @@ namespace Volo.Docs.GitHub.Documents var userAgent = project.GetGithubUserAgentOrNull(); var rawRootUrl = CalculateRawRootUrlWithLanguageCode(rootUrl, languageCode); var rawDocumentUrl = rawRootUrl + documentName; - var commitHistoryUrl = project.GetGitHubUrlForCommitHistory() + documentName; + var commitHistoryUrl = project.GetGitHubUrlForCommitHistory() + languageCode + "/" + documentName; var isNavigationDocument = documentName == project.NavigationDocumentName; var isParameterDocument = documentName == project.ParametersDocumentName; var editLink = rootUrl.ReplaceFirst("/tree/", "/blob/") + languageCode + "/" + documentName; @@ -49,7 +49,7 @@ namespace Volo.Docs.GitHub.Documents var fileCommits = await GetFileCommitsAsync(project, version, $"docs/{languageCode}/{documentName}"); - return new Document(GuidGenerator.Create(), + var document= new Document(GuidGenerator.Create(), project.Id, documentName, version, @@ -64,6 +64,23 @@ namespace Volo.Docs.GitHub.Documents fileCommits.FirstOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue, fileCommits.Count, DateTime.Now); + + var authors = fileCommits + .Where(x => x.Author != null) + .Select(x => x.Author) + .GroupBy(x => x.Id) + .OrderByDescending(x => x.Count()) + .Select(x => x.FirstOrDefault()).ToList(); + + if (!isNavigationDocument && !isParameterDocument) + { + foreach (var author in authors) + { + document.AddContributor(author.Login, author.HtmlUrl, author.AvatarUrl); + } + } + + return document; } public async Task> GetVersionsAsync(Project project) @@ -195,40 +212,6 @@ namespace Volo.Docs.GitHub.Documents } } - /* - private async Task> GetContributors(string url, string token, string userAgent) - { - var contributors = new List(); - - try - { - var commitsJsonAsString = await DownloadWebContentAsStringAsync(url, token, userAgent); - - var commits = JArray.Parse(commitsJsonAsString); - - foreach (var commit in commits) - { - var author = commit["author"]; - - contributors.Add(new DocumentContributor - { - Username = (string)author["login"], - UserProfileUrl = (string)author["html_url"], - AvatarUrl = (string)author["avatar_url"] - }); - } - - contributors = contributors.GroupBy(c => c.Username).OrderByDescending(c=>c.Count()) - .Select( c => c.FirstOrDefault()).ToList(); - } - catch (Exception ex) - { - Logger.LogWarning(ex.Message); - } - - return contributors; - } - */ private static string CalculateRawRootUrlWithLanguageCode(string rootUrl, string languageCode) { return (rootUrl From abfb823fc23be0206bd134b2d69724f3e4ee1cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 13 Feb 2020 13:38:43 +0300 Subject: [PATCH 31/68] #2625 remove unused method. --- .../Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs | 5 ----- .../Volo/Abp/Auditing/IAuditLogSaveHandle.cs | 1 - 2 files changed, 6 deletions(-) diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs index 7315c86e79..2a9dd8bf5a 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs @@ -154,11 +154,6 @@ namespace Volo.Abp.Auditing { await _auditingManager.SaveAsync(this); } - - public void AddException(Exception exception) - { - this.AuditLog.Exceptions.Add(exception); - } public void Dispose() { diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs index a549dcb755..4709b745d2 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs @@ -6,6 +6,5 @@ namespace Volo.Abp.Auditing public interface IAuditLogSaveHandle : IDisposable { Task SaveAsync(); - void AddException(Exception exception); } } \ No newline at end of file From f47a0c1f5f8e9d224663b2d8c2f829c8b78f91b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 13 Feb 2020 14:18:41 +0300 Subject: [PATCH 32/68] Fix audit log unit tests. --- .../AspNetCore/Auditing/AbpAuditingMiddleware.cs | 5 +---- .../Mvc/Auditing/AuditTestController.cs | 1 + .../Mvc/Auditing/AuditTestController_Tests.cs | 15 +++++++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index b634892029..be84495336 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -38,10 +38,7 @@ namespace Volo.Abp.AspNetCore.Auditing catch (Exception) { hasError = true; - if (!Options.HideErrors) - { - throw; - } + throw; } finally { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs index 967f3269d2..9b4892cea4 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs @@ -5,6 +5,7 @@ using Volo.Abp.Auditing; namespace Volo.Abp.AspNetCore.Mvc.Auditing { [Route("api/audit-test")] + [Audited] public class AuditTestController : AbpController { private readonly AbpAuditingOptions _options; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs index b411b20ee0..733fab3f49 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; @@ -33,7 +34,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing _options.IsEnabledForGetRequests = true; _options.AlwaysLogOnException = false; await GetResponseAsync("api/audit-test/audit-success"); - //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope + await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope } [Fact] @@ -41,8 +42,14 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing { _options.IsEnabled = true; _options.AlwaysLogOnException = true; - await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.BadRequest); - //await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope + + try + { + await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.Forbidden); + } + catch { } + + await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope } } } From a601a10d9abadfcb7264784554184788d71f962c Mon Sep 17 00:00:00 2001 From: TheDiaval Date: Thu, 13 Feb 2020 14:27:00 +0300 Subject: [PATCH 33/68] feat(core): add set environment action to config state --- .../core/src/lib/actions/config.actions.ts | 6 ++++++ .../packages/core/src/lib/states/config.state.ts | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts index 56d0874ebd..8fd11bd2cb 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts @@ -1,4 +1,5 @@ import { ABP } from '../models/common'; +import { Config } from '../models/config'; export class PatchRouteByName { static readonly type = '[Config] Patch Route By Name'; @@ -16,3 +17,8 @@ export class AddRoute { static readonly type = '[Config] Add Route'; constructor(public payload: Omit) {} } + +export class SetEnvironment { + static readonly type = '[Config] Set Environment'; + constructor(public environment: Config.Environment) {} +} diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index debbc9b8a0..ff964cfe92 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -2,7 +2,12 @@ import { Action, createSelector, Selector, State, StateContext, Store } from '@n import { of } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import snq from 'snq'; -import { GetAppConfiguration, PatchRouteByName, AddRoute } from '../actions/config.actions'; +import { + GetAppConfiguration, + PatchRouteByName, + AddRoute, + SetEnvironment, +} from '../actions/config.actions'; import { SetLanguage } from '../actions/session.actions'; import { ABP } from '../models/common'; import { Config } from '../models/config'; @@ -291,6 +296,13 @@ export class ConfigState { flattedRoutes, }); } + + @Action(SetEnvironment) + setEnvironment({ patchState }: StateContext, environment: Config.Environment) { + return patchState({ + environment, + }); + } } function patchRouteDeep( From 586047682b7e562a7c932537d8dec74d13656112 Mon Sep 17 00:00:00 2001 From: TheDiaval Date: Thu, 13 Feb 2020 14:27:28 +0300 Subject: [PATCH 34/68] feat(core): add dispatch function for set environment action --- .../core/src/lib/services/config-state.service.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts index 506b278634..cf97e42b0f 100644 --- a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts @@ -1,8 +1,12 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngxs/store'; +import { + AddRoute, + GetAppConfiguration, + PatchRouteByName, + SetEnvironment, +} from '../actions/config.actions'; import { ConfigState } from '../states'; -import { GetAppConfiguration, PatchRouteByName, AddRoute } from '../actions/config.actions'; -import { ABP } from '../models'; @Injectable({ providedIn: 'root', @@ -61,4 +65,8 @@ export class ConfigStateService { dispatchAddRoute(...args: ConstructorParameters) { return this.store.dispatch(new AddRoute(...args)); } + + dispatchSetEnvironment(...args: ConstructorParameters) { + return this.store.dispatch(new SetEnvironment(...args)); + } } From 4eb57ba270cd9b22fce284b9f8ee0a4a360a6721 Mon Sep 17 00:00:00 2001 From: TheDiaval Date: Thu, 13 Feb 2020 14:46:16 +0300 Subject: [PATCH 35/68] test(theme-shared): correct directive test --- .../src/lib/tests/table-sort.directive.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts index f359518099..231f627fa1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts @@ -1,16 +1,20 @@ import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest'; import { TableSortDirective } from '../directives/table-sort.directive'; +import { TableComponent } from '../components/table/table.component'; +import { DummyLocalizationPipe } from './table.component.spec'; +import { PaginationComponent } from '../components'; describe('TableSortDirective', () => { let spectator: SpectatorDirective; let directive: TableSortDirective; const createDirective = createDirectiveFactory({ directive: TableSortDirective, + declarations: [TableComponent, DummyLocalizationPipe, PaginationComponent], }); beforeEach(() => { spectator = createDirective( - ``, + ``, ); directive = spectator.directive; }); @@ -21,7 +25,7 @@ describe('TableSortDirective', () => { test('should change table value', () => { expect(directive.value).toEqual([1, 4, 2]); - const table = spectator.query(Table); + const table = spectator.query(TableComponent); expect(table.value).toEqual([1, 2, 4]); }); }); From 15fc720dd329c9a791c82d698089f3f447e64508 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 13 Feb 2020 20:42:26 +0800 Subject: [PATCH 36/68] Document contributors. --- .../Docs/Documents/IDocumentRepository.cs | 5 +++- .../Documents/EFCoreDocumentRepository.cs | 12 ++++++-- .../DocsEfCoreQueryableExtensions.cs | 14 +++++++++ .../Docs/Documents/MongoDocumentRepository.cs | 7 +++-- .../Pages/Documents/Project/Index.cshtml | 29 ++++++++++--------- 5 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/EntityFrameworkCore/DocsEfCoreQueryableExtensions.cs diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs index b9e0edaee5..aaabb2d8c7 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -8,6 +9,8 @@ namespace Volo.Docs.Documents { public interface IDocumentRepository : IBasicRepository { - Task FindAsync(Guid projectId, string name, string languageCode, string version); + Task FindAsync(Guid projectId, string name, string languageCode, string version, + bool includeDetails = true, + CancellationToken cancellationToken = default); } } diff --git a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs index e965bd11a6..d9a96e6c4f 100644 --- a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; @@ -14,10 +15,15 @@ namespace Volo.Docs.Documents { } - public async Task FindAsync(Guid projectId, string name, string languageCode, string version) + public async Task FindAsync(Guid projectId, string name, string languageCode, string version, + bool includeDetails = true, + CancellationToken cancellationToken = default) { - return await DbSet.FirstOrDefaultAsync(x => - x.ProjectId == projectId && x.Name == name && x.LanguageCode == languageCode && x.Version == version); + return await DbSet.IncludeDetails(includeDetails) + .FirstOrDefaultAsync(x => + x.ProjectId == projectId && x.Name == name && x.LanguageCode == languageCode && + x.Version == version, + cancellationToken); } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/EntityFrameworkCore/DocsEfCoreQueryableExtensions.cs b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/EntityFrameworkCore/DocsEfCoreQueryableExtensions.cs new file mode 100644 index 0000000000..0bd2e4fff8 --- /dev/null +++ b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/EntityFrameworkCore/DocsEfCoreQueryableExtensions.cs @@ -0,0 +1,14 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Volo.Docs.Documents; + +namespace Volo.Docs.EntityFrameworkCore +{ + public static class DocsEfCoreQueryableExtensions + { + public static IQueryable IncludeDetails(this IQueryable queryable, bool include = true) + { + return !include ? queryable : queryable.Include(x => x.Contributors); + } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs index 5e7cc5871a..548b89e246 100644 --- a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Repositories.MongoDB; @@ -14,12 +15,14 @@ namespace Volo.Docs.Documents { } - public async Task FindAsync(Guid projectId, string name, string languageCode, string version) + public async Task FindAsync(Guid projectId, string name, string languageCode, string version, + bool includeDetails = true, + CancellationToken cancellationToken = default) { return await GetMongoQueryable().FirstOrDefaultAsync(x => x.ProjectId == projectId && x.Name == name && x.LanguageCode == languageCode && - x.Version == version); + x.Version == version, cancellationToken); } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index a8d950b1ce..3430503f7d 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -190,25 +190,26 @@ @(L["Edit"]) (@L["LastEditTime"]: @Model.Document.LastUpdatedTime.ToShortDateString()) }
+ +
+ @if (Model.Document.Contributors != null && Model.Document.Contributors.Count > 0) + { + @(L["Contributors"].Value + " :") + @foreach (var contributor in Model.Document.Contributors) + { + + + + } + } +
+
- @if (Model.Document.Contributors != null && Model.Document.Contributors.Count > 0) - { -
- @(L["Contributors"].Value + " :") - @foreach (var contributor in Model.Document.Contributors) - { - - - - } -
- } - @if (Model.DocumentPreferences != null && Model.DocumentPreferences.Parameters != null && Model.DocumentPreferences.Parameters.Any()) {
From 1106d9021af2515493132e5ee1bc0d8ffbfe50aa Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Thu, 13 Feb 2020 16:34:06 +0300 Subject: [PATCH 37/68] closes volosoft/volo#926 --- .../Volo/Abp/Cli/Commands/SuiteCommand.cs | 20 +++++++++++++------ .../Cli/Licensing/DeveloperApiKeyResult.cs | 10 ++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs index a47a865617..a13621ea22 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs @@ -54,7 +54,7 @@ namespace Volo.Abp.Cli.Commands private async Task InstallSuiteAsync() { var nugetIndexUrl = await GetNuGetIndexUrlAsync(); - + if (nugetIndexUrl == null) { return; @@ -108,13 +108,21 @@ namespace Volo.Abp.Cli.Commands { var apiKeyResult = await _apiKeyService.GetApiKeyOrNullAsync(); - if (apiKeyResult == null || string.IsNullOrEmpty(apiKeyResult.ApiKey)) + if (apiKeyResult == null) + { + Logger.LogWarning("You are not signed in! Use the CLI command \"abp login \" to sign in, then try again."); + return null; + } + + if (!string.IsNullOrWhiteSpace(apiKeyResult.ErrorMessage)) { - Logger.LogError("Couldn't retrieve your NuGet API key!"); - Logger.LogWarning(File.Exists(CliPaths.AccessToken) - ? "Make sure you have an active session and license on commercial.abp.io. To re-sign in you can use the CLI command \"abp login \"." - : "You are not signed in to commercial.abp.io. Use the CLI command \"abp login \" to sign in."); + Logger.LogWarning(apiKeyResult.ErrorMessage); + return null; + } + if (string.IsNullOrEmpty(apiKeyResult.ApiKey)) + { + Logger.LogError("Couldn't retrieve your NuGet API key! You can re-sign in with the CLI command \"abp login \"."); return null; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs index fb3622f6eb..eb781c27b1 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs @@ -10,5 +10,15 @@ namespace Volo.Abp.Cli.Licensing public DateTime? LicenseEndTime { get; set; } public bool CanDownloadSourceCode { get; set; } public string LicenseCode { get; set; } + public string ErrorMessage { get; set; } + public LicenseErrorType? ErrorType { get; set; } + + public enum LicenseErrorType + { + NotAuthenticated = 1, + NotMemberOfAnOrganization = 2, + NoActiveLicense = 3, + NotDeveloperOfTheOrganization = 4 + } } } \ No newline at end of file From e80cb601c1baf9ec6d32c21c9b157904ee6efd15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87otur?= Date: Thu, 13 Feb 2020 16:44:07 +0300 Subject: [PATCH 38/68] Wrong class name is fixed 'BackgroundJobOptions' is changed to 'AbpBackgroundJobOptions' --- docs/en/Background-Jobs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Background-Jobs.md b/docs/en/Background-Jobs.md index 73815bb229..fa98542845 100644 --- a/docs/en/Background-Jobs.md +++ b/docs/en/Background-Jobs.md @@ -108,7 +108,7 @@ Enqueue method gets some optional arguments to control the background job: You may want to disable background job execution for your application. This is generally needed if you want to execute background jobs in another process and disable it for the current process. -Use `BackgroundJobOptions` to configure the job execution: +Use `AbpBackgroundJobOptions` to configure the job execution: ````csharp [DependsOn(typeof(AbpBackgroundJobsModule))] @@ -116,7 +116,7 @@ public class MyModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => + Configure(options => { options.IsJobExecutionEnabled = false; //Disables job execution }); From 2147fe52368ba6dfd62f28f5b004b6573b20c6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87otur?= Date: Thu, 13 Feb 2020 16:48:25 +0300 Subject: [PATCH 39/68] Update Background-Jobs.md --- docs/en/Background-Jobs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Background-Jobs.md b/docs/en/Background-Jobs.md index fa98542845..70f06bbadb 100644 --- a/docs/en/Background-Jobs.md +++ b/docs/en/Background-Jobs.md @@ -140,7 +140,7 @@ ABP framework includes a simple `IBackgroundJobManager` implementation that; ### Configuration -Use `BackgroundJobWorkerOptions` in your [module class](Module-Development-Basics.md) to configure the default background job manager. The example below changes the timeout duration for background jobs: +Use `AbpBackgroundJobWorkerOptions` in your [module class](Module-Development-Basics.md) to configure the default background job manager. The example below changes the timeout duration for background jobs: ````csharp [DependsOn(typeof(AbpBackgroundJobsModule))] @@ -148,7 +148,7 @@ public class MyModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => + Configure(options => { options.DefaultTimeout = 864000; //10 days (as seconds) }); From 69e6d1b752498fd6f6f6f47f9c8356bbe0ad4df8 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 13 Feb 2020 16:52:32 +0300 Subject: [PATCH 40/68] added AlwaysLogOnException documentation --- docs/en/Audit-Logging.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/Audit-Logging.md b/docs/en/Audit-Logging.md index 610efc0451..ebd110a450 100644 --- a/docs/en/Audit-Logging.md +++ b/docs/en/Audit-Logging.md @@ -1,4 +1,5 @@ -# Audit Logging + +# Audit Logging [Wikipedia](https://en.wikipedia.org/wiki/Audit_trail): "*An audit trail (also called **audit log**) is a security-relevant chronological record, set of records, and/or destination and source of records that provide documentary evidence of the sequence of activities that have affected at any time a specific operation, procedure, or event*". @@ -39,6 +40,7 @@ Here, a list of the options you can configure: * `IsEnabled` (default: `true`): A root switch to enable or disable the auditing system. Other options is not used if this value is `false`. * `HideErrors` (default: `true`): Audit log system hides and write regular [logs](Logging.md) if any error occurs while saving the audit log objects. If saving the audit logs is critical for your system, set this to `false` to throw exception in case of hiding the errors. * `IsEnabledForAnonymousUsers` (default: `true`): If you want to write audit logs only for the authenticated users, set this to `false`. If you save audit logs for anonymous users, you will see `null` for `UserId` values for these users. +* `AlwaysLogOnException` (default: `true`): Audit log option to save all the exceptions occur in the application. * `IsEnabledForGetRequests` (default: `false`): HTTP GET requests should not make any change in the database normally and audit log system doesn't save audit log objects for GET request. Set this to `true` to enable it also for the GET requests. * `ApplicationName`: If multiple applications saving audit logs into a single database, set this property to your application name, so you can distinguish the logs of different applications. * `IgnoredTypes`: A list of `Type`s to be ignored for audit logging. If this is an entity type, changes for this type of entities will not be saved. This list is also used while serializing the action parameters. From 905826f825dfb8cfc1c3b42edea28c265dee721f Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 13 Feb 2020 17:07:05 +0300 Subject: [PATCH 41/68] removed empty space on top. --- docs/en/Audit-Logging.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/en/Audit-Logging.md b/docs/en/Audit-Logging.md index ebd110a450..8fdb8cff8b 100644 --- a/docs/en/Audit-Logging.md +++ b/docs/en/Audit-Logging.md @@ -1,5 +1,4 @@ - -# Audit Logging +# Audit Logging [Wikipedia](https://en.wikipedia.org/wiki/Audit_trail): "*An audit trail (also called **audit log**) is a security-relevant chronological record, set of records, and/or destination and source of records that provide documentary evidence of the sequence of activities that have affected at any time a specific operation, procedure, or event*". From e37a53d19426113128e1d7c2aeb8d144278ea533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 13 Feb 2020 17:44:21 +0300 Subject: [PATCH 42/68] Add Data Filtering to docs-nav.json. --- docs/en/docs-nav.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 6657ac629a..edbe193c3f 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -111,6 +111,10 @@ { "text": "Settings", "path": "Settings.md" + }, + { + "text": "Data Filtering", + "path": "Data-Filtering.md" } ] }, From a52fda1133d0f401d2dc51ffa74e65a0f8518099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 13 Feb 2020 17:44:28 +0300 Subject: [PATCH 43/68] Update AspNet-Boilerplate-Migration-Guide.md --- docs/en/AspNet-Boilerplate-Migration-Guide.md | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md index b7453b9aff..bbd35d2409 100644 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ b/docs/en/AspNet-Boilerplate-Migration-Guide.md @@ -277,6 +277,31 @@ ABP Framework doesn't include the ` ICustomValidate ` that does exists in the AS ## The Infrastructure Layer +### Configuration vs Options System + +ASP.NET Boilerplate has its own configuration system to configure the framework and the modules. For example, you could disable the audit logging in the `Initialize` method of your [module](https://aspnetboilerplate.com/Pages/Documents/Module-System): + +````csharp +public override void Initialize() +{ + Configuration.Auditing.IsEnabled = false; +} +```` + +ABP Framework uses [the options pattern](Options.md) to configure the framework and the modules. You typically configure the options in the `ConfigureServices` method of your [module](Module-Development-Basics.md): + +````csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.IsEnabled = false; + }); +} +```` + +Instead of a central configuration object, there are separated option classes for every module and feature those are defined in the related documents. + ### IAbpSession vs ICurrentUser and ICurrentTenant ASP.NET Boilerplate's `IAbpSession` service is used to obtain the current user and tenant information, like ` UserId ` and `TenantId`. @@ -301,11 +326,57 @@ You inherit from the `AuthorizationProvider` in the ASP.NET Boilerplate to defin ### Unit of Work -TODO +Unit of work system has been designed to work seamlessly. For most of the cases, you don't need to change anything. + +`UnitOfWork` attribute of the ABP Framework doesn't have the `ScopeOption` (type of `TransactionScopeOption`) property. Instead, use `IUnitOfWorkManager.Begin()` method with `requiresNew = true` to create an independent inner transaction in a transaction scope. + +#### Data Filters + +ASP.NET Boilerplate implements the data filtering system as a part of the unit of work. ABP Framework has a separate `IDataFilter` service. + +See the [data filtering document](Data-Filtering.md) to learn how to enable/disable a filter. + +See [the UOW documentation](Unit-Of-Work.md) for more about the UOW system. ### Multi-Tenancy -TODO +#### IMustHaveTenant & IMayHaveTenant vs IMultiTenant + +ASP.NET Boilerplate defines `IMustHaveTenant` and `IMayHaveTenant` interfaces to implement them for your entities. In this way, your entities are automatically filtered according to the current tenant. Because of the design, there was a problem: You had to create a "Default" tenant in the database with "1" as the Id if you want to create a non multi-tenant application (this "Default" tenant was used as the single tenant). + +ABP Framework has a single interface for multi-tenant entities: `IMultiTenant` which defines a nullable `TenantId` property of type `Guid`. If your application is not multi-tenant, then your entities will have null TenantId (instead of a default one). + +On the migration, you need to change the TenantId field type and replace these interfaces with the `IMultiTenant` + +#### Switch Between Tenants + +In some cases you might need to switch to a tenant for a code scope and work with the tenant's data in this scope. + +In ASP.NET Boilerplate, it is done using the `IUnitOfWorkManager` service: + +````csharp +public async Task> GetProducts(int tenantId) +{ + using (_unitOfWorkManager.Current.SetTenantId(tenantId)) + { + return await _productRepository.GetAllListAsync(); + } +} +```` + +In the ABP Framework it is done with the `ICurrentTenant` service: + +````csharp +public async Task> GetProducts(Guid tenantId) +{ + using (_currentTenant.Change(tenantId)) + { + return await _productRepository.GetListAsync(); + } +} +```` + +Pass `null` to the `Change` method to switch to the host side. ## Missing Features From 4f00d1cf8e130f7d23d700a1f3e354d14ac74c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 13 Feb 2020 19:02:58 +0300 Subject: [PATCH 44/68] Update AspNet-Boilerplate-Migration-Guide.md --- docs/en/AspNet-Boilerplate-Migration-Guide.md | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md index bbd35d2409..cc5bf39228 100644 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ b/docs/en/AspNet-Boilerplate-Migration-Guide.md @@ -277,6 +277,54 @@ ABP Framework doesn't include the ` ICustomValidate ` that does exists in the AS ## The Infrastructure Layer +### Namespaces + +ASP.NET Boilerplate uses the `Abp.*` namespaces while the ABP Framework uses the `Volo.Abp.*` namespaces for the framework and pre-built fundamental modules. + +In addition, there are also some pre-built application modules (like docs and blog modules) those are using the `Volo.*` namespaces (like `Volo.Blogging.*` and `Volo.Docs.*`). We consider these modules as standalone open source products developed by Volosoft rather than add-ons or generic modules completing the ABP Framework and used in the applications. We've developed them as a module to make them re-usable as a part of a bigger solution. + +### Module System + +Both of the ASP.NET Boilerplate and the ABP Framework have the `AbpModule` while they are a bit different. + +ASP.NET Boilerplate's `AbpModule` class has `PreInitialize`, `Initialize` and `PostInitialize` methods you can override and configure the framework and the depended modules. You can also register and resolve dependencies in these methods. + +ABP Framework's `AbpModule` class has the `ConfigureServices` and `OnApplicationInitialization` methods (and their Pre and Post versions). It is similar to ASP.NET Core's Startup class. You configure other services and register dependencies in the `ConfigureServices`. However, you can now resolve dependencies in that point. You can resolve dependencies and configure the ASP.NET Core pipeline in the `OnApplicationInitialization` method while you can not register dependencies here. So, the new module classes separate dependency registration phase from dependency resolution phase since it follows the ASP.NET Core's approach. + +### Dependency Injection + +#### The DI Framework + +ASP.NET Boilerplate is using the [Castle Windsor](http://www.castleproject.org/projects/windsor/) as the dependency injection framework. This is a fundamental dependency of the ASP.NET Boilerplate framework. We've got a lot of feedback to make the ASP.NET Boilerplate DI framework agnostic, but it was not so easy because of the design. + +ABP Framework is dependency injection framework independent since it uses Microsoft's [Dependency Injection Extensions](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) library as an abstraction. None of the ABP Framework or module packages depends on any specific library. + +However, ABP Framework doesn't use the Microsoft's base DI library because it has some missing features ABP Framework needs to: Property Injection and Interception. All the startup templates and the samples are using the [Autofac](https://autofac.org/) as the DI library and it is the only [officially integrated](Autofac-Integration.md) library to the ABP Framework. We suggest you to use the Autofac with the ABP Framework if you have not a good reason. If you have a good reason, please create an [issue](https://github.com/abpframework/abp/issues/new) on GitHub to request it or just implement it and send a pull request :) + +#### Registering the Dependencies + +Registering the dependencies are similar and mostly handled by the framework conventionally (like repositories, application services, controllers... etc). Implement the same `ITransientDependency`, `ISingletonDependency` and `IScopedDependency` interfaces for the services not registered by conventions. + +When you need to manually register dependencies, use the `context.Services` in the `ConfigureServices` method of your module. Example: + +````csharp +public class BlogModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + //Register an instance as singleton + context.Services.AddSingleton(new TaxCalculator(taxRatio: 0.18)); + + //Register a factory method that resolves from IServiceProvider + context.Services.AddScoped( + sp => sp.GetRequiredService() + ); + } +} +```` + +See the ABP Framework [dependency injection document](https://docs.abp.io/en/abp/latest/Dependency-Injection) for details. + ### Configuration vs Options System ASP.NET Boilerplate has its own configuration system to configure the framework and the modules. For example, you could disable the audit logging in the `Initialize` method of your [module](https://aspnetboilerplate.com/Pages/Documents/Module-System): From 8efe5efff906152ce32c4eb5980d3e1b457ff863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 13 Feb 2020 19:32:28 +0300 Subject: [PATCH 45/68] Added more sections to the migration guide. --- docs/en/AspNet-Boilerplate-Migration-Guide.md | 89 +++++++++++++++++++ docs/en/Clock.md | 3 + docs/en/Logging.md | 3 +- docs/en/docs-nav.json | 4 + 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 docs/en/Clock.md diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md index cc5bf39228..0a2b3b17b3 100644 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ b/docs/en/AspNet-Boilerplate-Migration-Guide.md @@ -426,10 +426,99 @@ public async Task> GetProducts(Guid tenantId) Pass `null` to the `Change` method to switch to the host side. +### Caching + +ASP.NET Boilerplate has its [own distributed caching abstraction](https://aspnetboilerplate.com/Pages/Documents/Caching) which has in-memory and Redis implementations. You typically inject the `ICacheManager` service and use its `GetCache(...)` method to obtain a cache, then get and set objects in the cache. + +ABP Framework uses and extends ASP.NET Core's [distributed caching abstraction](Caching.md). It defines the `IDistributedCache` services to inject a cache and get/set objects. + +### Logging + +ASP.NET Boilerplate uses Castle Windsor's [logging facility](http://docs.castleproject.org/Windsor.Logging-Facility.ashx) as an abstraction and supports multiple logging providers including Log4Net (the default one comes with the startup projects) and Serilog. You typically property-inject the logger: + +````csharp +using Castle.Core.Logging; //1: Import Logging namespace + +public class TaskAppService : ITaskAppService +{ + //2: Getting a logger using property injection + public ILogger Logger { get; set; } + + public TaskAppService() + { + //3: Do not write logs if no Logger supplied. + Logger = NullLogger.Instance; + } + + public void CreateTask(CreateTaskInput input) + { + //4: Write logs + Logger.Info("Creating a new task with description: " + input.Description); + //... + } +} +```` + +ABP Framework depends on Microsoft's [logging extensions](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) library which is also an abstraction and there are many providers implement it. Startup templates are using the Serilog as the pre-configured logging libary while it is easy to change in your project. The usage pattern is similar: + +````csharp +//1: Import the Logging namespaces +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +public class TaskAppService : ITaskAppService +{ + //2: Getting a logger using property injection + public ILogger Logger { get; set; } + + public TaskAppService() + { + //3: Do not write logs if no Logger supplied. + Logger = NullLogger.Instance; + } + + public void CreateTask(CreateTaskInput input) + { + //4: Write logs + Logger.Info("Creating a new task with description: " + input.Description); + //... + } +} +```` + +You inject the `ILogger` instead of the `ILogger`. + +### Setting Management + +#### Defining the Settings + +In an ASP.NET Boilerplate based application, you create a class deriving from the `SettingProvider` class, implement the `GetSettingDefinitions` method and add your class to the `Configuration.Settings.Providers` list. + +In the ABP Framework, you need to derive your class from the `SettingDefinitionProvider` and implement the `Define` method. You don't need to register your class since the ABP Framework automatically discovers it. + +#### Getting the Setting Values + +ASP.NET Boilerplate provides the `ISettingManager` to read the setting values in the server side and `abp.setting.get(...)` method in the JavaScript side. + +ABP Framework has the `ISettingProvider` service to read the setting values in the server side and `abp.setting.get(...)` method in the JavaScript side. + +#### Setting the Setting Values + +For ASP.NET Boilerplate, you use the same `ISettingManager` service to change the setting values. + +ABP Framework separates it and provides the setting management module (pre-added to the startup projects) which has the ` ISettingManager ` to change the setting values. This separation was introduced to support tiered deployment scenarios (where `ISettingProvider` can also work in the client application while `ISettingManager ` can also work in the server (API) side). + +### Clock + +ASP.NET Boilerplate has a static `Clock` service ([see](https://aspnetboilerplate.com/Pages/Documents/Timing)) which is used to abstract the `DateTime` kind, so you can easily switch between Local and UTC times. You don't inject it, but just use the `Clock.Now` static method to obtain the current time. + +ABP Framework has the `IClock` service ([see](Clock.md)) which has a similar goal, but now you need to inject it whenever you need it. + ## Missing Features The following features are not present for the ABP Framework. Here, a list of major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository): * [Multi-Lingual Entities](https://aspnetboilerplate.com/Pages/Documents/Multi-Lingual-Entities) ([#1754](https://github.com/abpframework/abp/issues/1754)) +* ...TODO Most of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework by implementing these yourself. \ No newline at end of file diff --git a/docs/en/Clock.md b/docs/en/Clock.md new file mode 100644 index 0000000000..46ef1235e0 --- /dev/null +++ b/docs/en/Clock.md @@ -0,0 +1,3 @@ +# Clock + +TODO \ No newline at end of file diff --git a/docs/en/Logging.md b/docs/en/Logging.md index 9b9c604bed..7fa45c03b9 100644 --- a/docs/en/Logging.md +++ b/docs/en/Logging.md @@ -2,4 +2,5 @@ ABP Framework doesn't implement any logging infrastructure. It uses the [ASP.NET Core's logging system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging). -> .NET Core's logging system is actually independent from the ASP.NET Core. It is usable in any type of application. \ No newline at end of file +> .NET Core's logging system is actually independent from the ASP.NET Core. It is usable in any type of application. + diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index edbe193c3f..eb4ef13b8d 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -104,6 +104,10 @@ "text": "Caching", "path": "Caching.md" }, + { + "text": "Logging", + "path": "Logging.md" + }, { "text": "Audit Logging", "path": "Audit-Logging.md" From 41f918f2cf3fffb65d0d2378bf18bfc8798d1ce8 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 14 Feb 2020 13:36:53 +0800 Subject: [PATCH 46/68] Add unit testing for document repository and application services. --- .../Docs/DocumentAdminAppService_Tests.cs | 55 +++++++++++++++++++ .../Volo/Docs/DocumentSourceFactory_Tests.cs | 25 +++++++++ .../Volo/Docs/DocumentStoreFactory_Tests.cs | 25 --------- ...Tests.cs => GithubDocumentSource_Tests.cs} | 14 ++--- .../DocumentRepository_Tests.cs | 6 ++ .../Docs/Document/DocumentRepository_Tests.cs | 9 +++ .../Volo/Docs/DocsTestBase.cs | 26 ++++++++- .../Volo/Docs/DocsTestDataBuilder.cs | 15 ++++- .../Volo/Docs/DocumentRepository_Tests.cs | 28 ++++++++++ 9 files changed, 168 insertions(+), 35 deletions(-) create mode 100644 modules/docs/test/Volo.Docs.Admin.Application.Tests/Volo/Docs/DocumentAdminAppService_Tests.cs create mode 100644 modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentSourceFactory_Tests.cs delete mode 100644 modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentStoreFactory_Tests.cs rename modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/{GithubDocumentStore_Tests.cs => GithubDocumentSource_Tests.cs} (78%) create mode 100644 modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo/Docs/EntityFrameworkCore/DocumentRepository_Tests.cs create mode 100644 modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/Document/DocumentRepository_Tests.cs create mode 100644 modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocumentRepository_Tests.cs diff --git a/modules/docs/test/Volo.Docs.Admin.Application.Tests/Volo/Docs/DocumentAdminAppService_Tests.cs b/modules/docs/test/Volo.Docs.Admin.Application.Tests/Volo/Docs/DocumentAdminAppService_Tests.cs new file mode 100644 index 0000000000..a969e3390a --- /dev/null +++ b/modules/docs/test/Volo.Docs.Admin.Application.Tests/Volo/Docs/DocumentAdminAppService_Tests.cs @@ -0,0 +1,55 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Docs.Admin.Documents; +using Volo.Docs.Documents; +using Xunit; + +namespace Volo.Docs +{ + public class DocumentAdminAppService_Tests : DocsAdminApplicationTestBase + { + private readonly IDocumentAdminAppService _documentAdminAppService; + private readonly IDocumentRepository _documentRepository; + private readonly DocsTestData _testData; + + public DocumentAdminAppService_Tests() + { + _documentAdminAppService = GetRequiredService(); + _documentRepository = GetRequiredService(); + _testData = GetRequiredService(); + } + + [Fact] + public async Task PullAsync() + { + (await _documentRepository.FindAsync(_testData.PorjectId, "Part-I.md", "en", "1.0.0")).ShouldBeNull(); + + await _documentAdminAppService.PullAsync(new PullDocumentInput + { + ProjectId = _testData.PorjectId, + Name = "Part-I.md", + LanguageCode = "en", + Version = "1.0.0" + }); + + (await _documentRepository.FindAsync(_testData.PorjectId, "Part-I.md", "en", "1.0.0")).ShouldNotBeNull(); + } + + [Fact] + public async Task PullAllAsync() + { + (await _documentRepository.FindAsync(_testData.PorjectId, "Part-I.md", "en", "1.0.0")).ShouldBeNull(); + (await _documentRepository.FindAsync(_testData.PorjectId, "Part-II.md", "en", "1.0.0")).ShouldBeNull(); + + await _documentAdminAppService.PullAllAsync(new PullAllDocumentInput + { + ProjectId = _testData.PorjectId, + LanguageCode = "en", + Version = "1.0.0" + }); + + (await _documentRepository.FindAsync(_testData.PorjectId, "Part-I.md", "en", "1.0.0")).ShouldNotBeNull(); + (await _documentRepository.FindAsync(_testData.PorjectId, "Part-II.md", "en", "1.0.0")).ShouldNotBeNull(); + } + } +} diff --git a/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentSourceFactory_Tests.cs b/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentSourceFactory_Tests.cs new file mode 100644 index 0000000000..8e56a087d1 --- /dev/null +++ b/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentSourceFactory_Tests.cs @@ -0,0 +1,25 @@ +using Shouldly; +using Volo.Docs.Documents; +using Volo.Docs.FileSystem.Documents; +using Volo.Docs.GitHub.Documents; +using Xunit; + +namespace Volo.Docs +{ + public class DocumentSourceFactory_Tests : DocsDomainTestBase + { + private readonly IDocumentSourceFactory _documentSourceFactory; + + public DocumentSourceFactory_Tests() + { + _documentSourceFactory = GetRequiredService(); + } + + [Fact] + public void Create() + { + _documentSourceFactory.Create(GithubDocumentSource.Type).GetType().ShouldBe(typeof(GithubDocumentSource)); + _documentSourceFactory.Create(FileSystemDocumentSource.Type).GetType().ShouldBe(typeof(FileSystemDocumentSource)); + } + } +} diff --git a/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentStoreFactory_Tests.cs b/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentStoreFactory_Tests.cs deleted file mode 100644 index 23d34748a6..0000000000 --- a/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/DocumentStoreFactory_Tests.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Shouldly; -using Volo.Docs.Documents; -using Volo.Docs.FileSystem.Documents; -using Volo.Docs.GitHub.Documents; -using Xunit; - -namespace Volo.Docs -{ - public class DocumentStoreFactory_Tests : DocsDomainTestBase - { - private readonly IDocumentSourceFactory _documentStoreFactory; - - public DocumentStoreFactory_Tests() - { - _documentStoreFactory = GetRequiredService(); - } - - [Fact] - public void Create() - { - _documentStoreFactory.Create(GithubDocumentSource.Type).GetType().ShouldBe(typeof(GithubDocumentSource)); - _documentStoreFactory.Create(FileSystemDocumentSource.Type).GetType().ShouldBe(typeof(FileSystemDocumentSource)); - } - } -} diff --git a/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentStore_Tests.cs b/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentSource_Tests.cs similarity index 78% rename from modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentStore_Tests.cs rename to modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentSource_Tests.cs index 6b6bb0565c..1e89966518 100644 --- a/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentStore_Tests.cs +++ b/modules/docs/test/Volo.Docs.Domain.Tests/Volo/Docs/GithubDocumentSource_Tests.cs @@ -7,15 +7,15 @@ using Xunit; namespace Volo.Docs { - public class GithubDocumentStore_Tests : DocsDomainTestBase + public class GithubDocumentSource_Tests : DocsDomainTestBase { - private readonly IDocumentSourceFactory _documentStoreFactory; + private readonly IDocumentSourceFactory _documentSourceFactory; private readonly IProjectRepository _projectRepository; private readonly DocsTestData _testData; - public GithubDocumentStore_Tests() + public GithubDocumentSource_Tests() { - _documentStoreFactory = GetRequiredService(); + _documentSourceFactory = GetRequiredService(); _projectRepository = GetRequiredService(); _testData = GetRequiredService(); } @@ -23,7 +23,7 @@ namespace Volo.Docs [Fact] public async Task GetDocumentAsync() { - var source = _documentStoreFactory.Create(GithubDocumentSource.Type); + var source = _documentSourceFactory.Create(GithubDocumentSource.Type); var project = await _projectRepository.FindAsync(_testData.PorjectId); project.ShouldNotBeNull(); @@ -40,7 +40,7 @@ namespace Volo.Docs [Fact] public async Task GetVersionsAsync() { - var source = _documentStoreFactory.Create(GithubDocumentSource.Type); + var source = _documentSourceFactory.Create(GithubDocumentSource.Type); var project = await _projectRepository.FindAsync(_testData.PorjectId); project.ShouldNotBeNull(); @@ -55,7 +55,7 @@ namespace Volo.Docs [Fact] public async Task GetResource() { - var source = _documentStoreFactory.Create(GithubDocumentSource.Type); + var source = _documentSourceFactory.Create(GithubDocumentSource.Type); var project = await _projectRepository.FindAsync(_testData.PorjectId); project.ShouldNotBeNull(); diff --git a/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo/Docs/EntityFrameworkCore/DocumentRepository_Tests.cs b/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo/Docs/EntityFrameworkCore/DocumentRepository_Tests.cs new file mode 100644 index 0000000000..d36c97a2b7 --- /dev/null +++ b/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo/Docs/EntityFrameworkCore/DocumentRepository_Tests.cs @@ -0,0 +1,6 @@ +namespace Volo.Docs.EntityFrameworkCore +{ + public class DocumentRepository_Tests : DocumentRepository_Tests + { + } +} \ No newline at end of file diff --git a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/Document/DocumentRepository_Tests.cs b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/Document/DocumentRepository_Tests.cs new file mode 100644 index 0000000000..63e69d099a --- /dev/null +++ b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/Document/DocumentRepository_Tests.cs @@ -0,0 +1,9 @@ +using Volo.Docs.MongoDB; + +namespace Volo.Docs.Document +{ + public class DocumentRepository_Tests : DocumentRepository_Tests + { + + } +} \ No newline at end of file diff --git a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestBase.cs b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestBase.cs index ec8270ae01..56a8ea0203 100644 --- a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestBase.cs +++ b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using NSubstitute; @@ -23,6 +23,11 @@ namespace Volo.Docs var repositoryManager = Substitute.For(); repositoryManager.GetFileRawStringContentAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns("stringContent"); + repositoryManager.GetFileRawStringContentAsync( + Arg.Is(x => x.Contains("docs-nav.json", StringComparison.InvariantCultureIgnoreCase)), + Arg.Any(), Arg.Any()) + .Returns("{\"items\":[{\"text\":\"Part-I.md\",\"path\":\"Part-I.md\"},{\"text\":\"Part-II\",\"path\":\"Part-II.md\"}]}"); + repositoryManager.GetFileRawByteArrayContentAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(new byte[] { 0x01, 0x02, 0x03 }); repositoryManager.GetReleasesAsync(Arg.Any(), Arg.Any(), Arg.Any()) @@ -47,6 +52,25 @@ namespace Volo.Docs "https://api.github.com/repos/abpframework/abp/zipball/0.15.0", null) }); + repositoryManager.GetFileCommitsAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()) + .Returns(new List + { + new GitHubCommit("", "", "", "", "", null, null, + new Author("hikalkan ", 2, "", "https://avatars1.githubusercontent.com/u/1?v=4", "", + "https://github.com/hikalkan", "", "", "", "", "", "", "", "", "", "", false), "", + new Commit("", "", "", "", "", null, null, "", new Committer("", "", DateTimeOffset.Now), + null, null, new []{ new GitReference("", "", "", "", "", null, null) }, 1, null), + null, "", null, new []{ new GitReference("", "", "", "", "", null, null) }, null), + + new GitHubCommit("", "", "", "", "", null, null, + new Author("ebicoglu ", 2, "", "https://avatars1.githubusercontent.com/u/2?v=4", "", + "https://github.com/ebicoglu", "", "", "", "", "", "", "", "", "", "", false), "", + new Commit("", "", "", "", "", null, null, "", new Committer("", "", DateTimeOffset.Now), + null, null, new []{ new GitReference("", "", "", "", "", null, null) }, 1, null), + null, "", null, new []{ new GitReference("", "", "", "", "", null, null) }, null) + }); + services.AddSingleton(repositoryManager); } } diff --git a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs index be5bab81bc..1325ec04a3 100644 --- a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs +++ b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs @@ -1,6 +1,8 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; +using Volo.Docs.Documents; using Volo.Docs.GitHub.Documents; using Volo.Docs.Projects; @@ -10,13 +12,16 @@ namespace Volo.Docs { private readonly DocsTestData _testData; private readonly IProjectRepository _projectRepository; + private readonly IDocumentRepository _documentRepository; public DocsTestDataBuilder( DocsTestData testData, - IProjectRepository projectRepository) + IProjectRepository projectRepository, + IDocumentRepository documentRepository) { _testData = testData; _projectRepository = projectRepository; + _documentRepository = documentRepository; } public async Task BuildAsync() @@ -38,6 +43,12 @@ namespace Volo.Docs .SetProperty("GitHubUserAgent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); await _projectRepository.InsertAsync(project); + + await _documentRepository.InsertAsync(new Document(Guid.NewGuid(), project.Id, "CLI.md", "2.0.0", "en", "CLI.md", + "this is abp cli", "md", "https://github.com/abpframework/abp/blob/2.0.0/docs/en/CLI.md", + "https://github.com/abpframework/abp/tree/2.0.0/docs/", + "https://raw.githubusercontent.com/abpframework/abp/2.0.0/docs/en/", "", DateTime.Now, 1, + DateTime.Now)); } } } \ No newline at end of file diff --git a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocumentRepository_Tests.cs b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocumentRepository_Tests.cs new file mode 100644 index 0000000000..31d0e8a6fd --- /dev/null +++ b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocumentRepository_Tests.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Docs.Documents; +using Xunit; + +namespace Volo.Docs +{ + public abstract class DocumentRepository_Tests : DocsTestBase + where TStartupModule : IAbpModule + { + protected readonly IDocumentRepository DocumentRepository; + protected readonly DocsTestData DocsTestData; + + protected DocumentRepository_Tests() + { + DocumentRepository = GetRequiredService(); + DocsTestData = GetRequiredService(); + } + + [Fact] + public async Task FindAsync() + { + var document = await DocumentRepository.FindAsync(DocsTestData.PorjectId, "CLI.md", "en", "2.0.0"); + document.ShouldNotBeNull(); + } + } +} From 87890aa4d2000961dc2861586ce5d9903448a15a Mon Sep 17 00:00:00 2001 From: liangshiw Date: Fri, 14 Feb 2020 15:04:53 +0800 Subject: [PATCH 47/68] Update chinese document --- docs/en/Blog-Posts/2019-02-22/Post.md | 4 +- docs/zh-Hans/Background-Jobs.md | 8 +- docs/zh-Hans/Blog-Posts/2019-02-22/Post.md | 12 +- docs/zh-Hans/Connection-Strings.md | 16 ++ .../Contribution/Localization-Text-Files.md | 29 ++-- docs/zh-Hans/Dependency-Injection.md | 2 +- docs/zh-Hans/Entity-Framework-Core.md | 2 +- ...Getting-Started-AspNetCore-MVC-Template.md | 2 +- docs/zh-Hans/Modules/Docs.md | 144 +++++++++++++++--- docs/zh-Hans/Modules/Index.md | 2 +- .../Tutorials/AspNetCore-Mvc/Part-I.md | 16 +- .../Tutorials/AspNetCore-Mvc/Part-III.md | 22 +-- .../images/auditlog-object-diagram.png | Bin 0 -> 29134 bytes docs/zh-Hans/images/docs-section-ui.png | Bin 0 -> 9624 bytes .../Localization/BookStore/zh-Hans.json | 2 +- .../Localization/BookStore/zh-Hans.json | 2 +- .../Localization/BookStore/zh-Hans.json | 2 +- .../Localization/DashboardDemo/zh-Hans.json | 2 +- .../Localization/MyProjectName/zh-Hans.json | 2 +- 19 files changed, 184 insertions(+), 85 deletions(-) create mode 100644 docs/zh-Hans/images/auditlog-object-diagram.png create mode 100644 docs/zh-Hans/images/docs-section-ui.png diff --git a/docs/en/Blog-Posts/2019-02-22/Post.md b/docs/en/Blog-Posts/2019-02-22/Post.md index 35f8219014..b27a50ec05 100644 --- a/docs/en/Blog-Posts/2019-02-22/Post.md +++ b/docs/en/Blog-Posts/2019-02-22/Post.md @@ -1,6 +1,6 @@ # Microservice Demo, Projects Status and Road Map -After [the first announcement](https://abp.io/blog/abp/Abp-vNext-Announcement) on the ABP vNext, we have a lot of improvements on the codebase (1100+ commits on the [GitHub repository](https://github.com/abpframework/abp)). We've created features, samples, documentation and much more. In this post, I want to inform you about some news and the status of the project. +After [the first announcement](https://blog.abp.io/Abp-vNext-Announcement) on the ABP vNext, we have a lot of improvements on the codebase (1100+ commits on the [GitHub repository](https://github.com/abpframework/abp)). We've created features, samples, documentation and much more. In this post, I want to inform you about some news and the status of the project. ## Microservice Demo Solution @@ -38,7 +38,7 @@ First release may not include a SPA template. However, we want to prepare a simp ## Chinese Web Site -There is a big ABP community in China. They have created a Chinese version of the abp.io web site: https://cn.abp.io/ They are keeping it up to date. Thanks to the Chinese developers and especially to [Liming Ma](https://github.com/maliming). +There is a big ABP community in China. They have created a Chinese version of the abp.io web site: https://abp.io/ They are keeping it up to date. Thanks to the Chinese developers and especially to [Liming Ma](https://github.com/maliming). ## NDC {London} 2019 diff --git a/docs/zh-Hans/Background-Jobs.md b/docs/zh-Hans/Background-Jobs.md index 30b6f0f1ad..7c2a694005 100644 --- a/docs/zh-Hans/Background-Jobs.md +++ b/docs/zh-Hans/Background-Jobs.md @@ -110,7 +110,7 @@ Enqueue方法接收一些可选参数用于控制后台作业: 你可能希望在你的应用程序中禁用后台作业执行. 如果你希望在另一个进程中执行后台作业并在当前进程中禁用它,通常可以使用以下命令. -使用 `BackgroundJobOptions` 配置作业执行: +使用 `AbpBackgroundJobOptions` 配置作业执行: ````csharp [DependsOn(typeof(AbpBackgroundJobsModule))] @@ -118,7 +118,7 @@ public class MyModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => + Configure(options => { options.IsJobExecutionEnabled = false; //禁用作业执行 }); @@ -142,7 +142,7 @@ ABP framework 包含一个简单的 `IBackgroundJobManager` 实现; ### 配置 -在你的[模块类](Module-Development-Basics.md)中使用 `BackgroundJobWorkerOptions` 配置默认作业管理器. +在你的[模块类](Module-Development-Basics.md)中使用 `AbpBackgroundJobWorkerOptions` 配置默认作业管理器. 示例中更改后台作业的的超时时间: ````csharp @@ -151,7 +151,7 @@ public class MyModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => + Configure(options => { options.DefaultTimeout = 864000; //10 days (as seconds) }); diff --git a/docs/zh-Hans/Blog-Posts/2019-02-22/Post.md b/docs/zh-Hans/Blog-Posts/2019-02-22/Post.md index 69a8603baf..30a5d691e9 100644 --- a/docs/zh-Hans/Blog-Posts/2019-02-22/Post.md +++ b/docs/zh-Hans/Blog-Posts/2019-02-22/Post.md @@ -1,12 +1,12 @@ # 微服务演示,项目状态和路线图 -在ABP vNext上的[第一个公告](https://cn.abp.io/blog/abp/Abp-vNext-Announcement)之后,我们对代码库进行了很多改进([GitHub存储库](https://github.com/abpframework/abp)上的1100多次提交).我们已经创建了功能,示例,文档等等.在这篇文章中,我想告诉你一些新闻和项目的状态. +在ABP vNext上的[第一个公告](https://abp.io/blog/abp/Abp-vNext-Announcement)之后,我们对代码库进行了很多改进([GitHub存储库](https://github.com/abpframework/abp)上的1100多次提交).我们已经创建了功能,示例,文档等等.在这篇文章中,我想告诉你一些新闻和项目的状态. ## 微服务演示解决方案 -ABP框架的主要目标之一是提供[创建微服务解决方案的便利基础设施](https://cn.abp.io/documents/abp/latest/Microservice-Architecture). +ABP框架的主要目标之一是提供[创建微服务解决方案的便利基础设施](https://abp.io/documents/abp/latest/Microservice-Architecture). -我们一直在努力开发微服务解决方案演示.初始版本已完成并[文档化](https://cn.abp.io/documents/abp/latest/Samples/Microservice-Demo).该示例解决方案旨在演示一个简单而完整的微服务解决方案; +我们一直在努力开发微服务解决方案演示.初始版本已完成并[文档化](https://abp.io/documents/abp/latest/Samples/Microservice-Demo).该示例解决方案旨在演示一个简单而完整的微服务解决方案; - 具有多个独立的,可自我部署的**微服务**. - 多个**Web应用程序**,每个都使用不同的API网关. @@ -20,7 +20,7 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基 - 使用[Docker](https://www.docker.com/)和[Kubernates](https://kubernetes.io/)**部署**并运行所有服务和应用程序. - 使用[Elasticsearch](https://www.elastic.co/products/elasticsearch)和[Kibana](https://www.elastic.co/products/kibana)存储和可视化日志(使用[Serilog](https://serilog.net/)编写). -有关解决方案的详细说明,请参阅[其文档](https://cn.abp.io/documents/abp/latest/Samples/Microservice-Demo). +有关解决方案的详细说明,请参阅[其文档](https://abp.io/documents/abp/latest/Samples/Microservice-Demo). ## 改进/功能 @@ -32,13 +32,13 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基 根据我们的估计,我们计划在2019年第二季度(可能在五月或六月)发布v1.0.所以,不用等待太长时间了.我们也对第一个稳定版本感到非常兴奋. -我们还将完善[文档](https://cn.abp.io/documents/abp/latest),因为它现在还远未完成. +我们还将完善[文档](https://abp.io/documents/abp/latest),因为它现在还远未完成. 第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将您的想法写为对此帖的评论. ## 中文网 -中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站:https://cn.abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming). +中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站:https://abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming). ## NDC {London} 2019 diff --git a/docs/zh-Hans/Connection-Strings.md b/docs/zh-Hans/Connection-Strings.md index 2df4137b2b..562ae700ce 100644 --- a/docs/zh-Hans/Connection-Strings.md +++ b/docs/zh-Hans/Connection-Strings.md @@ -35,6 +35,22 @@ ABP框架的设计是[模块化](Module-Development-Basics.md), [微服务兼容 [预构建的应用程序模块](Modules/Index.md) 为连接字符串名称定义常量. 例如IdentityServer模块在 `AbpIdentityServerDbProperties` 类(位于 `Volo.Abp.IdentityServer` 命名空间)定义了 `ConnectionStringName` 常量 . 其他的模块类似的定义常量,你可以查看连接字符串的名称. +### AbpDbConnectionOptions + +ABP实际上使用 `AbpDbConnectionOptions` 获取连接字符串. 如果如上所述设置了连接字符串, `AbpDbConnectionOptions` 会被自动填充. 但是你也可以使用[选项模式](Options.md)设置或覆盖连接字符串. 你可以在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法配置`AbpDbConnectionOptions`). +如下所示: + +````csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.ConnectionStrings.Default = "..."; + options.ConnectionStrings["AbpPermissionManagement"] = "..."; + }); +} +```` + ## 设置连接字符串名称 模块通常使用 `ConnectionStringName` attribute 为 `DbContext` 类关联一个唯一的连接字符串名称. 示例: diff --git a/docs/zh-Hans/Contribution/Localization-Text-Files.md b/docs/zh-Hans/Contribution/Localization-Text-Files.md index 36bae2890a..8055d9cae2 100644 --- a/docs/zh-Hans/Contribution/Localization-Text-Files.md +++ b/docs/zh-Hans/Contribution/Localization-Text-Files.md @@ -3,7 +3,7 @@ 这是一个来自框架的本地化文本文件列表, 任何人都可以做出贡献. 我们会将此列表保持最新: * https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json -* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpValidation/en.json +* https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json * https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/en.json * https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json * https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json @@ -12,29 +12,20 @@ * https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/en.json * https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/en.json * https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/en.json -* https://github.com/abpframework/abp/tree/master/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/en.json +* https://github.com/abpframework/abp/blob/master/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json * https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/en.json -* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json +* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json * https://github.com/abpframework/abp/tree/master/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/en.json * https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json -* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Admin.Web/Localization/Resources/Docs/Web/en.json * https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json -* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/Localization/ApplicationContracts/en.json -* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/Localization/Domain/en.json -* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Localization/Resources/FeatureManagement/en.json -* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/en.json -* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/en.json -* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json -* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Localization/Resources/AbpPermissionManagement/en.json -* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Localization/Resources/AbpSettingManagement/en.json -* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json -* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/en.json +* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json +* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json +* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json +* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json * https://github.com/abpframework/abp/tree/master/samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json -* https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain/Localization/DashboardDemo/en.json +* https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json -* https://github.com/abpframework/abp/tree/master/templates/mvc-module/src/MyCompanyName.MyProjectName.Application.Contracts/Localization/MyProjectName/ApplicationContracts/en.json -* https://github.com/abpframework/abp/tree/master/templates/mvc-module/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/DomainShared/en.json -* https://github.com/abpframework/abp/tree/master/templates/mvc-module/src/MyCompanyName.MyProjectName.Web/Localization/MyProjectName/Web/en.json -* https://github.com/abpframework/abp/tree/master/templates/mvc/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/en.json +* https://github.com/abpframework/abp/tree/master/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/en.json \ No newline at end of file diff --git a/docs/zh-Hans/Dependency-Injection.md b/docs/zh-Hans/Dependency-Injection.md index 381b77ec35..2264a12930 100644 --- a/docs/zh-Hans/Dependency-Injection.md +++ b/docs/zh-Hans/Dependency-Injection.md @@ -319,4 +319,4 @@ public class AppModule : AbpModule ### 请参阅 -* [ASP.NET Core依赖注入最佳实践,提示和技巧](https://cn.abp.io/blog/Abp/asp-net-core-dependency-injection-best-practices-tips-tricks) +* [ASP.NET Core依赖注入最佳实践,提示和技巧](https://blog.abp.io/asp-net-core-dependency-injection-best-practices-tips-tricks) diff --git a/docs/zh-Hans/Entity-Framework-Core.md b/docs/zh-Hans/Entity-Framework-Core.md index 328a4b74cc..b70630e38f 100644 --- a/docs/zh-Hans/Entity-Framework-Core.md +++ b/docs/zh-Hans/Entity-Framework-Core.md @@ -181,7 +181,7 @@ public interface IBookRepository : IRepository } ```` -你通常希望从IRepository派生以继承标准存储库方法. 然而,你没有必要这样做. 仓储接口在分层应用程序的领域层中定义,它在数据访问/基础设施层([启动模板](https://cn.abp.io/Templates)中的`EntityFrameworkCore`项目)中实现 +你通常希望从IRepository派生以继承标准存储库方法. 然而,你没有必要这样做. 仓储接口在分层应用程序的领域层中定义,它在数据访问/基础设施层([启动模板](https://abp.io/Templates)中的`EntityFrameworkCore`项目)中实现 IBookRepository接口的实现示例: diff --git a/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md b/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md index 4d6695c79b..d65573d9db 100644 --- a/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md +++ b/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md @@ -2,7 +2,7 @@ ### 创建新项目 -本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://cn.abp.io/get-started)页面. +本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://abp.io/get-started)页面. 如果你之前未安装,请使用命令行安装ABP CLI: diff --git a/docs/zh-Hans/Modules/Docs.md b/docs/zh-Hans/Modules/Docs.md index cfe287082b..3bd71d554f 100644 --- a/docs/zh-Hans/Modules/Docs.md +++ b/docs/zh-Hans/Modules/Docs.md @@ -14,21 +14,19 @@ ### 版本 -当你使用GitHub存储文档时,文档模块支持多版本. 如果你的文档具有多个版本, UI上有一个组合框,用于切换版本. 如果你选择使用文件系统存储文档, 那么它不支持多版本. +当你使用GitHub存储文档时,文档模块支持多版本. 如果你的文档具有多个版本, UI上有一个组合框,用于切换版本. 如果你选择使用文件系统存储文档, 那么它不支持多版本. -ABP框架的[文档](https://abp.io/documents/)也是使用的此模块. +ABP框架的[文档](docs.abp.io)也是使用的此模块. > 文档模块遵循 [模块化架构最佳实践](../Best-Practices/Module-Architecture.md) 指南. - - ## 安装 ### 1- 下载 -如果你没有现有的ABP项目, 这个步骤向你展示如何在[abp.io](https://cn.abp.io)创建一个新项目并添加文档模块. 如果你本地已经有了一个ABP项目, 那么你可以跳过这一步. +如果你没有现有的ABP项目, 这个步骤向你展示如何在[abp.io](https://abp.io)创建一个新项目并添加文档模块. 如果你本地已经有了一个ABP项目, 那么你可以跳过这一步. -打开 https://cn.abp.io/Templates. 输入项目名称为 `Acme.MyProject`, 选择 `ASP.NET Core Mvc Application` 和选择 `Entity Framework Core` 做为数据库提供者. +打开 https://abp.io/Templates. 输入项目名称为 `Acme.MyProject`, 选择 `ASP.NET Core Mvc Application` 和选择 `Entity Framework Core` 做为数据库提供者. 请注意,本文档包含了 `Entity Framework Core` 提供者 不过你也可以选择 `MongoDB` 做为数据库提供者. @@ -36,7 +34,7 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块. ### 2- 运行这个空项目 -下载项目后, 解压压缩文档并且打开 `Acme.MyProject.sln`. 你可以看到这个解决方案包含了 `Application`, `Domain `, `EntityFrameworkCore` 和 `Web` 项目. 右键选择 `Acme.MyProject.Web` 项目**设置为启动项目**. +下载项目后, 解压压缩文档并且打开 `Acme.MyProject.sln`. 你可以看到这个解决方案包含了 `Application`, `Domain`, `EntityFrameworkCore` 和 `Web` 项目. 右键选择 `Acme.MyProject.Web` 项目**设置为启动项目**. ![创建新项目](../images/docs-module_solution-explorer.png) @@ -67,31 +65,32 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块. ```csharp ``` + * [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) 需要安装到 `Acme.MyProject.EntityFrameworkCore` 项目. - - 修改 `Acme.MyProject.EntityFrameworkCore.csproj`文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. + * 修改 `Acme.MyProject.EntityFrameworkCore.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. ```csharp ``` + * [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) 需要安装到 `Acme.MyProject.Application` 项目. - * 修改 `Acme.MyProject.Application.csproj`文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. + * 修改 `Acme.MyProject.Application.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. ```csharp ``` -* [Volo.Docs.Web ](https://www.nuget.org/packages/Volo.Docs.Web/) 需要安装到 `Acme.MyProject.Web` 项目. - - 修改 `Acme.MyProject.Web.csproj`文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. +* [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Web/) 需要安装到 `Acme.MyProject.Web` 项目. + + * 修改 `Acme.MyProject.Web.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. ```csharp ``` - - -### 3- 添加模块添加 +### 3- 添加模块依赖 一个ABP模块必须声明 `[DependsOn]` attribute 如果它依赖于另一个模块. 每个模块都必须在相关的项目的`[DependsOn]`Attribute 中添加. @@ -155,7 +154,6 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块. } ``` - * 打开 `MyProjectWebModule.cs`并且添加 `typeof(DocsWebModule)` 如下所示; ```csharp @@ -174,15 +172,13 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块. } ``` - - ### 4- 数据库集成 #### 4.1- Entity Framework 集成 如果你选择了Entity Framework 做为数据库供应者,你需要在DbContext中配置文档模块. 做以下操作; -- 打开 `MyProjectDbContext.cs` 并且添加 `modelBuilder.ConfigureDocs()` 到 `OnModelCreating()` 方法中 +* 打开 `MyProjectDbContext.cs` 并且添加 `modelBuilder.ConfigureDocs()` 到 `OnModelCreating()` 方法中 ```csharp [ConnectionStringName("Default")] @@ -195,7 +191,7 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块. } protected override void OnModelCreating(ModelBuilder modelBuilder) - { + { //... modelBuilder.ConfigureDocs(); } @@ -218,7 +214,6 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块. 最后你可以查看数据库中创建的新表,例如你可以看到 `DocsProjects` 表已经添加到数据库中. - ### 5- 链接文档模块 文档模块的默认路由是; @@ -262,7 +257,7 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块. "texts": { "Menu:Home": "首页", "Welcome": "欢迎", - "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 cn.abp.io.", + "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io.", "Menu:Docs": "文档" } } @@ -352,10 +347,10 @@ INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocume - ExtraProperties: ```json - {"Path":"C:\\Github\\abp\\docs\\zh-Hans"} + {"Path":"C:\\Github\\abp\\docs"} ``` - 请注意 `Path` 必须使用本地docs目录替换. 你可以从https://github.com/abpframework/abp/tree/master/docs/zh-hans获取ABP Framework的文档并且复制到该目录 `C:\\Github\\abp\\docs\\zh-Hans` 使其正常工作. + 请注意 `Path` 必须使用本地docs目录替换. 你可以从https://github.com/abpframework/abp/tree/master/docs获取ABP Framework的文档并且复制到该目录 `C:\\Github\\abp\\docs` 使其正常工作. - MainWebsiteUrl: `/` @@ -364,7 +359,7 @@ INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocume 对于 `SQL` 数据库,你可以使用下面的 `T-SQL` 命令将指定的示例插入到 `DocsProjects` 表中: ```mssql -INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs\\zh-Hans"}', N'/', NULL) +INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL) ``` 添加上面的一个示例项目后运行该应用程序. 在菜单中你会看到`文档` 链接,点击菜单链接打开文档页面. @@ -408,6 +403,101 @@ public class Person [https://github.com/abpframework/abp/blob/master/docs/zh-Hans/](https://github.com/abpframework/abp/blob/master/docs/zh-Hans/) +#### 有条件的部分功能(使用Scriban) + +文档模块使用[Scriban]()有条件的显示或隐藏文档的某些部分. 使用该功能你需要为每一种语言创建一个JSON文件做为**参数文档**. 它包含所有键值以及它们的显示名称. + +例如 [en/docs-params.json](https://github.com/abpio/abp-commercial-docs/blob/master/en/docs-params.json): + +```json +{ + "parameters": [{ + "name": "UI", + "displayName": "UI", + "values": { + "MVC": "MVC / Razor Pages", + "NG": "Angular" + } + }, + { + "name": "DB", + "displayName": "Database", + "values": { + "EF": "Entity Framework Core", + "Mongo": "MongoDB" + } + }, + { + "name": "Tiered", + "displayName": "Tiered", + "values": { + "No": "Not Tiered", + "Yes": "Tiered" + } + }] +} +``` + +因为并不是项目中的每个文档都有章节或者不需要所有的参数,你必须声明哪些参数将用于对文档进行分段,在文档的任何地方都可以使用JSON块. + +例如 [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/blob/master/en/getting-started.md): + +``` +..... + +​````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF", "Mongo"], + "Tiered": ["Yes", "No"] +} +​```` + +........ +``` + +这个部分会在渲染时自动删除.前提是这些键值必须与**参数文档**中的键值匹配. + +![Interface](../images/docs-section-ui.png) + +现在你可以使用 **Scriban** 语法在文档中创建章节. + +示例 : + +```` +{{ if UI == "NG" }} + +* `-u` argument specifies the UI framework, `angular` in this case. + +{{ end }} + +{{ if DB == "Mongo" }} + +* `-d` argument specifies the database provider, `mongodb` in this case. + +{{ end }} + +{{ if Tiered == "Yes" }} + +* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated. + +{{ end }} + +```` + +还可以在文本中使用变量,在其键中添加 **_Value** 后缀: + +```` +This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. +```` + +如果你想要得到的当前文档的语言或版本,可以使用预定义的 **Document_Language_Code** 和 **DOCUMENT_VERSION** 键(这对于创建重定向到另一个地区中另一个文档系统的链接很有用). + +------ + +**重要提示**: Scriban 的语法是 "{{" and "}}". 如果要在文档(如Angular文档)中使用转义,则必须使用转义块. 参阅 [Scriban文档]( ) 了解更多信息. + ### 8- 创建文档导航 导航文档是文档页面的主菜单. 它位于页面的左侧,是一个`JSON` 文件. 请查看以下示例导航文档以了解结构. @@ -464,4 +554,8 @@ public class Person ![Navigation menu](../images/docs-module_download-sample-navigation-menu.png) -最后,为您的项目添加了一个新的Docs模块, 该模块由GitHub提供. \ No newline at end of file +最后,为您的项目添加了一个新的Docs模块, 该模块由GitHub提供. + +## 下一步 + +文档模块也可以做为独立的应用程序. 查看 [VoloDocs](../Apps/VoloDocs). \ No newline at end of file diff --git a/docs/zh-Hans/Modules/Index.md b/docs/zh-Hans/Modules/Index.md index 8e5edf35b6..e6f08d5451 100644 --- a/docs/zh-Hans/Modules/Index.md +++ b/docs/zh-Hans/Modules/Index.md @@ -14,7 +14,7 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages** * **Account**: 用于用户登录/注册应用程序. * **Audit Logging**: 用于将审计日志持久化到数据库. * **Background Jobs**: 用于在使用默认后台作业管理器时保存后台作业. -* **Blogging**: 用于创建精美的博客. ABP的[博客](https://abp.io/blog/abp/) 就使用了此模块. +* **Blogging**: 用于创建精美的博客. ABP的[博客](https://blog.abp.io/) 就使用了此模块. * [**Docs**](Docs.md): 用于创建技术文档页面. ABP的[文档](https://abp.io/documents/) 就使用了此模块. * **Identity**: 用于管理角色,用户和他们的权限. * **Identity Server**: 集成了IdentityServer4. diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md index f9314e5030..5394890f8d 100644 --- a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md +++ b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md @@ -50,6 +50,18 @@ namespace Acme.BookStore public DateTime PublishDate { get; set; } public float Price { get; set; } + + protected Book() + { + } + public Book(Guid id, string name, BookType type, DateTime publishDate, float price) + :base(id) + { + Name = name; + Type = type; + PublishDate = publishDate; + Price = price; + } } } ```` @@ -349,7 +361,7 @@ successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7 ```` * 此代码更改了Razor View Page Model的默认继承,因此它从`BookStorePage`类(而不是`PageModel`)继承.启动模板附带的`BookStorePage`类,提供所有页面使用的一些共享属性/方法. -* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Pages.Books`命名空间,或者在`Index.cshtml`中更新它. +* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Books`命名空间,或者在`Index.cshtml`中更新它. #### 将Books页面添加到主菜单 @@ -426,7 +438,7 @@ context.Menu.AddItem( ```` * `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](../../AspNetCore/Bundling-Minification.md)获取更多信息. -* `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](../../AspNetCore/Tag-Helpers.md). +* `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](../../AspNetCore/Tag-Helpers/Index.md). * 你可以像上面本地化菜单一样 **本地化** 列名. #### 添加脚本文件 diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md index 5bbe0e3424..e48cf6b710 100644 --- a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md +++ b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md @@ -56,26 +56,12 @@ namespace Acme.BookStore public async Task SeedAsync(DataSeedContext context) { await _bookRepository.InsertAsync( - new Book - { - Id = _guidGenerator.Create(), - Name = "Test book 1", - Type = BookType.Fantastic, - PublishDate = new DateTime(2015, 05, 24), - Price = 21 - } - ); + new Book(_guidGenerator.Create(), "Test book 1", + BookType.Fantastic, new DateTime(2015, 05, 24), 21)); await _bookRepository.InsertAsync( - new Book - { - Id = _guidGenerator.Create(), - Name = "Test book 2", - Type = BookType.Science, - PublishDate = new DateTime(2014, 02, 11), - Price = 15 - } - ); + new Book(_guidGenerator.Create(), "Test book 2", + BookType.Science, new DateTime(2014, 02, 11), 15)); } } } diff --git a/docs/zh-Hans/images/auditlog-object-diagram.png b/docs/zh-Hans/images/auditlog-object-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..a7e86a2bb214f27b93032292f78d8d24eedf2fca GIT binary patch literal 29134 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 Date: Fri, 14 Feb 2020 11:03:37 +0300 Subject: [PATCH 48/68] Update AspNet-Boilerplate-Migration-Guide.md --- docs/en/AspNet-Boilerplate-Migration-Guide.md | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md index 0a2b3b17b3..b47bb521e0 100644 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ b/docs/en/AspNet-Boilerplate-Migration-Guide.md @@ -488,6 +488,85 @@ public class TaskAppService : ITaskAppService You inject the `ILogger` instead of the `ILogger`. +### Object to Object Mapping + +#### IObjectMapper Service + +ASP.NET Boilerplate defines an `IObjectMapper` service ([see](https://aspnetboilerplate.com/Pages/Documents/Object-To-Object-Mapping)) and has an integration to the [AutoMapper](https://automapper.org/) library. + +Example usage: Create a `User` object with the given `CreateUserInput` object: + +````csharp +public void CreateUser(CreateUserInput input) +{ + var user = ObjectMapper.Map(input); + ... +} +```` + +Example: Update an existing `User` properties with the given `UpdateUserInput` object: + +````csharp +public async Task UpdateUserAsync(Guid id, UpdateUserInput input) +{ + var user = await _userRepository.GetAsync(id); + ObjectMapper.Map(input, user); +} +```` + +ABP Framework has the same `IObjectMapper` service ([see](Object-To-Object-Mapping.md)) and the AutoMapper integration with a slightly different mapping methods. + +Example usage: Create a `User` object with the given `CreateUserInput` object: + +````csharp +public void CreateUser(CreateUserInput input) +{ + var user = ObjectMapper.Map(input); +} +```` + +This time you need to explicitly declare the source type and target type (while ASP.NET Boilerplate was requiring only the target type). + +Example: Update an existing `User` properties with the given `UpdateUserInput` object: + +````csharp +public async Task UpdateUserAsync(Guid id, UpdateUserInput input) +{ + var user = await _userRepository.GetAsync(id); + ObjectMapper.Map(input, user); +} +```` + +Again, ABP Framework expects to explicitly set the source and target types. + +#### AutoMapper Integration + +##### Auto Mapping Attributes + +ASP.NET Boilerplate has `AutoMapTo`, `AutoMapFrom` and `AutoMap` attributes to automatically create mappings for the declared types. Example: + +````csharp +[AutoMapTo(typeof(User))] +public class CreateUserInput +{ + public string Name { get; set; } + public string Surname { get; set; } + ... +} +```` + +ABP Framework has no such attributes, because AutoMapper as a [similar attribute](https://automapper.readthedocs.io/en/latest/Attribute-mapping.html) now. You need to switch to AutoMapper's attribute. + +##### Mapping Definitions + +ABP Framework follows AutoMapper principles closely. You can define classes derived from the `Profile` class to define your mappings. + +##### Configuration Validation + +Configuration validation is a best practice for the AutoMapper to maintain your mapping configuration in a safe way. + +See [the documentation](Object-To-Object-Mapping.md) for more information related to the object mapping. + ### Setting Management #### Defining the Settings From ed7098faec496bcbb3bffbcf0821420f7b971442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 14 Feb 2020 11:32:25 +0300 Subject: [PATCH 49/68] Add Event Bus --- docs/en/AspNet-Boilerplate-Migration-Guide.md | 15 +++++++++++++++ .../Volo/Abp/EventBus/Local/ILocalEventHandler.cs | 1 + 2 files changed, 16 insertions(+) diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md index b47bb521e0..8aec88336a 100644 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ b/docs/en/AspNet-Boilerplate-Migration-Guide.md @@ -593,6 +593,21 @@ ASP.NET Boilerplate has a static `Clock` service ([see](https://aspnetboilerplat ABP Framework has the `IClock` service ([see](Clock.md)) which has a similar goal, but now you need to inject it whenever you need it. +### Event Bus + +ASP.NET Boilerplate has an in-process event bus system. You typically inject the `IEventBus` (or use the static instance `EventBus.Default`) to trigger an event. It automatically triggers events for entity changes (like `EntityCreatingEventData` and `EntityUpdatedEventData`). You create a class by implementing the `IEventHandler` interface. + +ABP Framework separates the event bus into two services: `ILocalEventBus` and `IDistributedEventBus`. + +The local event bus is similar to the event bus of the ASP.NET Boilerplate while the distributed event bus is new feature introduced in the ABP Framework. + +So, to migrate your code; + +* Use the `ILocalEventBus` instead of the `IEventBus`. +* Implement the `ILocalEventHandler` instead of the `IEventHandler`. + +> Note that ABP Framework has also an `IEventBus` interface, but it does exists to be a common interface for the local and distributed event bus. It is not injected and directly used. + ## Missing Features The following features are not present for the ABP Framework. Here, a list of major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository): diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/ILocalEventHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/ILocalEventHandler.cs index e3501294be..006097825c 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/ILocalEventHandler.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/ILocalEventHandler.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; namespace Volo.Abp.EventBus { + //TODO: Move to the right namespace in v3.0 public interface ILocalEventHandler : IEventHandler { ///

From 3cfe67c5e47b982414effe95a427176ac6e83429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 14 Feb 2020 13:10:52 +0300 Subject: [PATCH 50/68] Added sections to the migration guide. --- docs/en/AspNet-Boilerplate-Migration-Guide.md | 40 +++++++++++++++++++ docs/en/Features.md | 3 ++ docs/en/Modules/Feature-Management.md | 3 ++ docs/en/Modules/Index.md | 1 + 4 files changed, 47 insertions(+) create mode 100644 docs/en/Features.md create mode 100644 docs/en/Modules/Feature-Management.md diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md index 8aec88336a..190e11769f 100644 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ b/docs/en/AspNet-Boilerplate-Migration-Guide.md @@ -38,6 +38,10 @@ We also suggest you to compare the features of two products based on your needs. If you have an ASP.NET Zero based solution and want to migrate to the ABP Commercial, this guide will also help you. +### ASP.NET MVC 5.x Projects + +The ABP Framework doesn't support ASP.NET MVC 5.x, it only works with ASP.NET Core. So, if you migrate your ASP.NET MVC 5.x based projects, you will also deal with the .NET Core migration. + ## The Migration Progress We've designed the ABP Framework by **getting the best parts** of the ASP.NET Boilerplate framework, so it will be familiar to you if you've developed ASP.NET Boilerplate based applications. @@ -608,11 +612,47 @@ So, to migrate your code; > Note that ABP Framework has also an `IEventBus` interface, but it does exists to be a common interface for the local and distributed event bus. It is not injected and directly used. +### Feature Management + +Feature system is used in multi-tenant applications to define features of your application check if given feature is available for the current tenant. + +#### Defining Features + +In the ASP.NET Boilerplate ([see](https://aspnetboilerplate.com/Pages/Documents/Feature-Management)), you create a class inheriting from the `FeatureProvider`, override the `SetFeatures` method and add your class to the `Configuration.Features.Providers` list. + +In the ABP Framework ([see](Features.md)), you derive your class from the `FeatureDefinitionProvider` and override the `Define` method. No need to add your class to the configuration, it is automatically discovered by the framework. + +#### Checking Features + +You can continue to use the `RequiresFeature` attribute and `IFeatureChecker` service to check if a feature is enabled for the current tenant. + +#### Changing the Feature Values + +In the ABP Framework you use the `IFeatureManager` to change a feature value for a tenant. + +### Audit Logging + +TODO + +### Localization + +TODO + +### Navigation + +TODO + +### Background Jobs & Workers + +TODO + ## Missing Features The following features are not present for the ABP Framework. Here, a list of major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository): * [Multi-Lingual Entities](https://aspnetboilerplate.com/Pages/Documents/Multi-Lingual-Entities) ([#1754](https://github.com/abpframework/abp/issues/1754)) +* [Real time notification system](https://aspnetboilerplate.com/Pages/Documents/Notification-System) ([#633](https://github.com/abpframework/abp/issues/633)) +* [NHibernate Integration](https://aspnetboilerplate.com/Pages/Documents/NHibernate-Integration) ([#339](https://github.com/abpframework/abp/issues/339)) - We don't intent to work on this, but any community contribution welcome. * ...TODO Most of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework by implementing these yourself. \ No newline at end of file diff --git a/docs/en/Features.md b/docs/en/Features.md new file mode 100644 index 0000000000..4b31ca16e9 --- /dev/null +++ b/docs/en/Features.md @@ -0,0 +1,3 @@ +# Features + +TODO \ No newline at end of file diff --git a/docs/en/Modules/Feature-Management.md b/docs/en/Modules/Feature-Management.md new file mode 100644 index 0000000000..5c41277dd8 --- /dev/null +++ b/docs/en/Modules/Feature-Management.md @@ -0,0 +1,3 @@ +# Feature Management Module + +TODO \ No newline at end of file diff --git a/docs/en/Modules/Index.md b/docs/en/Modules/Index.md index 812f22e8dd..2ffb509667 100644 --- a/docs/en/Modules/Index.md +++ b/docs/en/Modules/Index.md @@ -16,6 +16,7 @@ There are some **free and open source** application modules developed and mainta * **Background Jobs**: Persist background jobs when using the default background job manager. * **Blogging**: Used to create fancy blogs. ABP's [own blog](https://blog.abp.io/) already using this module. * [**Docs**](Docs.md): Used to create technical documentation pages. ABP's [own documentation](https://docs.abp.io) already using this module. +* **Feature Management**: Used to persist and manage the [features](../Features.md). * **Identity**: Manages roles, users and their permissions, based on the Microsoft Identity library. * **IdentityServer**: Integrates to IdentityServer4. * **Permission Management**: Used to persist permissions. From e32e43eaed0ca9c6f2b9013ecb463b0689b780e8 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Fri, 14 Feb 2020 14:38:46 +0300 Subject: [PATCH 51/68] closes #2831. Use ` abp login MyUsername -p MyPassword` --- .../Volo/Abp/Cli/Commands/LoginCommand.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs index fe9ed2d98f..32bc6671bf 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs @@ -33,15 +33,19 @@ namespace Volo.Abp.Cli.Commands ); } - Console.Write("Password: "); - var password = ConsoleHelper.ReadSecret(); - if (password.IsNullOrWhiteSpace()) + var password = commandLineArgs.Options.GetOrNull(Options.Password.Short, Options.Password.Long); + if (password == null) { - throw new CliUsageException( - "Password is missing!" + - Environment.NewLine + Environment.NewLine + - GetUsageInfo() - ); + Console.Write("Password: "); + password = ConsoleHelper.ReadSecret(); + if (password.IsNullOrWhiteSpace()) + { + throw new CliUsageException( + "Password is missing!" + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } } await AuthService.LoginAsync( @@ -60,10 +64,12 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(""); sb.AppendLine("Usage:"); sb.AppendLine(" abp login "); + sb.AppendLine(" abp login -p "); sb.AppendLine(""); sb.AppendLine("Example:"); sb.AppendLine(""); sb.AppendLine(" abp login john"); + sb.AppendLine(" abp login john -p 1234"); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); @@ -82,6 +88,12 @@ namespace Volo.Abp.Cli.Commands public const string Short = "o"; public const string Long = "organization"; } + + public static class Password + { + public const string Short = "p"; + public const string Long = "password"; + } } } } \ No newline at end of file From 43a11ed8532f3640735313f486db7fa09ae193ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 14 Feb 2020 17:03:31 +0300 Subject: [PATCH 52/68] Resolved #2120: ASP.NET Boilerplate 5.x to ABP 1.x migration guide --- docs/en/AspNet-Boilerplate-Migration-Guide.md | 94 +++++++++++++++++-- docs/en/docs-nav.json | 4 + 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/docs/en/AspNet-Boilerplate-Migration-Guide.md b/docs/en/AspNet-Boilerplate-Migration-Guide.md index 190e11769f..d47b266839 100644 --- a/docs/en/AspNet-Boilerplate-Migration-Guide.md +++ b/docs/en/AspNet-Boilerplate-Migration-Guide.md @@ -632,27 +632,103 @@ In the ABP Framework you use the `IFeatureManager` to change a feature value for ### Audit Logging -TODO +The ASP.NET Boilerplate ([see](https://aspnetboilerplate.com/Pages/Documents/Audit-Logging)) and the ABP Framework ([see](Audit-Logging.md)) has similar audit logging systems. ABP Framework requires to add `UseAuditing()` middleware to the ASP.NET Core pipeline, which is already added in the startup templates. So, most of the times it will be work out of the box. ### Localization -TODO +ASP.NET Boilerplate supports XML and JSON files to define the localization key-values for the UI ([see](https://aspnetboilerplate.com/Pages/Documents/Localization)). ABP Framework only supports the JSON formatter localization files ([see](Localization.md)). So, you need to convert your XML file to JSON. -### Navigation +The ASP.NET Boilerplate has its own the `ILocalizationManager` service to be injected and used for the localization in the server side. -TODO +The ABP Framework uses [Microsoft localization extension](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) library, so it is completely integrated to ASP.NET Core. You use the `IStringLocalizer` service to get a localized text. Example: -### Background Jobs & Workers +````csharp +public class MyService +{ + private readonly IStringLocalizer _localizer; + + public MyService(IStringLocalizer localizer) + { + _localizer = localizer; + } + + public void Foo() + { + var str = _localizer["HelloWorld"]; //Get a localized text + } +} +```` + +So, you need to replace `ILocalizationManager` usage by the `IStringLocalizer`. + +It also provides API used in the client side: + +````js +var testResource = abp.localization.getResource('Test'); +var str = testResource('HelloWorld'); +```` + +It was like `abp.localization.localize(...)` in the ASP.NET Boilerplate. + +### Navigation vs Menu + +In ASP.NET you create a class deriving from the `NavigationProvider` to define your menu elements. Menu items has `requiredPermissionName` attributes to restrict access to a menu element. Menu items were static and your class is executed only one time. + +Int the ABP Framework you need to create a class implements the `IMenuContributor` interface. Your class is executed whenever the menu needs to be rendered. So, you can conditionally add menu items. + +As an example, this is the menu contributor of the tenant management module: + +````csharp +public class AbpTenantManagementWebMainMenuContributor : IMenuContributor +{ + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + //Add items only to the main menu + if (context.Menu.Name != StandardMenus.Main) + { + return; + } + + //Get the standard administration menu item + var administrationMenu = context.Menu.GetAdministration(); + + //Resolve some needed services from the DI container + var authorizationService = context.ServiceProvider + .GetRequiredService(); + var l = context.ServiceProvider + .GetRequiredService>(); + + var tenantManagementMenuItem = new ApplicationMenuItem( + TenantManagementMenuNames.GroupName, + l["Menu:TenantManagement"], + icon: "fa fa-users"); + + administrationMenu.AddItem(tenantManagementMenuItem); + + //Conditionally add the "Tenants" menu item based on the permission + if (await authorizationService + .IsGrantedAsync(TenantManagementPermissions.Tenants.Default)) + { + tenantManagementMenuItem.AddItem( + new ApplicationMenuItem( + TenantManagementMenuNames.Tenants, + l["Tenants"], + url: "/TenantManagement/Tenants")); + } + } +} +```` + +So, you need to check permission using the `IAuthorizationService` if you want to show a menu item only when the user has the related permission. -TODO +> Navigation/Menu system is only for ASP.NET Core MVC / Razor Pages applications. Angular applications has a different system implemented in the startup templates. ## Missing Features -The following features are not present for the ABP Framework. Here, a list of major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository): +The following features are not present for the ABP Framework. Here, a list of some major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository): * [Multi-Lingual Entities](https://aspnetboilerplate.com/Pages/Documents/Multi-Lingual-Entities) ([#1754](https://github.com/abpframework/abp/issues/1754)) * [Real time notification system](https://aspnetboilerplate.com/Pages/Documents/Notification-System) ([#633](https://github.com/abpframework/abp/issues/633)) * [NHibernate Integration](https://aspnetboilerplate.com/Pages/Documents/NHibernate-Integration) ([#339](https://github.com/abpframework/abp/issues/339)) - We don't intent to work on this, but any community contribution welcome. -* ...TODO -Most of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework by implementing these yourself. \ No newline at end of file +Some of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework, it is appreciated. \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index eb4ef13b8d..cbb4c80e72 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -49,6 +49,10 @@ } ] }, + { + "text": "ASP.NET Boilerplate Migration Guide", + "path": "AspNet-Boilerplate-Migration-Guide.md" + }, { "text": "CLI", "path": "CLI.md" From fc8d34eae94bb628ea170321fe8236fa4eb73b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 14 Feb 2020 19:33:29 +0300 Subject: [PATCH 53/68] Refactor and improvements for #2807 --- .../Volo/Abp/Domain/Entities/EntityHelper.cs | 21 ------- .../Abp/Domain/Repositories/RepositoryBase.cs | 1 + .../Repositories/RepositoryExtensions.cs | 61 +++++++++++++------ .../UnitOfWorkExtensionDataTypes.cs | 11 ---- .../Repositories/UnitOfWorkItemNames.cs | 7 +++ .../EntityFrameworkCore/EfCoreRepository.cs | 4 +- .../Abp/EntityFrameworkCore/AbpDbContext.cs | 40 ++++++------ .../Repositories/MongoDB/MongoDbRepository.cs | 23 ++----- .../Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWork.cs | 1 + .../Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs | 8 +-- .../Abp/TestApp/Testing/HardDelete_Tests.cs | 55 ++++++++--------- 11 files changed, 105 insertions(+), 127 deletions(-) delete mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkExtensionDataTypes.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkItemNames.cs diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs index cce1e9c18f..a33c5f56ec 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs @@ -112,26 +112,5 @@ namespace Volo.Abp.Domain.Entities idProperty.SetValue(entity, idFactory()); } - - public static object GetEntityId(object entity) - { - if (!IsEntity(entity.GetType())) - { - throw new AbpException(entity.GetType() + " is not an Entity !"); - } - - return ReflectionHelper.GetValueByPath(entity, entity.GetType(), "Id"); - } - public static string GetHardDeleteKey(object entity, string tenantId) - { - //if (entity is IMultiTenant) // IsMultiTenantEntity - if (typeof(IMultiTenant).IsAssignableFrom(entity.GetType())) - { - var tenantIdString = !string.IsNullOrEmpty(tenantId) ? tenantId : "null"; - return entity.GetType().FullName + ";TenantId=" + tenantIdString + ";Id=" + GetEntityId(entity); - } - - return entity.GetType().FullName + ";Id=" + GetEntityId(entity); - } } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs index b908827a98..3ade403017 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs @@ -18,6 +18,7 @@ namespace Volo.Abp.Domain.Repositories public IDataFilter DataFilter { get; set; } public ICurrentTenant CurrentTenant { get; set; } + public IUnitOfWorkManager UnitOfWorkManager { get; set; } public virtual Type ElementType => GetQueryable().ElementType; diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs index 9a2bc147af..ef44d6830d 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; @@ -44,31 +43,55 @@ namespace Volo.Abp.Domain.Repositories } } - public static async Task HardDeleteAsync(this IRepository repository, TEntity entity) - where TEntity : class, IEntity, ISoftDelete + public static async Task HardDeleteAsync( + this IBasicRepository repository, + TEntity entity, + bool autoSave = false, + CancellationToken cancellationToken = default + ) + where TEntity : class, IEntity, ISoftDelete { - var repo = ProxyHelper.UnProxy(repository) as IRepository; - if (repo != null) + if (!(ProxyHelper.UnProxy(repository) is IUnitOfWorkManagerAccessor unitOfWorkManagerAccessor)) { - var uow = ((IUnitOfWorkManagerAccessor)repo).UnitOfWorkManager; - var baseRepository = ((RepositoryBase)repo); - - var items = ((IUnitOfWorkManagerAccessor)repo).UnitOfWorkManager.Current.Items; - var hardDeleteEntities = items.GetOrAdd(UnitOfWorkExtensionDataTypes.HardDelete, () => new HashSet()) as HashSet; + throw new AbpException($"The given repository (of type {repository.GetType().AssemblyQualifiedName}) should implement the {typeof(IUnitOfWorkManagerAccessor).AssemblyQualifiedName} interface in order to invoke the {nameof(HardDeleteAsync)} method!"); + } - var hardDeleteKey = EntityHelper.GetHardDeleteKey(entity, baseRepository.CurrentTenant?.Id?.ToString()); - hardDeleteEntities.Add(hardDeleteKey); + var uowManager = unitOfWorkManagerAccessor.UnitOfWorkManager; + if (uowManager == null) + { + throw new AbpException($"{nameof(unitOfWorkManagerAccessor.UnitOfWorkManager)} property of the given {nameof(repository)} object is null!"); + } - await repo.DeleteAsync(entity); + if (uowManager.Current == null) + { + using (var uow = uowManager.Begin()) + { + await HardDeleteWithUnitOfWorkAsync(repository, entity, autoSave, cancellationToken, uowManager.Current); + await uow.CompleteAsync(cancellationToken); + } } - } - public static async Task HardDeleteAsync(this IRepository repository, Expression> predicate) - where TEntity : class, IEntity, ISoftDelete - { - foreach (var entity in repository.Where(predicate).ToList()) + else { - await repository.HardDeleteAsync(entity); + await HardDeleteWithUnitOfWorkAsync(repository, entity, autoSave, cancellationToken, uowManager.Current); } } + + private static async Task HardDeleteWithUnitOfWorkAsync( + IBasicRepository repository, + TEntity entity, + bool autoSave, + CancellationToken cancellationToken, IUnitOfWork currentUow + ) + where TEntity : class, IEntity, ISoftDelete + { + var hardDeleteEntities = (HashSet) currentUow.Items.GetOrAdd( + UnitOfWorkItemNames.HardDeletedEntities, + () => new HashSet() + ); + + hardDeleteEntities.Add(entity); + + await repository.DeleteAsync(entity, autoSave, cancellationToken); + } } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkExtensionDataTypes.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkExtensionDataTypes.cs deleted file mode 100644 index a95aca42c0..0000000000 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkExtensionDataTypes.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Volo.Abp.Domain.Repositories -{ - public class UnitOfWorkExtensionDataTypes - { - public static string HardDelete { get; } = "HardDelete"; - } -} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkItemNames.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkItemNames.cs new file mode 100644 index 0000000000..4ec067ceec --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/UnitOfWorkItemNames.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Domain.Repositories +{ + public static class UnitOfWorkItemNames + { + public const string HardDeletedEntities = "AbpHardDeletedEntities"; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index 6c3eb689d8..106e00f77a 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -80,7 +80,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore { return includeDetails ? await WithDetails().ToListAsync(GetCancellationToken(cancellationToken)) - : await DbSet.ToListAsync(GetCancellationToken(cancellationToken)); + : await DbSet.ToListAsync(GetCancellationToken(cancellationToken)); } public override async Task GetCountAsync(CancellationToken cancellationToken = default) @@ -208,7 +208,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore { return includeDetails ? await WithDetails().FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken)) - : await DbSet.FindAsync(new object[] { id }, GetCancellationToken(cancellationToken)); + : await DbSet.FindAsync(new object[] {id}, GetCancellationToken(cancellationToken)); } public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 4e2f4c0d51..ba9cee70ee 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -50,6 +50,7 @@ namespace Volo.Abp.EntityFrameworkCore public IEntityHistoryHelper EntityHistoryHelper { get; set; } public IAuditingManager AuditingManager { get; set; } + public IUnitOfWorkManager UnitOfWorkManager { get; set; } public IClock Clock { get; set; } @@ -199,37 +200,24 @@ namespace Volo.Abp.EntityFrameworkCore protected virtual void ApplyAbpConceptsForDeletedEntity(EntityEntry entry, EntityChangeReport changeReport) { - if (IsHardDeleteEntity(entry)) + if (TryCancelDeletionForSoftDelete(entry)) { - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); - return; + UpdateConcurrencyStamp(entry); + SetDeletionAuditProperties(entry); } - CancelDeletionForSoftDelete(entry); - UpdateConcurrencyStamp(entry); - SetDeletionAuditProperties(entry); + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); } - protected virtual bool IsHardDeleteEntity(EntityEntry entry) + protected virtual bool IsHardDeleted(EntityEntry entry) { - if (UnitOfWorkManager?.Current?.Items == null) - { - return false; - } - - if (!UnitOfWorkManager.Current.Items.ContainsKey(UnitOfWorkExtensionDataTypes.HardDelete)) + var hardDeletedEntities = UnitOfWorkManager?.Current?.Items.GetOrDefault(UnitOfWorkItemNames.HardDeletedEntities) as HashSet; + if (hardDeletedEntities == null) { return false; } - var hardDeleteItems = UnitOfWorkManager.Current.Items[UnitOfWorkExtensionDataTypes.HardDelete]; - if (!(hardDeleteItems is HashSet objects)) - { - return false; - } - string hardDeleteKey = EntityHelper.GetHardDeleteKey(entry.Entity, CurrentTenantId?.ToString()); - - return objects.Contains(hardDeleteKey); + return hardDeletedEntities.Contains(entry.Entity); } protected virtual void AddDomainEvents(EntityChangeReport changeReport, object entityAsObj) @@ -283,16 +271,22 @@ namespace Volo.Abp.EntityFrameworkCore entity.ConcurrencyStamp = Guid.NewGuid().ToString("N"); } - protected virtual void CancelDeletionForSoftDelete(EntityEntry entry) + protected virtual bool TryCancelDeletionForSoftDelete(EntityEntry entry) { if (!(entry.Entity is ISoftDelete)) { - return; + return false; + } + + if (IsHardDeleted(entry)) + { + return false; } entry.Reload(); entry.State = EntityState.Modified; entry.Entity.As().IsDeleted = true; + return true; } protected virtual void CheckAndSetId(EntityEntry entry) diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index b827292702..3333f1d505 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -111,7 +111,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB await ApplyAbpConceptsForDeletedEntityAsync(entity); var oldConcurrencyStamp = SetNewConcurrencyStamp(entity); - if (entity is ISoftDelete softDeleteEntity && !IsHardDeleteEntity(entity)) + if (entity is ISoftDelete softDeleteEntity && !IsHardDeleted(entity)) { softDeleteEntity.IsDeleted = true; var result = await Collection.ReplaceOneAsync( @@ -175,32 +175,21 @@ namespace Volo.Abp.Domain.Repositories.MongoDB Collection.AsQueryable() ); } - protected virtual bool IsHardDeleteEntity(TEntity entry) + protected virtual bool IsHardDeleted(TEntity entity) { - if (UnitOfWorkManager?.Current?.Items == null) + var hardDeletedEntities = UnitOfWorkManager?.Current?.Items.GetOrDefault(UnitOfWorkItemNames.HardDeletedEntities) as HashSet; + if (hardDeletedEntities == null) { return false; } - if (!UnitOfWorkManager.Current.Items.ContainsKey(UnitOfWorkExtensionDataTypes.HardDelete)) - { - return false; - } - - var hardDeleteItems = UnitOfWorkManager.Current.Items[UnitOfWorkExtensionDataTypes.HardDelete]; - if (!(hardDeleteItems is HashSet objects)) - { - return false; - } - string hardDeleteKey = EntityHelper.GetHardDeleteKey(entry, CurrentTenant?.Id.ToString()); - - return objects.Contains(hardDeleteKey); + return hardDeletedEntities.Contains(entity); } protected virtual FilterDefinition CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null) { throw new NotImplementedException( - $"{nameof(CreateEntityFilter)} is not implemented for MongoDB by default. It should be overrided and implemented by the deriving class!" + $"{nameof(CreateEntityFilter)} is not implemented for MongoDB by default. It should be overriden and implemented by the deriving class!" ); } diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWork.cs index 97fd81372b..ee5baa58bc 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/IUnitOfWork.cs @@ -9,6 +9,7 @@ namespace Volo.Abp.Uow public interface IUnitOfWork : IDatabaseApiContainer, ITransactionApiContainer, IDisposable { Guid Id { get; } + Dictionary Items { get; } //TODO: Switch to OnFailed (sync) and OnDisposed (sync) methods to be compatible with OnCompleted diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs index c494bfdfe6..05f49df180 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -31,6 +32,7 @@ namespace Volo.Abp.Uow public IServiceProvider ServiceProvider { get; } + [NotNull] public Dictionary Items { get; } private readonly Dictionary _databaseApis; @@ -48,6 +50,7 @@ namespace Volo.Abp.Uow _databaseApis = new Dictionary(); _transactionApis = new Dictionary(); + Items = new Dictionary(); } @@ -320,10 +323,5 @@ namespace Volo.Abp.Uow { return $"[UnitOfWork {Id}]"; } - - public Dictionary GetHardDeleteItems() - { - return Items; - } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HardDelete_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HardDelete_Tests.cs index 9d0430e968..1eac79fd7c 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HardDelete_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HardDelete_Tests.cs @@ -1,7 +1,5 @@ using Shouldly; using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using Volo.Abp.Data; using Volo.Abp.Domain.Repositories; @@ -15,58 +13,57 @@ namespace Volo.Abp.TestApp.Testing public abstract class HardDelete_Tests : TestAppTestBase where TStartupModule : IAbpModule { - protected readonly IRepository _personRepository; + protected readonly IRepository PersonRepository; protected readonly IDataFilter DataFilter; - protected readonly IUnitOfWorkManager _unitOfWorkManager; - public HardDelete_Tests() + protected readonly IUnitOfWorkManager UnitOfWorkManager; + + protected HardDelete_Tests() { - _personRepository = GetRequiredService>(); + PersonRepository = GetRequiredService>(); DataFilter = GetRequiredService(); - _unitOfWorkManager = GetRequiredService(); + UnitOfWorkManager = GetRequiredService(); } + [Fact] - public async Task Should_HardDelete_Entity_With_Collection() + public async Task Should_HardDelete_Entities() { - using (var uow = _unitOfWorkManager.Begin()) - { - using (DataFilter.Disable()) - { - var douglas = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId); - await _personRepository.HardDeleteAsync(x => x.Id == TestDataBuilder.UserDouglasId); - await uow.CompleteAsync(); - } + var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId); + await PersonRepository.HardDeleteAsync(douglas); - var deletedDougles = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId); - deletedDougles.ShouldBeNull(); - } + douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); + douglas.ShouldBeNull(); } + [Fact] public async Task Should_HardDelete_Soft_Deleted_Entities() { - var douglas = await _personRepository.GetAsync(TestDataBuilder.UserDouglasId); - await _personRepository.DeleteAsync(douglas); + var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId); + await PersonRepository.DeleteAsync(douglas); - douglas = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId); + douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); douglas.ShouldBeNull(); using (DataFilter.Disable()) { - douglas = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId); + douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); douglas.ShouldNotBeNull(); douglas.IsDeleted.ShouldBeTrue(); douglas.DeletionTime.ShouldNotBeNull(); } - using (var uow = _unitOfWorkManager.Begin()) + + using (var uow = UnitOfWorkManager.Begin()) { using (DataFilter.Disable()) { - douglas = await _personRepository.GetAsync(TestDataBuilder.UserDouglasId); - await _personRepository.HardDeleteAsync(douglas); - await uow.CompleteAsync(); - var deletedDougles = await _personRepository.FindAsync(TestDataBuilder.UserDouglasId); - deletedDougles.ShouldBeNull(); + douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId); } + + await PersonRepository.HardDeleteAsync(douglas); + await uow.CompleteAsync(); } + + douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); + douglas.ShouldBeNull(); } } } From 0325126eb33cac41983a6b692cc69d9fa8ab3c5e Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 15 Feb 2020 17:17:18 +0800 Subject: [PATCH 54/68] Only the latest version (dev) of the document needs to update the cache. --- .../Volo/Docs/Documents/DocumentAppService.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index 1dc43a4e0b..f4ae3a9158 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -178,9 +178,16 @@ namespace Volo.Docs.Documents } var document = await _documentRepository.FindAsync(project.Id, documentName, languageCode, version); + if (document == null) + { + return await GetDocumentAsync(); + } - //TODO: Configurable cache time? - if (document == null || document.LastCachedTime + TimeSpan.FromDays(30) < DateTime.Now) + //Only the latest version (dev) of the document needs to update the cache. + if (!project.LatestVersionBranchName.IsNullOrWhiteSpace() && + document.Version == project.LatestVersionBranchName && + //TODO: Configurable cache time? + document.LastCachedTime + TimeSpan.FromHours(2) < DateTime.Now) { return await GetDocumentAsync(); } From 2f6a29c481f198bb4b837eb512ff0c5cdef4e744 Mon Sep 17 00:00:00 2001 From: liangshiw Date: Sat, 15 Feb 2020 17:45:56 +0800 Subject: [PATCH 55/68] Translate audit-logging document --- docs/zh-Hans/Audit-Logging.md | 374 ++++++++++++++++++++++++++++++++++ docs/zh-Hans/docs-nav.json | 9 +- 2 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 docs/zh-Hans/Audit-Logging.md diff --git a/docs/zh-Hans/Audit-Logging.md b/docs/zh-Hans/Audit-Logging.md new file mode 100644 index 0000000000..36f80525d1 --- /dev/null +++ b/docs/zh-Hans/Audit-Logging.md @@ -0,0 +1,374 @@ +# 审计日志 + +[维基百科](https://en.wikipedia.org/wiki/Audit_trail): "*审计跟踪(也称为**审计日志**)是一种安全相关的按时间顺序记录,记录集或记录目的和来源. 这种记录提供了在任何特定时间的操作,过程或事件产生影响活动顺序的文件证据* ". + +ABP框架提供一个可扩展的**审计日志系统**,自动化的根据**约定**记录审计日志,并提供**配置**控制审计日志的级别. + +一个**审计日志对象**(参见下面的审计日志对象部分)通常是针对每个web请求创建和保存的.包括; + +* **请求和响应的细节** (如URL,HTTP方法,浏览器信息,HTTP状态代码...等). +* **执行的动作** (控制器操作和应用服务方法调用及其参数). +* **实体的变化** (在Web请求中). +* **异常信息** (如果在执行请求发生操作). +* **请求时长** (测量应用程序的性能). + +> [启动模板](Startup-Templates/Index.md)已经将审计日志系统配置为适用于大多数应用程序. 本文档介绍了对审计日志系统更精细的控制. + +## 数据库提供程序支持 + +* [Entity Framework Core](Entity-Framework-Core.md)提供程序完全支持. +* [MongoDB](MongoDB.md)提供程序不支持实体更改审计记录. 其他功能按预期工作. + +## UseAuditing() + +`UseAuditing()` 中间件应该被添加到ASP.NET Core请求管道,用于创建和保存审计日志. 如果你使用[启动模板](Startup-Templates/Index.md)创建的应用程序,它已经默认添加. + +## AbpAuditingOptions + +`AbpAuditingOptions` 是配置审计日志系统的主要[options对象](Options.md). 你可以在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法中进行配置: + +````csharp +Configure(options => +{ + options.IsEnabled = false; //Disables the auditing system +}); +```` + +这里是你可以配置的选项列表: + +* `IsEnabled` (默认值: `true`): 启用或禁用审计系统的总开关. 如果值为 `false`,则不使用其他选项. +* `HideErrors` (默认值: `true`): 在保存审计日志对象时如果发生任何错误,审计日志系统会将错误隐藏并写入常规[日志](Logging.md). 如果保存审计日志对系统非常重要那么将其设置为 `false` 以便在隐藏错误时抛出异常. +* `IsEnabledForAnonymousUsers` (默认值: `true`): 如果只想为经过身份验证的用户记录审计日志,请设置为 `false`.如果为匿名用户保存审计日志,你将看到这些用户的 `UserId` 值为 `null`. +* `IsEnabledForGetRequests` (默认值: `false`): HTTP GET请求通常不应该在数据库进行任何更改,审计日志系统不会为GET请求保存审计日志对象. 将此值设置为 `true` 可为GET请求启用审计日志系统. +* `ApplicationName`: 如果有多个应用程序保存审计日志到单一的数据库,使用此属性设置为你的应用程序名称区分不同的应用程序日志. +* `IgnoredTypes`: 审计日志系统忽略的 `Type` 列表. 如果它是实体类型,则不会保存此类型实体的更改. 在序列化操作参数时也使用此列表. +* `EntityHistorySelectors`:选择器列表,用于确定是否选择了用于保存实体更改的实体类型. 有关详细信息请参阅下面的部分. +* `Contributors`: `AuditLogContributor` 实现的列表. 贡献者是扩展审计日志系统的一种方式. 有关详细信息请参阅下面的"审计日志贡献者"部分. + +### 实体历史选择器 + +保存您的所有实体的所有变化将需要大量的数据库空间. 出于这个原因**审计日志系统不保存为实体的任何改变,除非你明确地对其进行配置**. + +要保存的所有实体的所有更改,只需使用 `AddAllEntities()` 扩展方法. + +````csharp +Configure(options => +{ + options.EntityHistorySelectors.AddAllEntities(); +}); +```` + +`options.EntityHistorySelectors` 实际上是一个类型谓词的列表,你可以写一个lambda表达式定义过滤器. + +下面的示例中与使用 `AddAllEntities()` 扩展方法效果相同: + +````csharp +Configure(options => +{ + options.EntityHistorySelectors.Add( + new NamedTypeSelector( + "MySelectorName", + type => + { + if (typeof(IEntity).IsAssignableFrom(type)) + { + return true; + } + else + { + return false; + } + } + ) + ); +}); +```` + +条件 `typeof(IEntity).IsAssignableFrom(type)` 对于任何实现 `IEntity` 接口的类(从技术上来这些都是你应用程序中的实体) 结果都为 `true` . 你可以根据自己的逻辑编写条件并返回 `true` 或 `false`. + +`options.EntityHistorySelectors` 是一种灵活动态的选择实体进行审计日志记录的方法. 另一种方法是为每个实体使用 `Audited` 和 `DisableAuditing` attribute. + +## 启用/禁用审计日志服务 + +### 启用/禁用 Controllers & Actions + +默认所有的控制器动作都会被记录下来(有关GET请求,请参阅上面的 `IsEnabledForGetRequests` ). + +你可以使用 `[DisableAuditing]` 来禁用特定的控制器: + +````csharp +[DisableAuditing] +public class HomeController : AbpController +{ + //... +} +```` + +使用 `[DisableAuditing]` 在action级别控制: + +````csharp +public class HomeController : AbpController +{ + [DisableAuditing] + public async Task Home() + { + //... + } + + public async Task OtherActionLogged() + { + //... + } +} +```` + +### 启用/禁用 应用服务&方法 + +[应用服务](Application-Services.md)也默认包含在审计日志中. 你可在服务或方法级别使用 `[DisableAuditing]`. + +#### 启用/禁用 其他服务 + +可以为任何类型的类(注册到[依赖注入](Dependency-Injection.md)并从依赖注入解析)启用审计日志,默认情况下仅对控制器和应用程序服务启用. + +对于任何需要被审计记录的类或方法都可以使用 `[Audited]` 和`IAuditingEnabled`.此外,您的类可以(直接或固有的)实现 `IAuditingEnabled` 接口以认启用该类的审计日志记录. + +### 启用/禁用 实体 & 属性 + +以下情况下实体在实体更改审计日志记录中忽略实体; + +* 如果将实体类型添加到 `AbpAuditingOptions.IgnoredTypes`(如前所述),它在审计日志系统中被完全忽略. +* 如果对象不是[实体](Entities.md)(没有直接或固有的实现 `IEntity` - 所有实体默认实现这个接口). +* 如果实体访问级别不是public的. + +你可以使用 `Audited` 来启用实体更改审计日志: + +````csharp +[Audited] +public class MyEntity : Entity +{ + //... +} +```` + +或者禁用实体: + +````csharp +[DisableAuditing] +public class MyEntity : Entity +{ + //... +} +```` + +只有前面提到的 `AbpAuditingOptions.EntityHistorySelector` 选择实体时才有必要禁用审计日志记录. + +你可以仅禁用实体的某些属性的审计,以审计日志记录进行精细控制: + +````csharp +[Audited] +public class MyUser : Entity +{ + public string Name { get; set; } + + public string Email { get; set; } + + [DisableAuditing] //Ignore the Passoword on audit logging + public string Password { get; set; } +} +```` + +审计日志系统保存 `MyUser` 实体的更改,出于安全的目的忽略 `Password` 属性. + +在某些情况下你可能要保存一些属性,但忽略所有其他属性. 为忽略的属性编写 `[DisableAuditing]` 将很乏味. 这种情况下将 `[Audited]` 用于所需的属性,使用 `[DisableAuditing]` 属性标记该实体: + +````csharp +[DisableAuditing] +public class MyUser : Entity +{ + [Audited] //Only log the Name change + public string Name { get; set; } + + public string Email { get; set; } + + public string Password { get; set; } +} +```` + +## IAuditingStore + +`IAuditingStore` 是一个接口,用于保存ABP框架的审计日志对象(下面说明). 如果需要将审计日志对象保存到自定义数据存储中,可以在自己的应用程序中实现 `IAuditingStore` 并在[依赖注入系统](Dependency-Injection.md)替换. + +如果没有注册审计存储,则使用 `SimpleLogAuditingStore`. 它只是将审计对象写入标准[日志系统](Logging.md). + +[审计日志模块](Modules/Audit-Logging.md)已在[启动模板](Startup-Templates/Index.md)中配置,它将审计日志对象保存到数据库中(支持多个数据库提供程序). 所以大多数时候你并不需要关心 `IAuditingStore` 是如何实现和使用的. + +## 审计日志对象 + +默认为每个**web请求**创建一个**审计日志对象**,审计日志对象可以由以下关系图表示: + +![**auditlog-object-diagram**](images/auditlog-object-diagram.png) + +* **AuditLogInfo**: 具有以下属性: + * `ApplicationName`: 当你保存不同的应用审计日志到同一个数据库,这个属性用来区分应用程序. + * `UserId`:当前用户的Id,用户未登录为 `null`. + * `UserName`:当前用户的用户名,如果用户已经登录(这里的值不依赖于标识模块/系统进行查找). + * `TenantId`: 当前租户的Id,对于多租户应用. + * `TenantName`: 当前租户的名称,对于多租户应用. + * `ExecutionTime`: 审计日志对象创建的时间. + * `ExecutionDuration`: 请求的总执行时间,以毫秒为单位. 可以用来观察应用程序的性能. + * `ClientId`: 当前客户端的Id,如果客户端已经通过认证.客户端通常是使用HTTP API的第三方应用程序. + * `ClientName`: 当前客户端的名称,如果有的话. + * `ClientIpAddress`: 客户端/用户设备的IP地址. + * `CorrelationId`: 当前[相关Id](CorrelationId.md). 相关Id用于在单个逻辑操作中关联由不同应用程序(或微服务)写入的审计日志. + * `BrowserInfo`: 当前用户的浏览器名称/版本信息,如果有的话. + * `HttpMethod`: 当前HTTP请求的方法(GET,POST,PUT,DELETE ...等). + * `HttpStatusCode`: HTTP响应状态码. + * `Url`: 请求的URL. +* **AuditLogActionInfo**: 一个 审计日志动作通常是web请求期间控制器动作或[应用服务](Application-Services.md)方法调用. 一个审计日志可以包含多个动作. 动作对象具有以下属性: + * `ServiceName`:执行的控制器/服务的名称. + * `MethodName`:控制器/服务执行的方法的名称. + * `Parameters`:传递给方法的参数的JSON格文本. + * `ExecutionTime`: 执行的时间. + * `ExecutionDuration`: 方法执行时长,以毫秒为单位. 可以用来观察方法的性能. +* **EntityChangeInfo**: 表示一个实体在Web请求中的变更. 审计日志可以包含0个或多个实体的变更. 实体变更具有以下属性: + * `ChangeTime`: 当实体被改变的时间. + * `ChangeType`:具有以下字段的枚举: `Created`(0), `Updated`(1)和 `Deleted`(2). + * `EntityId`: 更改实体的Id. + * `EntityTenantId`:实体所属的租户Id. + * `EntityTypeFullName`: 实体的类型(类)的完整命名空间名称(例如Book实体的*Acme.BookStore.Book*. +* **EntityPropertyChangeInfo**: 表示一个实体的属性的更改.一个实体的更改信息(上面已说明)可含有具有以下属性的一个或多个属性的更改: + * `NewValue`: 属性的新值. 如果实体已被删除为 `null`. + * `OriginalValue`:变更前旧/初始值. 如果实体是新创建为 `null`. + * `PropertyName`: 实体类的属性名称. + * `PropertyTypeFullName`:属性类型的完整命名空间名称. +* **Exception**: 审计日志对象可能包含零个或多个异常. 可以得到失败请求的异常信息. +* **Comment**:用于将自定义消息添加到审计日志条目的任意字符串值. 审计日志对象可能包含零个或多个注释. + +除了上面说明的标准属性之外,`AuditLogInfo`, `AuditLogActionInfo` 和 `EntityChangeInfo` 对象还实现了`IHasExtraProperties` 接口,你可以向这些对象添加自定义属性. + +## 审计日志贡献者 + +你可以创建类继承 `AuditLogContributor`类 来扩展审计系统,该类定义了 `PreContribute` 和 `PostContribute` 方法. + +唯一预构建的贡献者是 `AspNetCoreAuditLogContributor` 类,它设置HTTP请求的相关属性. + +贡献者可以设置 `AuditLogInfo` 类的属性和集合来添加更多信息. + +例: + +````csharp +public class MyAuditLogContributor : AuditLogContributor +{ + public override void PreContribute(AuditLogContributionContext context) + { + var currentUser = context.ServiceProvider.GetRequiredService(); + context.AuditInfo.SetProperty( + "MyCustomClaimValue", + currentUser.FindClaimValue("MyCustomClaim") + ); + } + + public override void PostContribute(AuditLogContributionContext context) + { + context.AuditInfo.Comments.Add("Some comment..."); + } +} +```` + +* `context.ServiceProvider` 可以从[依赖注入系统](Dependency-Injection.md)中解析服务. +* `context.AuditInfo` 可以用来访问当前审计日志的对象并进行操作. + +创建贡献者后,需要将其添加到 `AbpAuditingOptions.Contributors` 列表中: + +````csharp +Configure(options => +{ + options.Contributors.Add(new MyAuditLogContributor()); +}); +```` + +## IAuditLogScope & IAuditingManager + +本节介绍用于高级用例的 `IAuditLogScope` 和 `IAuditingManager` 服务. + +**审计日志范围**是**构建**和**保存**审计日志对象的[环境范围](Ambient-Context-Pattern.md)(前面解释过). 默认审计日志中间件会为Web请求创建审计日志范围(请参阅上面的 `UseAuditing()` 部分). + +### 获取当前审计日志范围 + +上面提到,审计日志贡献者是操作审计日志对象的全局方法. 你可从服务中获得值. + +如果需要在应用程序的任意位置上操作审计日志对象,可以访问当前审计日志范围并获取当前审计日志对象(与范围的管理方式无关). +例: + +````csharp +public class MyService : ITransientDependency +{ + private readonly IAuditingManager _auditingManager; + + public MyService(IAuditingManager auditingManager) + { + _auditingManager = auditingManager; + } + + public async Task DoItAsync() + { + var currentAuditLogScope = _auditingManager.Current; + if (currentAuditLogScope != null) + { + currentAuditLogScope.Log.Comments.Add( + "Executed the MyService.DoItAsync method :)" + ); + + currentAuditLogScope.Log.SetProperty("MyCustomProperty", 42); + } + } +} +```` + +总是检查 `_auditingManager.Current` 是否为空,因为它是在外部范围中控制的,在调用方法之前你不知道是否创建了审计日志范围. + +### 手动创建审计日志范围 + +你很少需要手动创建审计日志的范围,但如果你需要,可以使用 `IAuditingManager` 创建审计日志的范围. +例: + +````csharp +public class MyService : ITransientDependency +{ + private readonly IAuditingManager _auditingManager; + + public MyService(IAuditingManager auditingManager) + { + _auditingManager = auditingManager; + } + + public async Task DoItAsync() + { + using (var auditingScope = _auditingManager.BeginScope()) + { + try + { + //Call other services... + } + catch (Exception ex) + { + //Add exceptions + _auditingManager.Current.Log.Exceptions.Add(ex); + } + finally + { + //Always save the log + await auditingScope.SaveAsync(); + } + } + } +} +```` + +您可以调用其他服务,它们可能调用其他服务,它们可能更改实体,等等. 所有这些交互都保存为finally块中的一个审计日志对象. + +## 审计日志模块 + +审计日志模块基本上实现了 `IAuditingStore`, 将审计日志对象保存到数据库中并支持多个数据库提供程序. 默认此模块已添加到启动模板中. + +参见[审计日志模块文档](Modules/Audit-Logging.md)了解更多. \ No newline at end of file diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 44856b34ed..ad811cc5b3 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -97,8 +97,13 @@ "path": "Caching.md" }, { - "text": "审计" - }, + "text": "日志", + "path": "Logging.md" + }, + { + "text": "审计日志", + "path": "Audit-Logging.md" + }, { "text": "设置管理", "path": "Settings.md" From 712029da61094258aa93a906a1b12731bfd5b29a Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 17 Feb 2020 12:51:13 +0800 Subject: [PATCH 56/68] Cache document update information. --- .../Documents/DocumentAdminAppService.cs | 37 ++++++++++------- .../Volo/Docs/Documents/DocumentAppService.cs | 40 ++++++++++++++----- .../Volo/Docs/Documents/DocumentUpdateInfo.cs | 14 +++++++ .../Docs/Documents/IDocumentRepository.cs | 7 ++-- .../Documents/EFCoreDocumentRepository.cs | 7 ++++ .../Docs/Documents/MongoDocumentRepository.cs | 8 ++++ .../Documents/TagHelpers/TreeTagHelper.cs | 16 ++++++-- .../Volo/Docs/DocumentRepository_Tests.cs | 10 +++++ 8 files changed, 106 insertions(+), 33 deletions(-) create mode 100644 modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs diff --git a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs index e50e0c4d03..5fa746907b 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Newtonsoft.Json; using Volo.Abp.Application.Services; +using Volo.Abp.Caching; using Volo.Docs.Documents; using Volo.Docs.Projects; @@ -16,14 +17,17 @@ namespace Volo.Docs.Admin.Documents private readonly IProjectRepository _projectRepository; private readonly IDocumentRepository _documentRepository; private readonly IDocumentSourceFactory _documentStoreFactory; + private readonly IDistributedCache _documentUpdateCache; public DocumentAdminAppService(IProjectRepository projectRepository, IDocumentRepository documentRepository, - IDocumentSourceFactory documentStoreFactory) + IDocumentSourceFactory documentStoreFactory, + IDistributedCache documentUpdateCache) { _projectRepository = projectRepository; _documentRepository = documentRepository; _documentStoreFactory = documentStoreFactory; + _documentUpdateCache = documentUpdateCache; } public async Task PullAllAsync(PullAllDocumentInput input) @@ -54,16 +58,12 @@ namespace Volo.Docs.Admin.Documents foreach (var document in documents) { - var oldDocument = await _documentRepository.FindAsync(document.ProjectId, document.Name, + await _documentRepository.DeleteAsync(document.ProjectId, document.Name, document.LanguageCode, document.Version); - if (oldDocument != null) - { - await _documentRepository.DeleteAsync(oldDocument); - } - - await _documentRepository.InsertAsync(document); + await _documentRepository.InsertAsync(document, true); + await UpdateDocumentUpdateInfoCache(document); } } @@ -74,15 +74,21 @@ namespace Volo.Docs.Admin.Documents var source = _documentStoreFactory.Create(project.DocumentStoreType); var sourceDocument = await source.GetDocumentAsync(project, input.Name, input.LanguageCode, input.Version); - var oldDocument = await _documentRepository.FindAsync(sourceDocument.ProjectId, sourceDocument.Name, + await _documentRepository.DeleteAsync(sourceDocument.ProjectId, sourceDocument.Name, sourceDocument.LanguageCode, sourceDocument.Version); + await _documentRepository.InsertAsync(sourceDocument, true); + await UpdateDocumentUpdateInfoCache(sourceDocument); + } - if (oldDocument != null) + private async Task UpdateDocumentUpdateInfoCache(Document document) + { + var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}"; + await _documentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo { - await _documentRepository.DeleteAsync(oldDocument); - } - - await _documentRepository.InsertAsync(sourceDocument); + Name = document.Name, + LastUpdatedTime = document.LastUpdatedTime, + UpdatedCount = document.UpdatedCount + }); } private async Task GetDocumentAsync( @@ -93,7 +99,8 @@ namespace Volo.Docs.Admin.Documents { version = string.IsNullOrWhiteSpace(version) ? project.LatestVersionBranchName : version; var source = _documentStoreFactory.Create(project.DocumentStoreType); - return await source.GetDocumentAsync(project, documentName, languageCode, version); + var document = await source.GetDocumentAsync(project, documentName, languageCode, version); + return document; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index f4ae3a9158..1ef35fda54 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -18,6 +18,7 @@ namespace Volo.Docs.Documents private readonly IDocumentSourceFactory _documentStoreFactory; protected IDistributedCache LanguageCache { get; } protected IDistributedCache ResourceCache { get; } + protected IDistributedCache DocumentUpdateCache { get; } protected IHostEnvironment HostEnvironment { get; } public DocumentAppService( IProjectRepository projectRepository, @@ -25,6 +26,7 @@ namespace Volo.Docs.Documents IDocumentSourceFactory documentStoreFactory, IDistributedCache languageCache, IDistributedCache resourceCache, + IDistributedCache documentUpdateCache, IHostEnvironment hostEnvironment) { _projectRepository = projectRepository; @@ -32,6 +34,7 @@ namespace Volo.Docs.Documents _documentStoreFactory = documentStoreFactory; LanguageCache = languageCache; ResourceCache = resourceCache; + DocumentUpdateCache = documentUpdateCache; HostEnvironment = hostEnvironment; } @@ -73,20 +76,18 @@ namespace Volo.Docs.Documents var navigationNode = JsonConvert.DeserializeObject(navigationDocument.Content); var leafs = navigationNode.Items.GetAllNodes(x => x.Items) - .Where(x => x.IsLeaf && !x.Path.IsNullOrWhiteSpace()) + .Where(x => !x.Path.IsNullOrWhiteSpace()) .ToList(); foreach (var leaf in leafs) { - var document = await GetDocumentWithDetailsDtoAsync( - project, - leaf.Path, - input.LanguageCode, - input.Version - ); - - leaf.LastUpdatedTime = document.LastUpdatedTime; - leaf.UpdatedCount = document.UpdatedCount; + var cacheKey = $"DocumentUpdateInfo{project.Id}#{leaf.Path}#{input.LanguageCode}#{input.Version}"; + var documentUpdateInfo = await DocumentUpdateCache.GetAsync(cacheKey); + if (documentUpdateInfo != null) + { + leaf.LastUpdatedTime = documentUpdateInfo.LastUpdatedTime; + leaf.UpdatedCount = documentUpdateInfo.UpdatedCount; + } } return navigationNode; @@ -165,10 +166,19 @@ namespace Volo.Docs.Documents var source = _documentStoreFactory.Create(project.DocumentStoreType); var sourceDocument = await source.GetDocumentAsync(project, documentName, languageCode, version); - await _documentRepository.InsertAsync(sourceDocument); + await _documentRepository.DeleteAsync(project.Id, sourceDocument.Name, sourceDocument.LanguageCode, sourceDocument.Version); + await _documentRepository.InsertAsync(sourceDocument, true); Logger.LogInformation($"Document retrieved: {documentName}"); + var cacheKey = $"DocumentUpdateInfo{sourceDocument.ProjectId}#{sourceDocument.Name}#{sourceDocument.LanguageCode}#{sourceDocument.Version}"; + await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo + { + Name = sourceDocument.Name, + LastUpdatedTime = sourceDocument.LastUpdatedTime, + UpdatedCount = sourceDocument.UpdatedCount + }); + return CreateDocumentWithDetailsDto(project, sourceDocument); } @@ -192,6 +202,14 @@ namespace Volo.Docs.Documents return await GetDocumentAsync(); } + var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}"; + await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo + { + Name = document.Name, + LastUpdatedTime = document.LastUpdatedTime, + UpdatedCount = document.UpdatedCount + }); + return CreateDocumentWithDetailsDto(project, document); } diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs new file mode 100644 index 0000000000..fbdc7056a5 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs @@ -0,0 +1,14 @@ +using System; + +namespace Volo.Docs.Documents +{ + [Serializable] + public class DocumentUpdateInfo + { + public virtual string Name { get; set; } + + public virtual DateTime LastUpdatedTime { get; set; } + + public virtual int UpdatedCount { get; set; } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs index aaabb2d8c7..75e8a22e6d 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -12,5 +10,8 @@ namespace Volo.Docs.Documents Task FindAsync(Guid projectId, string name, string languageCode, string version, bool includeDetails = true, CancellationToken cancellationToken = default); + + Task DeleteAsync(Guid projectId, string name, string languageCode, string version, + CancellationToken cancellationToken = default); } -} +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs index d9a96e6c4f..7698ef0b39 100644 --- a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs @@ -25,5 +25,12 @@ namespace Volo.Docs.Documents x.Version == version, cancellationToken); } + + public async Task DeleteAsync(Guid projectId, string name, string languageCode, string version, CancellationToken cancellationToken = default) + { + await DeleteAsync(x => + x.ProjectId == projectId && x.Name == name && x.LanguageCode == languageCode && + x.Version == version, cancellationToken: cancellationToken); + } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs index 548b89e246..ee72171e0d 100644 --- a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs @@ -24,5 +24,13 @@ namespace Volo.Docs.Documents x.LanguageCode == languageCode && x.Version == version, cancellationToken); } + + public async Task DeleteAsync(Guid projectId, string name, string languageCode, string version, + CancellationToken cancellationToken = default) + { + await DeleteAsync(x => + x.ProjectId == projectId && x.Name == name && x.LanguageCode == languageCode && + x.Version == version, cancellationToken: cancellationToken); + } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs index 23be8a8cff..485de84e4d 100644 --- a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs +++ b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs @@ -123,10 +123,18 @@ namespace Volo.Docs.Areas.Documents.TagHelpers } else { - var badge = node.Path.IsNullOrWhiteSpace() - ? "" - : "" + - (node.LastUpdatedTime + TimeSpan.FromDays(30) > DateTime.Now ? (node.UpdatedCount == 1 ? _localizer["New"] : _localizer["Upd"]) : "") + ""; + var badge = ""; + if (!node.Path.IsNullOrWhiteSpace() && node.LastUpdatedTime.HasValue && node.LastUpdatedTime + TimeSpan.FromDays(30) > DateTime.Now) + { + if (node.UpdatedCount > 1) + { + badge = "" + _localizer["Upd"] + ""; + } + else + { + badge = "" + _localizer["New"] + ""; + } + } listInnerItem = string.Format(ListItemAnchor, NormalizePath(node.Path), textCss, node.Text.IsNullOrEmpty() diff --git a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocumentRepository_Tests.cs b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocumentRepository_Tests.cs index 31d0e8a6fd..68a2ebb5e6 100644 --- a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocumentRepository_Tests.cs +++ b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocumentRepository_Tests.cs @@ -24,5 +24,15 @@ namespace Volo.Docs var document = await DocumentRepository.FindAsync(DocsTestData.PorjectId, "CLI.md", "en", "2.0.0"); document.ShouldNotBeNull(); } + + [Fact] + public async Task DeleteAsync() + { + (await DocumentRepository.GetListAsync()).ShouldNotBeEmpty(); + + await DocumentRepository.DeleteAsync(DocsTestData.PorjectId, "CLI.md", "en", "2.0.0"); + + (await DocumentRepository.GetListAsync()).ShouldBeEmpty(); + } } } From 1033a3db55a6b479fde805624fbeb9adaf6afb4e Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem <25867+gterdem@users.noreply.github.com> Date: Mon, 17 Feb 2020 08:36:45 +0300 Subject: [PATCH 57/68] Update docs/en/Audit-Logging.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated with reviewer's suggestion Co-Authored-By: Halil İbrahim Kalkan --- docs/en/Audit-Logging.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Audit-Logging.md b/docs/en/Audit-Logging.md index 8fdb8cff8b..799b3b76c3 100644 --- a/docs/en/Audit-Logging.md +++ b/docs/en/Audit-Logging.md @@ -39,7 +39,7 @@ Here, a list of the options you can configure: * `IsEnabled` (default: `true`): A root switch to enable or disable the auditing system. Other options is not used if this value is `false`. * `HideErrors` (default: `true`): Audit log system hides and write regular [logs](Logging.md) if any error occurs while saving the audit log objects. If saving the audit logs is critical for your system, set this to `false` to throw exception in case of hiding the errors. * `IsEnabledForAnonymousUsers` (default: `true`): If you want to write audit logs only for the authenticated users, set this to `false`. If you save audit logs for anonymous users, you will see `null` for `UserId` values for these users. -* `AlwaysLogOnException` (default: `true`): Audit log option to save all the exceptions occur in the application. +* `AlwaysLogOnException` (default: `true`): If you set to true, it always saves the audit log on an exception/error case without checking other options (except `IsEnabled`, which completely disables the audit logging). * `IsEnabledForGetRequests` (default: `false`): HTTP GET requests should not make any change in the database normally and audit log system doesn't save audit log objects for GET request. Set this to `true` to enable it also for the GET requests. * `ApplicationName`: If multiple applications saving audit logs into a single database, set this property to your application name, so you can distinguish the logs of different applications. * `IgnoredTypes`: A list of `Type`s to be ignored for audit logging. If this is an entity type, changes for this type of entities will not be saved. This list is also used while serializing the action parameters. @@ -370,4 +370,4 @@ You can call other services, they may call others, they may change entities and The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. It supports multiple database providers. This module is added to the startup templates by default. -See [the Audit Logging Module document](Modules/Audit-Logging.md) for more about it. \ No newline at end of file +See [the Audit Logging Module document](Modules/Audit-Logging.md) for more about it. From 398146bb47535786d23f20040738b82f9edcec87 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Mon, 17 Feb 2020 11:40:35 +0300 Subject: [PATCH 58/68] Update Caching.md resolves https://github.com/abpframework/abp/issues/2842 --- docs/en/Caching.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/en/Caching.md b/docs/en/Caching.md index 8aa9f00657..bb11b2b45a 100644 --- a/docs/en/Caching.md +++ b/docs/en/Caching.md @@ -2,6 +2,32 @@ ABP framework extends ASP.NET Core's distributed caching system. +## Volo.Abp.Caching Package + +> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. + +Volo.Abp.Caching is the core package of the caching system. Install it to your project using the package manager console (PMC): + +``` +Install-Package Volo.Abp.Caching +``` + +Then you can add **AbpCachingModule** dependency to your module: + +```c# +using Volo.Abp.Modularity; +using Volo.Abp.Caching; + +namespace MyCompany.MyProject +{ + [DependsOn(typeof(AbpCachingModule))] + public class MyModule : AbpModule + { + //... + } +} +``` + ## `IDistributedCache` Interface ASP.NET Core defines the `IDistributedCache` interface to get/set cache values. But it has some difficulties: From a341528aa65f36b8cd986faf25030fe65a5583d9 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Mon, 17 Feb 2020 13:39:17 +0300 Subject: [PATCH 59/68] Updated AuditingMidware exception check and new test --- .../AspNetCore/Auditing/AbpAuditingMiddleware.cs | 5 +++++ .../AspNetCore/Mvc/Auditing/AuditTestController.cs | 5 +++++ .../Mvc/Auditing/AuditTestController_Tests.cs | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index be84495336..f59c5b6b3a 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; @@ -34,6 +35,10 @@ namespace Volo.Abp.AspNetCore.Auditing try { await next(context); + if (_auditingManager.Current.Log.Exceptions.Any()) + { + hasError = true; + } } catch (Exception) { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs index 9b4892cea4..b6bfe203db 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs @@ -26,5 +26,10 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing { throw new UserFriendlyException("Exception occurred!"); } + [Route("audit-fail-object")] + public object AuditFailForGetRequestsReturningObject() + { + throw new UserFriendlyException("Exception occurred!"); + } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs index 733fab3f49..f0371ef51a 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs @@ -34,7 +34,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing _options.IsEnabledForGetRequests = true; _options.AlwaysLogOnException = false; await GetResponseAsync("api/audit-test/audit-success"); - await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope + await _auditingStore.Received().SaveAsync(Arg.Any()); } [Fact] @@ -49,7 +49,17 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing } catch { } - await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope + await _auditingStore.Received().SaveAsync(Arg.Any()); + } + [Fact] + public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object() + { + _options.IsEnabled = true; + _options.AlwaysLogOnException = true; + + await GetResponseAsync("api/audit-test/audit-fail-object", System.Net.HttpStatusCode.Forbidden); + + await _auditingStore.Received().SaveAsync(Arg.Any()); } } } From 614aa0d77cc7121f43aa98b139426f8d62170eed Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 17 Feb 2020 19:38:48 +0800 Subject: [PATCH 60/68] Translate audit-logging module doc --- docs/en/Modules/Audit-Logging.md | 2 +- docs/zh-Hans/Ambient-Context-Pattern.md | 3 +++ docs/zh-Hans/Audit-Logging.md | 1 + docs/zh-Hans/Caching.md | 26 ++++++++++++++++++++++ docs/zh-Hans/Modules/Audit-Logging.md | 7 ++++++ docs/zh-Hans/Modules/Index.md | 16 ++++++++----- docs/zh-Hans/Modules/Setting-Management.md | 2 ++ 7 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 docs/zh-Hans/Ambient-Context-Pattern.md create mode 100644 docs/zh-Hans/Modules/Audit-Logging.md diff --git a/docs/en/Modules/Audit-Logging.md b/docs/en/Modules/Audit-Logging.md index 922f99284b..039c61d3a2 100644 --- a/docs/en/Modules/Audit-Logging.md +++ b/docs/en/Modules/Audit-Logging.md @@ -1,6 +1,6 @@ # Audit Logging Module -The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. +The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. > Audit Logging module is already installed and configured for [the startup templates](../Startup-Templates/Index.md). So, most of the times you don't need to manually add this module to your application. diff --git a/docs/zh-Hans/Ambient-Context-Pattern.md b/docs/zh-Hans/Ambient-Context-Pattern.md new file mode 100644 index 0000000000..d00a721ce2 --- /dev/null +++ b/docs/zh-Hans/Ambient-Context-Pattern.md @@ -0,0 +1,3 @@ +## Ambient Context Pattern + +TODO \ No newline at end of file diff --git a/docs/zh-Hans/Audit-Logging.md b/docs/zh-Hans/Audit-Logging.md index 36f80525d1..a028e640b2 100644 --- a/docs/zh-Hans/Audit-Logging.md +++ b/docs/zh-Hans/Audit-Logging.md @@ -39,6 +39,7 @@ Configure(options => * `IsEnabled` (默认值: `true`): 启用或禁用审计系统的总开关. 如果值为 `false`,则不使用其他选项. * `HideErrors` (默认值: `true`): 在保存审计日志对象时如果发生任何错误,审计日志系统会将错误隐藏并写入常规[日志](Logging.md). 如果保存审计日志对系统非常重要那么将其设置为 `false` 以便在隐藏错误时抛出异常. * `IsEnabledForAnonymousUsers` (默认值: `true`): 如果只想为经过身份验证的用户记录审计日志,请设置为 `false`.如果为匿名用户保存审计日志,你将看到这些用户的 `UserId` 值为 `null`. +* `AlwaysLogOnException`(默认值: `true`): 如果设置为 `true`,将始终在异常/错误情况下保存审计日志,不检查其他选项(`IsEnabled` 除外,它完全禁用了审计日志). * `IsEnabledForGetRequests` (默认值: `false`): HTTP GET请求通常不应该在数据库进行任何更改,审计日志系统不会为GET请求保存审计日志对象. 将此值设置为 `true` 可为GET请求启用审计日志系统. * `ApplicationName`: 如果有多个应用程序保存审计日志到单一的数据库,使用此属性设置为你的应用程序名称区分不同的应用程序日志. * `IgnoredTypes`: 审计日志系统忽略的 `Type` 列表. 如果它是实体类型,则不会保存此类型实体的更改. 在序列化操作参数时也使用此列表. diff --git a/docs/zh-Hans/Caching.md b/docs/zh-Hans/Caching.md index 7379104f51..0557ddcb4d 100644 --- a/docs/zh-Hans/Caching.md +++ b/docs/zh-Hans/Caching.md @@ -2,6 +2,32 @@ ABP框架扩展了ASP.NET Core的分布式缓存系统. +## Volo.Abp.Caching Package + +> 默认情况下启动模板已经安装了这个包,所以大部分情况下你不需要手动安装. + +Volo.Abp.Caching是缓存系统的核心包.使用包管理控制台(PMC)安装到项目: + +``` +Install-Package Volo.Abp.Caching +``` + +然后将 **AbpCachingModule** 依赖添加到你的模块: + +```c# +using Volo.Abp.Modularity; +using Volo.Abp.Caching; + +namespace MyCompany.MyProject +{ + [DependsOn(typeof(AbpCachingModule))] + public class MyModule : AbpModule + { + //... + } +} +``` + ## `IDistributedCache` 接口 ASP.NET Core 定义了 `IDistributedCache` 接口用于 get/set 缓存值 . 但是会有以下问题: diff --git a/docs/zh-Hans/Modules/Audit-Logging.md b/docs/zh-Hans/Modules/Audit-Logging.md new file mode 100644 index 0000000000..0748b27160 --- /dev/null +++ b/docs/zh-Hans/Modules/Audit-Logging.md @@ -0,0 +1,7 @@ +# 审计日志模块 + +审计日志模块实现了 `IAuditingStore` 将审计日志对象保存到数据库中. + +> [启动模板](../Startup-Templates/Index.md)已经安装并配置了审计日志模块,所以你不需要手动安装到你的应用程序. + +参阅[审计日志系统](../Audit-Logging.md)文档了解更多关于审计日志的内容. \ No newline at end of file diff --git a/docs/zh-Hans/Modules/Index.md b/docs/zh-Hans/Modules/Index.md index e6f08d5451..9b44967c53 100644 --- a/docs/zh-Hans/Modules/Index.md +++ b/docs/zh-Hans/Modules/Index.md @@ -11,16 +11,20 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages** 有一些由ABP社区开发和维护的 **开源免费** 的应用程序模块: -* **Account**: 用于用户登录/注册应用程序. -* **Audit Logging**: 用于将审计日志持久化到数据库. +* **Account**: 提供账户管理UI,并允许用户登录/注册应用程序. +* [**Audit Logging**](Audit-Logging.md): 用于将审计日志持久化到数据库. * **Background Jobs**: 用于在使用默认后台作业管理器时保存后台作业. * **Blogging**: 用于创建精美的博客. ABP的[博客](https://blog.abp.io/) 就使用了此模块. * [**Docs**](Docs.md): 用于创建技术文档页面. ABP的[文档](https://abp.io/documents/) 就使用了此模块. -* **Identity**: 用于管理角色,用户和他们的权限. +* **Identity**: 基于Microsoft Identity管理角色,用户和他们的权限. * **Identity Server**: 集成了IdentityServer4. * **Permission Management**: 用于保存权限. * **Setting Management**: 用于保存设置. -* **Tenant Management**: 用于管理[多租户](../Multi-Tenancy.md)应用程序的租户. -* **Users**: 用于抽象用户, 因此其他模块可以依赖此模块而不是Identity模块. +* **Tenant Management**: 管理[多租户](../Multi-Tenancy.md)应用程序的租户. +* **Users**: 抽象用户, 因此其他模块可以依赖此模块而不是Identity模块. -模块化文档正在编写中. 请参阅[这个仓库](https://github.com/abpframework/abp/tree/master/modules)获取所有模块的源代码. \ No newline at end of file +模块化文档正在编写中. 请参阅[这个仓库](https://github.com/abpframework/abp/tree/master/modules)获取所有模块的源代码. + +## 商业应用模块 + +[ABP商业](https://commercial.abp.io/)许可证在ABP框架上提供了额外的预构建应用程序模块. 参见ABP商业版提供的[模块列表](https://commercial.abp.io/module). \ No newline at end of file diff --git a/docs/zh-Hans/Modules/Setting-Management.md b/docs/zh-Hans/Modules/Setting-Management.md index 31ea2505ea..fd87dd734c 100644 --- a/docs/zh-Hans/Modules/Setting-Management.md +++ b/docs/zh-Hans/Modules/Setting-Management.md @@ -67,6 +67,8 @@ namespace Demo 你可以从不同的设置值提供程序中(默认,全局,用户,租户...等)中获取或设定设置值. +> 如果只需要读取设置值,建议使用 `ISettingProvider` 而不是`ISettingManager`,因为它实现了缓存并支持所有部署场景. 如果要创建设置管理UI,可以使用ISettingManager. + ### Setting Cache 设置值缓存在 [分布式缓存](../Caching.md) 系统中. 建议始终使用 `ISettingManager` 更改设置值. From 41604bf713e451c8850109755ada425a603c440b Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Mon, 17 Feb 2020 17:03:16 +0300 Subject: [PATCH 61/68] remove unused code --- .../Volo/Docs/GitHub/Documents/GithubDocumentSource.cs | 1 - .../Volo/Docs/GitHub/Projects/ProjectGithubExtensions.cs | 8 -------- 2 files changed, 9 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs index 972d8ecfbb..0159f20852 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs @@ -34,7 +34,6 @@ namespace Volo.Docs.GitHub.Documents var userAgent = project.GetGithubUserAgentOrNull(); var rawRootUrl = CalculateRawRootUrlWithLanguageCode(rootUrl, languageCode); var rawDocumentUrl = rawRootUrl + documentName; - var commitHistoryUrl = project.GetGitHubUrlForCommitHistory() + languageCode + "/" + documentName; var isNavigationDocument = documentName == project.NavigationDocumentName; var isParameterDocument = documentName == project.ParametersDocumentName; var editLink = rootUrl.ReplaceFirst("/tree/", "/blob/") + languageCode + "/" + documentName; diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Projects/ProjectGithubExtensions.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Projects/ProjectGithubExtensions.cs index ff1979d037..d518132950 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Projects/ProjectGithubExtensions.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Projects/ProjectGithubExtensions.cs @@ -21,14 +21,6 @@ namespace Volo.Docs.GitHub.Projects .Replace("{version}", version); } - public static string GetGitHubUrlForCommitHistory([NotNull] this Project project) - { - return project - .GetGitHubUrl() - .Replace("github.com", "api.github.com/repos") - .Replace("tree/{version}/", "commits?path="); - } - public static void SetGitHubUrl([NotNull] this Project project, string value) { CheckGitHubProject(project); From 479fc27ec74a2f4149c59ec1b0b3dfe13b7c9a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 17 Feb 2020 19:12:41 +0300 Subject: [PATCH 62/68] Docs module update badge improvement --- .../Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json | 4 +++- .../Volo.Docs.Domain/Volo/Docs/Localization/Domain/tr.json | 4 +++- .../Areas/Documents/TagHelpers/TreeTagHelper.cs | 7 ++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json index 5115dd1489..92858a87f0 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json @@ -19,6 +19,8 @@ "FilterTopics": "Filter topics", "MultipleVersionDocumentInfo": "This document has multiple versions. Select the options best fit for you.", "New": "New", - "Upd": "Upd" + "Upd": "Upd", + "NewExplanation": "Created in the last two weeks.", + "UpdatedExplanation": "Updated in the last two weeks." } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/tr.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/tr.json index a546f626dc..612274f95d 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/tr.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/tr.json @@ -18,6 +18,8 @@ "DocumentNotFoundInSelectedLanguage": "İstediğiniz dilde belge bulunamadı. Varsayılan dilde belge gösterilir.", "MultipleVersionDocumentInfo": "Bu dökümanın birden çok versiyonu bulunmaktadır. Sizin için en uygun olan seçenekleri seçiniz.", "New": "Yeni", - "Upd": "güncellenmiş" + "Upd": "Günc", + "NewExplanation": "Son iki hafta içinde oluşturuldu.", + "UpdatedExplanation": "Son iki hafta içinde güncellendi." } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs index 485de84e4d..aa66f4c82f 100644 --- a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs +++ b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs @@ -124,15 +124,16 @@ namespace Volo.Docs.Areas.Documents.TagHelpers else { var badge = ""; - if (!node.Path.IsNullOrWhiteSpace() && node.LastUpdatedTime.HasValue && node.LastUpdatedTime + TimeSpan.FromDays(30) > DateTime.Now) + + if (!node.Path.IsNullOrWhiteSpace() && node.LastUpdatedTime.HasValue && node.LastUpdatedTime + TimeSpan.FromDays(14) > DateTime.Now) { if (node.UpdatedCount > 1) { - badge = "" + _localizer["Upd"] + ""; + badge = "" + _localizer["Upd"] + ""; } else { - badge = "" + _localizer["New"] + ""; + badge = "" + _localizer["New"] + ""; } } From 893c8a42b7da8447d2bfa2f0d8188f9058557d77 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Feb 2020 10:15:53 +0800 Subject: [PATCH 63/68] Docs module "new" label improvement Resolve #2848 --- ....Designer.cs => 20200218014727_init.Designer.cs} | 8 ++++---- ...0200212135141_init.cs => 20200218014727_init.cs} | 2 +- .../Migrations/VoloDocsDbContextModelSnapshot.cs | 6 +++--- .../Docs/Admin/Documents/DocumentAdminAppService.cs | 4 ++-- .../Volo/Docs/Documents/DocumentWithDetailsDto.cs | 4 ++-- .../Volo/Docs/Documents/DocumentAppService.cs | 8 ++++---- .../Volo/Docs/Documents/DocumentUpdateInfo.cs | 4 ++-- .../Volo/Docs/Documents/NavigationNode.cs | 4 ++-- .../Volo/Docs/Documents/Document.cs | 9 +++++---- .../Documents/FileSystemDocumentSource.cs | 2 +- .../Docs/GitHub/Documents/GithubDocumentSource.cs | 2 +- .../Volo/Docs/Localization/Domain/zh-Hans.json | 4 +++- .../Volo/Docs/Localization/Domain/zh-Hant.json | 4 +++- .../Areas/Documents/TagHelpers/TreeTagHelper.cs | 13 ++++++++----- .../Volo/Docs/DocsTestDataBuilder.cs | 2 +- 15 files changed, 42 insertions(+), 34 deletions(-) rename modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/{20200212135141_init.Designer.cs => 20200218014727_init.Designer.cs} (99%) rename modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/{20200212135141_init.cs => 20200218014727_init.cs} (99%) diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200212135141_init.Designer.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200218014727_init.Designer.cs similarity index 99% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200212135141_init.Designer.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200218014727_init.Designer.cs index 3c21209e8c..522b3e3ba5 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200212135141_init.Designer.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200218014727_init.Designer.cs @@ -10,7 +10,7 @@ using VoloDocs.EntityFrameworkCore; namespace VoloDocs.EntityFrameworkCore.Migrations { [DbContext(typeof(VoloDocsDbContext))] - [Migration("20200212135141_init")] + [Migration("20200218014727_init")] partial class init { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -464,6 +464,9 @@ namespace VoloDocs.EntityFrameworkCore.Migrations .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("CreationTime") + .HasColumnType("datetime2"); + b.Property("EditLink") .HasColumnType("nvarchar(2048)") .HasMaxLength(2048); @@ -512,9 +515,6 @@ namespace VoloDocs.EntityFrameworkCore.Migrations .HasColumnType("nvarchar(2048)") .HasMaxLength(2048); - b.Property("UpdatedCount") - .HasColumnType("int"); - b.Property("Version") .IsRequired() .HasColumnType("nvarchar(128)") diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200212135141_init.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200218014727_init.cs similarity index 99% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200212135141_init.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200218014727_init.cs index cb18bebed6..7e85e1bbc1 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200212135141_init.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200218014727_init.cs @@ -130,8 +130,8 @@ namespace VoloDocs.EntityFrameworkCore.Migrations RootUrl = table.Column(maxLength: 2048, nullable: true), RawRootUrl = table.Column(maxLength: 2048, nullable: true), LocalDirectory = table.Column(maxLength: 512, nullable: true), + CreationTime = table.Column(nullable: false), LastUpdatedTime = table.Column(nullable: false), - UpdatedCount = table.Column(nullable: false), LastCachedTime = table.Column(nullable: false) }, constraints: table => diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs index 1c595ac3f8..0780f28624 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs @@ -462,6 +462,9 @@ namespace VoloDocs.EntityFrameworkCore.Migrations .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("CreationTime") + .HasColumnType("datetime2"); + b.Property("EditLink") .HasColumnType("nvarchar(2048)") .HasMaxLength(2048); @@ -510,9 +513,6 @@ namespace VoloDocs.EntityFrameworkCore.Migrations .HasColumnType("nvarchar(2048)") .HasMaxLength(2048); - b.Property("UpdatedCount") - .HasColumnType("int"); - b.Property("Version") .IsRequired() .HasColumnType("nvarchar(128)") diff --git a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs index 5fa746907b..255fed246f 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs @@ -86,8 +86,8 @@ namespace Volo.Docs.Admin.Documents await _documentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo { Name = document.Name, - LastUpdatedTime = document.LastUpdatedTime, - UpdatedCount = document.UpdatedCount + CreationTime = document.CreationTime, + LastUpdatedTime = document.LastUpdatedTime }); } diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentWithDetailsDto.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentWithDetailsDto.cs index c78a46d2ef..0364a2e26f 100644 --- a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentWithDetailsDto.cs +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentWithDetailsDto.cs @@ -27,9 +27,9 @@ namespace Volo.Docs.Documents public virtual string LocalDirectory { get; set; } - public virtual DateTime LastUpdatedTime { get; set; } + public virtual DateTime CreationTime { get; set; } - public virtual int UpdatedCount { get; set; } + public virtual DateTime LastUpdatedTime { get; set; } public virtual DateTime LastCachedTime { get; set; } diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index 1ef35fda54..7ea04ec0c0 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -85,8 +85,8 @@ namespace Volo.Docs.Documents var documentUpdateInfo = await DocumentUpdateCache.GetAsync(cacheKey); if (documentUpdateInfo != null) { + leaf.CreationTime = documentUpdateInfo.CreationTime; leaf.LastUpdatedTime = documentUpdateInfo.LastUpdatedTime; - leaf.UpdatedCount = documentUpdateInfo.UpdatedCount; } } @@ -175,8 +175,8 @@ namespace Volo.Docs.Documents await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo { Name = sourceDocument.Name, - LastUpdatedTime = sourceDocument.LastUpdatedTime, - UpdatedCount = sourceDocument.UpdatedCount + CreationTime = sourceDocument.CreationTime, + LastUpdatedTime = sourceDocument.LastUpdatedTime }); return CreateDocumentWithDetailsDto(project, sourceDocument); @@ -206,8 +206,8 @@ namespace Volo.Docs.Documents await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo { Name = document.Name, + CreationTime = document.CreationTime, LastUpdatedTime = document.LastUpdatedTime, - UpdatedCount = document.UpdatedCount }); return CreateDocumentWithDetailsDto(project, document); diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs index fbdc7056a5..fcabe91549 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs @@ -7,8 +7,8 @@ namespace Volo.Docs.Documents { public virtual string Name { get; set; } - public virtual DateTime LastUpdatedTime { get; set; } + public virtual DateTime CreationTime { get; set; } - public virtual int UpdatedCount { get; set; } + public virtual DateTime LastUpdatedTime { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs index 4042344581..b5c2704eb8 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs @@ -22,9 +22,9 @@ namespace Volo.Docs.Documents public bool IsEmpty => Text == null && Path == null; - public DateTime? LastUpdatedTime { get; set; } + public virtual DateTime? CreationTime { get; set; } - public int UpdatedCount { get; set; } + public virtual DateTime? LastUpdatedTime { get; set; } public bool IsSelected(string documentName) { diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Document.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Document.cs index ded3fa8530..ecbc3aa617 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Document.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Document.cs @@ -31,10 +31,10 @@ namespace Volo.Docs.Documents public virtual string LocalDirectory { get; set; } + public virtual DateTime CreationTime { get; set; } + public virtual DateTime LastUpdatedTime { get; set; } - public virtual int UpdatedCount { get; set; } - public virtual DateTime LastCachedTime { get; set; } public virtual List Contributors { get; set; } @@ -58,8 +58,8 @@ namespace Volo.Docs.Documents [NotNull] string rootUrl, [NotNull] string rawRootUrl, [NotNull] string localDirectory, + DateTime creationTime, DateTime lastUpdatedTime, - int updatedCount, DateTime lastCachedTime ) { @@ -76,8 +76,9 @@ namespace Volo.Docs.Documents RootUrl = Check.NotNullOrWhiteSpace(rootUrl, nameof(rootUrl)); RawRootUrl = Check.NotNullOrWhiteSpace(rawRootUrl, nameof(rawRootUrl)); LocalDirectory = Check.NotNull(localDirectory, nameof(localDirectory)); + + CreationTime = creationTime; LastUpdatedTime = lastUpdatedTime; - UpdatedCount = updatedCount; LastCachedTime = lastCachedTime; Contributors = new List(); diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs index 6b28b42e17..d93b378dcd 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs @@ -43,8 +43,8 @@ namespace Volo.Docs.FileSystem.Documents "/", $"/document-resources?projectId={project.Id.ToString()}&version={version}&languageCode={languageCode}&name=", localDirectory, + File.GetCreationTime(path), File.GetLastWriteTime(path), - File.GetLastWriteTime(path) == File.GetCreationTime(path) ? 1 : 2, DateTime.Now); } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs index 0159f20852..0c837991d1 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs @@ -60,8 +60,8 @@ namespace Volo.Docs.GitHub.Documents rootUrl, rawRootUrl, localDirectory, + fileCommits.LastOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue, fileCommits.FirstOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue, - fileCommits.Count, DateTime.Now); var authors = fileCommits diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json index bbc0014c95..3eec4562bb 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json @@ -18,6 +18,8 @@ "DocumentNotFoundInSelectedLanguage": "本文档不适用于所选语言, 将以默认语言显示文档.", "FilterTopics": "过滤主题", "New": "新文档", - "Upd": "更新" + "Upd": "更新", + "NewExplanation": "在最近两周内创建.", + "UpdatedExplanation": "在最近两周内更新." } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json index 9d24c75510..5c3d723b0a 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json @@ -18,6 +18,8 @@ "DocumentNotFoundInSelectedLanguage": "本文件不適用於所選語系,將以預設語系顯示.", "FilterTopics": "過濾主題", "New": "新文檔", - "Upd": "更新" + "Upd": "更新", + "NewExplanation": "在最近兩周內創建.", + "UpdatedExplanation": "在最近兩周內更新." } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs index aa66f4c82f..1c57191d0a 100644 --- a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs +++ b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs @@ -125,15 +125,18 @@ namespace Volo.Docs.Areas.Documents.TagHelpers { var badge = ""; - if (!node.Path.IsNullOrWhiteSpace() && node.LastUpdatedTime.HasValue && node.LastUpdatedTime + TimeSpan.FromDays(14) > DateTime.Now) + if (!node.Path.IsNullOrWhiteSpace() && node.CreationTime.HasValue && node.LastUpdatedTime.HasValue) { - if (node.UpdatedCount > 1) + var newBadge = "" + _localizer["New"] + ""; + var updBadge = "" + _localizer["Upd"] + ""; + + if(node.CreationTime + TimeSpan.FromDays(14) > DateTime.Now) { - badge = "" + _localizer["Upd"] + ""; + badge = newBadge; } - else + else if (node.LastUpdatedTime + TimeSpan.FromDays(14) > DateTime.Now) { - badge = "" + _localizer["New"] + ""; + badge = updBadge; } } diff --git a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs index 1325ec04a3..e7eadd7562 100644 --- a/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs +++ b/modules/docs/test/Volo.Docs.TestBase/Volo/Docs/DocsTestDataBuilder.cs @@ -47,7 +47,7 @@ namespace Volo.Docs await _documentRepository.InsertAsync(new Document(Guid.NewGuid(), project.Id, "CLI.md", "2.0.0", "en", "CLI.md", "this is abp cli", "md", "https://github.com/abpframework/abp/blob/2.0.0/docs/en/CLI.md", "https://github.com/abpframework/abp/tree/2.0.0/docs/", - "https://raw.githubusercontent.com/abpframework/abp/2.0.0/docs/en/", "", DateTime.Now, 1, + "https://raw.githubusercontent.com/abpframework/abp/2.0.0/docs/en/", "", DateTime.Now, DateTime.Now, DateTime.Now)); } } From 7c15623dd2b079484837a277b0e835187df7c6d2 Mon Sep 17 00:00:00 2001 From: TheDiaval Date: Tue, 18 Feb 2020 10:09:15 +0300 Subject: [PATCH 64/68] doc: add deprecation warning --- npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts index 34fe04f351..27a2b6b460 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts @@ -21,6 +21,9 @@ export namespace Toaster { export type Severity = 'neutral' | 'success' | 'info' | 'warning' | 'error'; + /** + * @deprecated Status will be removed from toaster model in v2.2 + */ export enum Status { confirm = 'confirm', reject = 'reject', From ea391d7d6c79078957f2b499ecb6fc7f6b6b019f Mon Sep 17 00:00:00 2001 From: TheDiaval Date: Tue, 18 Feb 2020 10:09:51 +0300 Subject: [PATCH 65/68] refactor(theme-shared): add status enum to confirmation model and use it instead of toaster status enum --- .../confirmation/confirmation.component.ts | 9 ++++----- .../src/lib/models/confirmation.ts | 6 ++++++ .../src/lib/services/confirmation.service.ts | 19 +++++++++---------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts index afdb024888..92188acb34 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts @@ -2,7 +2,6 @@ import { Component } from '@angular/core'; import { ConfirmationService } from '../../services/confirmation.service'; import { Confirmation } from '../../models/confirmation'; import { LocalizationService } from '@abp/ng.core'; -import { Toaster } from '../../models/toaster'; @Component({ selector: 'abp-confirmation', @@ -10,9 +9,9 @@ import { Toaster } from '../../models/toaster'; styleUrls: ['./confirmation.component.scss'], }) export class ConfirmationComponent { - confirm = Toaster.Status.confirm; - reject = Toaster.Status.reject; - dismiss = Toaster.Status.dismiss; + confirm = Confirmation.Status.confirm; + reject = Confirmation.Status.reject; + dismiss = Confirmation.Status.dismiss; visible = false; @@ -43,7 +42,7 @@ export class ConfirmationComponent { }); } - close(status: Toaster.Status) { + close(status: Confirmation.Status) { this.confirmationService.clear(status); } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts index 40925c1b2c..815fd65c20 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts @@ -20,4 +20,10 @@ export namespace Confirmation { } export type Severity = 'neutral' | 'success' | 'info' | 'warning' | 'error'; + + export enum Status { + confirm = 'confirm', + reject = 'reject', + dismiss = 'dismiss', + } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts index f66dfd768d..2db6d38091 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts @@ -2,19 +2,18 @@ import { Injectable } from '@angular/core'; import { Confirmation } from '../models/confirmation'; import { fromEvent, Observable, Subject, ReplaySubject } from 'rxjs'; import { takeUntil, debounceTime, filter } from 'rxjs/operators'; -import { Toaster } from '../models/toaster'; import { Config } from '@abp/ng.core'; @Injectable({ providedIn: 'root' }) export class ConfirmationService { - status$: Subject; + status$: Subject; confirmation$ = new ReplaySubject(1); info( message: Config.LocalizationParam, title: Config.LocalizationParam, options?: Partial, - ): Observable { + ): Observable { return this.show(message, title, 'info', options); } @@ -22,7 +21,7 @@ export class ConfirmationService { message: Config.LocalizationParam, title: Config.LocalizationParam, options?: Partial, - ): Observable { + ): Observable { return this.show(message, title, 'success', options); } @@ -30,7 +29,7 @@ export class ConfirmationService { message: Config.LocalizationParam, title: Config.LocalizationParam, options?: Partial, - ): Observable { + ): Observable { return this.show(message, title, 'warning', options); } @@ -38,16 +37,16 @@ export class ConfirmationService { message: Config.LocalizationParam, title: Config.LocalizationParam, options?: Partial, - ): Observable { + ): Observable { return this.show(message, title, 'error', options); } show( message: Config.LocalizationParam, title: Config.LocalizationParam, - severity?: Toaster.Severity, + severity?: Confirmation.Severity, options?: Partial, - ): Observable { + ): Observable { this.confirmation$.next({ message, title, @@ -59,9 +58,9 @@ export class ConfirmationService { return this.status$; } - clear(status?: Toaster.Status) { + clear(status?: Confirmation.Status) { this.confirmation$.next(); - this.status$.next(status || Toaster.Status.dismiss); + this.status$.next(status || Confirmation.Status.dismiss); } listenToEscape() { From 1638d5cefb30a09965df1b1e602c463aaaab0c5d Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Feb 2020 19:31:54 +0800 Subject: [PATCH 66/68] Make sure the script is executed before the callback method. --- npm/packs/jquery/src/abp.jquery.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/npm/packs/jquery/src/abp.jquery.js b/npm/packs/jquery/src/abp.jquery.js index bf4c920815..ece722404d 100644 --- a/npm/packs/jquery/src/abp.jquery.js +++ b/npm/packs/jquery/src/abp.jquery.js @@ -359,13 +359,17 @@ var _loadScript = function (url, loadCallback, failCallback) { _loadFromUrl(url, loadCallback, failCallback, function (urlInfo) { - $.getScript(url) - .done(function () { - urlInfo.succeed(); - }) - .fail(function () { - urlInfo.failed(); - }); + $.get({ + url: url, + dataType: 'text' + }) + .done(function (script) { + jQuery.globalEval(script); + urlInfo.succeed(); + }) + .fail(function () { + urlInfo.failed(); + }); }); }; From 561be154db196f6e0ed5c6b4187cdf47d8f91cde Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Feb 2020 19:36:54 +0800 Subject: [PATCH 67/68] Use $ instead of jQuery. --- npm/packs/jquery/src/abp.jquery.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/packs/jquery/src/abp.jquery.js b/npm/packs/jquery/src/abp.jquery.js index ece722404d..76f54df1a0 100644 --- a/npm/packs/jquery/src/abp.jquery.js +++ b/npm/packs/jquery/src/abp.jquery.js @@ -364,7 +364,7 @@ dataType: 'text' }) .done(function (script) { - jQuery.globalEval(script); + $.globalEval(script); urlInfo.succeed(); }) .fail(function () { From bee0d50b26a9430cfbca35dd6f860bbe1800c759 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 19 Feb 2020 16:59:03 +0800 Subject: [PATCH 68/68] Add AbpAccountApplicationModule to the AuthServer.Host project of the MicroserviceDemo. https://github.com/abpframework/abp/pull/2642 --- .../applications/AuthServer.Host/AuthServer.Host.csproj | 1 + .../applications/AuthServer.Host/AuthServerHostModule.cs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServer.Host.csproj b/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServer.Host.csproj index 158c9360ed..c828edc0ae 100644 --- a/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServer.Host.csproj +++ b/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServer.Host.csproj @@ -29,6 +29,7 @@ + diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs b/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs index 7ff9ae7c51..9c9ccd2a99 100644 --- a/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs +++ b/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs @@ -1,9 +1,10 @@ -using AuthServer.Host.EntityFrameworkCore; +using AuthServer.Host.EntityFrameworkCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.DependencyInjection; using StackExchange.Redis; using Volo.Abp; +using Volo.Abp.Account; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; using Volo.Abp.Auditing; @@ -34,6 +35,7 @@ namespace AuthServer.Host typeof(AbpSettingManagementEntityFrameworkCoreModule), typeof(AbpIdentityEntityFrameworkCoreModule), typeof(AbpIdentityApplicationContractsModule), + typeof(AbpAccountApplicationModule), typeof(AbpIdentityServerEntityFrameworkCoreModule), typeof(AbpEntityFrameworkCoreSqlServerModule), typeof(AbpAccountWebIdentityServerModule),