Skip to content

Commit 2ff6a5c

Browse files
Components router refactoring. Fixes #10493 #10445 (#12800)
1 parent 9f2b534 commit 2ff6a5c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1516
-590
lines changed
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
<Router AppAssembly="typeof(Program).Assembly">
1+
<Router AppAssembly="@typeof(Program).Assembly">
2+
<Found Context="routeData">
3+
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
4+
</Found>
25
<NotFound>
3-
<p>Sorry, there's nothing at this address.</p>
6+
<LayoutView Layout="@typeof(MainLayout)">
7+
<p>Sorry, there's nothing at this address.</p>
8+
</LayoutView>
49
</NotFound>
510
</Router>

src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/Pages/_Imports.razor

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
<Router AppAssembly=typeof(Program).Assembly />
1+
<Router AppAssembly=typeof(Program).Assembly>
2+
<Found Context="routeData">
3+
<RouteView RouteData="@routeData" />
4+
</Found>
5+
<NotFound>
6+
Sorry, there's nothing here.
7+
</NotFound>
8+
</Router>
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
<!--
2-
Configuring this stuff here is temporary. Later we'll move the app config
3-
into Program.cs, and it won't be necessary to specify AppAssembly.
4-
-->
5-
<Router AppAssembly=typeof(StandaloneApp.Program).Assembly />
1+
<Router AppAssembly=typeof(StandaloneApp.Program).Assembly>
2+
<Found Context="routeData">
3+
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
4+
</Found>
5+
<NotFound>
6+
<LayoutView Layout="@typeof(MainLayout)">
7+
<h2>Not found</h2>
8+
Sorry, there's nothing at this address.
9+
</LayoutView>
10+
</NotFound>
11+
</Router>

