Skip to content

Commit 2573200

Browse files
committed
fix #167: create new option to provide server deployment sub-path
1 parent fc24934 commit 2573200

File tree

7 files changed

+66
-21
lines changed

7 files changed

+66
-21
lines changed

samples/WebApp/Program.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@
3636
app.UseAuthentication();
3737
app.UseAuthorization();
3838

39+
var serverSubPath = bool.Parse(builder.Configuration["SerilogUi:AddServerSubPath"] ?? "false") == true ? "log" : "";
3940
app.UseSerilogUi(options => options
4041
.WithHomeUrl("/#Test")
42+
.WithServerSubPath(serverSubPath)
4143
.WithAuthenticationType(AuthenticationType.Jwt)
4244
.WithExpandedDropdownsByDefault()
4345
.EnableAuthorizationOnAppRoutes()
44-
.InjectJavascript("/js/serilog-ui/custom.js")
46+
.InjectJavascript($"{serverSubPath}/js/serilog-ui/custom.js")
4547
);
4648

4749
app.MapControllerRoute(

samples/WebApp/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"windowsAuthentication": false,
55
"anonymousAuthentication": true,
66
"iisExpress": {
7-
"applicationUrl": "http://localhost:40034",
7+
"applicationUrl": "http://localhost:40034/logs",
88
"sslPort": 44304
99
}
1010
},

samples/WebApp/appsettings.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@
1717
},
1818
"SerilogUi": {
1919
"UserName": "BasicSampleUser",
20-
"Password": "BasicSamplePwd"
20+
"Password": "BasicSamplePwd",
21+
"AddServerSubPath": true
2122
},
2223
"Serilog": {
23-
"Using": [
24-
"Serilog.Sinks.MongoDB"
25-
],
24+
"Using": ["Serilog.Sinks.MongoDB"],
2625
"MinimumLevel": "Debug",
2726
"WriteTo": [
2827
{

src/Serilog.Ui.Web/Endpoints/SerilogUiAppRoutes.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public Task RedirectHomeAsync()
5151

5252
private async Task<string> LoadStream(Stream stream, UiOptions options)
5353
{
54-
StringBuilder htmlStringBuilder = new StringBuilder(await new StreamReader(stream).ReadToEndAsync());
54+
StringBuilder htmlStringBuilder = new(await new StreamReader(stream).ReadToEndAsync());
5555
string authType = options.Authorization.AuthenticationType.ToString();
5656

5757
var feOpts = new
@@ -63,7 +63,7 @@ private async Task<string> LoadStream(Stream stream, UiOptions options)
6363
options.ShowBrand,
6464
options.HomeUrl,
6565
BlockHomeAccess,
66-
options.RoutePrefix,
66+
routePrefix = ConstructRoutesPrefix(options),
6767
options.ExpandDropdownsByDefault
6868
};
6969
string encodeAuthOpts = Uri.EscapeDataString(JsonSerializer.Serialize(feOpts, JsonSerializerOptionsFactory.GetDefaultOptions));
@@ -75,4 +75,16 @@ private async Task<string> LoadStream(Stream stream, UiOptions options)
7575

7676
return htmlStringBuilder.ToString();
7777
}
78+
79+
private static string ConstructRoutesPrefix(UiOptions options)
80+
{
81+
var safeHostPath = string.IsNullOrWhiteSpace(options.ServerSubPath) ? "" : options.ServerSubPath;
82+
var hostPathWithoutInitialSlash = safeHostPath.StartsWith("/", StringComparison.OrdinalIgnoreCase) ?
83+
safeHostPath[1..] : safeHostPath;
84+
var hostPathWithDivider = !string.IsNullOrWhiteSpace(hostPathWithoutInitialSlash) &&
85+
!hostPathWithoutInitialSlash.EndsWith('/') ?
86+
$"{hostPathWithoutInitialSlash}/" : hostPathWithoutInitialSlash;
87+
88+
return $"{hostPathWithDivider}{options.RoutePrefix}";
89+
}
7890
}

src/Serilog.Ui.Web/Models/UiOptions.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ public class UiOptions(ProvidersOptions options)
2727
/// <value>The route prefix.</value>
2828
public string RoutePrefix { get; private set; } = "serilog-ui";
2929

30+
/// <summary>
31+
/// Gets the server sub-path deployment.
32+
/// </summary>
33+
/// <value>The server sub path.</value>
34+
public string ServerSubPath { get; private set; } = string.Empty;
35+
3036
/// <summary>
3137
/// Get the option to auto-expand dropdowns in the log viewer.
3238
/// </summary>
@@ -126,6 +132,23 @@ public UiOptions WithRoutePrefix(string routePrefix)
126132
return this;
127133
}
128134

