Browse Source

Merge pull request #26143 from abpframework/maliming/blazor-logo-pathbase

Resolve `IBrandingProvider` urls in the Blazor themes
pull/26144/head
Enis Necipoglu 12 hours ago
committed by GitHub
parent
commit
446be8dbe8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      docs/en/framework/ui/blazor/branding.md
  2. 8
      docs/en/framework/ui/mvc-razor-pages/branding.md
  3. 52
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Branding/NavigationManagerBrandingExtensions.cs
  4. 37
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Branding/UrlHelperBrandingExtensions.cs
  5. 69
      framework/src/Volo.Abp.UI/Volo/Abp/Ui/Branding/BrandingUrlHelper.cs
  6. 143
      framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/Theming/Branding/NavigationManagerBrandingExtensions_Tests.cs
  7. 110
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests/Volo/Abp/AspNetCore/Mvc/UI/Theme/Shared/Branding/UrlHelperBrandingExtensions_Tests.cs
  8. 49
      framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Branding/BrandingUrlHelper_Tests.cs
  9. 11
      modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/Branding.razor

8
docs/en/framework/ui/blazor/branding.md

@ -41,4 +41,12 @@ The result will be like shown below:
* `LogoUrl`: A URL to show the application logo.
* `LogoReverseUrl`: A URL to show the application logo on a reverse color theme (dark, for example).
ABP's built-in Blazor themes resolve these URLs for the current application. `logo.png`, `/logo.png` and `~/logo.png` all mean the same application relative URL and include the base path of the application, so they keep working when it is deployed to a non-root path, like an IIS virtual directory. A URL that already contains the path gets it twice. External URLs are used as they are. `LogoReverseUrl` is used by the themes that have a dark style, like LeptonX, and falls back to `LogoUrl`.
> **Note**: The `<base href>` of the host page has to match the path the application is served from, like `<base href="/myapp/" />`.
A logo that the project defines in its own CSS is not resolved. The LeptonX Lite startup templates set the logo that way, so remove that declaration to deploy them to a non-root path.
To resolve a branding URL in a custom theme or component, use `NavigationManager.ResolveBrandingUrl(...)` with `@using Volo.Abp.AspNetCore.Components.Web.Theming.Branding`, or `ResolveBrandingCssUrl(...)` when it is rendered into `url('...')` in CSS.
> **Tip**: `IBrandingProvider` is used in every page refresh. For a multi-tenant application, you can return a tenant specific application name to customize it per tenant.

8
docs/en/framework/ui/mvc-razor-pages/branding.md