src/Components/Blazor/testassets/StandaloneApp/Pages/_Imports.razor

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ public event Microsoft.AspNetCore.Components.AuthenticationStateChangedHandler A
1616
public abstract System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.AuthenticationState> GetAuthenticationStateAsync();
1717
protected void NotifyAuthenticationStateChanged(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.AuthenticationState> task) { }
1818
}
19+
public sealed partial class AuthorizeRouteView : Microsoft.AspNetCore.Components.RouteView
20+
{
21+
public AuthorizeRouteView() { }
22+
[Microsoft.AspNetCore.Components.ParameterAttribute]
23+
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
24+
[Microsoft.AspNetCore.Components.ParameterAttribute]
25+
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
26+
protected override void Render(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
27+
}
1928
public partial class AuthorizeView : Microsoft.AspNetCore.Components.AuthorizeViewCore
2029
{
2130
public AuthorizeView() { }
@@ -278,6 +287,16 @@ protected LayoutComponentBase() { }
278287
[Microsoft.AspNetCore.Components.ParameterAttribute]
279288
public Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
280289
}
290+
public partial class LayoutView : Microsoft.AspNetCore.Components.IComponent
291+
{
292+
public LayoutView() { }
293+
[Microsoft.AspNetCore.Components.ParameterAttribute]
294+
public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
295+
[Microsoft.AspNetCore.Components.ParameterAttribute]
296+
public System.Type Layout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
297+
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
298+
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; }
299+
}
281300
public sealed partial class LocationChangeException : System.Exception
282301
{
283302
public LocationChangeException(string message, System.Exception innerException) { }
@@ -323,20 +342,6 @@ public abstract partial class OwningComponentBase<TService> : Microsoft.AspNetCo
323342
protected OwningComponentBase() { }
324343
protected TService Service { get { throw null; } }
325344
}
326-
public partial class PageDisplay : Microsoft.AspNetCore.Components.IComponent
327-
{
328-
public PageDisplay() { }
329-
[Microsoft.AspNetCore.Components.ParameterAttribute]
330-
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
331-
[Microsoft.AspNetCore.Components.ParameterAttribute]
332-
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
333-
[Microsoft.AspNetCore.Components.ParameterAttribute]
334-
public System.Type Page { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
335-
[Microsoft.AspNetCore.Components.ParameterAttribute]
336-
public System.Collections.Generic.IDictionary<string, object> PageParameters { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
337-
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
338-
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; }
339-
}
340345
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
341346
public sealed partial class ParameterAttribute : System.Attribute
342347
{
@@ -391,6 +396,23 @@ public sealed partial class RouteAttribute : System.Attribute
391396
public RouteAttribute(string template) { }
392397
public string Template { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
393398
}
399+
public sealed partial class RouteData
400+
{
401+
public RouteData(System.Type pageType, System.Collections.Generic.IReadOnlyDictionary<string, object> routeValues) { }
402+
public System.Type PageType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
403+
public System.Collections.Generic.IReadOnlyDictionary<string, object> RouteValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
404+
}
405+
public partial class RouteView : Microsoft.AspNetCore.Components.IComponent
406+
{
407+
public RouteView() { }
408+
[Microsoft.AspNetCore.Components.ParameterAttribute]
409+
public System.Type DefaultLayout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
410+
[Microsoft.AspNetCore.Components.ParameterAttribute]
411+
public Microsoft.AspNetCore.Components.RouteData RouteData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
412+
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
413+
protected virtual void Render(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
414+
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; }
415+
}
394416
}
395417
namespace Microsoft.AspNetCore.Components.CompilerServices
396418
{
@@ -648,15 +670,12 @@ public Router() { }
648670
[Microsoft.AspNetCore.Components.ParameterAttribute]
649671
public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
650672
[Microsoft.AspNetCore.Components.ParameterAttribute]
651-
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
652-
[Microsoft.AspNetCore.Components.ParameterAttribute]
653-
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
673+
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.RouteData> Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
654674
[Microsoft.AspNetCore.Components.ParameterAttribute]
655675
public Microsoft.AspNetCore.Components.RenderFragment NotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
656676
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
657677
public void Dispose() { }
658678
System.Threading.Tasks.Task Microsoft.AspNetCore.Components.IHandleAfterRender.OnAfterRenderAsync() { throw null; }
659-
protected virtual void Render(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder, System.Type handler, System.Collections.Generic.IDictionary<string, object> parameters) { }
660679
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; }
661680
}
662681
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Authorization;
6+
using Microsoft.AspNetCore.Components.Auth;
7+
using Microsoft.AspNetCore.Components.RenderTree;
8+
9+
namespace Microsoft.AspNetCore.Components
10+
{
11+
/// <summary>
12+
/// Combines the behaviors of <see cref="AuthorizeView"/> and <see cref="RouteView"/>,
13+
/// so that it displays the page matching the specified route but only if the user
14+
/// is authorized to see it.
15+
///
16+
/// Additionally, this component supplies a cascading parameter of type <see cref="Task{AuthenticationState}"/>,
17+
/// which makes the user's current authentication state available to descendants.
18+
/// </summary>
19+
public sealed class AuthorizeRouteView : RouteView
20+
{
21+
// We expect applications to supply their own authorizing/not-authorized content, but
22+
// it's better to have defaults than to make the parameters mandatory because in some
23+
// cases they will never be used (e.g., "authorizing" in out-of-box server-side Blazor)
24+
private static readonly RenderFragment<AuthenticationState> _defaultNotAuthorizedContent
25+
= state => builder => builder.AddContent(0, "Not authorized");
26+
private static readonly RenderFragment _defaultAuthorizingContent
27+
= builder => builder.AddContent(0, "Authorizing...");
28+
29+
private readonly RenderFragment _renderAuthorizeRouteViewCoreDelegate;
30+
private readonly RenderFragment<AuthenticationState> _renderAuthorizedDelegate;
31+
private readonly RenderFragment<AuthenticationState> _renderNotAuthorizedDelegate;
32+
private readonly RenderFragment _renderAuthorizingDelegate;
33+
34+
public AuthorizeRouteView()
35+
{
36+
// Cache the rendering delegates so that we only construct new closure instances
37+
// when they are actually used (e.g., we never prepare a RenderFragment bound to
38+
// the NotAuthorized content except when you are displaying that particular state)
39+
RenderFragment renderBaseRouteViewDelegate = builder => base.Render(builder);
40+
_renderAuthorizedDelegate = authenticateState => renderBaseRouteViewDelegate;
41+
_renderNotAuthorizedDelegate = authenticationState => builder => RenderNotAuthorizedInDefaultLayout(builder, authenticationState);
42+
_renderAuthorizingDelegate = RenderAuthorizingInDefaultLayout;
43+
_renderAuthorizeRouteViewCoreDelegate = RenderAuthorizeRouteViewCore;
44+
}
45+
46+
/// <summary>
47+
/// The content that will be displayed if the user is not authorized.
48+
/// </summary>
49+
[Parameter]
50+
public RenderFragment<AuthenticationState> NotAuthorized { get; set; }
51+
52+
/// <summary>
53+
/// The content that will be displayed while asynchronous authorization is in progress.
54+
/// </summary>
55+
[Parameter]
56+
public RenderFragment Authorizing { get; set; }
57+
58+
[CascadingParameter]
59+
private Task<AuthenticationState> ExistingCascadedAuthenticationState { get; set; }
60+
61+
/// <inheritdoc />
62+
protected override void Render(RenderTreeBuilder builder)
63+
{
64+
if (ExistingCascadedAuthenticationState != null)
65+
{
66+
// If this component is already wrapped in a <CascadingAuthenticationState> (or another
67+
// compatible provider), then don't interfere with the cascaded authentication state.
68+
_renderAuthorizeRouteViewCoreDelegate(builder);
69+
}
70+
else
71+
{
72+
// Otherwise, implicitly wrap the output in a <CascadingAuthenticationState>
73+
builder.OpenComponent<CascadingAuthenticationState>(0);
74+
builder.AddAttribute(1, nameof(CascadingAuthenticationState.ChildContent), _renderAuthorizeRouteViewCoreDelegate);
75+
builder.CloseComponent();
76+
}
77+
}
78+
79+
private void RenderAuthorizeRouteViewCore(RenderTreeBuilder builder)
80+
{
81+
builder.OpenComponent<AuthorizeRouteViewCore>(0);
82+
builder.AddAttribute(1, nameof(AuthorizeRouteViewCore.RouteData), RouteData);
83+
builder.AddAttribute(2, nameof(AuthorizeRouteViewCore.Authorized), _renderAuthorizedDelegate);
84+
builder.AddAttribute(3, nameof(AuthorizeRouteViewCore.Authorizing), _renderAuthorizingDelegate);
85+
builder.AddAttribute(4, nameof(AuthorizeRouteViewCore.NotAuthorized), _renderNotAuthorizedDelegate);
86+
builder.CloseComponent();
87+
}
88+
89+
private void RenderContentInDefaultLayout(RenderTreeBuilder builder, RenderFragment content)
90+
{
91+
builder.OpenComponent<LayoutView>(0);
92+
builder.AddAttribute(1, nameof(LayoutView.Layout), DefaultLayout);
93+
builder.AddAttribute(2, nameof(LayoutView.ChildContent), content);
94+
builder.CloseComponent();
95+
}
96+
97+
private void RenderNotAuthorizedInDefaultLayout(RenderTreeBuilder builder, AuthenticationState authenticationState)
98+
{
99+
var content = NotAuthorized ?? _defaultNotAuthorizedContent;
100+
RenderContentInDefaultLayout(builder, content(authenticationState));
101+
}
102+
103+
private void RenderAuthorizingInDefaultLayout(RenderTreeBuilder builder)
104+
{
105+
var content = Authorizing ?? _defaultAuthorizingContent;
106+
RenderContentInDefaultLayout(builder, content);
107+
}
108+
109+
private class AuthorizeRouteViewCore : AuthorizeViewCore
110+
{
111+
[Parameter]
112+
public RouteData RouteData { get; set; }
113+
114+
protected override IAuthorizeData[] GetAuthorizeData()
115+
=> AttributeAuthorizeDataCache.GetAuthorizeDataForType(RouteData.PageType);
116+
}
117+
}
118+
}

