From ea7383ad350358adddf1871054f27ae5dd350137 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:48:16 +0800 Subject: [PATCH 1/3] Update `AbpRedisCache` to compatible net 9. --- .../AbpCachingStackExchangeRedisModule.cs | 3 +- .../StackExchangeRedis/AbpRedisCache.cs | 183 +++++++++--------- .../Abp/EntityFrameworkCore/AbpDbContext.cs | 4 +- .../StackExchangeRedis/AbpRedisCache_Tests.cs | 2 +- .../AbpEntityFrameworkCoreTestModule.cs | 2 +- 5 files changed, 99 insertions(+), 95 deletions(-) diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs index 5b1036653d..817c42af18 100644 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs @@ -27,8 +27,7 @@ public class AbpCachingStackExchangeRedisModule : AbpModule } }); - //liangshiwei will update the AbpRedisCache - //context.Services.Replace(ServiceDescriptor.Singleton()); + context.Services.Replace(ServiceDescriptor.Singleton()); } } } diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs index c5ae0b5393..80815eaa0b 100644 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -16,21 +17,21 @@ namespace Volo.Abp.Caching.StackExchangeRedis; [DisableConventionalRegistration] public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems { - protected static readonly string AbsoluteExpirationKey; - protected static readonly string SlidingExpirationKey; - protected static readonly string DataKey; - protected static readonly long NotPresent; - protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpirationData; - protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpiration; - - private readonly static FieldInfo SetScriptField; - private readonly static FieldInfo RedisDatabaseField; - private readonly static MethodInfo ConnectMethod; - private readonly static MethodInfo ConnectAsyncMethod; - private readonly static MethodInfo MapMetadataMethod; - private readonly static MethodInfo GetAbsoluteExpirationMethod; - private readonly static MethodInfo GetExpirationInSecondsMethod; - private readonly static MethodInfo OnRedisErrorMethod; + protected readonly static string AbsoluteExpirationKey; + protected readonly static string SlidingExpirationKey; + protected readonly static string DataKey; + protected readonly static long NotPresent; + protected readonly static RedisValue[] HashMembersAbsoluteExpirationSlidingExpirationData; + protected readonly static RedisValue[] HashMembersAbsoluteExpirationSlidingExpiration; + + protected readonly static FieldInfo RedisDatabaseField; + protected readonly static MethodInfo ConnectMethod; + protected readonly static MethodInfo ConnectAsyncMethod; + protected readonly static MethodInfo MapMetadataMethod; + protected readonly static MethodInfo GetAbsoluteExpirationMethod; + protected readonly static MethodInfo GetExpirationInSecondsMethod; + protected readonly static MethodInfo OnRedisErrorMethod; + protected readonly static MethodInfo RecycleMethodInfo; protected RedisKey InstancePrefix { get; } @@ -40,8 +41,6 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems RedisDatabaseField = Check.NotNull(type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic), nameof(RedisDatabaseField)); - SetScriptField = Check.NotNull(type.GetField("_setScript", BindingFlags.Instance | BindingFlags.NonPublic), nameof(SetScriptField)); - ConnectMethod = Check.NotNull(type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectMethod)); ConnectAsyncMethod = Check.NotNull(type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectAsyncMethod)); @@ -51,9 +50,11 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems GetAbsoluteExpirationMethod = Check.NotNull(type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetAbsoluteExpirationMethod)); GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod)); - + OnRedisErrorMethod = Check.NotNull(type.GetMethod("OnRedisError", BindingFlags.Instance | BindingFlags.NonPublic), nameof(OnRedisErrorMethod)); + RecycleMethodInfo = Check.NotNull(type.GetMethod("Recycle", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static), nameof(RecycleMethodInfo)); + AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!; SlidingExpirationKey = type.GetField("SlidingExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!; @@ -61,9 +62,9 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!; NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.To(); - + HashMembersAbsoluteExpirationSlidingExpirationData = [AbsoluteExpirationKey, SlidingExpirationKey, DataKey]; - + HashMembersAbsoluteExpirationSlidingExpiration = [AbsoluteExpirationKey, SlidingExpirationKey]; } @@ -78,7 +79,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems } protected virtual IDatabase Connect() - { + { return (IDatabase)ConnectMethod.Invoke(this, Array.Empty())!; } @@ -87,32 +88,36 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems return await (ValueTask)ConnectAsyncMethod.Invoke(this, new object[] { token })!; } - public byte[]?[] GetMany( - IEnumerable keys) + protected virtual void Recycle(byte[]? lease) + { + RecycleMethodInfo.Invoke(this, new object[] { lease! }); + } + + public byte[]?[] GetMany(IEnumerable keys) { keys = Check.NotNull(keys, nameof(keys)); return GetAndRefreshMany(keys, true); } - public async Task GetManyAsync( - IEnumerable keys, - CancellationToken token = default) + public async Task GetManyAsync(IEnumerable keys, CancellationToken token = default) { keys = Check.NotNull(keys, nameof(keys)); return await GetAndRefreshManyAsync(keys, true, token); } - public void SetMany( - IEnumerable> items, - DistributedCacheEntryOptions options) + public void SetMany(IEnumerable> items, DistributedCacheEntryOptions options) { var cache = Connect(); try { - Task.WaitAll(PipelineSetMany(cache, items, options)); + Task.WaitAll(PipelineSetMany(cache, items, options, out var leases)); + foreach (var lease in leases) + { + Recycle(lease); + } } catch (Exception ex) { @@ -121,10 +126,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems } } - public async Task SetManyAsync( - IEnumerable> items, - DistributedCacheEntryOptions options, - CancellationToken token = default) + public async Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options, CancellationToken token = default) { token.ThrowIfCancellationRequested(); @@ -132,7 +134,11 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems try { - await Task.WhenAll(PipelineSetMany(cache, items, options)); + await Task.WhenAll(PipelineSetMany(cache, items, options, out var leases)); + foreach (var lease in leases) + { + Recycle(lease); + } } catch (Exception ex) { @@ -141,17 +147,14 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems } } - public void RefreshMany( - IEnumerable keys) + public void RefreshMany(IEnumerable keys) { keys = Check.NotNull(keys, nameof(keys)); GetAndRefreshMany(keys, false); } - public async Task RefreshManyAsync( - IEnumerable keys, - CancellationToken token = default) + public async Task RefreshManyAsync(IEnumerable keys, CancellationToken token = default) { keys = Check.NotNull(keys, nameof(keys)); @@ -193,9 +196,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems } } - protected virtual byte[]?[] GetAndRefreshMany( - IEnumerable keys, - bool getData) + protected virtual byte[]?[] GetAndRefreshMany(IEnumerable keys, bool getData) { var cache = Connect(); @@ -217,10 +218,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems return bytes; } - protected virtual async Task GetAndRefreshManyAsync( - IEnumerable keys, - bool getData, - CancellationToken token = default) + protected virtual async Task GetAndRefreshManyAsync(IEnumerable keys, bool getData, CancellationToken token = default) { token.ThrowIfCancellationRequested(); @@ -239,15 +237,11 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems OnRedisError(ex, cache); throw; } - + return bytes; } - protected virtual Task[] PipelineRefreshManyAndOutData( - IDatabase cache, - RedisKey[] keys, - RedisValue[][] results, - out byte[]?[] bytes) + protected virtual Task[] PipelineRefreshManyAndOutData(IDatabase cache, RedisKey[] keys, RedisValue[][] results, out byte[]?[] bytes) { bytes = new byte[keys.Length][]; var tasks = new Task[keys.Length]; @@ -293,37 +287,36 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems return tasks; } - protected virtual Task[] PipelineSetMany( - IDatabase cache, - IEnumerable> items, - DistributedCacheEntryOptions options) + protected virtual Task[] PipelineSetMany(IDatabase cache, IEnumerable> items, DistributedCacheEntryOptions options, out List leases) { - items = Check.NotNull(items, nameof(items)); - options = Check.NotNull(options, nameof(options)); + var tasks = new List(); + leases = new List(); - var itemArray = items.ToArray(); - var tasks = new Task[itemArray.Length]; var creationTime = DateTimeOffset.UtcNow; + var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); - for (var i = 0; i < itemArray.Length; i++) + foreach (var item in items) { - tasks[i] = cache.ScriptEvaluateAsync(GetSetScript(), new RedisKey[] { InstancePrefix.Append(itemArray[i].Key) }, - [ - absoluteExpiration?.Ticks ?? NotPresent, - options.SlidingExpiration?.Ticks ?? NotPresent, - GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, - itemArray[i].Value - ]); + var prefixedKey = InstancePrefix.Append(item.Key); + var ttl = GetExpirationInSeconds(creationTime, absoluteExpiration, options); + var fields = GetHashFields(Linearize(new ReadOnlySequence(item.Value), out var lease), absoluteExpiration, options.SlidingExpiration); + leases.Add(lease); + if (ttl is null) + { + tasks.Add(cache.HashSetAsync(prefixedKey, fields)); + } + else + { + tasks.Add(cache.HashSetAsync(prefixedKey, fields)); + tasks.Add( cache.KeyExpireAsync(prefixedKey, TimeSpan.FromSeconds(ttl.GetValueOrDefault()))); + } } - return tasks; + return tasks.ToArray(); } - protected virtual void MapMetadata( - RedisValue[] results, - out DateTimeOffset? absoluteExpiration, - out TimeSpan? slidingExpiration) + protected virtual void MapMetadata(RedisValue[] results, out DateTimeOffset? absoluteExpiration, out TimeSpan? slidingExpiration) { var parameters = new object?[] { results, null, null }; MapMetadataMethod.Invoke(this, parameters); @@ -332,36 +325,50 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems slidingExpiration = (TimeSpan?)parameters[2]; } - protected virtual long? GetExpirationInSeconds( - DateTimeOffset creationTime, - DateTimeOffset? absoluteExpiration, - DistributedCacheEntryOptions options) + protected virtual long? GetExpirationInSeconds(DateTimeOffset creationTime, DateTimeOffset? absoluteExpiration, DistributedCacheEntryOptions options) { - return (long?)GetExpirationInSecondsMethod.Invoke(null, - new object?[] { creationTime, absoluteExpiration, options }); + return (long?)GetExpirationInSecondsMethod.Invoke(null, new object?[] { creationTime, absoluteExpiration, options }); } - protected virtual DateTimeOffset? GetAbsoluteExpiration( - DateTimeOffset creationTime, - DistributedCacheEntryOptions options) + protected virtual DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset creationTime, DistributedCacheEntryOptions options) { return (DateTimeOffset?)GetAbsoluteExpirationMethod.Invoke(null, new object[] { creationTime, options }); } - + protected virtual void OnRedisError(Exception ex, IDatabase cache) { OnRedisErrorMethod.Invoke(this, [ex, cache]); } - - private string GetSetScript() + + private static ReadOnlyMemory Linearize(in ReadOnlySequence value, out byte[]? lease) { - return SetScriptField.GetValue(this)!.ToString()!; + // RedisValue only supports single-segment chunks; this will almost never be an issue, but + // on those rare occasions: use a leased array to harmonize things + if (value.IsSingleSegment) + { + lease = null; + return value.First; + } + var length = checked((int)value.Length); + lease = ArrayPool.Shared.Rent(length); + value.CopyTo(lease); + return new(lease, 0, length); } - + private static RedisValue[] GetHashFields(bool getData) { return getData ? HashMembersAbsoluteExpirationSlidingExpirationData : HashMembersAbsoluteExpirationSlidingExpiration; } + + private static HashEntry[] GetHashFields(RedisValue value, DateTimeOffset? absoluteExpiration, TimeSpan? slidingExpiration) + { + return + [ + new HashEntry(AbsoluteExpirationKey, absoluteExpiration?.Ticks ?? NotPresent), + new HashEntry(SlidingExpirationKey, slidingExpiration?.Ticks ?? NotPresent), + new HashEntry(DataKey, value) + ]; + } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index da8aebbeec..4cd33e2bc0 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -114,7 +114,6 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - //TODO: Re-check. optionsBuilder.ConfigureWarnings(c => c.Ignore(RelationalEventId.PendingModelChangesWarning)); base.OnConfiguring(optionsBuilder); } @@ -628,7 +627,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, originalExtraProperties = entry.OriginalValues.GetValue(nameof(IHasExtraProperties.ExtraProperties)); } - //TODO: Reload will throw an exception. Check it later. + //TODO: Reload will throw an exception. Check it when new EF Core versions released. //entry.Reload(); var storeValues = entry.OriginalValues; @@ -636,7 +635,6 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, entry.OriginalValues.SetValues(storeValues); entry.State = EntityState.Unchanged; - if (entry.Entity is IHasExtraProperties) { ObjectHelper.TrySetProperty(entry.Entity.As(), x => x.ExtraProperties, () => originalExtraProperties); diff --git a/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs index 12210428c3..58b5deafdf 100644 --- a/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs @@ -13,7 +13,7 @@ public class AbpRedisCache_Tests : AbpCachingStackExchangeRedisTestBase _distributedCache = GetRequiredService(); } - [Fact(Skip = "liangshiwei will update the AbpRedisCache")] + [Fact] public void Should_Replace_RedisCache() { (_distributedCache is AbpRedisCache).ShouldBeTrue(); diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index a6fccfd6d8..e75af9a556 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -79,7 +79,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule public override void OnPreApplicationInitialization(ApplicationInitializationContext context) { - //context.ServiceProvider.GetRequiredService().Database.Migrate(); + context.ServiceProvider.GetRequiredService().Database.Migrate(); using (var scope = context.ServiceProvider.CreateScope()) { var categoryRepository = scope.ServiceProvider.GetRequiredService>(); From 8c574829a77122dd3e70275760a697ac6a0a3e2e Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 24 Sep 2024 19:38:18 +0800 Subject: [PATCH 2/3] `UseStaticFiles` for `Pages` folder. --- .../AbpApplicationBuilderExtensions.cs | 21 ++++++++++++++++++- .../WebContentFileProvider.cs | 9 +++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs index 22fec38929..c40e6f27a4 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.RequestLocalization; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticAssets; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Volo.Abp; @@ -144,6 +145,7 @@ public static class AbpApplicationBuilderExtensions } app.UseVirtualStaticFiles(); + app.UseVirtualStaticFiles("Pages"); return endpoints.MapStaticAssets(staticAssetsManifestPath); } @@ -154,7 +156,7 @@ public static class AbpApplicationBuilderExtensions /// public static IApplicationBuilder UseVirtualStaticFiles(this IApplicationBuilder app) { - return app.UseStaticFiles(new StaticFileOptions() + app.UseStaticFiles(new StaticFileOptions() { ContentTypeProvider = app.ApplicationServices.GetRequiredService(), FileProvider = new WebContentFileProvider( @@ -163,5 +165,22 @@ public static class AbpApplicationBuilderExtensions app.ApplicationServices.GetRequiredService>() ) }); + + return app; + } + + /// + /// This static file provider is used to serve the files from the folder. + /// + public static IApplicationBuilder UseVirtualStaticFiles(this IApplicationBuilder app, string folder) + { + app.UseStaticFiles(new StaticFileOptions + { + ContentTypeProvider = app.ApplicationServices.GetRequiredService(), + FileProvider = new PhysicalFileProvider(Path.Combine(app.ApplicationServices.GetRequiredService().ContentRootPath, folder)), + RequestPath = $"/{folder}" + }); + + return app; } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs index b6e5e1ed21..cffa9687e2 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs @@ -93,16 +93,13 @@ public class WebContentFileProvider : IWebContentFileProvider, ISingletonDepende protected virtual IFileProvider CreateFileProvider() { - var fileProviders = new List - { - _virtualFileProvider - }; - + var fileProviders = new List(); if (_hostingEnvironment != null) { - fileProviders.Add(_hostingEnvironment.WebRootFileProvider); + fileProviders.Add(new PhysicalFileProvider(_hostingEnvironment.ContentRootPath)); } + fileProviders.Add(_virtualFileProvider); return new CompositeFileProvider(fileProviders); } From 61165fade607083aabf6764f31c12295dbc30b9c Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 24 Sep 2024 19:42:16 +0800 Subject: [PATCH 3/3] Use `EmptyHostingEnvironment` to create `WebContentFileProvider`. --- .../AspNetCore/Builder/AbpApplicationBuilderExtensions.cs | 2 +- .../AspNetCore/VirtualFileSystem/WebContentFileProvider.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs index c40e6f27a4..4dafe38bcc 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs @@ -161,7 +161,7 @@ public static class AbpApplicationBuilderExtensions ContentTypeProvider = app.ApplicationServices.GetRequiredService(), FileProvider = new WebContentFileProvider( app.ApplicationServices.GetRequiredService(), - null, + new EmptyHostingEnvironment(), app.ApplicationServices.GetRequiredService>() ) }); diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs index cffa9687e2..313ded53ac 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs @@ -15,14 +15,14 @@ public class WebContentFileProvider : IWebContentFileProvider, ISingletonDepende { private readonly IVirtualFileProvider _virtualFileProvider; private readonly IFileProvider _fileProvider; - private readonly IWebHostEnvironment? _hostingEnvironment; + private readonly IWebHostEnvironment _hostingEnvironment; private string _rootPath = "/wwwroot"; protected AbpAspNetCoreContentOptions Options { get; } public WebContentFileProvider( IVirtualFileProvider virtualFileProvider, - IWebHostEnvironment? hostingEnvironment, + IWebHostEnvironment hostingEnvironment, IOptions options) { _virtualFileProvider = virtualFileProvider; @@ -94,7 +94,7 @@ public class WebContentFileProvider : IWebContentFileProvider, ISingletonDepende protected virtual IFileProvider CreateFileProvider() { var fileProviders = new List(); - if (_hostingEnvironment != null) + if (!_hostingEnvironment.ContentRootPath.IsNullOrEmpty()) { fileProviders.Add(new PhysicalFileProvider(_hostingEnvironment.ContentRootPath)); }