@ -43,9 +43,11 @@ The result will be like shown below:
* `LogoUrl`: A URL to show the application logo.
* `LogoReverseUrl`: A URL to show the application logo on a reverse color theme (dark, for example).
ABP's built-in MVC themes resolve the branding URLs for the current request. `/logo.png`, `logo.png` and `~/logo.png` are treated as application relative URLs and include the `PathBase` of the request, so they keep working when the application is deployed to a non-root path, like an IIS virtual directory. Absolute HTTP(S) URLs, like `https://cdn.example.com/logo.png`, and protocol relative URLs, like `//cdn.example.com/logo.png`, are returned unchanged. `null` and white space values are treated as not set.
ABP's built-in MVC themes resolve these URLs for the current request. `logo.png`, `/logo.png` and `~/logo.png` all mean the same application relative URL and include the `PathBase` of the request, so they keep working when the application is deployed to a non-root path, like an IIS virtual directory. A URL that already contains the path gets it twice. External URLs are used as they are. `LogoReverseUrl` is used by the themes that have a dark style, like LeptonX, and falls back to `LogoUrl`.
If you render a branding URL in a custom MVC theme or view, resolve it with `Url.ResolveBrandingUrl(...)`.
A logo that the project defines in its own CSS is not resolved. The LeptonX Lite startup templates set the logo that way, so remove that declaration to deploy them to a non-root path.
To resolve a branding URL in a custom theme or view, use `Url.ResolveBrandingUrl(...)` with `@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Branding`, or `Url.ResolveBrandingCssUrl(...)` when it is rendered into `url('...')` in CSS.
> **Tip**: `IBrandingProvider` is used in every page refresh. For a multi-tenant application, you can return a tenant specific application name to customize it per tenant.
@ -80,7 +82,7 @@ Both properties return `null` by default and follow the same URL rules as `LogoU
The active theme decides whether and where to use the compact logo. The LeptonX MVC theme enables its compact branding when `LogoIconUrl` is not empty: it uses the compact logo instead of the full logo in its branding areas and shows `AppName` next to it where there is room for both. Dark and dim styles use `LogoIconReverseUrl` and fall back to `LogoIconUrl` when it is not set. Themes that don't support `IBrandingLogoProvider` ignore these properties.
> This URL resolution and the compact logo apply to the ASP.NET Core MVC / Razor Pages themes. The Blazor themes handle branding on their own.
> The compact logo applies to the ASP.NET Core MVC / Razor Pages themes. The Blazor themes resolve the branding URLs by the same rules, see [Blazor UI: Branding](../blazor/branding.md).
## Overriding the Branding Area

52
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Branding/NavigationManagerBrandingExtensions.cs

@ -0,0 +1,52 @@
using System;
using Microsoft.AspNetCore.Components;
using Volo.Abp.Ui.Branding;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.Branding;
public static class NavigationManagerBrandingExtensions
{
/// <summary>
/// Resolves a branding url of <see cref="IBrandingProvider"/>: "logo.svg", "/logo.svg" and
/// "~/logo.svg" all keep working under a non-root base path.
/// </summary>
public static string? ResolveBrandingUrl(this NavigationManager navigationManager, string? url)
{
Check.NotNull(navigationManager, nameof(navigationManager));
if (url.IsNullOrWhiteSpace())
{
return null;
}
var brandingUrl = url!.Trim();
if (BrandingUrlHelper.IsExternalUrl(brandingUrl))
{
return brandingUrl;
}
var applicationRelativeUrl = BrandingUrlHelper.RemoveApplicationRelativePrefix(brandingUrl);
var baseUri = new Uri(navigationManager.BaseUri);
// "/http://host/logo.svg" would silently lose its host when only the path is taken.
if (BrandingUrlHelper.IsExternalUrl(applicationRelativeUrl) ||
!Uri.TryCreate(baseUri, applicationRelativeUrl, out var absoluteUrl) ||
absoluteUrl.GetLeftPart(UriPartial.Authority) != baseUri.GetLeftPart(UriPartial.Authority))
{
return brandingUrl;
}
return absoluteUrl.PathAndQuery + absoluteUrl.Fragment;
}
/// <summary>
/// Same as <see cref="ResolveBrandingUrl"/>, escaped for url('...') in css.
/// </summary>
public static string? ResolveBrandingCssUrl(this NavigationManager navigationManager, string? url)
{
var resolvedUrl = navigationManager.ResolveBrandingUrl(url);
return resolvedUrl == null ? null : BrandingUrlHelper.EscapeCssValue(resolvedUrl);
}
}

37
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Branding/UrlHelperBrandingExtensions.cs

@ -1,16 +1,14 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Ui.Branding;
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Branding;
public static class UrlHelperBrandingExtensions
{
/// <summary>
/// Resolves a branding url of <see cref="Volo.Abp.Ui.Branding.IBrandingProvider"/> for the current request.
/// "logo.svg", "/logo.svg" and "~/logo.svg" all mean the same application relative url and keep working
/// under a non-root <see cref="Microsoft.AspNetCore.Http.HttpRequest.PathBase"/>.
/// External urls ("http://", "https://" and "//host/") are returned as they are.
/// Returns null when <paramref name="url"/> is null or white space.
/// Resolves a branding url of <see cref="IBrandingProvider"/>: "logo.svg", "/logo.svg" and
/// "~/logo.svg" all keep working under a non-root <see cref="Microsoft.AspNetCore.Http.HttpRequest.PathBase"/>.
/// </summary>
public static string? ResolveBrandingUrl(this IUrlHelper urlHelper, string? url)
{
@ -19,22 +17,31 @@ public static class UrlHelperBrandingExtensions
return null;
}
if (IsExternalUrl(url!))
var brandingUrl = url!.Trim();
if (BrandingUrlHelper.IsExternalUrl(brandingUrl))
{
return url;
return brandingUrl;
}
var applicationRelativeUrl = url!.StartsWith("~/", StringComparison.Ordinal)
? url
: "~/" + url.TrimStart('/');
var relativeUrl = BrandingUrlHelper.RemoveApplicationRelativePrefix(brandingUrl);
// "/http://host/logo.svg" would become a local path that does not exist.
if (BrandingUrlHelper.IsExternalUrl(relativeUrl))
{
return brandingUrl;
}
return urlHelper.Content(applicationRelativeUrl);
return urlHelper.Content("~/" + relativeUrl);
}
private static bool IsExternalUrl(string url)
/// <summary>
/// Same as <see cref="ResolveBrandingUrl"/>, escaped for url('...') in css.
/// </summary>
public static string? ResolveBrandingCssUrl(this IUrlHelper urlHelper, string? url)
{
return url.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
|| url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)
|| url.StartsWith("//", StringComparison.Ordinal);
var resolvedUrl = urlHelper.ResolveBrandingUrl(url);
return resolvedUrl == null ? null : BrandingUrlHelper.EscapeCssValue(resolvedUrl);
}
}

69
framework/src/Volo.Abp.UI/Volo/Abp/Ui/Branding/BrandingUrlHelper.cs

@ -0,0 +1,69 @@
using System;
namespace Volo.Abp.Ui.Branding;
public static class BrandingUrlHelper
{
// "//host/" and any url with a scheme are used as they are, the others are application relative.
public static bool IsExternalUrl(string? url)
{
if (url.IsNullOrWhiteSpace())
{
return false;
}
var brandingUrl = url!.Trim();
return brandingUrl.StartsWith("//", StringComparison.Ordinal) || HasScheme(brandingUrl);
}
public static string RemoveApplicationRelativePrefix(string url)
{
return url.StartsWith("~/", StringComparison.Ordinal)
? url.Substring(2)
: url.TrimStart('/');
}
// Rendered into url('...') inside a style element and must not be able to end either of them.
public static string EscapeCssValue(string url)
{
return url
.Replace("\\", "\\\\")
.Replace("'", "\\'")
.Replace("<", "%3C")
.Replace("\r", string.Empty)
.Replace("\n", string.Empty)
.Replace("\f", string.Empty);
}
// A broken value like "http://[" also has a scheme, so it is matched here instead of by Uri.TryCreate.
private static bool HasScheme(string url)
{
var schemeLength = url.IndexOf(':');
if (schemeLength < 1 || !IsLetter(url[0]))
{
return false;
}
for (var i = 1; i < schemeLength; i++)
{
var character = url[i];
if (!IsLetter(character) && !IsDigit(character) && character != '+' && character != '-' && character != '.')
{
return false;
}
}
return true;
}
private static bool IsLetter(char character)
{
return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z');
}
private static bool IsDigit(char character)
{
return character >= '0' && character <= '9';
}
}

143
framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/Theming/Branding/NavigationManagerBrandingExtensions_Tests.cs

@ -0,0 +1,143 @@
using Microsoft.AspNetCore.Components;
using Shouldly;
using Xunit;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.Branding;
public class NavigationManagerBrandingExtensions_Tests
{
[Theory]
[InlineData("logo.svg")]
[InlineData("/logo.svg")]
[InlineData("~/logo.svg")]
public void Should_Treat_All_Local_Formats_As_Application_Relative(string url)
{
CreateNavigationManager("https://localhost/").ResolveBrandingUrl(url)
.ShouldBe("/logo.svg");
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingUrl(url)
.ShouldBe("/myapp/logo.svg");
}
// Keep in sync with the same test of the mvc themes.
[Theory]
[InlineData("logo.svg", "/myapp/logo.svg")]
[InlineData("/logo.svg", "/myapp/logo.svg")]
[InlineData("~/logo.svg", "/myapp/logo.svg")]
[InlineData("images/logo.svg?v=42", "/myapp/images/logo.svg?v=42")]
[InlineData("images/logo.svg#brand", "/myapp/images/logo.svg#brand")]
[InlineData("https://cdn.example.com/logo.svg", "https://cdn.example.com/logo.svg")]
[InlineData("//cdn.example.com/logo.svg", "//cdn.example.com/logo.svg")]
[InlineData("data:image/svg+xml;base64,PHN2Zy8+", "data:image/svg+xml;base64,PHN2Zy8+")]
[InlineData("http://[", "http://[")]
[InlineData("https://", "https://")]
[InlineData("http://a b", "http://a b")]
[InlineData(" ~/logo.svg ", "/myapp/logo.svg")]
[InlineData(" https://cdn.example.com/logo.svg ", "https://cdn.example.com/logo.svg")]
[InlineData("/http://cdn.example.com/logo.svg", "/http://cdn.example.com/logo.svg")]
[InlineData("~/http://cdn.example.com/logo.svg", "~/http://cdn.example.com/logo.svg")]
public void Should_Resolve_The_Same_As_The_Mvc_Themes(string url, string expected)
{
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingUrl(url)
.ShouldBe(expected);
}
[Theory]
[InlineData("images/../logo.svg", "/myapp/logo.svg")]
[InlineData("images/../../logo.svg", "/logo.svg")]
public void Should_Resolve_A_Dot_Segment(string url, string expected)
{
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingUrl(url).ShouldBe(expected);
}
[Fact]
public void Should_Not_Resolve_A_Url_That_Points_To_Another_Host()
{
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingUrl("/http://cdn.example.com/logo.svg")
.ShouldBe("/http://cdn.example.com/logo.svg");
}
[Fact]
public void Should_Escape_A_Form_Feed_In_Css()
{
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingCssUrl("data:image/svg+xml,\u000C<svg/>")
.ShouldBe("data:image/svg+xml,%3Csvg/>");
}
[Fact]
public void Should_Keep_Query_And_Fragment()
{
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingUrl("~/images/logo.svg?v=42")
.ShouldBe("/myapp/images/logo.svg?v=42");
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingUrl("images/logo.svg#brand")
.ShouldBe("/myapp/images/logo.svg#brand");
}
[Theory]
[InlineData("http://cdn.example.com/logo.svg")]
[InlineData("https://cdn.example.com/logo.svg")]
[InlineData("//cdn.example.com/logo.svg")]
[InlineData("data:image/svg+xml;base64,PHN2Zy8+")]
[InlineData("http://[")]
[InlineData("https://")]
[InlineData("http://a b")]
public void Should_Return_External_Urls_As_They_Are(string url)
{
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingUrl(url)
.ShouldBe(url);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Should_Return_Null_When_Url_Is_Empty(string? url)
{
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingUrl(url)
.ShouldBeNull();
}
[Fact]
public void Should_Escape_Css_Breaking_Characters()
{
CreateNavigationManager("https://localhost/myapp/")
.ResolveBrandingCssUrl("data:image/svg+xml,<svg/>')</style><script>alert(1)</script>")
.ShouldBe("data:image/svg+xml,%3Csvg/>\\')%3C/style>%3Cscript>alert(1)%3C/script>");
}
[Fact]
public void Should_Escape_Backslashes_And_Drop_Line_Breaks()
{
CreateNavigationManager("https://localhost/myapp/")
.ResolveBrandingCssUrl("data:image/svg+xml,a\\b\r\nc")
.ShouldBe("data:image/svg+xml,a\\\\bc");
}
[Fact]
public void Should_Resolve_And_Escape_Application_Relative_Urls()
{
CreateNavigationManager("https://localhost/myapp/")
.ResolveBrandingCssUrl("~/images/logo.svg")
.ShouldBe("/myapp/images/logo.svg");
}
[Fact]
public void Should_Return_Null_From_Css_Overload_When_Url_Is_Empty()
{
CreateNavigationManager("https://localhost/myapp/").ResolveBrandingCssUrl(" ").ShouldBeNull();
}
private static NavigationManager CreateNavigationManager(string baseUri)
{
return new TestNavigationManager(baseUri);
}
private class TestNavigationManager : NavigationManager
{
public TestNavigationManager(string baseUri)
{
Initialize(baseUri, baseUri);
}
}
}

110
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests/Volo/Abp/AspNetCore/Mvc/UI/Theme/Shared/Branding/UrlHelperBrandingExtensions_Tests.cs

@ -0,0 +1,110 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Shouldly;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Branding;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Branding;
public class UrlHelperBrandingExtensions_Tests
{
private readonly IUrlHelper _urlHelper;
public UrlHelperBrandingExtensions_Tests()
{
var httpContext = new DefaultHttpContext {
Request = { PathBase = "/myapp" }
};
_urlHelper = new UrlHelper(new ActionContext(httpContext, new RouteData(), new ActionDescriptor()));
}
[Theory]
[InlineData("logo.svg")]
[InlineData("/logo.svg")]
[InlineData("~/logo.svg")]
public void Should_Treat_All_Local_Formats_As_Application_Relative(string url)
{
_urlHelper.ResolveBrandingUrl(url).ShouldBe("/myapp/logo.svg");
}
// Keep in sync with the same test of the blazor themes.
[Theory]
[InlineData("logo.svg", "/myapp/logo.svg")]
[InlineData("/logo.svg", "/myapp/logo.svg")]
[InlineData("~/logo.svg", "/myapp/logo.svg")]
[InlineData("images/logo.svg?v=42", "/myapp/images/logo.svg?v=42")]
[InlineData("images/logo.svg#brand", "/myapp/images/logo.svg#brand")]
[InlineData("https://cdn.example.com/logo.svg", "https://cdn.example.com/logo.svg")]
[InlineData("//cdn.example.com/logo.svg", "//cdn.example.com/logo.svg")]
[InlineData("data:image/svg+xml;base64,PHN2Zy8+", "data:image/svg+xml;base64,PHN2Zy8+")]
[InlineData("http://[", "http://[")]
[InlineData("https://", "https://")]
[InlineData("http://a b", "http://a b")]
[InlineData(" ~/logo.svg ", "/myapp/logo.svg")]
[InlineData(" https://cdn.example.com/logo.svg ", "https://cdn.example.com/logo.svg")]
[InlineData("/http://cdn.example.com/logo.svg", "/http://cdn.example.com/logo.svg")]
[InlineData("~/http://cdn.example.com/logo.svg", "~/http://cdn.example.com/logo.svg")]
public void Should_Resolve_The_Same_As_The_Blazor_Themes(string url, string expected)
{
_urlHelper.ResolveBrandingUrl(url).ShouldBe(expected);
}
[Fact]
public void Should_Keep_A_Dot_Segment_Of_An_Application_Relative_Url()
{
_urlHelper.ResolveBrandingUrl("images/../logo.svg").ShouldBe("/myapp/images/../logo.svg");
}
[Fact]
public void Should_Escape_A_Form_Feed_In_Css()
{
_urlHelper.ResolveBrandingCssUrl("data:image/svg+xml,\u000C<svg/>")
.ShouldBe("data:image/svg+xml,%3Csvg/>");
}
[Fact]
public void Should_Keep_Query_And_Fragment()
{
_urlHelper.ResolveBrandingUrl("~/images/logo.svg?v=42").ShouldBe("/myapp/images/logo.svg?v=42");
_urlHelper.ResolveBrandingUrl("images/logo.svg#brand").ShouldBe("/myapp/images/logo.svg#brand");
}
[Theory]
[InlineData("http://cdn.example.com/logo.svg")]
[InlineData("https://cdn.example.com/logo.svg")]
[InlineData("//cdn.example.com/logo.svg")]
[InlineData("data:image/svg+xml;base64,PHN2Zy8+")]
[InlineData("http://[")]
[InlineData("https://")]
[InlineData("http://a b")]
public void Should_Return_External_Urls_As_They_Are(string url)
{
_urlHelper.ResolveBrandingUrl(url).ShouldBe(url);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Should_Return_Null_When_Url_Is_Empty(string? url)
{
_urlHelper.ResolveBrandingUrl(url).ShouldBeNull();
}
[Fact]
public void Should_Escape_Css_Breaking_Characters()
{
_urlHelper.ResolveBrandingCssUrl("data:image/svg+xml,<svg/>')</style>")
.ShouldBe("data:image/svg+xml,%3Csvg/>\\')%3C/style>");
}
[Fact]
public void Should_Resolve_And_Escape_Application_Relative_Urls()
{
_urlHelper.ResolveBrandingCssUrl("~/images/logo.svg").ShouldBe("/myapp/images/logo.svg");
}
}

49
framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Branding/BrandingUrlHelper_Tests.cs

@ -0,0 +1,49 @@
using Shouldly;
using Xunit;
namespace Volo.Abp.Ui.Branding;
public class BrandingUrlHelper_Tests
{
[Theory]
[InlineData("//cdn.example.com/logo.svg")]
[InlineData("https://cdn.example.com/logo.svg")]
[InlineData("data:image/svg+xml;base64,PHN2Zy8+")]
[InlineData("blob:1234")]
[InlineData(" https://cdn.example.com/logo.svg ")]
[InlineData("http://[")]
public void Should_Detect_External_Urls(string url)
{
BrandingUrlHelper.IsExternalUrl(url).ShouldBeTrue();
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("logo.svg")]
[InlineData("/logo.svg")]
[InlineData("~/logo.svg")]
[InlineData("/images/a:b.svg")]
[InlineData("1st:logo.svg")]
public void Should_Not_Detect_Other_Urls_As_External(string? url)
{
BrandingUrlHelper.IsExternalUrl(url).ShouldBeFalse();
}
[Theory]
[InlineData("~/images/logo.svg", "images/logo.svg")]
[InlineData("/images/logo.svg", "images/logo.svg")]
[InlineData("images/logo.svg", "images/logo.svg")]
public void Should_Remove_The_Application_Relative_Prefix(string url, string expected)
{
BrandingUrlHelper.RemoveApplicationRelativePrefix(url).ShouldBe(expected);
}
[Fact]
public void Should_Escape_The_Characters_That_End_A_Css_Url_Or_A_Style_Element()
{
BrandingUrlHelper.EscapeCssValue("logo.svg\\')\r\n\f</style>")
.ShouldBe("logo.svg\\\\\\')%3C/style>");
}
}

11
modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/Branding.razor

@ -1,9 +1,16 @@
@using Volo.Abp.AspNetCore.Components.Web.Theming.Branding
@using Volo.Abp.Ui.Branding
@inject IBrandingProvider BrandingProvider
@inject NavigationManager NavigationManager
@{
var logoUrl = NavigationManager.ResolveBrandingUrl(BrandingProvider.LogoUrl);
}
<a class="navbar-brand" href="">
@if (!BrandingProvider.LogoUrl.IsNullOrWhiteSpace())
@if (!logoUrl.IsNullOrWhiteSpace())
{
<img src="@BrandingProvider.LogoUrl.TrimStart('/', '~')" alt="@BrandingProvider.AppName">
<img src="@logoUrl" alt="@BrandingProvider.AppName">
}
@BrandingProvider.AppName
</a>

Loading…
Cancel
Save