src/Components/Components/src/Auth/AuthorizeViewCore.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,20 @@ public abstract class AuthorizeViewCore : ComponentBase
5252
/// <inheritdoc />
5353
protected override void BuildRenderTree(RenderTreeBuilder builder)
5454
{
55+
// We're using the same sequence number for each of the content items here
56+
// so that we can update existing instances if they are the same shape
5557
if (currentAuthenticationState == null)
5658
{
5759
builder.AddContent(0, Authorizing);
5860
}
5961
else if (isAuthorized)
6062
{
6163
var authorized = Authorized ?? ChildContent;
62-
builder.AddContent(1, authorized?.Invoke(currentAuthenticationState));
64+
builder.AddContent(0, authorized?.Invoke(currentAuthenticationState));
6365
}
6466
else
6567
{
66-
builder.AddContent(2, NotAuthorized?.Invoke(currentAuthenticationState));
68+
builder.AddContent(0, NotAuthorized?.Invoke(currentAuthenticationState));
6769
}
6870
}
6971

@@ -102,6 +104,12 @@ protected override async Task OnParametersSetAsync()
102104
private async Task<bool> IsAuthorizedAsync(ClaimsPrincipal user)
103105
{
104106
var authorizeData = GetAuthorizeData();
107+
if (authorizeData == null)
108+
{
109+
// No authorization applies, so no need to consult the authorization service
110+
return true;
111+
}
112+
105113
EnsureNoAuthenticationSchemeSpecified(authorizeData);
106114

107115
var policy = await AuthorizationPolicy.CombineAsync(
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Reflection;
6+
using System.Threading.Tasks;
7+
8+
namespace Microsoft.AspNetCore.Components
9+
{
10+
/// <summary>
11+
/// Displays the specified content inside the specified layout and any further
12+
/// nested layouts.
13+
/// </summary>
14+
public class LayoutView : IComponent
15+
{
16+
private static readonly RenderFragment EmptyRenderFragment = builder => { };
17+
18+
private RenderHandle _renderHandle;
19+
20+
/// <summary>
21+
/// Gets or sets the content to display.
22+
/// </summary>
23+
[Parameter]
24+
public RenderFragment ChildContent { get; set; }
25+
26+
/// <summary>
27+
/// Gets or sets the type of the layout in which to display the content.
28+
/// The type must implement <see cref="IComponent"/> and accept a parameter named <see cref="LayoutComponentBase.Body"/>.
29+
/// </summary>
30+
[Parameter]
31+
public Type Layout { get; set; }
32+
33+
/// <inheritdoc />
34+
public void Attach(RenderHandle renderHandle)
35+
{
36+
_renderHandle = renderHandle;
37+
}
38+
39+
/// <inheritdoc />
40+
public Task SetParametersAsync(ParameterView parameters)
41+
{
42+
parameters.SetParameterProperties(this);
43+
Render();
44+
return Task.CompletedTask;
45+
}
46+
47+
private void Render()
48+
{
49+
// In the middle goes the supplied content
50+
var fragment = ChildContent ?? EmptyRenderFragment;
51+
52+
// Then repeatedly wrap that in each layer of nested layout until we get
53+
// to a layout that has no parent
54+
var layoutType = Layout;
55+
while (layoutType != null)
56+
{
57+
fragment = WrapInLayout(layoutType, fragment);
58+
layoutType = GetParentLayoutType(layoutType);
59+
}
60+
61+
_renderHandle.Render(fragment);
62+
}
63+
64+
private static RenderFragment WrapInLayout(Type layoutType, RenderFragment bodyParam)
65+
{
66+
return builder =>
67+
{
68+
builder.OpenComponent(0, layoutType);
69+
builder.AddAttribute(1, LayoutComponentBase.BodyPropertyName, bodyParam);
70+
builder.CloseComponent();
71+
};
72+
}
73+
74+
private static Type GetParentLayoutType(Type type)
75+
=> type.GetCustomAttribute<LayoutAttribute>()?.LayoutType;
76+
}
77+
}

0 commit comments

Comments
 (0)