135+
/// <summary>
136+
/// Sets the server hosting base-path.
137+
/// If the application is deployed under a server sub-path, this property provides the additional sub-path to
138+
/// the dashboard to help the client-side routing.<br/>
139+
/// If provided, the final routing result will be:<br/><c>{server-base-path}/{UiOptions.ServerSubPath}/{UiOptions.RoutePrefix}</c>.
140+
/// </summary>
141+
/// <example>
142+
/// Server root: "server.com"
143+
/// IF the application is deployed at root level ("server.com/{my-app}") => NOT set ServerSubPath
144+
/// IF the application is deployed at sub-path level ("server.com/{additional-path}/{my-app}") => MUST set ServerSubPath
145+
/// </example>
146+
public UiOptions WithServerSubPath(string serverSubPath)
147+
{
148+
ServerSubPath = serverSubPath;
149+
return this;
150+
}
151+
129152
internal void Validate()
130153
{
131154
if (RoutePrefix.EndsWith('/')) throw new ArgumentException($"{nameof(RoutePrefix)} can't end with a slash.");

tests/Serilog.Ui.Web.Tests/Endpoints/SerilogUiAppRoutesTest.cs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,25 @@ public SerilogUiAppRoutesTest()
3939
_sut = new SerilogUiAppRoutes(_contextAccessor, _streamLoaderMock);
4040
}
4141

42-
[Fact]
43-
public async Task It_gets_app_home()
42+
[Theory]
43+
[InlineData(null, "test")]
44+
[InlineData("", "test")]
45+
[InlineData(" ", "test")]
46+
[InlineData("sub-path", "sub-path/test")]
47+
[InlineData("sub-path/", "sub-path/test")]
48+
[InlineData("/sub-path/", "sub-path/test")]
49+
public async Task It_gets_app_home(string? serverSubPath, string expectedRoutePrefix)
4450
{
4551
// Arrange
46-
_sut.SetOptions(new UiOptions(new ProvidersOptions())
47-
{
48-
BodyContent = "<div>body-test</div>",
49-
HeadContent = "<div>head-test</div>"
50-
}
51-
.WithAuthenticationType(AuthenticationType.Jwt)
52-
.WithRoutePrefix("test")
53-
.WithHomeUrl("home-url")
52+
var body = "<div>body-test</div>";
53+
var head = "<div>head-test</div>";
54+
var baseOpts = new UiOptions(new()) { BodyContent = body, HeadContent = head };
55+
_sut
56+
.SetOptions(baseOpts
57+
.WithAuthenticationType(AuthenticationType.Jwt)
58+
.WithRoutePrefix("test")
59+
.WithServerSubPath(serverSubPath!)
60+
.WithHomeUrl("home-url")
5461
);
5562
_testContext.Request.Path = "/serilog-ui-url/";
5663
_testContext.Response.Body = new MemoryStream();
@@ -75,9 +82,8 @@ public async Task It_gets_app_home()
7582
"<body><div id=\"serilog-ui-app\"></div>" +
7683
"<script>const config = '%7B%22authType%22%3A%22Jwt%22%2C%22columnsInfo%22%3A%7B%7D%2C%22" +
7784
"disabledSortOnKeys%22%3A%5B%5D%2C%22renderExceptionAsStringKeys%22%3A%5B%5D%2C%22showBrand%22%3Atrue%2C%22homeUrl%22%3A%22home-url" +
78-
"%22%2C%22blockHomeAccess%22%3Afalse%2C%22routePrefix%22%3A%22" +
79-
"test%22%2C%22expandDropdownsByDefault%22%3Afalse%7D';</script><div>body-test</div></body></html>"
80-
);
85+
"%22%2C%22blockHomeAccess%22%3Afalse%2C%22routePrefix%22%3A%22" + Uri.EscapeDataString(expectedRoutePrefix) +
86+
"%22%2C%22expandDropdownsByDefault%22%3Afalse%7D';</script><div>body-test</div></body></html>");
8187
}
8288

8389
[Fact]

tests/Serilog.Ui.Web.Tests/Models/UiOptionsTest.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public void It_gets_default_options()
1616

1717
sut.HomeUrl.Should().Be("/");
1818
sut.RoutePrefix.Should().Be("serilog-ui");
19+
sut.ServerSubPath.Should().BeEmpty();
1920
sut.BodyContent.Should().BeEmpty();
2021
sut.HeadContent.Should().BeEmpty();
2122
sut.ShowBrand.Should().BeTrue();
@@ -33,12 +34,14 @@ public void It_sets_options()
3334
.WithHomeUrl("test")
3435
.WithAuthenticationType(AuthenticationType.Basic)
3536
.WithRoutePrefix("prefix")
37+
.WithServerSubPath("log")
3638
.HideSerilogUiBrand()
3739
.WithExpandedDropdownsByDefault()
3840
.EnableAuthorizationOnAppRoutes();
3941

4042
sut.HomeUrl.Should().Be("test");
4143
sut.RoutePrefix.Should().Be("prefix");
44+
sut.ServerSubPath.Should().Be("log");
4245
sut.ShowBrand.Should().BeFalse();
4346
sut.ExpandDropdownsByDefault.Should().BeTrue();
4447
sut.Authorization.AuthenticationType.Should().Be(AuthenticationType.Basic);

0 commit comments

Comments
 (0)