From dc68fe77d78cf7f8ce3703758471d3cc4d8d3f06 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 30 Nov 2022 11:53:55 +0800 Subject: [PATCH 01/46] Add abp headers to Dapr's `HttpClient`. --- .../Volo/Abp/Dapr/AbpDaprClientFactory.cs | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs index aa14bbe7f2..b94c113fca 100644 --- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs @@ -1,10 +1,14 @@ using System; +using System.Globalization; using System.Net.Http; +using System.Net.Http.Headers; using System.Text.Json; using Dapr.Client; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Json.SystemTextJson; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Tracing; namespace Volo.Abp.Dapr; @@ -13,13 +17,21 @@ public class AbpDaprClientFactory : IAbpDaprClientFactory, ISingletonDependency protected AbpDaprOptions DaprOptions { get; } protected JsonSerializerOptions JsonSerializerOptions { get; } protected IDaprApiTokenProvider DaprApiTokenProvider { get; } + protected ICurrentTenant CurrentTenant { get; } + protected ICorrelationIdProvider CorrelationIdProvider { get; } + protected IOptions AbpCorrelationIdOptions { get; } public AbpDaprClientFactory( IOptions options, IOptions systemTextJsonSerializerOptions, - IDaprApiTokenProvider daprApiTokenProvider) + IDaprApiTokenProvider daprApiTokenProvider, + ICurrentTenant currentTenant, ICorrelationIdProvider correlationIdProvider, + IOptions abpCorrelationIdOptions) { DaprApiTokenProvider = daprApiTokenProvider; + CurrentTenant = currentTenant; + CorrelationIdProvider = correlationIdProvider; + AbpCorrelationIdOptions = abpCorrelationIdOptions; DaprOptions = options.Value; JsonSerializerOptions = CreateJsonSerializerOptions(systemTextJsonSerializerOptions.Value); } @@ -61,11 +73,39 @@ public class AbpDaprClientFactory : IAbpDaprClientFactory, ISingletonDependency daprEndpoint = DaprOptions.HttpEndpoint; } - return DaprClient.CreateInvokeHttpClient( + var httpClient = DaprClient.CreateInvokeHttpClient( appId, daprEndpoint, daprApiToken ?? DaprApiTokenProvider.GetDaprApiToken() ); + + AddHeaders(httpClient); + + return httpClient; + } + + protected virtual void AddHeaders(HttpClient httpClient) + { + //CorrelationId + httpClient.DefaultRequestHeaders.Add(AbpCorrelationIdOptions.Value.HttpHeaderName, CorrelationIdProvider.Get()); + + //TenantId + if (CurrentTenant.Id.HasValue) + { + //TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key + httpClient.DefaultRequestHeaders.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); + } + + //Culture + //TODO: Is that the way we want? Couldn't send the culture (not ui culture) + var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name; + if (!currentCulture.IsNullOrEmpty()) + { + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture)); + } + + //X-Requested-With + httpClient.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest"); } protected virtual JsonSerializerOptions CreateJsonSerializerOptions(AbpSystemTextJsonSerializerOptions systemTextJsonSerializerOptions) From 300c05fc2ebd5dcdc6da7a6325b322157ca152e5 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 17 Oct 2023 17:43:27 +0800 Subject: [PATCH 02/46] Add CDN support for bundles. --- .../UI/Bundling/BundleConfigurationContext.cs | 4 +- .../Bundling/BundleConfigurationExtensions.cs | 7 +++ .../BundleContributorCollectionExtensions.cs | 4 ++ .../AspNetCore/Mvc/UI/Bundling/BundleFile.cs | 14 +++++ .../Mvc/UI/Bundling/BundleFileContributor.cs | 14 +++-- .../UI/Bundling/BundleFileListExtensions.cs | 32 +++++++++++ .../Bundling/IBundleConfigurationContext.cs | 2 +- .../Mvc/UI/Bundling/BundleCacheItem.cs | 4 +- .../Mvc/UI/Bundling/BundleManager.cs | 54 +++++++++++++++---- .../Mvc/UI/Bundling/IBundleManager.cs | 4 +- .../TagHelpers/AbpTagHelperResourceService.cs | 34 +++++++----- .../TagHelpers/AbpTagHelperScriptService.cs | 11 ++-- .../TagHelpers/AbpTagHelperStyleService.cs | 16 +++--- .../Mvc/UI/Resources/IWebRequestResources.cs | 3 +- .../Mvc/UI/Resources/WebRequestResources.cs | 7 +-- 15 files changed, 161 insertions(+), 49 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileListExtensions.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs index b67c8f6d3d..bc4dc14d11 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; public class BundleConfigurationContext : IBundleConfigurationContext { - public List Files { get; } + public List Files { get; } public IFileProvider FileProvider { get; } @@ -18,7 +18,7 @@ public class BundleConfigurationContext : IBundleConfigurationContext public BundleConfigurationContext(IServiceProvider serviceProvider, IFileProvider fileProvider) { - Files = new List(); + Files = new List(); ServiceProvider = serviceProvider; LazyServiceProvider = ServiceProvider.GetRequiredService(); FileProvider = fileProvider; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs index 3ee0fe6cd9..ca628f4d67 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; @@ -10,6 +11,12 @@ public static class BundleConfigurationExtensions return bundleConfiguration; } + public static BundleConfiguration AddCdnFiles(this BundleConfiguration bundleConfiguration, params string[] files) + { + bundleConfiguration.Contributors.AddCdnFiles(files.Select(x => new BundleFile(x, true)).ToArray()); + return bundleConfiguration; + } + public static BundleConfiguration AddContributors(this BundleConfiguration bundleConfiguration, params IBundleContributor[] contributors) { Check.NotNull(contributors, nameof(contributors)); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs index dc0ac8bfbb..3c0dfdeab8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs @@ -6,4 +6,8 @@ public static class BundleContributorCollectionExtensions { contributors.Add(new BundleFileContributor(files)); } + public static void AddCdnFiles(this BundleContributorCollection contributors, params BundleFile[] files) + { + contributors.Add(new BundleFileContributor(files)); + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs new file mode 100644 index 0000000000..58427b6c40 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +public class BundleFile +{ + public string File { get; set; } + + public bool IsCdn { get; set; } + + public BundleFile(string file, bool isCdn = false) + { + File = file; + IsCdn = isCdn; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs index 7f218e7fbf..5107c006a0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs @@ -1,22 +1,30 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; public class BundleFileContributor : BundleContributor { - public string[] Files { get; } + public List Files { get; } + + public BundleFileContributor(params BundleFile[] files) + { + Files = new List(); + Files.AddRange(files); + } public BundleFileContributor(params string[] files) { - Files = files ?? Array.Empty(); + Files = new List(); + Files.AddRange(files.Select(file => new BundleFile(file))); } public override void ConfigureBundle(BundleConfigurationContext context) { foreach (var file in Files) { - context.Files.AddIfNotContains(x => x.Equals(file, StringComparison.OrdinalIgnoreCase), () => file); + context.Files.AddIfNotContains(x => x.File.Equals(file.File, StringComparison.OrdinalIgnoreCase), () => file); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileListExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileListExtensions.cs new file mode 100644 index 0000000000..b3088b293d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileListExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +/// +/// This class is used to compatible with old code. +/// +public static class BundleFileListExtensions +{ + public static void Add(this List bundleFiles, params string[] files) + { + bundleFiles.AddRange(files.Select(file => new BundleFile(file))); + } + + public static void AddRange(this List bundleFiles, params string[] files) + { + bundleFiles.AddRange(files.Select(file => new BundleFile(file))); + } + + public static void AddIfNotContains(this List bundleFiles, params string[] files) + { + foreach (var file in files) + { + if (!bundleFiles.Any(x => x.File.Equals(file, StringComparison.OrdinalIgnoreCase))) + { + bundleFiles.Add(new BundleFile(file)); + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleConfigurationContext.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleConfigurationContext.cs index 61d772d638..96df341514 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleConfigurationContext.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleConfigurationContext.cs @@ -5,5 +5,5 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; public interface IBundleConfigurationContext : IServiceProviderAccessor { - List Files { get; } + List Files { get; } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs index 5c3890a852..f6830cbd74 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs @@ -5,11 +5,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; public class BundleCacheItem { - public List Files { get; } + public List Files { get; } public List WatchDisposeHandles { get; } - public BundleCacheItem(List files) + public BundleCacheItem(List files) { Files = files; WatchDisposeHandles = new List(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs index 8291c2c75f..5a46e1ff5a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs @@ -12,7 +12,6 @@ using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.UI.Bundling.Scripts; using Volo.Abp.AspNetCore.Mvc.UI.Bundling.Styles; using Volo.Abp.AspNetCore.Mvc.UI.Resources; -using Volo.Abp.AspNetCore.VirtualFileSystem; using Volo.Abp.DependencyInjection; using Volo.Abp.VirtualFileSystem; @@ -56,19 +55,22 @@ public class BundleManager : IBundleManager, ITransientDependency Logger = NullLogger.Instance; } - public virtual async Task> GetStyleBundleFilesAsync(string bundleName) + public virtual async Task> GetStyleBundleFilesAsync(string bundleName) { return await GetBundleFilesAsync(Options.StyleBundles, bundleName, StyleBundler); } - public virtual async Task> GetScriptBundleFilesAsync(string bundleName) + public virtual async Task> GetScriptBundleFilesAsync(string bundleName) { return await GetBundleFilesAsync(Options.ScriptBundles, bundleName, ScriptBundler); } - protected virtual async Task> GetBundleFilesAsync(BundleConfigurationCollection bundles, string bundleName, IBundler bundler) + protected virtual async Task> GetBundleFilesAsync(BundleConfigurationCollection bundles, string bundleName, IBundler bundler) { + var result = new List(); + var contributors = GetContributors(bundles, bundleName); + var bundleFiles = RequestResources.TryAdd(await GetBundleFilesAsync(contributors)); var dynamicResources = RequestResources.TryAdd(await GetDynamicResourcesAsync(contributors)); @@ -77,16 +79,48 @@ public class BundleManager : IBundleManager, ITransientDependency return bundleFiles.Union(dynamicResources).ToImmutableList(); } + var localBundleFiles = new List(); + foreach (var bundleFile in bundleFiles) + { + if (bundleFile.IsCdn) + { + if (localBundleFiles.Any()) + { + var cacheItem = AddToBundleCache(bundleName, bundler, localBundleFiles); + result.AddRange(cacheItem.Files); + localBundleFiles.Clear(); + } + + result.Add(bundleFile); + } + else + { + localBundleFiles.Add(bundleFile.File); + } + } + + if (localBundleFiles.Any()) + { + var cacheItem = AddToBundleCache(bundleName, bundler, localBundleFiles); + result.AddRange(cacheItem.Files); + localBundleFiles.Clear(); + } + + return result.Union(dynamicResources).ToImmutableList(); + } + + private BundleCacheItem AddToBundleCache(string bundleName, IBundler bundler, List bundleFiles) + { var bundleRelativePath = Options.BundleFolderName.EnsureEndsWith('/') + bundleName + "." + bundleFiles.JoinAsString("|").ToMd5() + "." + bundler.FileExtension; - var cacheItem = BundleCache.GetOrAdd(bundleRelativePath, () => + return BundleCache.GetOrAdd(bundleRelativePath, () => { var cacheValue = new BundleCacheItem( - new List + new List { - "/" + bundleRelativePath + new BundleFile("/" + bundleRelativePath) } ); @@ -104,8 +138,6 @@ public class BundleManager : IBundleManager, ITransientDependency return cacheValue; }); - - return cacheItem.Files.Union(dynamicResources).ToImmutableList(); } private void WatchChanges(BundleCacheItem cacheValue, List files, string bundleRelativePath) @@ -176,7 +208,7 @@ public class BundleManager : IBundleManager, ITransientDependency } } - protected async Task> GetBundleFilesAsync(List contributors) + protected async Task> GetBundleFilesAsync(List contributors) { var context = CreateBundleConfigurationContext(); @@ -198,7 +230,7 @@ public class BundleManager : IBundleManager, ITransientDependency return context.Files; } - protected virtual async Task> GetDynamicResourcesAsync(List contributors) + protected virtual async Task> GetDynamicResourcesAsync(List contributors) { var context = CreateBundleConfigurationContext(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleManager.cs index b4ce56cb62..067554e102 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleManager.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleManager.cs @@ -5,7 +5,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; public interface IBundleManager { - Task> GetStyleBundleFilesAsync(string bundleName); + Task> GetStyleBundleFilesAsync(string bundleName); - Task> GetScriptBundleFilesAsync(string bundleName); + Task> GetScriptBundleFilesAsync(string bundleName); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs index a247022674..1926cb48cf 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs @@ -3,10 +3,12 @@ using Microsoft.AspNetCore.Razor.TagHelpers; using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -63,18 +65,24 @@ public abstract class AbpTagHelperResourceService : ITransientDependency foreach (var bundleFile in bundleFiles) { - var file = HostingEnvironment.WebRootFileProvider.GetFileInfo(bundleFile); - - if (file == null || !file.Exists) + if (bundleFile.IsCdn) { - Logger.LogError($"Could not find the bundle file '{bundleFile}' for the bundle '{bundleName}'!"); - AddErrorScript(viewContext, tagHelper, context, output, bundleFile, bundleName!); - continue; + AddHtmlTag(viewContext, tagHelper, context, output, bundleFile, null); } - - if (file.Length > 0) + else { - AddHtmlTag(viewContext, tagHelper, context, output, bundleFile + "?_v=" + file.LastModified.UtcTicks); + var file = HostingEnvironment.WebRootFileProvider.GetFileInfo(bundleFile.File); + if (file == null || !file.Exists) + { + Logger.LogError($"Could not find the bundle file '{bundleFile.File}' for the bundle '{bundleName}'!"); + AddErrorScript(viewContext, tagHelper, context, output, bundleFile, bundleName!); + continue; + } + + if (file.Length > 0) + { + AddHtmlTag(viewContext, tagHelper, context, output, bundleFile, file); + } } } @@ -84,13 +92,13 @@ public abstract class AbpTagHelperResourceService : ITransientDependency protected abstract void CreateBundle(string bundleName, List bundleItems); - protected abstract Task> GetBundleFilesAsync(string bundleName); + protected abstract Task> GetBundleFilesAsync(string bundleName); - protected abstract void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file); + protected abstract void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, BundleFile file, IFileInfo? fileInfo = null); - protected virtual void AddErrorScript(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file, string bundleName) + protected virtual void AddErrorScript(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, BundleFile file, string bundleName) { - output.Content.AppendHtml($"{Environment.NewLine}"); + output.Content.AppendHtml($"{Environment.NewLine}"); } protected virtual string GenerateBundleName(List bundleItems) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs index 260f28f812..ec6ed2c99c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; @@ -32,12 +33,12 @@ public class AbpTagHelperScriptService : AbpTagHelperResourceService ); } - protected override async Task> GetBundleFilesAsync(string bundleName) + protected override async Task> GetBundleFilesAsync(string bundleName) { return await BundleManager.GetScriptBundleFilesAsync(bundleName); } - protected override void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file) + protected override void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, BundleFile file, IFileInfo? fileInfo = null) { var defer = tagHelper switch { @@ -46,12 +47,14 @@ public class AbpTagHelperScriptService : AbpTagHelperResourceService _ => false }; - var deferText = (defer || Options.DeferScriptsByDefault || Options.DeferScripts.Any(x => file.StartsWith(x, StringComparison.OrdinalIgnoreCase))) + var deferText = (defer || Options.DeferScriptsByDefault || Options.DeferScripts.Any(x => file.File.StartsWith(x, StringComparison.OrdinalIgnoreCase))) ? "defer" : string.Empty; var nonceText = (viewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString)) ? $"nonce=\"{nonceString}\"" : string.Empty; - output.Content.AppendHtml($"{Environment.NewLine}"); + + var src = file.IsCdn ? file.File : viewContext.GetUrlHelper().Content((file.File + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); + output.Content.AppendHtml($"{Environment.NewLine}"); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs index 0f68433f4c..a43221841e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Security; @@ -18,7 +19,7 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService public AbpTagHelperStyleService( IBundleManager bundleManager, IOptions options, - IWebHostEnvironment hostingEnvironment, + IWebHostEnvironment hostingEnvironment, IOptions securityHeadersOptions) : base( bundleManager, options, @@ -36,12 +37,12 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService ); } - protected override async Task> GetBundleFilesAsync(string bundleName) + protected override async Task> GetBundleFilesAsync(string bundleName) { return await BundleManager.GetStyleBundleFilesAsync(bundleName); } - protected override void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file) + protected override void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, BundleFile file, IFileInfo? fileInfo = null) { var preload = tagHelper switch { @@ -50,15 +51,16 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService _ => false }; - if (preload || Options.PreloadStylesByDefault || Options.PreloadStyles.Any(x => file.StartsWith(x, StringComparison.OrdinalIgnoreCase))) + var href = file.IsCdn ? file.File : viewContext.GetUrlHelper().Content((file.File + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); + if (preload || Options.PreloadStylesByDefault || Options.PreloadStyles.Any(x => file.File.StartsWith(x, StringComparison.OrdinalIgnoreCase))) { output.Content.AppendHtml(SecurityHeadersOptions.UseContentSecurityPolicyScriptNonce - ? $"{Environment.NewLine}" - : $"{Environment.NewLine}"); + ? $"{Environment.NewLine}" + : $"{Environment.NewLine}"); } else { - output.Content.AppendHtml($"{Environment.NewLine}"); + output.Content.AppendHtml($"{Environment.NewLine}"); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs index 2eefbcc0dd..9479dec8ce 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; namespace Volo.Abp.AspNetCore.Mvc.UI.Resources; public interface IWebRequestResources { - List TryAdd(List resources); + List TryAdd(List resources); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs index 3e046499ab..e68b671fc6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs @@ -1,23 +1,24 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.DependencyInjection; namespace Volo.Abp.AspNetCore.Mvc.UI.Resources; public class WebRequestResources : IWebRequestResources, IScopedDependency { - protected Dictionary> Resources { get; } + protected Dictionary> Resources { get; } protected IHttpContextAccessor HttpContextAccessor { get; } public WebRequestResources(IHttpContextAccessor httpContextAccessor) { HttpContextAccessor = httpContextAccessor; - Resources = new Dictionary>(); + Resources = new Dictionary>(); } - public List TryAdd(List resources) + public List TryAdd(List resources) { var path = HttpContextAccessor.HttpContext?.Request?.Path ?? ""; From aa1d939200f79993f23f17f148e15e22e1c8ae6b Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 17 Oct 2023 20:12:06 +0800 Subject: [PATCH 03/46] Some minor improvements. --- .../Mvc/UI/Bundling/BundleManager.cs | 28 ++++++++----------- .../TagHelpers/AbpTagHelperScriptService.cs | 7 ++--- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs index 5a46e1ff5a..fb7b3cd489 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs @@ -67,10 +67,9 @@ public class BundleManager : IBundleManager, ITransientDependency protected virtual async Task> GetBundleFilesAsync(BundleConfigurationCollection bundles, string bundleName, IBundler bundler) { - var result = new List(); + var files = new List(); var contributors = GetContributors(bundles, bundleName); - var bundleFiles = RequestResources.TryAdd(await GetBundleFilesAsync(contributors)); var dynamicResources = RequestResources.TryAdd(await GetDynamicResourcesAsync(contributors)); @@ -82,31 +81,28 @@ public class BundleManager : IBundleManager, ITransientDependency var localBundleFiles = new List(); foreach (var bundleFile in bundleFiles) { - if (bundleFile.IsCdn) + if (!bundleFile.IsCdn) + { + localBundleFiles.Add(bundleFile.File); + } + else { - if (localBundleFiles.Any()) + if (localBundleFiles.Count != 0) { - var cacheItem = AddToBundleCache(bundleName, bundler, localBundleFiles); - result.AddRange(cacheItem.Files); + files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files); localBundleFiles.Clear(); } - result.Add(bundleFile); - } - else - { - localBundleFiles.Add(bundleFile.File); + files.Add(bundleFile); } } - if (localBundleFiles.Any()) + if (localBundleFiles.Count != 0) { - var cacheItem = AddToBundleCache(bundleName, bundler, localBundleFiles); - result.AddRange(cacheItem.Files); - localBundleFiles.Clear(); + files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files); } - return result.Union(dynamicResources).ToImmutableList(); + return files.Union(dynamicResources).ToImmutableList(); } private BundleCacheItem AddToBundleCache(string bundleName, IBundler bundler, List bundleFiles) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs index ec6ed2c99c..3ff8e05552 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs @@ -48,13 +48,12 @@ public class AbpTagHelperScriptService : AbpTagHelperResourceService }; var deferText = (defer || Options.DeferScriptsByDefault || Options.DeferScripts.Any(x => file.File.StartsWith(x, StringComparison.OrdinalIgnoreCase))) - ? "defer" + ? "defer " : string.Empty; var nonceText = (viewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString)) - ? $"nonce=\"{nonceString}\"" + ? $"nonce=\"{nonceString}\" " : string.Empty; - var src = file.IsCdn ? file.File : viewContext.GetUrlHelper().Content((file.File + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); - output.Content.AppendHtml($"{Environment.NewLine}"); + output.Content.AppendHtml($"{Environment.NewLine}"); } } From 519b300ef2fde8e78c39cd4147c095590363c137 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 18 Oct 2023 09:50:47 +0800 Subject: [PATCH 04/46] Add `is-cdn` to script/style taghelper. --- .../Bundling/BundleConfigurationExtensions.cs | 6 ++++ .../BundleContributorCollectionExtensions.cs | 6 ++++ .../AspNetCore/Mvc/UI/Bundling/BundleFile.cs | 14 ++++++-- .../Mvc/UI/Bundling/BundleFileContributor.cs | 2 +- .../UI/Bundling/BundleFileListExtensions.cs | 32 ------------------- .../Mvc/UI/Bundling/BundleManager.cs | 2 +- .../TagHelpers/AbpBundleItemTagHelper.cs | 6 +++- .../TagHelpers/AbpTagHelperResourceService.cs | 6 ++-- .../TagHelpers/AbpTagHelperScriptService.cs | 4 +-- .../TagHelpers/AbpTagHelperStyleService.cs | 4 +-- .../TagHelpers/BundleTagHelperFileItem.cs | 6 ++-- .../SharedThemeGlobalScriptContributor.cs | 22 ++++++------- .../SharedThemeGlobalStyleContributor.cs | 8 ++--- 13 files changed, 55 insertions(+), 63 deletions(-) delete mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileListExtensions.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs index ca628f4d67..8dd51dc5a3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs @@ -11,6 +11,12 @@ public static class BundleConfigurationExtensions return bundleConfiguration; } + public static BundleConfiguration AddFiles(this BundleConfiguration bundleConfiguration, params BundleFile[] files) + { + bundleConfiguration.Contributors.AddFiles(files); + return bundleConfiguration; + } + public static BundleConfiguration AddCdnFiles(this BundleConfiguration bundleConfiguration, params string[] files) { bundleConfiguration.Contributors.AddCdnFiles(files.Select(x => new BundleFile(x, true)).ToArray()); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs index 3c0dfdeab8..22bfe2947c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs @@ -6,6 +6,12 @@ public static class BundleContributorCollectionExtensions { contributors.Add(new BundleFileContributor(files)); } + + public static void AddFiles(this BundleContributorCollection contributors, params BundleFile[] files) + { + contributors.Add(new BundleFileContributor(files)); + } + public static void AddCdnFiles(this BundleContributorCollection contributors, params BundleFile[] files) { contributors.Add(new BundleFileContributor(files)); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs index 58427b6c40..091a4b3823 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs @@ -2,13 +2,21 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; public class BundleFile { - public string File { get; set; } + public string FileName { get; set; } public bool IsCdn { get; set; } - public BundleFile(string file, bool isCdn = false) + public BundleFile(string fileName, bool isCdn = false) { - File = file; + FileName = fileName; IsCdn = isCdn; } + + /// + /// This method is used to compatible with old code. + /// + public static implicit operator BundleFile(string fileName) + { + return new BundleFile(fileName); + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs index 5107c006a0..df55266110 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs @@ -24,7 +24,7 @@ public class BundleFileContributor : BundleContributor { foreach (var file in Files) { - context.Files.AddIfNotContains(x => x.File.Equals(file.File, StringComparison.OrdinalIgnoreCase), () => file); + context.Files.AddIfNotContains(x => x.FileName.Equals(file.FileName, StringComparison.OrdinalIgnoreCase), () => file); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileListExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileListExtensions.cs deleted file mode 100644 index b3088b293d..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileListExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -/// -/// This class is used to compatible with old code. -/// -public static class BundleFileListExtensions -{ - public static void Add(this List bundleFiles, params string[] files) - { - bundleFiles.AddRange(files.Select(file => new BundleFile(file))); - } - - public static void AddRange(this List bundleFiles, params string[] files) - { - bundleFiles.AddRange(files.Select(file => new BundleFile(file))); - } - - public static void AddIfNotContains(this List bundleFiles, params string[] files) - { - foreach (var file in files) - { - if (!bundleFiles.Any(x => x.File.Equals(file, StringComparison.OrdinalIgnoreCase))) - { - bundleFiles.Add(new BundleFile(file)); - } - } - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs index fb7b3cd489..9e5ca4bdef 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs @@ -83,7 +83,7 @@ public class BundleManager : IBundleManager, ITransientDependency { if (!bundleFile.IsCdn) { - localBundleFiles.Add(bundleFile.File); + localBundleFiles.Add(bundleFile.FileName); } else { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs index 1240dbef11..81c06df7c3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers; namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers; @@ -17,6 +18,9 @@ public abstract class AbpBundleItemTagHelper : Ab /// public Type? Type { get; set; } + [HtmlAttributeName("is-cdn")] + public bool IsCdn { get; set; } + protected AbpBundleItemTagHelper(TTagHelperService service) : base(service) { @@ -49,7 +53,7 @@ public abstract class AbpBundleItemTagHelper : Ab if (Src != null) { - return new BundleTagHelperFileItem(Src); + return new BundleTagHelperFileItem(new BundleFile(Src, IsCdn)); } throw new AbpException("abp-script tag helper requires to set either src or type!"); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs index 1926cb48cf..5a19dee915 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs @@ -71,10 +71,10 @@ public abstract class AbpTagHelperResourceService : ITransientDependency } else { - var file = HostingEnvironment.WebRootFileProvider.GetFileInfo(bundleFile.File); + var file = HostingEnvironment.WebRootFileProvider.GetFileInfo(bundleFile.FileName); if (file == null || !file.Exists) { - Logger.LogError($"Could not find the bundle file '{bundleFile.File}' for the bundle '{bundleName}'!"); + Logger.LogError($"Could not find the bundle file '{bundleFile.FileName}' for the bundle '{bundleName}'!"); AddErrorScript(viewContext, tagHelper, context, output, bundleFile, bundleName!); continue; } @@ -98,7 +98,7 @@ public abstract class AbpTagHelperResourceService : ITransientDependency protected virtual void AddErrorScript(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, BundleFile file, string bundleName) { - output.Content.AppendHtml($"{Environment.NewLine}"); + output.Content.AppendHtml($"{Environment.NewLine}"); } protected virtual string GenerateBundleName(List bundleItems) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs index 3ff8e05552..ea0f11b777 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs @@ -47,13 +47,13 @@ public class AbpTagHelperScriptService : AbpTagHelperResourceService _ => false }; - var deferText = (defer || Options.DeferScriptsByDefault || Options.DeferScripts.Any(x => file.File.StartsWith(x, StringComparison.OrdinalIgnoreCase))) + var deferText = (defer || Options.DeferScriptsByDefault || Options.DeferScripts.Any(x => file.FileName.StartsWith(x, StringComparison.OrdinalIgnoreCase))) ? "defer " : string.Empty; var nonceText = (viewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString)) ? $"nonce=\"{nonceString}\" " : string.Empty; - var src = file.IsCdn ? file.File : viewContext.GetUrlHelper().Content((file.File + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); + var src = file.IsCdn ? file.FileName : viewContext.GetUrlHelper().Content((file.FileName + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); output.Content.AppendHtml($"{Environment.NewLine}"); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs index a43221841e..63f2e2304d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs @@ -51,8 +51,8 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService _ => false }; - var href = file.IsCdn ? file.File : viewContext.GetUrlHelper().Content((file.File + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); - if (preload || Options.PreloadStylesByDefault || Options.PreloadStyles.Any(x => file.File.StartsWith(x, StringComparison.OrdinalIgnoreCase))) + var href = file.IsCdn ? file.FileName : viewContext.GetUrlHelper().Content((file.FileName + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); + if (preload || Options.PreloadStylesByDefault || Options.PreloadStyles.Any(x => file.FileName.StartsWith(x, StringComparison.OrdinalIgnoreCase))) { output.Content.AppendHtml(SecurityHeadersOptions.UseContentSecurityPolicyScriptNonce ? $"{Environment.NewLine}" diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/BundleTagHelperFileItem.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/BundleTagHelperFileItem.cs index 45ab8b64eb..b5f6ca74aa 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/BundleTagHelperFileItem.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/BundleTagHelperFileItem.cs @@ -5,16 +5,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers; public class BundleTagHelperFileItem : BundleTagHelperItem { [NotNull] - public string File { get; } + public BundleFile File { get; } - public BundleTagHelperFileItem([NotNull] string file) + public BundleTagHelperFileItem([NotNull] BundleFile file) { File = Check.NotNull(file, nameof(file)); } public override string ToString() { - return File; + return File.FileName; } public override void AddToConfiguration(BundleConfiguration configuration) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs index 2ac299959a..2ca7c191c8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs @@ -35,20 +35,20 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; )] public class SharedThemeGlobalScriptContributor : BundleContributor { - + public override void ConfigureBundle(BundleConfigurationContext context) { - context.Files.AddRange(new[] + context.Files.AddRange(new BundleFile[] { - "/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery-form/jquery-form-extensions.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/sweetalert2/abp-sweetalert2.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js" + "/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery-form/jquery-form-extensions.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/sweetalert2/abp-sweetalert2.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js" }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs index 01f490425c..f959d73f05 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs @@ -27,10 +27,10 @@ public class SharedThemeGlobalStyleContributor : BundleContributor { public override void ConfigureBundle(BundleConfigurationContext context) { - context.Files.AddRange(new[] + context.Files.AddRange(new BundleFile[] { - "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-styles.css", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-styles.css" - }); + "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-styles.css", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-styles.css" + }); } } From 8df549aed9a64c59ceb2d9f6af878bc2aed3d355 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 18 Oct 2023 10:22:34 +0800 Subject: [PATCH 05/46] Update `Bundling-Minification.md`. --- .../en/UI/AspNetCore/Bundling-Minification.md | 77 ++++++++++++++++++ .../UI/AspNetCore/Bundling-Minification.md | 78 +++++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/docs/en/UI/AspNetCore/Bundling-Minification.md b/docs/en/UI/AspNetCore/Bundling-Minification.md index dd2282c883..650d3e3e77 100644 --- a/docs/en/UI/AspNetCore/Bundling-Minification.md +++ b/docs/en/UI/AspNetCore/Bundling-Minification.md @@ -382,6 +382,83 @@ Configure(options => ```` +### CDN Support + +You can configure `AbpBundlingOptions` or use `is-cdn` on `script/style` taghelper to load all or a single js/css file from CDN. + +#### Add CDN files in `AbpBundlingOptions` + +````csharp +Configure(options => +{ + options.StyleBundles + .Add("MyStyleBundle", configuration => + { + configuration + .AddFiles("/styles/my-style1.css") + .AddFiles("/styles/my-style2.css") + .AddCdnFiles("https://cdn.abp.io/bootstrap.css") + .AddFiles("/styles/my-style3.css") + .AddFiles("/styles/my-style4.css"); + }); + + options.ScriptBundles + .Add("MyScriptBundle", configuration => + { + configuration + .AddFiles("/scripts/my-script1.js") + .AddFiles("/scripts/my-script2.js") + .AddCdnFiles("https://cdn.abp.io/bootstrap.js") + .AddFiles("/scripts/my-script3.js") + .AddFiles("/scripts/my-script4.js"); + }); +}); +```` + +**Output HTML:** + +````html + + + + + + + +```` + +#### Add CDN files in Tag Helpers. + +````html + + + + + + + + + + + + + + + +```` + +**Output HTML:** + +````html + + + + + + + +```` + ## Themes Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more. diff --git a/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md b/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md index 338afe8967..fab0893a76 100644 --- a/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md +++ b/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md @@ -353,6 +353,84 @@ services.Configure(options => }); ```` +### CDN 支持 + +你可以配置`AbpBundlingOptions`或在`script/style` taghelper上使用`is-cdn`来从CDN加载js/css. + +#### 在`AbpBundlingOptions`中添加CDN文件 + +````csharp +Configure(options => +{ + options.StyleBundles + .Add("MyStyleBundle", configuration => + { + configuration + .AddFiles("/styles/my-style1.css") + .AddFiles("/styles/my-style2.css") + .AddCdnFiles("https://cdn.abp.io/bootstrap.css") + .AddFiles("/styles/my-style3.css") + .AddFiles("/styles/my-style4.css"); + }); + + options.ScriptBundles + .Add("MyScriptBundle", configuration => + { + configuration + .AddFiles("/scripts/my-script1.js") + .AddFiles("/scripts/my-script2.js") + .AddCdnFiles("https://cdn.abp.io/bootstrap.js") + .AddFiles("/scripts/my-script3.js") + .AddFiles("/scripts/my-script4.js"); + }); +}); +```` + +**输出HTMl:** + +````html + + + + + + + +```` + +#### 在TagHelpers中添加CDN文件 + +````html + + + + + + + + + + + + + + + +```` + +**输出HTMl:** + +````html + + + + + + + +```` + + ### 主题 主题使用标准包贡献者将库资源添加到页面布局. 主题还可以定义一些标准/全局包, 因此任何模块都可以为这些标准/全局包做出贡献. 有关更多信息, 请参阅[主题文档](Theming.md). From cde7446d325def8730552d53133d90db64bca7c3 Mon Sep 17 00:00:00 2001 From: Huang Tao Date: Fri, 27 Oct 2023 16:02:10 +0800 Subject: [PATCH 06/46] performance: multiple attach on Update with EfCore --- .../Repositories/EntityFrameworkCore/EfCoreRepository.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 3274360ba2..d73e18615d 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 @@ -161,16 +161,17 @@ public class EfCoreRepository : RepositoryBase, IE { var dbContext = await GetDbContextAsync(); - dbContext.Attach(entity); - - var updatedEntity = dbContext.Update(entity).Entity; + if (dbContext.Set().Local.All(e => e != entity)) + { + dbContext.Update(entity); + } if (autoSave) { await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken)); } - return updatedEntity; + return entity; } public async override Task UpdateManyAsync(IEnumerable entities, bool autoSave = false, CancellationToken cancellationToken = default) From 21c218641ee7d480da864a8b428563e47c44fab3 Mon Sep 17 00:00:00 2001 From: htve Date: Fri, 27 Oct 2023 17:30:29 +0800 Subject: [PATCH 07/46] Fix error test caused by multiple attach --- .../Volo/Abp/Auditing/Auditing_Tests.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index 3aa0da5a9b..4ac434d96c 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs @@ -409,16 +409,13 @@ public class Auditing_Tests : AbpAuditingTestBase } #pragma warning disable 4014 - AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 && x.EntityChanges[0].ChangeType == EntityChangeType.Updated && - x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && - - x.EntityChanges[1].ChangeType == EntityChangeType.Updated && - x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && - x.EntityChanges[1].PropertyChanges.Count == 1 && - x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObjectAddress.Country) && - x.EntityChanges[1].PropertyChanges[0].OriginalValue == "\"England\"" && - x.EntityChanges[1].PropertyChanges[0].NewValue == "\"Germany\"")); + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && + x.EntityChanges[0].PropertyChanges.Count == 1 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObjectAddress.Country) && + x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"England\"" && + x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Germany\"")); #pragma warning restore 4014 } From 0ba1d5cdf180ddf0a1c633d2f6e3b3fcad80b1f8 Mon Sep 17 00:00:00 2001 From: linch90 Date: Mon, 30 Oct 2023 00:02:18 +0800 Subject: [PATCH 08/46] Add `ColumnVisible` to EntityProp for show/hide the whole table column, as the `Visible` only show or hide the cell content, not the whole column. --- .../extensible-table.component.html | 25 ++++++++++--------- .../extensions/src/lib/models/entity-props.ts | 13 ++++++++-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html index 2d7105ae52..59bb0d1d6a 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html @@ -24,23 +24,24 @@ - - - {{column.name}} - - - {{column.name}} + + + {{ column.name }} + + + {{ column.name }} + - diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/entity-props.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/entity-props.ts index e376fe44df..e86e3ae34e 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/entity-props.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/entity-props.ts @@ -1,5 +1,5 @@ import { ABP, escapeHtmlChars } from '@abp/ng.core'; -import { Type } from '@angular/core'; +import { InjectFlags, InjectOptions, InjectionToken, Type } from '@angular/core'; import { Observable, of } from 'rxjs'; import { O } from 'ts-toolbelt'; import { ActionCallback } from './actions'; @@ -31,6 +31,7 @@ export class EntityProp extends Prop { readonly component?: Type; readonly enumList?: Array>; readonly tooltip?: string; + readonly columnVisible: ColumnPredicate; constructor(options: EntityPropOptions) { super( @@ -41,7 +42,8 @@ export class EntityProp extends Prop { options.visible, options.isExtra, ); - + + this.columnVisible = options.columnVisible || (() => true); this.columnWidth = options.columnWidth; this.sortable = options.sortable || false; this.valueResolver = @@ -72,6 +74,7 @@ export type EntityPropOptions = O.Optional< O.Writable>, | 'permission' | 'visible' + | 'columnVisible' | 'displayName' | 'isExtra' | 'columnWidth' @@ -85,4 +88,10 @@ export type EntityPropOptions = O.Optional< export type EntityPropDefaults = Record[]>; export type EntityPropContributorCallback = PropContributorCallback>; export type EntityPropContributorCallbacks = PropContributorCallbacks>; +export type ColumnPredicate = (getInjected: GetInjected, auxData?: any) => boolean; +export type GetInjected = ( + token: Type | InjectionToken, + notFoundValue?: T, + options?: InjectOptions | InjectFlags, +) => T; type PropDataObject = { [key: string]: any }; From 6cb222461169481840845084cc730ca1829e78a1 Mon Sep 17 00:00:00 2001 From: htve Date: Mon, 30 Oct 2023 13:26:33 +0800 Subject: [PATCH 09/46] Fix missing Attach in EfCoreRepository.UpdateAsync --- .../Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs | 1 + 1 file changed, 1 insertion(+) 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 d73e18615d..799397218c 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 @@ -163,6 +163,7 @@ public class EfCoreRepository : RepositoryBase, IE if (dbContext.Set().Local.All(e => e != entity)) { + dbContext.Set().Attach(entity); dbContext.Update(entity); } From 42df954932693ea345f3ba7d5cabf16f5289c087 Mon Sep 17 00:00:00 2001 From: Volosoft Agent <43883821+voloagent@users.noreply.github.com> Date: Mon, 30 Oct 2023 09:06:14 +0300 Subject: [PATCH 10/46] Update latest-versions.json to version 7.4.1 --- latest-versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latest-versions.json b/latest-versions.json index 7823e6af9d..e2911011e7 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,6 +1,6 @@ [ { - "version": "7.4.0", + "version": "7.4.1", "releaseDate": "", "type": "stable", "message": "" From ab170e3821c8f3781e78d3ce8b34c47718b6db93 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 30 Oct 2023 16:47:26 +0800 Subject: [PATCH 11/46] Update Auditing_Tests.cs --- .../Volo/Abp/Auditing/Auditing_Tests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index 4ac434d96c..42317376cc 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs @@ -409,7 +409,10 @@ public class Auditing_Tests : AbpAuditingTestBase } #pragma warning disable 4014 - AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 && + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + x.EntityChanges[1].ChangeType == EntityChangeType.Updated && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && + x.EntityChanges[0].ChangeType == EntityChangeType.Updated && x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && x.EntityChanges[0].PropertyChanges.Count == 1 && From b663efdb289d635fcc59b3d49dee50fd050b76a2 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 31 Oct 2023 11:27:53 +0800 Subject: [PATCH 12/46] Add more unittests for entity changes. --- .../EntityHistory/EntityHistoryHelper.cs | 37 +++- .../App/Entities/AppEntityWithNavigations.cs | 51 ++++++ .../AbpAuditingTestDbContext.cs | 11 ++ .../Volo/Abp/Auditing/Auditing_Tests.cs | 160 +++++++++++++++++- 4 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index cb97111c59..dc3754be3b 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -85,8 +85,10 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency case EntityState.Modified: changeType = IsDeleted(entityEntry) ? EntityChangeType.Deleted : EntityChangeType.Updated; break; - case EntityState.Detached: case EntityState.Unchanged: + changeType = EntityChangeType.Updated; // Navigation property changes. + break; + case EntityState.Detached: default: return null; } @@ -184,6 +186,21 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency } } + if (entityEntry.State == EntityState.Unchanged) + { + foreach (var navigation in entityEntry.Navigations) + { + if (navigation.IsModified || (navigation is ReferenceEntry && navigation.As().TargetEntry?.State == EntityState.Modified)) + { + propertyChanges.Add(new EntityPropertyChangeInfo + { + PropertyName = navigation.Metadata.Name, + PropertyTypeFullName = navigation.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName! + }); + } + } + } + return propertyChanges; } @@ -205,12 +222,26 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency protected virtual bool ShouldSaveEntityHistory(EntityEntry entityEntry, bool defaultValue = false) { - if (entityEntry.State == EntityState.Detached || - entityEntry.State == EntityState.Unchanged) + if (entityEntry.State == EntityState.Detached) { return false; } + if (entityEntry.State == EntityState.Unchanged) + { + if (entityEntry.Navigations.Any(navigationEntry => navigationEntry.IsModified)) + { + return true; + } + + if (entityEntry.Navigations.Where(x => x is ReferenceEntry).Cast().Any(x => x.TargetEntry != null && x.TargetEntry.State == EntityState.Modified)) + { + return true; + } + + return false; + } + var entityType = entityEntry.Metadata.ClrType; if (!EntityHelper.IsEntity(entityType) && !EntityHelper.IsValueObject(entityType)) diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs new file mode 100644 index 0000000000..e68d9d94b8 --- /dev/null +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.Auditing.App.Entities; + +[Audited] +public class AppEntityWithNavigations : AggregateRoot +{ + protected AppEntityWithNavigations() + { + + } + + public AppEntityWithNavigations(Guid id, string name) + : base(id) + { + Name = name; + FullName = name; + } + + public string Name { get; set; } + + public string FullName { get; set; } + + public virtual AppEntityWithNavigationChildOneToOne OneToOne { get; set; } + + public virtual List OneToMany { get; set; } + + public virtual List ManyToMany { get; set; } +} + +[Audited] +public class AppEntityWithNavigationChildOneToOne : Entity +{ + public string ChildName { get; set; } +} + +[Audited] +public class AppEntityWithNavigationChildOneToMany : Entity +{ + public Guid AppEntityWithNavigationId { get; set; } + + public string ChildName { get; set; } +} + +[Audited] +public class AppEntityWithNavigationChildManyToMany : Entity +{ + public string ChildName { get; set; } +} diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs index 8a0a798598..f6d4bf49ef 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs @@ -27,6 +27,8 @@ public class AbpAuditingTestDbContext : AbpDbContext public DbSet AppEntityWithValueObject { get; set; } + public DbSet AppEntityWithNavigations { get; set; } + public AbpAuditingTestDbContext(DbContextOptions options) : base(options) { @@ -42,5 +44,14 @@ public class AbpAuditingTestDbContext : AbpDbContext b.ConfigureByConvention(); b.OwnsOne(v => v.AppEntityWithValueObjectAddress); }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasOne(x => x.OneToOne).WithOne().HasForeignKey(x => x.Id); + b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId); + b.HasMany(x => x.ManyToMany).WithMany(); + }); + } } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index 42317376cc..7154fd805c 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs @@ -410,15 +410,173 @@ public class Auditing_Tests : AbpAuditingTestBase #pragma warning disable 4014 AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + x.EntityChanges[0].ChangeType == EntityChangeType.Updated && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && + x.EntityChanges[0].PropertyChanges.Count == 1 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObjectAddress.Country) && + x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"England\"" && + x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Germany\"" && + x.EntityChanges[1].ChangeType == EntityChangeType.Updated && x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && + x.EntityChanges[1].PropertyChanges.Count == 1 && + x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObject.AppEntityWithValueObjectAddress))); + +#pragma warning restore 4014 + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + entity.AppEntityWithValueObjectAddress = null; + + await repository.UpdateAsync(entity); + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && x.EntityChanges[0].ChangeType == EntityChangeType.Updated && x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && x.EntityChanges[0].PropertyChanges.Count == 1 && x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObjectAddress.Country) && x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"England\"" && - x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Germany\"")); + x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Germany\"" && + + x.EntityChanges[1].ChangeType == EntityChangeType.Updated && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName)); + +#pragma warning restore 4014 + } + + [Fact] + public virtual async Task Should_Write_AuditLog_For_Navigations_Changes() + { + var entityId = Guid.NewGuid(); + var repository = ServiceProvider.GetRequiredService>(); + await repository.InsertAsync(new AppEntityWithNavigations(entityId, "test name")); + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.FullName = "test full name"; + + await repository.UpdateAsync(entity); + + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 && + x.EntityChanges[0].ChangeType == EntityChangeType.Updated && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && + x.EntityChanges[0].PropertyChanges.Count == 1 && + x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"test name\"" && + x.EntityChanges[0].PropertyChanges[0].NewValue == "\"test full name\"" && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.FullName) && + x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(string).FullName)); +#pragma warning restore 4014 + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.OneToOne = new AppEntityWithNavigationChildOneToOne + { + ChildName = "ChildName" + }; + + await repository.UpdateAsync(entity); + + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + x.EntityChanges[0].ChangeType == EntityChangeType.Created && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName && + x.EntityChanges[1].ChangeType == EntityChangeType.Updated && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && + x.EntityChanges[1].PropertyChanges.Count == 1 && + x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) && + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName)); +#pragma warning restore 4014 + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.OneToMany = new List() + { + new AppEntityWithNavigationChildOneToMany + { + AppEntityWithNavigationId = entity.Id, + ChildName = "ChildName1" + } + }; + + await repository.UpdateAsync(entity); + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + x.EntityChanges[0].ChangeType == EntityChangeType.Created && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName && + x.EntityChanges[1].ChangeType == EntityChangeType.Updated && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && + x.EntityChanges[1].PropertyChanges.Count == 1 && + x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) && + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName)); + +#pragma warning restore 4014 + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.ManyToMany = new List() + { + new AppEntityWithNavigationChildManyToMany + { + ChildName = "ChildName1" + } + }; + + await repository.UpdateAsync(entity); + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + x.EntityChanges[0].ChangeType == EntityChangeType.Created && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildManyToMany).FullName && + x.EntityChanges[1].ChangeType == EntityChangeType.Updated && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && + x.EntityChanges[1].PropertyChanges.Count == 1 && + x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) && + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName)); #pragma warning restore 4014 } From a3e1bed6eb274a428aa52de07e698867d39b4c4c Mon Sep 17 00:00:00 2001 From: Salih Date: Tue, 31 Oct 2023 09:29:27 +0300 Subject: [PATCH 13/46] Add JSON Column Enhancements article --- .../POST.md | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md diff --git a/docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md b/docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md new file mode 100644 index 0000000000..db8f72ca17 --- /dev/null +++ b/docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md @@ -0,0 +1,124 @@ +# EF Core 8 - Enhancements to JSON column mapping + +In this article, we will examine the enhancements introduced in EF Core 8 for the JSON column feature, building upon the foundation laid by [JSON columns in Entity Framework Core 7](https://community.abp.io/posts/json-columns-in-entity-framework-core-7-cjaom76j). + +## The entity classes we will be using in the article + +```csharp +public class Person +{ + public int Id { get; set; } + [Required] + public string Name { get; set; } + [Required] + public ContactDetails ContactDetails { get; set; } +} + +public class ContactDetails +{ + public List
Addresses { get; set; } = new(); + public string? Phone { get; set; } +} + +public class Address +{ + public Address(string street, string city, string postcode, string country) + { + Street = street; + City = city; + Postcode = postcode; + Country = country; + } + + public string Street { get; set; } + public string City { get; set; } + public string Postcode { get; set; } + public string Country { get; set; } + public bool IsMainAddress { get; set; } +} +``` + +## The DbContext class we will be using in the article + +```csharp +public class AppDbContext : DbContext +{ + public DbSet Persons { get; set; } = null!; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { +#if SQLSERVER + optionsBuilder.UseSqlServer("Server=localhost;Database=EfCore8Json;Trusted_Connection=True;TrustServerCertificate=True"); +#elif SQLITE + optionsBuilder.UseSqlite("Data Source=EfCore8Json.db"); +#endif + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(b => + { + b.ToTable("Persons"); + b.HasKey(x => x.Id); + b.Property(x => x.Name).IsRequired(); + b.OwnsOne(x => x.ContactDetails, cb => + { + cb.ToJson(); + cb.Property(x => x.Phone); + cb.OwnsMany(x => x.Addresses); + }); + }); + + base.OnModelCreating(modelBuilder); + } +} +``` + +## Translate element access into JSON arrays + +EF Core 8 supports indexing in JSON arrays when executing queries. For example, the following query returns individuals whose first address is the main address in the database: + +```csharp +var query = dbContext.Persons + .Select(x => x.ContactDetails.Addresses[0]) + .Where(x => x.IsMainAddress == true) + .ToListAsync(); +``` + +The generated SQL query is as follows when using SQL Server: + +```sql +SELECT JSON_QUERY([p].[ContactDetails], '$.Addresses[0]'), [p].[Id] +FROM [Persons] AS [p] +WHERE CAST(JSON_VALUE([p].[ContactDetails], '$.Addresses[0].IsMainAddress') AS bit) = CAST(1 AS bit) +``` + +> Note: If you attempt to access an index that is outside of the array, it will return null. + +## JSON Columns for SQLite + +In EF Core 7, JSON column mapping was supported for Azure SQL/SQL Server. In EF Core 8, this support has been extended to include SQLite as well. + +### Queries into JSON columns + +The following query returns individuals whose first address is the main address in the database: + +```csharp +var query = dbContext.Persons + .Select(x => x.ContactDetails.Addresses[0]) + .Where(x => x.IsMainAddress == true) + .ToListAsync(); +``` + +The generated SQL query is as follows when using SQLite: + +```sql +SELECT "p"."ContactDetails" ->> '$.Addresses[0]', "p"."Id" +FROM "Persons" AS "p" +WHERE "p"."ContactDetails" ->> '$.Addresses[0].IsMainAddress' = 0 +``` + +## References + +- [EF Core 8 - Enhancements to JSON column mapping](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#enhancements-to-json-column-mapping) \ No newline at end of file From 19a74fc9ddbd39e417f98772096f89fd5985820f Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 31 Oct 2023 17:12:30 +0800 Subject: [PATCH 14/46] Add New Minimal APIs features in ASP.NET Core 8.0 article --- .../POST.md | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md diff --git a/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md b/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md new file mode 100644 index 0000000000..2a8f625e09 --- /dev/null +++ b/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md @@ -0,0 +1,156 @@ +# New Minimal APIs features in ASP.NET Core 8.0 + +In this article, we will see the new features of Minimal APIs in ASP.NET Core 8.0. + +## Binding to forms + +We can bind to forms using the [FromForm] attribute. Let's see an example: + +```csharp +app.MapPost("/books", async ([FromForm] string name, + [FromForm] BookType bookType, IFormFile? cover, BookDb db) => +{ + var book = new Book + { + Name = name, + BookType = bookType + }; + + if (cover is not null) + { + var coverName = Path.GetRandomFileName(); + + using var stream = File.Create(Path.Combine("wwwroot", coverName)); + await cover.CopyToAsync(stream); + book.Cover = coverName; + } + + db.Books.Add(book); + await db.SaveChangesAsync(); + + return Results.Ok(); +}); +``` + +Another way is to use the [AsParameters] attribute, the following code binds from form values to properties of the `NewBookRequest` record struct: + +```csharp +public record NewBookRequest([FromForm] string Name, [FromForm] BookType BookType, IFormFile? Cover); + +app.MapPost("/books", async ([AsParameters] NewBookRequest request, BookDb db) => +{ + var book = new Book + { + Name = request.Name, + BookType = request.BookType + }; + + if (request.Cover is not null) + { + var coverName = Path.GetRandomFileName(); + + using var stream = File.Create(Path.Combine("wwwroot", coverName)); + await request.Cover.CopyToAsync(stream); + book.Cover = coverName; + } + + db.Books.Add(book); + await db.SaveChangesAsync(); + + return Results.Ok(); +}); +``` + +## Antiforgery + +ASP.NET Core 8.0 adds support for antiforgery tokens. We can call the `AddAntiforgery` method to register the antiforgery services and `WebApplicationBuilder` will automatically add the antiforgery middleware to the pipeline: + +```csharp +var builder = WebApplication.CreateBuilder(); + +builder.Services.AddAntiforgery(); + +var app = builder.Build(); + +// Implicitly added by WebApplicationBuilder if AddAntiforgery is called. +// app.UseAntiforgery(); + +app.MapGet("/", () => "Hello World!"); + +app.Run(); +``` + +Example of using antiforgery tokens: + +```csharp + +// Use the antiforgery service to generate tokens. +app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) => +{ + var token = antiforgery.GetAndStoreTokens(context); + return Results.Content(...., "text/html"); +}); + +// It will automatically validate the token. +app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo)); + +// Disable antiforgery validation for this endpoint. +app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo)) + .DisableAntiforgery(); +``` + + +## Native AOT + +ASP.NET Core 8.0 adds support for Native AOT. + +You can add `PublishAot` to the project file to enable Native AOT: + +```xml + + true + +``` + +### Web API (native AOT) template + +You can use the `dotnet new webapiaot` command to create a new Minimal APIs project with Native AOT enabled. + +```diff ++using System.Text.Json.Serialization; + +-var builder = WebApplication.CreateBuilder(); ++var builder = WebApplication.CreateSlimBuilder(args); + ++builder.Services.ConfigureHttpJsonOptions(options => ++{ ++ options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); ++}); + +var app = builder.Build(); + +var sampleTodos = TodoGenerator.GenerateTodos().ToArray(); + +var todosApi = app.MapGroup("/todos"); +todosApi.MapGet("/", () => sampleTodos); +todosApi.MapGet("/{id}", (int id) => + sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo + ? Results.Ok(todo) + : Results.NotFound()); + +app.Run(); + ++[JsonSerializable(typeof(Todo[]))] ++internal partial class AppJsonSerializerContext : JsonSerializerContext ++{ ++ ++} +``` + +* Reflection isn't supported in native AOT, you must use the `JsonSerializable` attribute to specify the types that you want to serialize/deserialize. + +## References + +* https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-8.0#minimal-apis +* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/parameter-binding?view=aspnetcore-8.0#explicit-binding-from-form-values +* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot?view=aspnetcore-8.0 \ No newline at end of file From d2af798b7eda460983b249487d0c6b3e29a0ac68 Mon Sep 17 00:00:00 2001 From: linch90 Date: Fri, 3 Nov 2023 16:36:01 +0800 Subject: [PATCH 15/46] use `abpVisible` instead of 'ngIf' --- .../components/extensible-table/extensible-table.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html index 59bb0d1d6a..0d3aa9dd16 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html @@ -24,7 +24,7 @@ Date: Fri, 3 Nov 2023 17:01:33 +0300 Subject: [PATCH 16/46] add tests --- .../services/http-error-reporter.service.ts | 4 +- .../src/lib/tests/permission.guard.spec.ts | 117 +++++++++++++++--- .../oauth/src/lib/tests/auth.guard.spec.ts | 65 +++++++++- 3 files changed, 165 insertions(+), 21 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts index 65d063f82d..f43c114c8b 100644 --- a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts @@ -19,8 +19,8 @@ export class HttpErrorReporterService { return this._errors$.value; } - reportError = (error: HttpErrorResponse) => { + reportError(error: HttpErrorResponse) { this._reporter$.next(error); this._errors$.next([...this.errors, error]); - }; + } } diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index 372f3434f3..6464f76a66 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts @@ -1,15 +1,26 @@ import { APP_BASE_HREF } from '@angular/common'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { Component } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; +import { provideRouter, Route, Router, RouterModule } from '@angular/router'; +import { + createServiceFactory, + createSpyObject, + SpectatorService, + SpyObject, +} from '@ngneat/spectator/jest'; import { of } from 'rxjs'; -import { PermissionGuard } from '../guards/permission.guard'; +import { permissionGuard, PermissionGuard } from '../guards/permission.guard'; import { HttpErrorReporterService } from '../services/http-error-reporter.service'; import { PermissionService } from '../services/permission.service'; import { RoutesService } from '../services/routes.service'; import { CORE_OPTIONS } from '../tokens/options.token'; import { IncludeLocalizationResourcesProvider } from '../providers'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingHarness } from '@angular/router/testing'; +import { OAuthService } from 'angular-oauth2-oidc'; +import { OTHERS_GROUP } from '../tokens'; +import { SORT_COMPARE_FUNC, compareFuncFactory } from '../tokens/compare-func.token'; +import { CoreModule } from '../core.module'; describe('PermissionGuard', () => { let spectator: SpectatorService; @@ -18,6 +29,10 @@ describe('PermissionGuard', () => { let httpErrorReporter: SpyObject; let permissionService: SpyObject; + const mockOAuthService = { + hasValidAccessToken: jest.fn().mockReturnValue(true), + }; + @Component({ template: '' }) class DummyComponent {} @@ -27,25 +42,25 @@ describe('PermissionGuard', () => { declarations: [DummyComponent], imports: [ HttpClientTestingModule, - RouterModule.forRoot( - [ - { - path: 'test', - component: DummyComponent, - data: { - requiredPolicy: 'TestPolicy', - }, + RouterModule.forRoot([ + { + path: 'test', + component: DummyComponent, + data: { + requiredPolicy: 'TestPolicy', }, - ], - {}, - ), + }, + ]), ], providers: [ { provide: APP_BASE_HREF, useValue: '/', }, + { provide: OAuthService, useValue: mockOAuthService }, { provide: CORE_OPTIONS, useValue: { skipGetAppConfiguration: true } }, + { provide: OTHERS_GROUP, useValue: 'AbpUi::OthersGroup' }, + { provide: SORT_COMPARE_FUNC, useValue: compareFuncFactory }, IncludeLocalizationResourcesProvider, ], }); @@ -108,3 +123,77 @@ describe('PermissionGuard', () => { }); }); }); + +@Component({ standalone: true, template: '' }) +class DummyComponent {} +describe('authGuard', () => { + let permissionService: SpyObject; + let httpErrorReporter: SpyObject; + + const mockOAuthService = { + hasValidAccessToken: jest.fn().mockReturnValue(true), + }; + + let routes: Route[] = [ + { + path: 'dummy', + component: DummyComponent, + canActivate: [permissionGuard], + data: { + requiredPolicy: 'TestPolicy', + }, + }, + { + path: 'zibzib', + component: DummyComponent, + canActivate: [permissionGuard], + }, + ]; + + beforeEach(() => { + httpErrorReporter = createSpyObject(HttpErrorReporterService); + permissionService = createSpyObject(PermissionService); + + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, CoreModule.forRoot()], + providers: [ + { provide: OAuthService, useValue: mockOAuthService }, + { provide: PermissionService, useValue: permissionService }, + { provide: HttpErrorReporterService, useValue: httpErrorReporter }, + provideRouter(routes), + ], + }); + }); + + it('should return true when the grantedPolicy is true', async () => { + permissionService.getGrantedPolicy$.andReturn(of(true)); + await RouterTestingHarness.create('/dummy'); + + expect(TestBed.inject(Router).url).toEqual('/dummy'); + expect(httpErrorReporter.reportError).not.toHaveBeenCalled(); + }); + + it('should return false and report an error when the grantedPolicy is false', async () => { + permissionService.getGrantedPolicy$.andReturn(of(false)); + await RouterTestingHarness.create('/dummy'); + + expect(TestBed.inject(Router).url).not.toEqual('/dummy'); + expect(httpErrorReporter.reportError).toHaveBeenCalled(); + expect(httpErrorReporter.reportError).toBeCalledWith({ status: 403 }); + }); + + it('should check the requiredPolicy from RoutesService', async () => { + permissionService.getGrantedPolicy$.mockImplementation(policy => { + return of(policy === 'TestPolicy'); + }); + await RouterTestingHarness.create('/dummy'); + + expect(TestBed.inject(Router).url).toEqual('/dummy'); + expect(httpErrorReporter.reportError).not.toHaveBeenCalled(); + }); + + it('should return Observable if RoutesService does not have requiredPolicy for given URL', async () => { + await RouterTestingHarness.create('/zibzib'); + expect(TestBed.inject(Router).url).toEqual('/zibzib'); + }); +}); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts index feca76666e..47f57bc50c 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts @@ -1,14 +1,25 @@ import { createServiceFactory, SpectatorService, createSpyObject } from '@ngneat/spectator/jest'; import { OAuthService } from 'angular-oauth2-oidc'; -import { AbpOAuthGuard } from '../guards/oauth.guard'; +import { AbpOAuthGuard, abpOAuthGuard } from '../guards/oauth.guard'; import { AuthService } from '@abp/ng.core'; -import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { + ActivatedRouteSnapshot, + Route, + Router, + RouterStateSnapshot, + provideRouter, +} from '@angular/router'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { Component } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingHarness } from '@angular/router/testing'; +import { SpyObject } from '@ngneat/spectator'; describe('AuthGuard', () => { let spectator: SpectatorService; - let guard : AbpOAuthGuard; - const route = createSpyObject(ActivatedRouteSnapshot) - const state = createSpyObject(RouterStateSnapshot) + let guard: AbpOAuthGuard; + const route = createSpyObject(ActivatedRouteSnapshot); + const state = createSpyObject(RouterStateSnapshot); const createService = createServiceFactory({ service: AbpOAuthGuard, @@ -34,3 +45,47 @@ describe('AuthGuard', () => { expect(navigateToLoginSpy).toHaveBeenCalled(); }); }); + +@Component({ standalone: true, template: '' }) +class DummyComponent {} +describe('authGuard', () => { + let oAuthService: SpyObject; + let authService: SpyObject; + let routes: Route[] = [ + { + path: 'dummy', + canActivate: [abpOAuthGuard], + component: DummyComponent, + }, + ]; + + beforeEach(() => { + authService = createSpyObject(AuthService); + oAuthService = createSpyObject(OAuthService); + + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: AuthService, useValue: authService }, + { provide: OAuthService, useValue: oAuthService }, + provideRouter(routes), + ], + }); + }); + + it('should move to the dummy route', async () => { + oAuthService.hasValidAccessToken.andReturn(true); + await RouterTestingHarness.create('/dummy'); + + expect(TestBed.inject(Router).url).toEqual('/dummy'); + }); + + it("should'nt move to the dummy route", async () => { + oAuthService.hasValidAccessToken.andReturn(false); + await RouterTestingHarness.create('/dummy'); + + expect(authService.navigateToLogin).toHaveBeenCalled(); + expect(TestBed.inject(Router).url).not.toEqual('/dummy'); + expect(TestBed.inject(Router).url).toEqual('/'); + }); +}); From dd30a52bdcafefc5d21e9e019b856c359c92bccb Mon Sep 17 00:00:00 2001 From: Sinan997 Date: Sun, 5 Nov 2023 12:00:02 +0300 Subject: [PATCH 17/46] update doc --- .../POST.md | 2 +- docs/en/Startup-Templates/Application.md | 4 ++-- docs/en/Tutorials/Part-5.md | 8 ++++---- docs/en/UI/Angular/OAuth-Module.md | 1 - docs/en/UI/Angular/Permission-Management.md | 8 ++++---- docs/zh-Hans/Startup-Templates/Application.md | 4 ++-- docs/zh-Hans/Tutorials/Part-5.md | 8 ++++---- docs/zh-Hans/UI/Angular/Permission-Management.md | 10 +++++----- 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md index a8f1aeadfa..73d2e6bad5 100644 --- a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md +++ b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md @@ -315,7 +315,7 @@ import { CreateBookComponent } from './create-book/create-book.component'; import { EditBookComponent } from './edit-book/edit-book.component'; const routes: Routes = [ - { path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] }, + { path: '', component: BookComponent, canActivate: [authGuard, permissionGuard] }, { path: 'create', component: CreateBookComponent }, { path: 'edit/:id', component: EditBookComponent }, ]; diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md index 0c5e9c4c38..fdb7569ca0 100644 --- a/docs/en/Startup-Templates/Application.md +++ b/docs/en/Startup-Templates/Application.md @@ -316,7 +316,7 @@ You should add `routes` property in the `data` object to add a link on the menu { path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule), - canActivate: [AuthGuard, PermissionGuard], + canActivate: [authGuard, permissionGuard], data: { routes: { name: 'ProjectName::Menu:Dashboard', @@ -328,7 +328,7 @@ You should add `routes` property in the `data` object to add a link on the menu } ``` In the above example; -* If the user is not logged in, AuthGuard blocks access and redirects to the login page. +* If the user is not logged in, authGuard blocks access and redirects to the login page. * PermissionGuard checks the user's permission with the `requiredPolicy` property of the `routes` object. If the user is not authorized to access the page, the 403 page appears. * The `name` property of `routes` is the menu link label. A localization key can be defined. * The `iconClass` property of the `routes` object is the menu link icon class. diff --git a/docs/en/Tutorials/Part-5.md b/docs/en/Tutorials/Part-5.md index f33a41f392..f306b7f4a2 100644 --- a/docs/en/Tutorials/Part-5.md +++ b/docs/en/Tutorials/Part-5.md @@ -323,11 +323,11 @@ Open the `/src/app/book/book-routing.module.ts` and replace with the following c ````js import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { AuthGuard, PermissionGuard } from '@abp/ng.core'; +import { authGuard, permissionGuard } from '@abp/ng.core'; import { BookComponent } from './book.component'; const routes: Routes = [ - { path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] }, + { path: '', component: BookComponent, canActivate: [authGuard, permissionGuard] }, ]; @NgModule({ @@ -337,8 +337,8 @@ const routes: Routes = [ export class BookRoutingModule {} ```` -* Imported `AuthGuard` and `PermissionGuard` from the `@abp/ng.core`. -* Added `canActivate: [AuthGuard, PermissionGuard]` to the route definition. +* Imported `authGuard` and `permissionGuard` from the `@abp/ng.core`. +* Added `canActivate: [authGuard, permissionGuard]` to the route definition. Open the `/src/app/route.provider.ts` and add `requiredPolicy: 'BookStore.Books'` to the `/books` route. The `/books` route block should be following: diff --git a/docs/en/UI/Angular/OAuth-Module.md b/docs/en/UI/Angular/OAuth-Module.md index e7a62d1d2e..b747c5221d 100644 --- a/docs/en/UI/Angular/OAuth-Module.md +++ b/docs/en/UI/Angular/OAuth-Module.md @@ -6,7 +6,6 @@ If your app is version 7.0 or higher, you should include "AbpOAuthModule.forRoot Those abstractions can be found in the @abp/ng-core packages. - `AuthService` (the class that implements the IAuthService interface). - `NAVIGATE_TO_MANAGE_PROFILE` Inject token. -- `AuthGuard` (the class that implements the IAuthGuard interface). - `ApiInterceptor` (the class that implements the IApiInterceptor interface). Those base classes are overridden by the "AbpOAuthModule" for oAuth. There are also three functions provided with AbpOAuthModule. diff --git a/docs/en/UI/Angular/Permission-Management.md b/docs/en/UI/Angular/Permission-Management.md index dd373d6274..63d919f599 100644 --- a/docs/en/UI/Angular/Permission-Management.md +++ b/docs/en/UI/Angular/Permission-Management.md @@ -54,20 +54,20 @@ As shown above you can remove elements from DOM with `abpPermission` structural ## Permission Guard -You can use `PermissionGuard` if you want to control authenticated user's permission to access to the route during navigation. +You can use `permissionGuard` if you want to control authenticated user's permission to access to the route during navigation. * Import the PermissionGuard from @abp/ng.core. -* Add `canActivate: [PermissionGuard]` to your route object. +* Add `canActivate: [permissionGuard]` to your route object. * Add `requiredPolicy` to the `data` property of your route in your routing module. ```js -import { PermissionGuard } from '@abp/ng.core'; +import { permissionGuard } from '@abp/ng.core'; // ... const routes: Routes = [ { path: 'path', component: YourComponent, - canActivate: [PermissionGuard], + canActivate: [permissionGuard], data: { requiredPolicy: 'YourProjectName.YourComponent', // policy key for your component }, diff --git a/docs/zh-Hans/Startup-Templates/Application.md b/docs/zh-Hans/Startup-Templates/Application.md index 91d7308226..d6733b8633 100644 --- a/docs/zh-Hans/Startup-Templates/Application.md +++ b/docs/zh-Hans/Startup-Templates/Application.md @@ -302,7 +302,7 @@ ABP 配置模块也已经导入到 `AppModule` 中, 以满足可延迟加载 ABP { path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule), - canActivate: [AuthGuard, PermissionGuard], + canActivate: [authGuard, permissionGuard], data: { routes: { name: 'ProjectName::Menu:Dashboard', @@ -315,7 +315,7 @@ ABP 配置模块也已经导入到 `AppModule` 中, 以满足可延迟加载 ABP ``` 在上面的例子中; * 如果用户没有登录, AuthGuard 会阻塞访问并重定向到登录页面. -* PermissionGuard 使用 `rotues` 对象的 `requiredPolicy` 属性检查用户的权限. 如果用户未被授权访问该页, 则显示403页. +* permissionGuard 使用 `rotues` 对象的 `requiredPolicy` 属性检查用户的权限. 如果用户未被授权访问该页, 则显示403页. * `routes` 的 `name` 属性是菜单链接标签. 可以定义本地化 key. * `routes` 对象的 `iconClass` 属性是菜单链接图标类. * `routes` 对象的 `requiredPolicy` 属性是访问页面所需的策略 key. diff --git a/docs/zh-Hans/Tutorials/Part-5.md b/docs/zh-Hans/Tutorials/Part-5.md index 5bdad55c1c..ec308f3ee7 100644 --- a/docs/zh-Hans/Tutorials/Part-5.md +++ b/docs/zh-Hans/Tutorials/Part-5.md @@ -389,11 +389,11 @@ UI的第一步是防止未认证用户看见"图书"菜单项并进入图书管 ````js import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { AuthGuard, PermissionGuard } from '@abp/ng.core'; +import { authGuard, permissionGuard } from '@abp/ng.core'; import { BookComponent } from './book.component'; const routes: Routes = [ - { path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] }, + { path: '', component: BookComponent, canActivate: [authGuard, permissionGuard] }, ]; @NgModule({ @@ -403,8 +403,8 @@ const routes: Routes = [ export class BookRoutingModule {} ```` -* 从 `@abp/ng.core` 引入 `AuthGuard` 和 `PermissionGuard`. -* 在路由定义中添加 `canActivate: [AuthGuard, PermissionGuard]`. +* 从 `@abp/ng.core` 引入 `authGuard` 和 `permissionGuard`. +* 在路由定义中添加 `canActivate: [authGuard, permissionGuard]`. 打开 `/src/app/route.provider.ts`, 在 `/books` 路由中添加 `requiredPolicy: 'BookStore.Books'`. `/books` 路由应该如以下配置: diff --git a/docs/zh-Hans/UI/Angular/Permission-Management.md b/docs/zh-Hans/UI/Angular/Permission-Management.md index 08496f03fe..05e3dacbf1 100644 --- a/docs/zh-Hans/UI/Angular/Permission-Management.md +++ b/docs/zh-Hans/UI/Angular/Permission-Management.md @@ -53,20 +53,20 @@ export class YourComponent { ## 权限守卫 -如果你想要在导航过程中控制经过身份验证的用户对路由的访问权限,可以使用 `PermissionGuard`. +如果你想要在导航过程中控制经过身份验证的用户对路由的访问权限,可以使用 `permissionGuard`. -* 从@abp/ng.core导入PermissionGuard. -* 添加 `canActivate: [PermissionGuard]` 到你的路由对象. +* 从@abp/ng.core导入permissionGuard. +* 添加 `canActivate: [permissionGuard]` 到你的路由对象. * 添加 `requiredPolicy` 到路由模块路由的 `data` 属性. ```js -import { PermissionGuard } from '@abp/ng.core'; +import { permissionGuard } from '@abp/ng.core'; // ... const routes: Routes = [ { path: 'path', component: YourComponent, - canActivate: [PermissionGuard], + canActivate: [permissionGuard], data: { requiredPolicy: 'YourProjectName.YourComponent', // policy key for your component }, From eb8a6ece426a149f61c84af6df295cb2b1431c74 Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Mon, 6 Nov 2023 10:19:32 +0300 Subject: [PATCH 18/46] Update http-error-reporter.service.ts --- .../core/src/lib/services/http-error-reporter.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts index f43c114c8b..f07c5db9ee 100644 --- a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts @@ -19,7 +19,7 @@ export class HttpErrorReporterService { return this._errors$.value; } - reportError(error: HttpErrorResponse) { + reportError = (error: HttpErrorResponse) => { this._reporter$.next(error); this._errors$.next([...this.errors, error]); } From eb36abdb4bf1530a96d5ebd0c0d5efbaa5e99724 Mon Sep 17 00:00:00 2001 From: Sinan997 Date: Mon, 6 Nov 2023 11:40:46 +0300 Subject: [PATCH 19/46] fix lint error --- .../core/src/lib/services/http-error-reporter.service.ts | 2 +- .../packages/core/src/lib/tests/permission.guard.spec.ts | 2 +- npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts index f07c5db9ee..f43c114c8b 100644 --- a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts @@ -19,7 +19,7 @@ export class HttpErrorReporterService { return this._errors$.value; } - reportError = (error: HttpErrorResponse) => { + reportError(error: HttpErrorResponse) { this._reporter$.next(error); this._errors$.next([...this.errors, error]); } diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index 6464f76a66..b6dbc7b1d9 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts @@ -134,7 +134,7 @@ describe('authGuard', () => { hasValidAccessToken: jest.fn().mockReturnValue(true), }; - let routes: Route[] = [ + const routes: Route[] = [ { path: 'dummy', component: DummyComponent, diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts index 47f57bc50c..e8a0f2dbf0 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts @@ -51,7 +51,7 @@ class DummyComponent {} describe('authGuard', () => { let oAuthService: SpyObject; let authService: SpyObject; - let routes: Route[] = [ + const routes: Route[] = [ { path: 'dummy', canActivate: [abpOAuthGuard], From d379eb12c9947b37390653b706a0c4eefed883e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Ali=20=C3=96ZKAYA?= Date: Mon, 6 Nov 2023 14:10:53 +0300 Subject: [PATCH 20/46] Add `HierarchyId Support in Entity Framework Core` Blog Post --- .../2023-11-06-EF-Core_Hierarchy-Id/POST.md | 79 ++++++++++++++++++ .../hierarchy-tree.png | Bin 0 -> 219581 bytes 2 files changed, 79 insertions(+) create mode 100644 docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md create mode 100644 docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png diff --git a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md new file mode 100644 index 0000000000..8484f88473 --- /dev/null +++ b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md @@ -0,0 +1,79 @@ +# HierarchyId Support in Entity Framework Core + +Entity Framework Core has official support for HierarchyId, which allows you to store and query hierarchical data in SQL Server databases. Hierarchical data is a common data structure found in many applications. Whether you are dealing with organizational structures, product categories, or threaded discussions, handling hierarchies efficiently is crucial. In this blog post, we will explore how to manage hierarchical data using Entity Framework Core (EF Core) in combination with HierarchyId. + +## How to use HierarchyId in EF Core + +To use HierarchyId in EF Core, you need to install the [Microsoft.EntityFrameworkCore.SqlServer.HierarchyId](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer.HierarchyId/) NuGet package. This package contains query and update support for HierarchyId. + +To configure EF Core to use HierarchyId, you need to call the `UseHierarchyId` method when configuring the SQL Server provider. + +```csharp +options.UseSqlServer( + connectionString, + x => x.UseHierarchyId()); +``` + +## Modeling Hierarchies + +The `HierarchyId` type can be used for properties of an entity type. For example, assume we want to model personnel in an organization. Each person has a name and a manager. The manager is also a person. We can model this using the following entity type: + +```csharp +public class Person +{ + public int Id { get; set; } + public string Name { get; set; } + public HierarchyId Manager { get; set; } +} +``` + +Each person can be traced from the patriarch down the tree using its `Manager` property. SQL Server uses a compact binary format for these paths, but it is common to parse to and from a human-readable string representation when when working with code. In this representation, the position at each level is separated by a `/` character. + +![Hierarchy Tree](hierarchy-tree.png) + +## Querying Hierarchies + +HierarchyId exposes several methods that can be used in LINQ queries. + +* `GetAncestor(int level)` returns the ancestor at the specified level. + +* `GetDescendant(HierarchyId? child1, HierarchyId? child2)` returns the descendant at the specified level. + +* `GetLevel()` returns the level of the node in the hierarchy. + +* `GetReparentedValue(HierarchyId? oldRoot, HierarchyId? newRoot)` returns the node with a new parent. + +* `IsDescendantOf(HierarchyId? parent)` returns whether the node is a descendant of the specified node. + +Example: + +```csharp +// Get entities at a given level in the tree +var generation = await context.Persons.Where(x => x.Manager.GetLevel() == level).ToListAsync(); + +// Get the direct ancestor of an entity +var parent = await context.Persons.Where(x => x.Manager.GetAncestor(1) == manager).SingleAsync(); +``` + +For example, your company may want to change manager for an organizational unit. To do this, you can use the `GetReparentedValue` method to update the manager for all descendants of the organizational unit. + +```csharp +var newManager = await context.Persons.SingleAsync(x => x.Id == newManagerId); + +var descendants = await context.Persons + .Where(x => x.Manager.IsDescendantOf(oldManager)) + .ToListAsync(); + +foreach (var descendant in descendants) +{ + descendant.Manager = descendant.Manager.GetReparentedValue(oldManager.Manager, newManager.Manager); +} + +await context.SaveChangesAsync(); +``` + +## References + +For more information about hierarchy id, see the following resource: + +https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core \ No newline at end of file diff --git a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png new file mode 100644 index 0000000000000000000000000000000000000000..e22ed9781cf8dab090c6c08b095e8fc1f18ad533 GIT binary patch literal 219581 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�%H_q?h6If*(OZG|66! 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 z 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 !xp_0q0SCdI28+F=HHj;UefrH> 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 EFJ Date: Fri, 3 Nov 2023 17:01:33 +0300 Subject: [PATCH 21/46] add tests --- .../services/http-error-reporter.service.ts | 4 +- .../src/lib/tests/permission.guard.spec.ts | 117 +++++++++++++++--- .../oauth/src/lib/tests/auth.guard.spec.ts | 65 +++++++++- 3 files changed, 165 insertions(+), 21 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts index 65d063f82d..f43c114c8b 100644 --- a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts @@ -19,8 +19,8 @@ export class HttpErrorReporterService { return this._errors$.value; } - reportError = (error: HttpErrorResponse) => { + reportError(error: HttpErrorResponse) { this._reporter$.next(error); this._errors$.next([...this.errors, error]); - }; + } } diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index 372f3434f3..6464f76a66 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts @@ -1,15 +1,26 @@ import { APP_BASE_HREF } from '@angular/common'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { Component } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; +import { provideRouter, Route, Router, RouterModule } from '@angular/router'; +import { + createServiceFactory, + createSpyObject, + SpectatorService, + SpyObject, +} from '@ngneat/spectator/jest'; import { of } from 'rxjs'; -import { PermissionGuard } from '../guards/permission.guard'; +import { permissionGuard, PermissionGuard } from '../guards/permission.guard'; import { HttpErrorReporterService } from '../services/http-error-reporter.service'; import { PermissionService } from '../services/permission.service'; import { RoutesService } from '../services/routes.service'; import { CORE_OPTIONS } from '../tokens/options.token'; import { IncludeLocalizationResourcesProvider } from '../providers'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingHarness } from '@angular/router/testing'; +import { OAuthService } from 'angular-oauth2-oidc'; +import { OTHERS_GROUP } from '../tokens'; +import { SORT_COMPARE_FUNC, compareFuncFactory } from '../tokens/compare-func.token'; +import { CoreModule } from '../core.module'; describe('PermissionGuard', () => { let spectator: SpectatorService; @@ -18,6 +29,10 @@ describe('PermissionGuard', () => { let httpErrorReporter: SpyObject; let permissionService: SpyObject; + const mockOAuthService = { + hasValidAccessToken: jest.fn().mockReturnValue(true), + }; + @Component({ template: '' }) class DummyComponent {} @@ -27,25 +42,25 @@ describe('PermissionGuard', () => { declarations: [DummyComponent], imports: [ HttpClientTestingModule, - RouterModule.forRoot( - [ - { - path: 'test', - component: DummyComponent, - data: { - requiredPolicy: 'TestPolicy', - }, + RouterModule.forRoot([ + { + path: 'test', + component: DummyComponent, + data: { + requiredPolicy: 'TestPolicy', }, - ], - {}, - ), + }, + ]), ], providers: [ { provide: APP_BASE_HREF, useValue: '/', }, + { provide: OAuthService, useValue: mockOAuthService }, { provide: CORE_OPTIONS, useValue: { skipGetAppConfiguration: true } }, + { provide: OTHERS_GROUP, useValue: 'AbpUi::OthersGroup' }, + { provide: SORT_COMPARE_FUNC, useValue: compareFuncFactory }, IncludeLocalizationResourcesProvider, ], }); @@ -108,3 +123,77 @@ describe('PermissionGuard', () => { }); }); }); + +@Component({ standalone: true, template: '' }) +class DummyComponent {} +describe('authGuard', () => { + let permissionService: SpyObject; + let httpErrorReporter: SpyObject; + + const mockOAuthService = { + hasValidAccessToken: jest.fn().mockReturnValue(true), + }; + + let routes: Route[] = [ + { + path: 'dummy', + component: DummyComponent, + canActivate: [permissionGuard], + data: { + requiredPolicy: 'TestPolicy', + }, + }, + { + path: 'zibzib', + component: DummyComponent, + canActivate: [permissionGuard], + }, + ]; + + beforeEach(() => { + httpErrorReporter = createSpyObject(HttpErrorReporterService); + permissionService = createSpyObject(PermissionService); + + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, CoreModule.forRoot()], + providers: [ + { provide: OAuthService, useValue: mockOAuthService }, + { provide: PermissionService, useValue: permissionService }, + { provide: HttpErrorReporterService, useValue: httpErrorReporter }, + provideRouter(routes), + ], + }); + }); + + it('should return true when the grantedPolicy is true', async () => { + permissionService.getGrantedPolicy$.andReturn(of(true)); + await RouterTestingHarness.create('/dummy'); + + expect(TestBed.inject(Router).url).toEqual('/dummy'); + expect(httpErrorReporter.reportError).not.toHaveBeenCalled(); + }); + + it('should return false and report an error when the grantedPolicy is false', async () => { + permissionService.getGrantedPolicy$.andReturn(of(false)); + await RouterTestingHarness.create('/dummy'); + + expect(TestBed.inject(Router).url).not.toEqual('/dummy'); + expect(httpErrorReporter.reportError).toHaveBeenCalled(); + expect(httpErrorReporter.reportError).toBeCalledWith({ status: 403 }); + }); + + it('should check the requiredPolicy from RoutesService', async () => { + permissionService.getGrantedPolicy$.mockImplementation(policy => { + return of(policy === 'TestPolicy'); + }); + await RouterTestingHarness.create('/dummy'); + + expect(TestBed.inject(Router).url).toEqual('/dummy'); + expect(httpErrorReporter.reportError).not.toHaveBeenCalled(); + }); + + it('should return Observable if RoutesService does not have requiredPolicy for given URL', async () => { + await RouterTestingHarness.create('/zibzib'); + expect(TestBed.inject(Router).url).toEqual('/zibzib'); + }); +}); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts index feca76666e..47f57bc50c 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts @@ -1,14 +1,25 @@ import { createServiceFactory, SpectatorService, createSpyObject } from '@ngneat/spectator/jest'; import { OAuthService } from 'angular-oauth2-oidc'; -import { AbpOAuthGuard } from '../guards/oauth.guard'; +import { AbpOAuthGuard, abpOAuthGuard } from '../guards/oauth.guard'; import { AuthService } from '@abp/ng.core'; -import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { + ActivatedRouteSnapshot, + Route, + Router, + RouterStateSnapshot, + provideRouter, +} from '@angular/router'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { Component } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingHarness } from '@angular/router/testing'; +import { SpyObject } from '@ngneat/spectator'; describe('AuthGuard', () => { let spectator: SpectatorService; - let guard : AbpOAuthGuard; - const route = createSpyObject(ActivatedRouteSnapshot) - const state = createSpyObject(RouterStateSnapshot) + let guard: AbpOAuthGuard; + const route = createSpyObject(ActivatedRouteSnapshot); + const state = createSpyObject(RouterStateSnapshot); const createService = createServiceFactory({ service: AbpOAuthGuard, @@ -34,3 +45,47 @@ describe('AuthGuard', () => { expect(navigateToLoginSpy).toHaveBeenCalled(); }); }); + +@Component({ standalone: true, template: '' }) +class DummyComponent {} +describe('authGuard', () => { + let oAuthService: SpyObject; + let authService: SpyObject; + let routes: Route[] = [ + { + path: 'dummy', + canActivate: [abpOAuthGuard], + component: DummyComponent, + }, + ]; + + beforeEach(() => { + authService = createSpyObject(AuthService); + oAuthService = createSpyObject(OAuthService); + + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: AuthService, useValue: authService }, + { provide: OAuthService, useValue: oAuthService }, + provideRouter(routes), + ], + }); + }); + + it('should move to the dummy route', async () => { + oAuthService.hasValidAccessToken.andReturn(true); + await RouterTestingHarness.create('/dummy'); + + expect(TestBed.inject(Router).url).toEqual('/dummy'); + }); + + it("should'nt move to the dummy route", async () => { + oAuthService.hasValidAccessToken.andReturn(false); + await RouterTestingHarness.create('/dummy'); + + expect(authService.navigateToLogin).toHaveBeenCalled(); + expect(TestBed.inject(Router).url).not.toEqual('/dummy'); + expect(TestBed.inject(Router).url).toEqual('/'); + }); +}); From c944457b77b055e20bf32fe00106801805bc7e4a Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Mon, 6 Nov 2023 10:19:32 +0300 Subject: [PATCH 22/46] Update http-error-reporter.service.ts --- .../core/src/lib/services/http-error-reporter.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts index f43c114c8b..f07c5db9ee 100644 --- a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts @@ -19,7 +19,7 @@ export class HttpErrorReporterService { return this._errors$.value; } - reportError(error: HttpErrorResponse) { + reportError = (error: HttpErrorResponse) => { this._reporter$.next(error); this._errors$.next([...this.errors, error]); } From 698740c8def66d36d24c17370f93c7e00d895005 Mon Sep 17 00:00:00 2001 From: Sinan997 Date: Mon, 6 Nov 2023 11:40:46 +0300 Subject: [PATCH 23/46] fix lint error --- .../core/src/lib/services/http-error-reporter.service.ts | 2 +- .../packages/core/src/lib/tests/permission.guard.spec.ts | 2 +- npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts index f07c5db9ee..f43c114c8b 100644 --- a/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts @@ -19,7 +19,7 @@ export class HttpErrorReporterService { return this._errors$.value; } - reportError = (error: HttpErrorResponse) => { + reportError(error: HttpErrorResponse) { this._reporter$.next(error); this._errors$.next([...this.errors, error]); } diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index 6464f76a66..b6dbc7b1d9 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts @@ -134,7 +134,7 @@ describe('authGuard', () => { hasValidAccessToken: jest.fn().mockReturnValue(true), }; - let routes: Route[] = [ + const routes: Route[] = [ { path: 'dummy', component: DummyComponent, diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts index 47f57bc50c..e8a0f2dbf0 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts @@ -51,7 +51,7 @@ class DummyComponent {} describe('authGuard', () => { let oAuthService: SpyObject; let authService: SpyObject; - let routes: Route[] = [ + const routes: Route[] = [ { path: 'dummy', canActivate: [abpOAuthGuard], From 9c6e66f00795abae45cd6116495349b63b513a34 Mon Sep 17 00:00:00 2001 From: Sinan997 Date: Mon, 6 Nov 2023 15:33:11 +0300 Subject: [PATCH 24/46] update tests --- .../packages/core/src/lib/tests/permission.guard.spec.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index b6dbc7b1d9..5eeb7a44b7 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts @@ -21,6 +21,7 @@ import { OAuthService } from 'angular-oauth2-oidc'; import { OTHERS_GROUP } from '../tokens'; import { SORT_COMPARE_FUNC, compareFuncFactory } from '../tokens/compare-func.token'; import { CoreModule } from '../core.module'; +import { AuthService } from '../abstracts'; describe('PermissionGuard', () => { let spectator: SpectatorService; @@ -30,7 +31,7 @@ describe('PermissionGuard', () => { let permissionService: SpyObject; const mockOAuthService = { - hasValidAccessToken: jest.fn().mockReturnValue(true), + isAuthenticated: true, }; @Component({ template: '' }) @@ -57,7 +58,7 @@ describe('PermissionGuard', () => { provide: APP_BASE_HREF, useValue: '/', }, - { provide: OAuthService, useValue: mockOAuthService }, + { provide: AuthService, useValue: mockOAuthService }, { provide: CORE_OPTIONS, useValue: { skipGetAppConfiguration: true } }, { provide: OTHERS_GROUP, useValue: 'AbpUi::OthersGroup' }, { provide: SORT_COMPARE_FUNC, useValue: compareFuncFactory }, @@ -131,7 +132,7 @@ describe('authGuard', () => { let httpErrorReporter: SpyObject; const mockOAuthService = { - hasValidAccessToken: jest.fn().mockReturnValue(true), + isAuthenticated: true, }; const routes: Route[] = [ @@ -157,7 +158,7 @@ describe('authGuard', () => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule, CoreModule.forRoot()], providers: [ - { provide: OAuthService, useValue: mockOAuthService }, + { provide: AuthService, useValue: mockOAuthService }, { provide: PermissionService, useValue: permissionService }, { provide: HttpErrorReporterService, useValue: httpErrorReporter }, provideRouter(routes), From 620b3654a94e6a2320763adc47aeb64c90666d71 Mon Sep 17 00:00:00 2001 From: Sinan997 Date: Mon, 6 Nov 2023 17:13:26 +0300 Subject: [PATCH 25/46] update doc --- docs/en/UI/Angular/Entity-Action-Extensions.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/en/UI/Angular/Entity-Action-Extensions.md b/docs/en/UI/Angular/Entity-Action-Extensions.md index 3ab035b04a..d93b2045f6 100644 --- a/docs/en/UI/Angular/Entity-Action-Extensions.md +++ b/docs/en/UI/Angular/Entity-Action-Extensions.md @@ -16,19 +16,16 @@ In this example, we will add a "Click Me!" action and alert the current row's `u The following code prepares a constant named `identityEntityActionContributors`, ready to be imported and used in your root module: -```js +```ts // src/app/entity-action-contributors.ts -import { - eIdentityComponents, - IdentityEntityActionContributors, - IdentityUserDto, -} from '@abp/ng.identity'; +import { eIdentityComponents, IdentityEntityActionContributors } from '@abp/ng.identity'; +import { IdentityUserDto } from '@abp/ng.identity/proxy'; import { EntityAction, EntityActionList } from '@abp/ng.theme.shared/extensions'; const alertUserName = new EntityAction({ text: 'Click Me!', - action: data => { + action: (data) => { // Replace alert with your custom code alert(data.record.userName); }, From 08d1cc75c4becdf83de2512b01901529dcc8161b Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Tue, 7 Nov 2023 10:55:33 +0300 Subject: [PATCH 26/46] Create Native AOT Compilation in .NET 8 --- .../2023-11-97-AOT-Compilation/Post.md | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md diff --git a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md new file mode 100644 index 0000000000..bb4f5cfd9f --- /dev/null +++ b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md @@ -0,0 +1,74 @@ +# Native AOT Compilation in .NET 8 +Native AOT (Ahead-of-Time) compilation is a feature that allows developers to create a self-contained app compiled to native code that can run on machines without the .NET runtime installed. It results in benefits such as minimized disk footprint, reduced executable size, reduced startup time, and reduced memory demand + +Native AOT compilation isn't a new feature in .NET 8. It's first introduced in .NET 7. + + +Differences between .NET 7 and .NET 8 AOT Compilation are: + + +- **System.Text.Json improvements**: .NET 8 adds support for more types, source generation, interface hierarchies, naming policies, read-only properties, and more. +- **New types for performance**: .NET 8 introduces new types such as FrozenDictionary, FrozenSet, SearchValues, CompositeFormat, TimeProvider, and ITimer for improving app performance. +- **System.Numerics and System.Runtime.Intrinsics enhancements**: .NET 8 adds support for Vector512, AVX-512, IUtf8SpanFormattable, Lerp, and more. +- **System.ComponentModel.DataAnnotations additions**: .NET 8 adds new data validation attributes for cloud-native services and a new ValidateOptionsResultBuilder type. +- **Hosted services lifecycle methods**: .NET 8 adds new methods such as StartAsync, StopAsync, StartBackgroundAsync, and StopBackgroundAsync for hosted services. + +It's important to note that not all features in ASP.NET Core are currently compatible with native AOT For more information, see [Native AOT deployment overview](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/). + +## How to use Native AOT Compilation in .NET 8 + +You can add `true` in your project file to enable Native AOT Compilation. + + - For the new projects you can creage with `--aot` parameter. Example: `dotnet new console --aot`. + +By default, compiler chooses a blended approach code optimization but you can specify an optimization preference inside your csproj file. You can choose **size** or **speed** according your requirements. + +```xml +Size +``` + +or + +```xml +Speed +``` + +### Results + +I have created a simple console application to test the Native AOT Compilation. I have used a simple console application that writes "Hello World!" to the console 100 times. I have tested the application with different optimization preferences. I have used the following results: + + +| | Size | Speed | +| --- | --- | --- | +| Self-Contained, Single File | 65938 kb | 00.0051806 ~5ms | +| AOT (default) | 1242 kb | 00.0028638 ~2ms | +| AOT (Speed)| 1280 kb | 00.0023838 ~2ms | +| AOT (Size) | 1111 kb | 00.0025145 ~2ms | + +Most of existing libraries don't support AOT compiling yet, so I couldn't use [BenchmarkDotnet](https://github.com/dotnet/BenchmarkDotNet) to measure the performance. I have used [Stopwatch](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch?view=net-8.0) to measure the performance. So performance results may not be accurate but gives insight about the performance difference. + +## AOT Support in MAUI +You can now use Native AOT Compilation iOS-like target platforms in .NET MAUI. You can enable AOT compilation with exact same method by adding `true` to your project file. According to dotnet team, apps sizes reduced by 35% and startup times reduced by 28% with AOT compilation. And runtime performance is also improved by 50%. + +But there are some limitations in MAUI AOT Compilation. A lot of libraries still don't support AOT compilation and some of platform-specific feaetures may not work at the moment. + +## When to use Native AOT Compilation? + +Native AOT Compilation is beneficial when you need to optimize your .NET application for speed and size. It's particularly useful for applications that require quick startup times and efficient runtime performance, such as mobile apps or high-performance computing applications. + +However, due to its current limitations, it might not be suitable for all projects. If your project relies heavily on libraries that do not support AOT compilation, or if it uses platform-specific features that are not yet compatible with AOT, you might want to hold off on using Native AOT Compilation until further improvements are made. + +Always consider the specific needs and constraints of your project before deciding to use Native AOT Compilation. + +## Conclusion + +Native AOT Compilation is a great feature that improves the performance of .NET applications. It's still in early-stages and not all libraries support it yet. But it's a great beginning for the future of .NET 🚀 + + +## Links +- Native AOT deployment overview - .NET | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/. +- +- Optimize AOT deployments https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/optimizing +- +- What's new in .NET 8 | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8. + From 14ebb0b598649ac6208eebe7caa0c391142124ac Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Tue, 7 Nov 2023 10:58:35 +0300 Subject: [PATCH 27/46] Add .NET 7 result --- docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md index bb4f5cfd9f..eb01babd76 100644 --- a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md +++ b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md @@ -41,7 +41,8 @@ I have created a simple console application to test the Native AOT Compilation. | | Size | Speed | | --- | --- | --- | | Self-Contained, Single File | 65938 kb | 00.0051806 ~5ms | -| AOT (default) | 1242 kb | 00.0028638 ~2ms | +| .NET 7 AOT (default) | 4452 kb | 00.0029823 ~2ms | +| .NET 8 AOT (default) | 1242 kb | 00.0028638 ~2ms | | AOT (Speed)| 1280 kb | 00.0023838 ~2ms | | AOT (Size) | 1111 kb | 00.0025145 ~2ms | From 333f0cc8373e5d1f584aa2f0818a353a75a78b64 Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Tue, 7 Nov 2023 10:59:46 +0300 Subject: [PATCH 28/46] Update Post.md --- docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md index eb01babd76..0145de4415 100644 --- a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md +++ b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md @@ -40,7 +40,7 @@ I have created a simple console application to test the Native AOT Compilation. | | Size | Speed | | --- | --- | --- | -| Self-Contained, Single File | 65938 kb | 00.0051806 ~5ms | +| .NET 8
Self-Contained, Single File | 65938 kb | 00.0051806 ~5ms | | .NET 7 AOT (default) | 4452 kb | 00.0029823 ~2ms | | .NET 8 AOT (default) | 1242 kb | 00.0028638 ~2ms | | AOT (Speed)| 1280 kb | 00.0023838 ~2ms | From 5152917ee8485982c50dbd5dc28fb4714848d52b Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Tue, 7 Nov 2023 11:00:35 +0300 Subject: [PATCH 29/46] Update Post.md --- docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md index 0145de4415..8aebc3ba58 100644 --- a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md +++ b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md @@ -40,7 +40,7 @@ I have created a simple console application to test the Native AOT Compilation. | | Size | Speed | | --- | --- | --- | -| .NET 8
Self-Contained, Single File | 65938 kb | 00.0051806 ~5ms | +| .NET 8
_(Self-Contained, Single File)_ | 65938 kb | 00.0051806 ~5ms | | .NET 7 AOT (default) | 4452 kb | 00.0029823 ~2ms | | .NET 8 AOT (default) | 1242 kb | 00.0028638 ~2ms | | AOT (Speed)| 1280 kb | 00.0023838 ~2ms | From b7d2d554ee6f1241b95f86cbac2d0c7c5c46fd02 Mon Sep 17 00:00:00 2001 From: Hamza Albreem <94292623+braim23@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:47:26 +0300 Subject: [PATCH 30/46] Update POST.md --- .../2023-11-06-EF-Core_Hierarchy-Id/POST.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md index 8484f88473..9255c39a5a 100644 --- a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md +++ b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md @@ -16,7 +16,7 @@ options.UseSqlServer( ## Modeling Hierarchies -The `HierarchyId` type can be used for properties of an entity type. For example, assume we want to model personnel in an organization. Each person has a name and a manager. The manager is also a person. We can model this using the following entity type: +The `HierarchyId` type can be used for properties of an entity type. For example, assume we want to model the personnel in an organization. Each person has a name and a manager. The manager is also a person. We can model this using the following entity type: ```csharp public class Person @@ -27,7 +27,7 @@ public class Person } ``` -Each person can be traced from the patriarch down the tree using its `Manager` property. SQL Server uses a compact binary format for these paths, but it is common to parse to and from a human-readable string representation when when working with code. In this representation, the position at each level is separated by a `/` character. +Each person can be traced from the patriarch down the tree using its `Manager` property. SQL Server uses a compact binary format for these paths, but it is common to parse to and from a human-readable string representation when working with code. In this representation, the position at each level is separated by a `/` character. ![Hierarchy Tree](hierarchy-tree.png) @@ -55,7 +55,7 @@ var generation = await context.Persons.Where(x => x.Manager.GetLevel() == level) var parent = await context.Persons.Where(x => x.Manager.GetAncestor(1) == manager).SingleAsync(); ``` -For example, your company may want to change manager for an organizational unit. To do this, you can use the `GetReparentedValue` method to update the manager for all descendants of the organizational unit. +For example, your company may want to change the manager of an organizational unit. To do this, you can use the `GetReparentedValue` method to update the manager for all descendants of the organizational unit. ```csharp var newManager = await context.Persons.SingleAsync(x => x.Id == newManagerId); @@ -76,4 +76,4 @@ await context.SaveChangesAsync(); For more information about hierarchy id, see the following resource: -https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core \ No newline at end of file +https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core From 2872b32f25b118381ef9e1c877645c68993c6f22 Mon Sep 17 00:00:00 2001 From: Hamza Albreem <94292623+braim23@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:11:55 +0300 Subject: [PATCH 31/46] Update Post.md --- .../2023-11-97-AOT-Compilation/Post.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md index 8aebc3ba58..2b994b59c3 100644 --- a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md +++ b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md @@ -1,27 +1,27 @@ # Native AOT Compilation in .NET 8 -Native AOT (Ahead-of-Time) compilation is a feature that allows developers to create a self-contained app compiled to native code that can run on machines without the .NET runtime installed. It results in benefits such as minimized disk footprint, reduced executable size, reduced startup time, and reduced memory demand +Native AOT (Ahead-of-Time) compilation is a feature that allows developers to create a self-contained app compiled to native code that can run on machines without the .NET runtime installed. It results in benefits such as minimized disk footprint, reduced executable size, reduced startup time, and reduced memory demand. Native AOT compilation isn't a new feature in .NET 8. It's first introduced in .NET 7. -Differences between .NET 7 and .NET 8 AOT Compilation are: +Differences between the AOT Compilation of .NET 7 and .NET 8 are: - **System.Text.Json improvements**: .NET 8 adds support for more types, source generation, interface hierarchies, naming policies, read-only properties, and more. -- **New types for performance**: .NET 8 introduces new types such as FrozenDictionary, FrozenSet, SearchValues, CompositeFormat, TimeProvider, and ITimer for improving app performance. +- **New types for performance**: .NET 8 introduces new types such as FrozenDictionary, FrozenSet, SearchValues, CompositeFormat, TimeProvider, and ITimer to improve the app performance. - **System.Numerics and System.Runtime.Intrinsics enhancements**: .NET 8 adds support for Vector512, AVX-512, IUtf8SpanFormattable, Lerp, and more. - **System.ComponentModel.DataAnnotations additions**: .NET 8 adds new data validation attributes for cloud-native services and a new ValidateOptionsResultBuilder type. - **Hosted services lifecycle methods**: .NET 8 adds new methods such as StartAsync, StopAsync, StartBackgroundAsync, and StopBackgroundAsync for hosted services. -It's important to note that not all features in ASP.NET Core are currently compatible with native AOT For more information, see [Native AOT deployment overview](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/). +It's important to note that not all features in ASP.NET Core are currently compatible with native AOT. For more information, see [Native AOT deployment overview](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/). ## How to use Native AOT Compilation in .NET 8 -You can add `true` in your project file to enable Native AOT Compilation. +You can add `true` in your project .csproj file to enable Native AOT Compilation. - - For the new projects you can creage with `--aot` parameter. Example: `dotnet new console --aot`. + - For the new projects, you can create them with the `--aot` parameter. Example: `dotnet new console --aot`. -By default, compiler chooses a blended approach code optimization but you can specify an optimization preference inside your csproj file. You can choose **size** or **speed** according your requirements. +By default, the compiler chooses a blended approach code optimization but you can specify an optimization preference inside your .csproj file. You can choose **size** or **speed** according your requirements. ```xml Size @@ -46,10 +46,10 @@ I have created a simple console application to test the Native AOT Compilation. | AOT (Speed)| 1280 kb | 00.0023838 ~2ms | | AOT (Size) | 1111 kb | 00.0025145 ~2ms | -Most of existing libraries don't support AOT compiling yet, so I couldn't use [BenchmarkDotnet](https://github.com/dotnet/BenchmarkDotNet) to measure the performance. I have used [Stopwatch](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch?view=net-8.0) to measure the performance. So performance results may not be accurate but gives insight about the performance difference. +Most of existing libraries don't support AOT compilation yet, so I couldn't use [BenchmarkDotnet](https://github.com/dotnet/BenchmarkDotNet) to measure the performance. I have used [Stopwatch](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch?view=net-8.0) to measure the performance. So the performance results may not be accurate but gives insight about the performance difference. ## AOT Support in MAUI -You can now use Native AOT Compilation iOS-like target platforms in .NET MAUI. You can enable AOT compilation with exact same method by adding `true` to your project file. According to dotnet team, apps sizes reduced by 35% and startup times reduced by 28% with AOT compilation. And runtime performance is also improved by 50%. +You can now use Native AOT Compilation on iOS-like target frameworks in .NET MAUI. You can enable AOT compilation with the exact same method by adding `true` to your project .csproj file. According to the dotnet team, apps sizes reduced by 35% and startup times reduced by 28% with AOT compilation. And runtime performance is also improved by 50%. But there are some limitations in MAUI AOT Compilation. A lot of libraries still don't support AOT compilation and some of platform-specific feaetures may not work at the moment. From 98063af6dbb57f3a37809403f4879870cd756e9e Mon Sep 17 00:00:00 2001 From: Salih Date: Wed, 8 Nov 2023 15:20:27 +0300 Subject: [PATCH 32/46] Update en.json --- .../Commercial/Localization/Resources/en.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index b14d1ffa70..12bc62525a 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -601,7 +601,7 @@ "PaymentSucceed_ViewOrganization": "Click here to view organization", "Purchase_TotalAnnualPrice": "TOTAL (annual fee)", "Purchase_TrainingPrice": "Training Price", - "Purchase_OnboardingTraining": "ABP Onboarding & Web Application Development Live Training", + "Purchase_OnboardingTraining": "Onboarding & Web Application Development Live Training", "TotalDeveloperPrice": "Total Developer Price", "Purchase_PricePerDeveloper": "{0} {1} per developer", "Purchase_IncludedDeveloperInfo": "{0} {1} included.", @@ -1081,6 +1081,10 @@ "CampaignInfo": "Buy a new license or renew your existing license and get an additional 2 months at no additional cost! This offer is valid for all license plans. Ensure you take advantage of this limited-time promotion to expand your access to premium features and upgrades.", "HurryUpLastDay": "Hurry Up! Last Day: {0}", "CreatingCRUDPagesWithABPSuite": "Creating CRUD pages with ABP Suite", - "Testimonials": "Testimonials" + "Testimonials": "Testimonials", + "MultipleYearDiscount": "Multiple year discount", + "CampaignDiscountText": "Black friday discount", + "CampaignDiscountName": "Black friday", + "CampaignName:BlackFriday": "Black friday" } } From 7027470ab6d18d8b9f7180926132eda2931696ae Mon Sep 17 00:00:00 2001 From: Hamza Albreem <94292623+braim23@users.noreply.github.com> Date: Wed, 8 Nov 2023 15:49:15 +0300 Subject: [PATCH 33/46] Update POST.md --- .../2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md b/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md index 2a8f625e09..03ca906884 100644 --- a/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md +++ b/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md @@ -32,7 +32,7 @@ app.MapPost("/books", async ([FromForm] string name, }); ``` -Another way is to use the [AsParameters] attribute, the following code binds from form values to properties of the `NewBookRequest` record struct: +Another way is using the [AsParameters] attribute, the following code binds from form values to properties of the `NewBookRequest` record struct: ```csharp public record NewBookRequest([FromForm] string Name, [FromForm] BookType BookType, IFormFile? Cover); @@ -153,4 +153,4 @@ app.Run(); * https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-8.0#minimal-apis * https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/parameter-binding?view=aspnetcore-8.0#explicit-binding-from-form-values -* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot?view=aspnetcore-8.0 \ No newline at end of file +* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot?view=aspnetcore-8.0 From b3330c113da61be5a4f5b604e2fb4a92cc0b7f04 Mon Sep 17 00:00:00 2001 From: Salih Date: Wed, 8 Nov 2023 19:17:21 +0300 Subject: [PATCH 34/46] Update en.json --- .../Commercial/Localization/Resources/en.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index 12bc62525a..92d7450319 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -978,10 +978,10 @@ "Purchase_DevelopersAlreadyIncluded": "{0} developers already included", "1Year": "1 year", "{0}Years": "{0} years", - "1YearLicense": "1 year license", - "{0}YearsLicense": "{0} years license", - "1AdditionalDeveloper": "1 additional developer", - "{0}AdditionalDevelopers": "{0} additional developers", + "1YearLicense": "1 Year License", + "{0}YearsLicense": "{0} Years License", + "1AdditionalDeveloper": "1 Additional Developer", + "{0}AdditionalDevelopers": "{0} Additional Developers", "Discount": "Discount ({0}%)", "Summary": "Summary", "TrainingPack": "Training pack", @@ -1082,9 +1082,9 @@ "HurryUpLastDay": "Hurry Up! Last Day: {0}", "CreatingCRUDPagesWithABPSuite": "Creating CRUD pages with ABP Suite", "Testimonials": "Testimonials", - "MultipleYearDiscount": "Multiple year discount", - "CampaignDiscountText": "Black friday discount", - "CampaignDiscountName": "Black friday", - "CampaignName:BlackFriday": "Black friday" + "MultipleYearDiscount": "Multiple Year Discount", + "CampaignDiscountText": "Black Friday Discount", + "CampaignDiscountName": "Black Friday", + "CampaignName:BlackFriday": "Black Friday" } } From b0ae2f8e049c6a87be356dda6549f771f8c698e4 Mon Sep 17 00:00:00 2001 From: Salih Date: Wed, 8 Nov 2023 20:02:19 +0300 Subject: [PATCH 35/46] Add missing localizations --- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json | 4 +++- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json | 4 +++- 24 files changed, 72 insertions(+), 24 deletions(-) diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json index bcbab76211..a7de87d5ef 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "سيتم حذف {0}!", "ItemWillBeDeletedMessage": "سوف يتم حذف هذا البند!", "ManageYourAccount": "إدارة حسابك", - "OthersGroup": "آخرون" + "OthersGroup": "آخرون", + "Today": "اليوم", + "Apply": "يتقدم" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json index 5dc41952a6..a1f1856c9e 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} bude smazáno!", "ItemWillBeDeletedMessage": "Tato položka bude smazána!", "ManageYourAccount": "Spravujte svůj účet", - "OthersGroup": "Jiný" + "OthersGroup": "Jiný", + "Today": "Dnes", + "Apply": "Aplikovat" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json index dc99f75586..22fddc3e27 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} wird gelöscht!", "ItemWillBeDeletedMessage": "Dieses Element wird gelöscht!", "ManageYourAccount": "Verwalten Sie Ihr Benutzerkonto", - "OthersGroup":"Andere" + "OthersGroup":"Andere", + "Today": "Heute", + "Apply": "Anwenden" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json index 24a7642eb0..8cb7b4c0dc 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "Το {0} θα διαγραφεί", "ItemWillBeDeletedMessage": "Αυτό το στοιχείο θα διαγραφεί!", "ManageYourAccount": "Διαχείριση Λογαριασμού", - "OthersGroup":"άλλος" + "OthersGroup":"άλλος", + "Today": "Σήμερα", + "Apply": "Ισχύουν" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json index c6ea44f1f3..68ad3dda07 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json @@ -50,6 +50,8 @@ "ItemWillBeDeletedMessage": "This item will be deleted!", "ManageYourAccount": "Manage your account", "OthersGroup": "Other", - "NotAssigned": "Not Assigned" + "NotAssigned": "Not Assigned", + "Today": "Today", + "Apply": "Apply" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json index c28e8049bc..abf136d87b 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} serán borrados!", "ItemWillBeDeletedMessage": "Este elemento será borrado", "ManageYourAccount": "Administrar cuenta", - "OthersGroup": "Otra" + "OthersGroup": "Otra", + "Today": "Hoy", + "Apply": "Aplicar" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json index 4fd179573b..bcc6250656 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} حذف خواهد شد!", "ItemWillBeDeletedMessage": "این مورد حذف خواهد شد!", "ManageYourAccount": "حساب خود را مدیریت کنید", - "OthersGroup": "دیگر" + "OthersGroup": "دیگر", + "Today": "امروز", + "Apply": "درخواست دادن" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json index 25811c92eb..25ab3ee59a 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} poistetaan!", "ItemWillBeDeletedMessage": "Tämä kohde poistetaan!", "ManageYourAccount": "Hallitse tiliäsi", - "OthersGroup": "Muut" + "OthersGroup": "Muut", + "Today": "Tänään", + "Apply": "Käytä" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json index b850315dfe..6e5c67d2d6 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} sera supprimé!", "ItemWillBeDeletedMessage": "Cet objet va être supprimé!", "ManageYourAccount": "Gérer votre compte", - "OthersGroup": "Autre" + "OthersGroup": "Autre", + "Today": "Aujourd'hui", + "Apply": "Appliquer" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json index 0a20f1dcbb..96b73848f5 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} हटा दिया जाएगा!", "ItemWillBeDeletedMessage": "यह आइटम हटा दिया जाएगा!", "ManageYourAccount": "अपने खाते का प्रबंधन", - "OthersGroup": "अन्य" + "OthersGroup": "अन्य", + "Today": "आज", + "Apply": "आवेदन करना" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json index a6285a4cee..3126f17bbf 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} zapis će biti obrisan!", "ItemWillBeDeletedMessage": "Ovaj zapis će biti obrisan!", "ManageYourAccount": "Upravljaj korisničkim računom", - "OthersGroup": "Drugi" + "OthersGroup": "Drugi", + "Today": "Danas", + "Apply": "primijeniti" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json index 98c0c46f54..f3e6454b5b 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} törlésre kerül!", "ItemWillBeDeletedMessage": "Ez az elem törlődik!", "ManageYourAccount": "Kezelje fiókját", - "OthersGroup": "Egyéb" + "OthersGroup": "Egyéb", + "Today": "Ma", + "Apply": "Alkalmaz" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json index 12b1565c2e..5a9d9eab54 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} verður eytt!", "ItemWillBeDeletedMessage": "Þessum lið verður eytt!", "ManageYourAccount": "Stillingar notandaaðgangs", - "OthersGroup": "Annað" + "OthersGroup": "Annað", + "Today": "Í dag", + "Apply": "Sækja um" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json index f07bf67e2d..171681980d 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} sarà eliminato!", "ItemWillBeDeletedMessage": "Questo elemento sarà eliminato!", "ManageYourAccount": "Gestisci il tuo account", - "OthersGroup": "Altra" + "OthersGroup": "Altra", + "Today": "Oggi", + "Apply": "Fare domanda a" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json index 5875fe07b9..42d48d5900 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} wordt verwijderd!", "ItemWillBeDeletedMessage": "Dit item wordt verwijderd!", "ManageYourAccount": "Beheer uw account", - "OthersGroup": "Ander" + "OthersGroup": "Ander", + "Today": "Vandaag", + "Apply": "Toepassen" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json index f2b8227137..bd7a6d5818 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} zostanie usunięty!", "ItemWillBeDeletedMessage": "Ten element zostanie usunięty!", "ManageYourAccount": "Zarządzaj kontem", - "OthersGroup": "Inny" + "OthersGroup": "Inny", + "Today": "Dzisiaj", + "Apply": "Stosować" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json index 928c9299fb..a205b9442a 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} será excluído!", "ItemWillBeDeletedMessage": "Este item será excluído!", "ManageYourAccount": "Gerenciar sua conta", - "OthersGroup": "Outra" + "OthersGroup": "Outra", + "Today": "Hoje", + "Apply": "Aplicar" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json index 544b893c39..0edf60e833 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} va fi şters!", "ItemWillBeDeletedMessage": "Acest articol va fi şters!", "ManageYourAccount": "Administraţi-vă contul", - "OthersGroup": "Alte" + "OthersGroup": "Alte", + "Today": "Astăzi", + "Apply": "aplica" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json index 66f24ca65d..53104c18f4 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} будет удален!", "ItemWillBeDeletedMessage": "Этот предмет будет удален!", "ManageYourAccount": "Настройте свой аккаунт", - "OthersGroup": "Другой" + "OthersGroup": "Другой", + "Today": "Сегодня", + "Apply": "Применять" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json index 28278f04f2..313ce021de 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} sa vymaže!", "ItemWillBeDeletedMessage": "Táto položka bude vymazaná!", "ManageYourAccount": "Spravovať svoje konto", - "OthersGroup": "Iné" + "OthersGroup": "Iné", + "Today": "Dnes", + "Apply": "Použiť" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json index c05c1dc202..e7ff5ddc66 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} bo izbrisan!", "ItemWillBeDeletedMessage": "Ta element bo izbrisan!", "ManageYourAccount": "Upravljajte svoj račun", - "OthersGroup": "Ostalo" + "OthersGroup": "Ostalo", + "Today": "Danes", + "Apply": "Prijavite se" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json index 1b8bc5fb5b..581964a9b8 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json @@ -49,6 +49,8 @@ "ItemWillBeDeletedMessageWithFormat": "{0} sẽ bị xóa!", "ItemWillBeDeletedMessage": "Vật phẩm này sẽ bị xoá!", "ManageYourAccount": "Quản lý tài khoản của bạn", - "OthersGroup": "Khác" + "OthersGroup": "Khác", + "Today": "Hôm nay", + "Apply": "Áp dụng" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json index bcf68dbcaa..eb91eab151 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json @@ -51,6 +51,8 @@ "ManageYourAccount": "管理你的账户", "OthersGroup": "其他", "CopiedToTheClipboard": "已复制到剪贴板", - "NotAssigned": "未分配" + "NotAssigned": "未分配", + "Today": "今天", + "Apply": "申请" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json index 1c2719b954..bac9aaefb1 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json @@ -51,6 +51,8 @@ "ManageYourAccount": "管理個人帳號", "OthersGroup": "其他", "CopiedToTheClipboard": "已复制到剪贴板", - "NotAssigned": "未分配" + "NotAssigned": "未分配", + "Today": "今天", + "Apply": "申請" } } From f0016fe7496ac4ffa58c9be2503763ffef217af4 Mon Sep 17 00:00:00 2001 From: Salih Date: Wed, 8 Nov 2023 20:02:31 +0300 Subject: [PATCH 36/46] Fix de map --- .../Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs index 4970015a46..7ba8105d43 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs @@ -30,11 +30,13 @@ public class AbpAspNetCoreMvcUiPackagesModule : AbpModule //moment options.AddLanguagesMapOrUpdate(MomentScriptContributor.PackageName, new NameValue("zh-Hans", "zh-cn"), - new NameValue("zh-Hant", "zh-tw")); + new NameValue("zh-Hant", "zh-tw"), + new NameValue("de-DE", "de")); options.AddLanguageFilesMapOrUpdate(MomentScriptContributor.PackageName, new NameValue("zh-Hans", "zh-cn"), - new NameValue("zh-Hant", "zh-tw")); + new NameValue("zh-Hant", "zh-tw"), + new NameValue("de-DE", "de")); //Timeago options.AddLanguageFilesMapOrUpdate(TimeagoScriptContributor.PackageName, From 6b9acc365327f4a702904a98064ba8cdd19c5f27 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Nov 2023 08:45:06 +0800 Subject: [PATCH 37/46] Update zh-Hans.json --- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json index eb91eab151..51d5865924 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json @@ -53,6 +53,6 @@ "CopiedToTheClipboard": "已复制到剪贴板", "NotAssigned": "未分配", "Today": "今天", - "Apply": "申请" + "Apply": "应用" } } From cff8e32ad68935f87cfac4d0fcd1be469458a050 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Nov 2023 08:46:58 +0800 Subject: [PATCH 38/46] Update zh-Hant.json --- .../src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json index bac9aaefb1..dac062d1cd 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json @@ -53,6 +53,6 @@ "CopiedToTheClipboard": "已复制到剪贴板", "NotAssigned": "未分配", "Today": "今天", - "Apply": "申請" + "Apply": "應用" } } From e583b37fd7c4c856dd4a2d12fd3c4f68d5f85bd2 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Nov 2023 09:39:20 +0800 Subject: [PATCH 39/46] Add `username` to social register page. --- .../src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml | 2 +- .../src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml index fabe41ae6f..745368402c 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml @@ -12,7 +12,7 @@
@L["Login"]
- @if ((!Model.IsExternalLogin || Model.UserNameExtracted) && Model.EnableLocalRegister) + @if (Model.EnableLocalRegister || Model.IsExternalLogin) { } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index 722eca26b7..322a17e82f 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -37,7 +37,6 @@ public class RegisterModel : AccountPageModel [BindProperty(SupportsGet = true)] public string ExternalLoginAuthSchema { get; set; } - public bool UserNameExtracted { get; set; } public IEnumerable ExternalProviders { get; set; } public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !string.IsNullOrWhiteSpace(x.DisplayName)); public bool EnableLocalRegister { get; set; } @@ -128,7 +127,6 @@ public class RegisterModel : AccountPageModel { Input.UserName = await GetUserNameFromEmail(Input.EmailAddress); } - UserNameExtracted = true; await RegisterExternalUserAsync(externalLoginInfo, Input.UserName, Input.EmailAddress); } else From 0b9ae266701949af28ec1db8a8a6cd90ffc8e03d Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Nov 2023 15:20:55 +0800 Subject: [PATCH 40/46] Redirect to register page on social login. --- .../Pages/Account/Login.cshtml.cs | 43 ++++--------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index ab5e2819cb..d4bb0dbc5e 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -242,14 +242,16 @@ public class LoginModel : AccountPageModel var user = await UserManager.FindByEmailAsync(email); if (user == null) { - user = await CreateExternalUserAsync(loginInfo); + return RedirectToPage("./Register", new { + IsExternalLogin = true, + ExternalLoginAuthSchema = loginInfo.LoginProvider, + ReturnUrl = returnUrl + }); } - else + + if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null) { - if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null) - { - CheckIdentityErrors(await UserManager.AddLoginAsync(user, loginInfo)); - } + CheckIdentityErrors(await UserManager.AddLoginAsync(user, loginInfo)); } await SignInManager.SignInAsync(user, false); @@ -264,35 +266,6 @@ public class LoginModel : AccountPageModel return RedirectSafely(returnUrl, returnUrlHash); } - protected virtual async Task CreateExternalUserAsync(ExternalLoginInfo info) - { - await IdentityOptions.SetAsync(); - - var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email) ?? info.Principal.FindFirstValue(ClaimTypes.Email); - var userName = await GetUserNameFromEmail(emailAddress); - - var user = new IdentityUser(GuidGenerator.Create(), userName, emailAddress, CurrentTenant.Id); - - CheckIdentityErrors(await UserManager.CreateAsync(user)); - CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress)); - CheckIdentityErrors(await UserManager.AddLoginAsync(user, info)); - CheckIdentityErrors(await UserManager.AddDefaultRolesAsync(user)); - - user.Name = info.Principal.FindFirstValue(AbpClaimTypes.Name); - user.Surname = info.Principal.FindFirstValue(AbpClaimTypes.SurName); - - var phoneNumber = info.Principal.FindFirstValue(AbpClaimTypes.PhoneNumber); - if (!phoneNumber.IsNullOrWhiteSpace()) - { - var phoneNumberConfirmed = string.Equals(info.Principal.FindFirstValue(AbpClaimTypes.PhoneNumberVerified), "true", StringComparison.InvariantCultureIgnoreCase); - user.SetPhoneNumber(phoneNumber, phoneNumberConfirmed); - } - - await UserManager.UpdateAsync(user); - - return user; - } - protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds() { if (!ValidationHelper.IsValidEmailAddress(LoginInput.UserNameOrEmailAddress)) From 8fa1b0155dca48d602cf6cdf8a72401e10e3b1e2 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Nov 2023 16:12:34 +0800 Subject: [PATCH 41/46] Add `AbpMultiTenancyAbstractionsModule` into `AbpDaprModule ` --- framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj | 1 + framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj b/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj index 92377ef51a..34a505110c 100644 --- a/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj +++ b/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj @@ -12,6 +12,7 @@ + diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs index e1ca4bb0db..65605bc342 100644 --- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs @@ -4,10 +4,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Json; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Dapr; -[DependsOn(typeof(AbpJsonModule))] +[DependsOn(typeof(AbpJsonModule), typeof(AbpMultiTenancyAbstractionsModule))] public class AbpDaprModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) From 9db31e75b4f890f1141fc7fb1caf536a3d693aa0 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Nov 2023 17:13:52 +0800 Subject: [PATCH 42/46] Rename `CDN` to `External`. --- docs/en/UI/AspNetCore/Bundling-Minification.md | 16 ++++++++-------- .../UI/AspNetCore/Bundling-Minification.md | 17 ++++++++--------- .../Bundling/BundleConfigurationExtensions.cs | 4 ++-- .../BundleContributorCollectionExtensions.cs | 2 +- .../AspNetCore/Mvc/UI/Bundling/BundleFile.cs | 15 ++++++++++++--- .../AspNetCore/Mvc/UI/Bundling/BundleManager.cs | 2 +- .../TagHelpers/AbpBundleItemTagHelper.cs | 5 +---- .../TagHelpers/AbpTagHelperResourceService.cs | 2 +- .../TagHelpers/AbpTagHelperScriptService.cs | 2 +- .../TagHelpers/AbpTagHelperStyleService.cs | 2 +- 10 files changed, 36 insertions(+), 31 deletions(-) diff --git a/docs/en/UI/AspNetCore/Bundling-Minification.md b/docs/en/UI/AspNetCore/Bundling-Minification.md index 650d3e3e77..6164d00c7d 100644 --- a/docs/en/UI/AspNetCore/Bundling-Minification.md +++ b/docs/en/UI/AspNetCore/Bundling-Minification.md @@ -382,11 +382,11 @@ Configure(options => ```` -### CDN Support +### External/CDN file Support -You can configure `AbpBundlingOptions` or use `is-cdn` on `script/style` taghelper to load all or a single js/css file from CDN. +The bundling system automatically recognizes the external/CDN files and adds them to the page without any change. -#### Add CDN files in `AbpBundlingOptions` +#### Using External/CDN files in `AbpBundlingOptions` ````csharp Configure(options => @@ -397,7 +397,7 @@ Configure(options => configuration .AddFiles("/styles/my-style1.css") .AddFiles("/styles/my-style2.css") - .AddCdnFiles("https://cdn.abp.io/bootstrap.css") + .AddFiles("https://cdn.abp.io/bootstrap.css") .AddFiles("/styles/my-style3.css") .AddFiles("/styles/my-style4.css"); }); @@ -408,7 +408,7 @@ Configure(options => configuration .AddFiles("/scripts/my-script1.js") .AddFiles("/scripts/my-script2.js") - .AddCdnFiles("https://cdn.abp.io/bootstrap.js") + .AddFiles("https://cdn.abp.io/bootstrap.js") .AddFiles("/scripts/my-script3.js") .AddFiles("/scripts/my-script4.js"); }); @@ -427,13 +427,13 @@ Configure(options => ```` -#### Add CDN files in Tag Helpers. +#### Using External/CDN files in Tag Helpers. ````html - + @@ -441,7 +441,7 @@ Configure(options => - + diff --git a/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md b/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md index fab0893a76..72bee2eedb 100644 --- a/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md +++ b/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md @@ -353,11 +353,11 @@ services.Configure(options => }); ```` -### CDN 支持 +### 外部/CDN文件支持 -你可以配置`AbpBundlingOptions`或在`script/style` taghelper上使用`is-cdn`来从CDN加载js/css. +捆绑系统会自动识别外部/CDN文件,并将其添加到页面中,无需进行任何更改。 -#### 在`AbpBundlingOptions`中添加CDN文件 +#### 在`AbpBundlingOptions`中添加外部/CDN文件 ````csharp Configure(options => @@ -368,7 +368,7 @@ Configure(options => configuration .AddFiles("/styles/my-style1.css") .AddFiles("/styles/my-style2.css") - .AddCdnFiles("https://cdn.abp.io/bootstrap.css") + .AddFiles("https://cdn.abp.io/bootstrap.css") .AddFiles("/styles/my-style3.css") .AddFiles("/styles/my-style4.css"); }); @@ -379,7 +379,7 @@ Configure(options => configuration .AddFiles("/scripts/my-script1.js") .AddFiles("/scripts/my-script2.js") - .AddCdnFiles("https://cdn.abp.io/bootstrap.js") + .AddFiles("https://cdn.abp.io/bootstrap.js") .AddFiles("/scripts/my-script3.js") .AddFiles("/scripts/my-script4.js"); }); @@ -398,13 +398,13 @@ Configure(options => ```` -#### 在TagHelpers中添加CDN文件 +#### 在TagHelpers中添加外部/CDN文件 ````html - + @@ -412,7 +412,7 @@ Configure(options => - + @@ -430,7 +430,6 @@ Configure(options => ```` - ### 主题 主题使用标准包贡献者将库资源添加到页面布局. 主题还可以定义一些标准/全局包, 因此任何模块都可以为这些标准/全局包做出贡献. 有关更多信息, 请参阅[主题文档](Theming.md). diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs index 8dd51dc5a3..62f7950c5a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs @@ -17,9 +17,9 @@ public static class BundleConfigurationExtensions return bundleConfiguration; } - public static BundleConfiguration AddCdnFiles(this BundleConfiguration bundleConfiguration, params string[] files) + public static BundleConfiguration AddExternalFiles(this BundleConfiguration bundleConfiguration, params string[] files) { - bundleConfiguration.Contributors.AddCdnFiles(files.Select(x => new BundleFile(x, true)).ToArray()); + bundleConfiguration.Contributors.AddExternalFiles(files.Select(x => new BundleFile(x, true)).ToArray()); return bundleConfiguration; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs index 22bfe2947c..addbe7e2cd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs @@ -12,7 +12,7 @@ public static class BundleContributorCollectionExtensions contributors.Add(new BundleFileContributor(files)); } - public static void AddCdnFiles(this BundleContributorCollection contributors, params BundleFile[] files) + public static void AddExternalFiles(this BundleContributorCollection contributors, params BundleFile[] files) { contributors.Add(new BundleFileContributor(files)); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs index 091a4b3823..fbd6c3eaa1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs @@ -1,15 +1,24 @@ +using System; + namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; public class BundleFile { public string FileName { get; set; } - public bool IsCdn { get; set; } + public bool IsExternalFile { get; set; } + + public BundleFile(string fileName) + { + FileName = fileName; + IsExternalFile = fileName.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("https://", StringComparison.OrdinalIgnoreCase); + } - public BundleFile(string fileName, bool isCdn = false) + public BundleFile(string fileName, bool isExternalFile) { FileName = fileName; - IsCdn = isCdn; + IsExternalFile = isExternalFile; } /// diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs index 9e5ca4bdef..20c33e86e5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs @@ -81,7 +81,7 @@ public class BundleManager : IBundleManager, ITransientDependency var localBundleFiles = new List(); foreach (var bundleFile in bundleFiles) { - if (!bundleFile.IsCdn) + if (!bundleFile.IsExternalFile) { localBundleFiles.Add(bundleFile.FileName); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs index 81c06df7c3..03ed327dc0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs @@ -18,9 +18,6 @@ public abstract class AbpBundleItemTagHelper : Ab /// public Type? Type { get; set; } - [HtmlAttributeName("is-cdn")] - public bool IsCdn { get; set; } - protected AbpBundleItemTagHelper(TTagHelperService service) : base(service) { @@ -53,7 +50,7 @@ public abstract class AbpBundleItemTagHelper : Ab if (Src != null) { - return new BundleTagHelperFileItem(new BundleFile(Src, IsCdn)); + return new BundleTagHelperFileItem(new BundleFile(Src)); } throw new AbpException("abp-script tag helper requires to set either src or type!"); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs index 5a19dee915..9be91c535f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs @@ -65,7 +65,7 @@ public abstract class AbpTagHelperResourceService : ITransientDependency foreach (var bundleFile in bundleFiles) { - if (bundleFile.IsCdn) + if (bundleFile.IsExternalFile) { AddHtmlTag(viewContext, tagHelper, context, output, bundleFile, null); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs index ea0f11b777..c8486ebadd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs @@ -53,7 +53,7 @@ public class AbpTagHelperScriptService : AbpTagHelperResourceService var nonceText = (viewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString)) ? $"nonce=\"{nonceString}\" " : string.Empty; - var src = file.IsCdn ? file.FileName : viewContext.GetUrlHelper().Content((file.FileName + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); + var src = file.IsExternalFile ? file.FileName : viewContext.GetUrlHelper().Content((file.FileName + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); output.Content.AppendHtml($"{Environment.NewLine}"); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs index 63f2e2304d..bcfd7d95e2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs @@ -51,7 +51,7 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService _ => false }; - var href = file.IsCdn ? file.FileName : viewContext.GetUrlHelper().Content((file.FileName + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); + var href = file.IsExternalFile ? file.FileName : viewContext.GetUrlHelper().Content((file.FileName + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~')); if (preload || Options.PreloadStylesByDefault || Options.PreloadStyles.Any(x => file.FileName.StartsWith(x, StringComparison.OrdinalIgnoreCase))) { output.Content.AppendHtml(SecurityHeadersOptions.UseContentSecurityPolicyScriptNonce From 21e53f756772f76eb253dfcbb335caab4185443a Mon Sep 17 00:00:00 2001 From: masumulu28 Date: Thu, 9 Nov 2023 14:58:54 +0300 Subject: [PATCH 43/46] expose enums & models & constants at schematic --- npm/ng-packs/packages/schematics/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/schematics/src/index.ts b/npm/ng-packs/packages/schematics/src/index.ts index cb0ff5c3b5..64f4e4cb7a 100644 --- a/npm/ng-packs/packages/schematics/src/index.ts +++ b/npm/ng-packs/packages/schematics/src/index.ts @@ -1 +1,3 @@ -export {}; +export * from './enums'; +export * from './models'; +export * from './constants'; From 676ee8be7746c5447a55dc67642271ebfb8e5da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 9 Nov 2023 15:11:15 +0300 Subject: [PATCH 44/46] Update StandardMenus.cs Removed outdated comment --- .../Volo/Abp/Ui/Navigation/StandardMenus.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/StandardMenus.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/StandardMenus.cs index c22645e33a..83f7f70cc9 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/StandardMenus.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/StandardMenus.cs @@ -2,12 +2,6 @@ namespace Volo.Abp.UI.Navigation; public static class StandardMenus { - /* TODO: Consider to create nested class like - * StandardMenus.Application.Main - * StandardMenus.Application.User - * StandardMenus.Application.Shortcut - */ - public const string Main = "Main"; public const string User = "User"; public const string Shortcut = "Shortcut"; From 544d876a6fe5ec9e1c15825ea53023889b9c6ec0 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 10 Nov 2023 10:53:03 +0800 Subject: [PATCH 45/46] Fix RadarSpinner component --- .../Bundling/BlazorGlobalStyleContributor.cs | 1 + .../ComponentsComponentsBundleContributor.cs | 1 + .../wwwroot/volo.abp.blazoriseui.css | 60 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 framework/src/Volo.Abp.BlazoriseUI/wwwroot/volo.abp.blazoriseui.css diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalStyleContributor.cs index 3223be417c..3a94e1a568 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalStyleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalStyleContributor.cs @@ -17,5 +17,6 @@ public class BlazorGlobalStyleContributor : BundleContributor context.Files.AddIfNotContains("/_content/Blazorise/blazorise.css"); context.Files.AddIfNotContains("/_content/Blazorise.Bootstrap5/blazorise.bootstrap5.css"); context.Files.AddIfNotContains("/_content/Blazorise.Snackbar/blazorise.snackbar.css"); + context.Files.AddIfNotContains("/_content/Volo.Abp.BlazoriseUI/volo.abp.blazoriseui.css"); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs index 61f42e8222..45b06e5484 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs @@ -26,5 +26,6 @@ public class ComponentsComponentsBundleContributor : IBundleContributor context.Add("_content/Blazorise/blazorise.css"); context.Add("_content/Blazorise.Bootstrap5/blazorise.bootstrap5.css"); context.Add("_content/Blazorise.Snackbar/blazorise.snackbar.css"); + context.Add("_content/Volo.Abp.BlazoriseUI/volo.abp.blazoriseui.css"); } } diff --git a/framework/src/Volo.Abp.BlazoriseUI/wwwroot/volo.abp.blazoriseui.css b/framework/src/Volo.Abp.BlazoriseUI/wwwroot/volo.abp.blazoriseui.css new file mode 100644 index 0000000000..84814c2ef3 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/wwwroot/volo.abp.blazoriseui.css @@ -0,0 +1,60 @@ +.radar-spinner, .radar-spinner * { + box-sizing: border-box; +} + +.radar-spinner { + height: 60px; + width: 60px; + position: relative; +} + +.radar-spinner .circle { + position: absolute; + height: 100%; + width: 100%; + top: 0; + left: 0; + animation: radar-spinner-animation 2s infinite; +} + +.radar-spinner .circle:nth-child(1) { + padding: calc(60px * 5 * 2 * 0 / 110); + animation-delay: 300ms; +} + +.radar-spinner .circle:nth-child(2) { + padding: calc(60px * 5 * 2 * 1 / 110); + animation-delay: 300ms; +} + +.radar-spinner .circle:nth-child(3) { + padding: calc(60px * 5 * 2 * 2 / 110); + animation-delay: 300ms; +} + +.radar-spinner .circle:nth-child(4) { + padding: calc(60px * 5 * 2 * 3 / 110); + animation-delay: 0ms; +} + +.radar-spinner .circle-inner, .radar-spinner .circle-inner-container { + height: 100%; + width: 100%; + border-radius: 50%; + border: calc(60px * 5 / 110) solid transparent; +} + +.radar-spinner .circle-inner { + border-left-color: var(--secondary, #ff1d5e); + border-right-color: var(--secondary, #ff1d5e); +} + +@keyframes radar-spinner-animation { + 50% { + transform: rotate(180deg); + } + + 100% { + transform: rotate(0deg); + } +} \ No newline at end of file From c365cccd0ca291f8a8267d556318fdfbdd743362 Mon Sep 17 00:00:00 2001 From: Salih Date: Fri, 10 Nov 2023 11:02:49 +0300 Subject: [PATCH 46/46] Fix date picker problem --- .../bootstrap/dom-event-handlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js index 1baa46de9a..8e567d626a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js @@ -417,7 +417,7 @@ var isUtc = options.isUtc; dateFormat = dateFormat || options.dateFormat; if (!date) { - return isUtc ? moment.utc() : moment(); + return isUtc ? moment.utc().startOf('day') : moment().startOf('day'); } if (isUtc) {