mirror of https://github.com/abpframework/abp.git
committed by
GitHub
8 changed files with 223 additions and 14 deletions
@ -0,0 +1,57 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Components.Web.Theming.Bundling; |
||||
|
|
||||
|
public class ComponentBundleUrlBuilder : IComponentBundleUrlBuilder, ITransientDependency |
||||
|
{ |
||||
|
public virtual Task<string> BuildAsync( |
||||
|
[NotNull] string fileName, |
||||
|
[CanBeNull] string? appBasePath, |
||||
|
[CanBeNull] string? navigationBaseUri) |
||||
|
{ |
||||
|
Check.NotNull(fileName, nameof(fileName)); |
||||
|
|
||||
|
if (IsExternalUrl(fileName)) |
||||
|
{ |
||||
|
return Task.FromResult(fileName); |
||||
|
} |
||||
|
|
||||
|
var pathBase = !string.IsNullOrWhiteSpace(appBasePath) |
||||
|
? appBasePath |
||||
|
: ExtractPathBaseFromNavigationBaseUri(navigationBaseUri); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(pathBase)) |
||||
|
{ |
||||
|
return Task.FromResult(fileName); |
||||
|
} |
||||
|
|
||||
|
var normalized = pathBase.EnsureEndsWith('/'); |
||||
|
if (normalized == "/") |
||||
|
{ |
||||
|
return Task.FromResult(fileName); |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult(normalized + fileName.RemovePreFix("/")); |
||||
|
} |
||||
|
|
||||
|
protected virtual bool IsExternalUrl([NotNull] string fileName) |
||||
|
{ |
||||
|
return fileName.StartsWith("//", StringComparison.Ordinal) || |
||||
|
(fileName.Contains(':') && Uri.TryCreate(fileName, UriKind.Absolute, out _)); |
||||
|
} |
||||
|
|
||||
|
protected virtual string? ExtractPathBaseFromNavigationBaseUri([CanBeNull] string? navigationBaseUri) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(navigationBaseUri)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return Uri.TryCreate(navigationBaseUri, UriKind.Absolute, out var uri) |
||||
|
? uri.AbsolutePath |
||||
|
: null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Components.Web.Theming.Bundling; |
||||
|
|
||||
|
public interface IComponentBundleUrlBuilder |
||||
|
{ |
||||
|
Task<string> BuildAsync(string fileName, string? appBasePath, string? navigationBaseUri); |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"role": "lib.test" |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.test.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<Nullable>enable</Nullable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Components.Web.Theming\Volo.Abp.AspNetCore.Components.Web.Theming.csproj" /> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,105 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Shouldly; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Components.Web.Theming.Bundling; |
||||
|
|
||||
|
public class ComponentBundleUrlBuilder_Tests |
||||
|
{ |
||||
|
private readonly IComponentBundleUrlBuilder _builder = new ComponentBundleUrlBuilder(); |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Return_FileName_When_No_PathBase_Available() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: null)) |
||||
|
.ShouldBe("/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Return_FileName_When_NavigationBaseUri_Has_Root_PathBase() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: "https://localhost/")) |
||||
|
.ShouldBe("/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Use_Explicit_AppBasePath_Over_NavigationBaseUri() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: "/explicit", navigationBaseUri: "https://localhost/from-nav/")) |
||||
|
.ShouldBe("/explicit/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Resolve_PathBase_From_NavigationBaseUri() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: "https://localhost/foo/")) |
||||
|
.ShouldBe("/foo/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Resolve_PathBase_From_NavigationBaseUri_When_AppBasePath_Empty() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: "", navigationBaseUri: "https://localhost/foo/")) |
||||
|
.ShouldBe("/foo/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Handle_Nested_PathBase() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: "https://localhost/foo/bar/")) |
||||
|
.ShouldBe("/foo/bar/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Normalize_AppBasePath_Without_Trailing_Slash() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: "/foo", navigationBaseUri: null)) |
||||
|
.ShouldBe("/foo/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Not_Duplicate_Leading_Slash() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: "/foo/", navigationBaseUri: null)) |
||||
|
.ShouldBe("/foo/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Handle_FileName_Without_Leading_Slash() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("__bundles/Global.css", appBasePath: "/foo", navigationBaseUri: null)) |
||||
|
.ShouldBe("/foo/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Return_FileName_When_NavigationBaseUri_Is_Invalid() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: "not-a-uri")) |
||||
|
.ShouldBe("/__bundles/Global.css"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Throw_When_FileName_Is_Null() |
||||
|
{ |
||||
|
await Should.ThrowAsync<System.ArgumentNullException>(async () => |
||||
|
await _builder.BuildAsync(null!, appBasePath: "/foo", navigationBaseUri: null)); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("https://cdn.example.com/foo.css")] |
||||
|
[InlineData("http://cdn.example.com/foo.css")] |
||||
|
[InlineData("//cdn.example.com/foo.css")] |
||||
|
[InlineData("data:text/css;base64,Zm9vIA==")] |
||||
|
public async Task Should_Not_Prefix_External_Urls(string externalUrl) |
||||
|
{ |
||||
|
(await _builder.BuildAsync(externalUrl, appBasePath: "/foo", navigationBaseUri: "https://localhost/foo/")) |
||||
|
.ShouldBe(externalUrl); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Treat_Whitespace_AppBasePath_As_Not_Provided() |
||||
|
{ |
||||
|
(await _builder.BuildAsync("/__bundles/Global.css", appBasePath: " ", navigationBaseUri: "https://localhost/foo/")) |
||||
|
.ShouldBe("/foo/__bundles/Global.css"); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue