Skip to content

Commit

Permalink
Components router refactoring. Fixes #10493 #10445 (#12800)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSandersonMS authored Aug 5, 2019
1 parent 9f2b534 commit 2ff6a5c
Show file tree
Hide file tree
Showing 43 changed files with 1,516 additions and 590 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<Router AppAssembly="typeof(Program).Assembly">
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<p>Sorry, there's nothing at this address.</p>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
<Router AppAssembly=typeof(Program).Assembly />
<Router AppAssembly=typeof(Program).Assembly>
<Found Context="routeData">
<RouteView RouteData="@routeData" />
</Found>
<NotFound>
Sorry, there's nothing here.
</NotFound>
</Router>
16 changes: 11 additions & 5 deletions src/Components/Blazor/testassets/StandaloneApp/App.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<!--
Configuring this stuff here is temporary. Later we'll move the app config
into Program.cs, and it won't be necessary to specify AppAssembly.
-->
<Router AppAssembly=typeof(StandaloneApp.Program).Assembly />
<Router AppAssembly=typeof(StandaloneApp.Program).Assembly>
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<h2>Not found</h2>
Sorry, there's nothing at this address.
</LayoutView>
</NotFound>
</Router>

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ public event Microsoft.AspNetCore.Components.AuthenticationStateChangedHandler A
public abstract System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.AuthenticationState> GetAuthenticationStateAsync();
protected void NotifyAuthenticationStateChanged(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.AuthenticationState> task) { }
}
public sealed partial class AuthorizeRouteView : Microsoft.AspNetCore.Components.RouteView
{
public AuthorizeRouteView() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
protected override void Render(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
}
public partial class AuthorizeView : Microsoft.AspNetCore.Components.AuthorizeViewCore
{
public AuthorizeView() { }
Expand Down Expand Up @@ -278,6 +287,16 @@ protected LayoutComponentBase() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
}
public partial class LayoutView : Microsoft.AspNetCore.Components.IComponent
{
public LayoutView() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public System.Type Layout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; }
}
public sealed partial class LocationChangeException : System.Exception
{
public LocationChangeException(string message, System.Exception innerException) { }
Expand Down Expand Up @@ -323,20 +342,6 @@ public abstract partial class OwningComponentBase<TService> : Microsoft.AspNetCo
protected OwningComponentBase() { }
protected TService Service { get { throw null; } }
}
public partial class PageDisplay : Microsoft.AspNetCore.Components.IComponent
{
public PageDisplay() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public System.Type Page { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public System.Collections.Generic.IDictionary<string, object> PageParameters { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
public sealed partial class ParameterAttribute : System.Attribute
{
Expand Down Expand Up @@ -391,6 +396,23 @@ public sealed partial class RouteAttribute : System.Attribute
public RouteAttribute(string template) { }
public string Template { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
public sealed partial class RouteData
{
public RouteData(System.Type pageType, System.Collections.Generic.IReadOnlyDictionary<string, object> routeValues) { }
public System.Type PageType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public System.Collections.Generic.IReadOnlyDictionary<string, object> RouteValues { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
public partial class RouteView : Microsoft.AspNetCore.Components.IComponent
{
public RouteView() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public System.Type DefaultLayout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RouteData RouteData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
protected virtual void Render(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; }
}
}
namespace Microsoft.AspNetCore.Components.CompilerServices
{
Expand Down Expand Up @@ -648,15 +670,12 @@ public Router() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public System.Reflection.Assembly AppAssembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.RouteData> Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment NotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
public void Dispose() { }
System.Threading.Tasks.Task Microsoft.AspNetCore.Components.IHandleAfterRender.OnAfterRenderAsync() { throw null; }
protected virtual void Render(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder, System.Type handler, System.Collections.Generic.IDictionary<string, object> parameters) { }
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; }
}
}
118 changes: 118 additions & 0 deletions src/Components/Components/src/Auth/AuthorizeRouteView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Auth;
using Microsoft.AspNetCore.Components.RenderTree;

namespace Microsoft.AspNetCore.Components
{
/// <summary>
/// Combines the behaviors of <see cref="AuthorizeView"/> and <see cref="RouteView"/>,
/// so that it displays the page matching the specified route but only if the user
/// is authorized to see it.
///
/// Additionally, this component supplies a cascading parameter of type <see cref="Task{AuthenticationState}"/>,
/// which makes the user's current authentication state available to descendants.
/// </summary>
public sealed class AuthorizeRouteView : RouteView
{
// We expect applications to supply their own authorizing/not-authorized content, but
// it's better to have defaults than to make the parameters mandatory because in some
// cases they will never be used (e.g., "authorizing" in out-of-box server-side Blazor)
private static readonly RenderFragment<AuthenticationState> _defaultNotAuthorizedContent
= state => builder => builder.AddContent(0, "Not authorized");
private static readonly RenderFragment _defaultAuthorizingContent
= builder => builder.AddContent(0, "Authorizing...");

private readonly RenderFragment _renderAuthorizeRouteViewCoreDelegate;
private readonly RenderFragment<AuthenticationState> _renderAuthorizedDelegate;
private readonly RenderFragment<AuthenticationState> _renderNotAuthorizedDelegate;
private readonly RenderFragment _renderAuthorizingDelegate;

public AuthorizeRouteView()
{
// Cache the rendering delegates so that we only construct new closure instances
// when they are actually used (e.g., we never prepare a RenderFragment bound to
// the NotAuthorized content except when you are displaying that particular state)
RenderFragment renderBaseRouteViewDelegate = builder => base.Render(builder);
_renderAuthorizedDelegate = authenticateState => renderBaseRouteViewDelegate;
_renderNotAuthorizedDelegate = authenticationState => builder => RenderNotAuthorizedInDefaultLayout(builder, authenticationState);
_renderAuthorizingDelegate = RenderAuthorizingInDefaultLayout;
_renderAuthorizeRouteViewCoreDelegate = RenderAuthorizeRouteViewCore;
}

/// <summary>
/// The content that will be displayed if the user is not authorized.
/// </summary>
[Parameter]
public RenderFragment<AuthenticationState> NotAuthorized { get; set; }

/// <summary>
/// The content that will be displayed while asynchronous authorization is in progress.
/// </summary>
[Parameter]
public RenderFragment Authorizing { get; set; }

[CascadingParameter]
private Task<AuthenticationState> ExistingCascadedAuthenticationState { get; set; }

/// <inheritdoc />
protected override void Render(RenderTreeBuilder builder)
{
if (ExistingCascadedAuthenticationState != null)
{
// If this component is already wrapped in a <CascadingAuthenticationState> (or another
// compatible provider), then don't interfere with the cascaded authentication state.
_renderAuthorizeRouteViewCoreDelegate(builder);
}
else
{
// Otherwise, implicitly wrap the output in a <CascadingAuthenticationState>
builder.OpenComponent<CascadingAuthenticationState>(0);
builder.AddAttribute(1, nameof(CascadingAuthenticationState.ChildContent), _renderAuthorizeRouteViewCoreDelegate);
builder.CloseComponent();
}
}

private void RenderAuthorizeRouteViewCore(RenderTreeBuilder builder)
{
builder.OpenComponent<AuthorizeRouteViewCore>(0);
builder.AddAttribute(1, nameof(AuthorizeRouteViewCore.RouteData), RouteData);
builder.AddAttribute(2, nameof(AuthorizeRouteViewCore.Authorized), _renderAuthorizedDelegate);
builder.AddAttribute(3, nameof(AuthorizeRouteViewCore.Authorizing), _renderAuthorizingDelegate);
builder.AddAttribute(4, nameof(AuthorizeRouteViewCore.NotAuthorized), _renderNotAuthorizedDelegate);
builder.CloseComponent();
}

private void RenderContentInDefaultLayout(RenderTreeBuilder builder, RenderFragment content)
{
builder.OpenComponent<LayoutView>(0);
builder.AddAttribute(1, nameof(LayoutView.Layout), DefaultLayout);
builder.AddAttribute(2, nameof(LayoutView.ChildContent), content);
builder.CloseComponent();
}

private void RenderNotAuthorizedInDefaultLayout(RenderTreeBuilder builder, AuthenticationState authenticationState)
{
var content = NotAuthorized ?? _defaultNotAuthorizedContent;
RenderContentInDefaultLayout(builder, content(authenticationState));
}

private void RenderAuthorizingInDefaultLayout(RenderTreeBuilder builder)
{
var content = Authorizing ?? _defaultAuthorizingContent;
RenderContentInDefaultLayout(builder, content);
}

private class AuthorizeRouteViewCore : AuthorizeViewCore
{
[Parameter]
public RouteData RouteData { get; set; }

protected override IAuthorizeData[] GetAuthorizeData()
=> AttributeAuthorizeDataCache.GetAuthorizeDataForType(RouteData.PageType);
}
}
}
12 changes: 10 additions & 2 deletions src/Components/Components/src/Auth/AuthorizeViewCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,20 @@ public abstract class AuthorizeViewCore : ComponentBase
/// <inheritdoc />
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// We're using the same sequence number for each of the content items here
// so that we can update existing instances if they are the same shape
if (currentAuthenticationState == null)
{
builder.AddContent(0, Authorizing);
}
else if (isAuthorized)
{
var authorized = Authorized ?? ChildContent;
builder.AddContent(1, authorized?.Invoke(currentAuthenticationState));
builder.AddContent(0, authorized?.Invoke(currentAuthenticationState));
}
else
{
builder.AddContent(2, NotAuthorized?.Invoke(currentAuthenticationState));
builder.AddContent(0, NotAuthorized?.Invoke(currentAuthenticationState));
}
}

Expand Down Expand Up @@ -102,6 +104,12 @@ protected override async Task OnParametersSetAsync()
private async Task<bool> IsAuthorizedAsync(ClaimsPrincipal user)
{
var authorizeData = GetAuthorizeData();
if (authorizeData == null)
{
// No authorization applies, so no need to consult the authorization service
return true;
}

EnsureNoAuthenticationSchemeSpecified(authorizeData);

var policy = await AuthorizationPolicy.CombineAsync(
Expand Down
77 changes: 77 additions & 0 deletions src/Components/Components/src/LayoutView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Reflection;
using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Components
{
/// <summary>
/// Displays the specified content inside the specified layout and any further
/// nested layouts.
/// </summary>
public class LayoutView : IComponent
{
private static readonly RenderFragment EmptyRenderFragment = builder => { };

private RenderHandle _renderHandle;

/// <summary>
/// Gets or sets the content to display.
/// </summary>
[Parameter]
public RenderFragment ChildContent { get; set; }

/// <summary>
/// Gets or sets the type of the layout in which to display the content.
/// The type must implement <see cref="IComponent"/> and accept a parameter named <see cref="LayoutComponentBase.Body"/>.
/// </summary>
[Parameter]
public Type Layout { get; set; }

/// <inheritdoc />
public void Attach(RenderHandle renderHandle)
{
_renderHandle = renderHandle;
}

/// <inheritdoc />
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
Render();
return Task.CompletedTask;
}

private void Render()
{
// In the middle goes the supplied content
var fragment = ChildContent ?? EmptyRenderFragment;

// Then repeatedly wrap that in each layer of nested layout until we get
// to a layout that has no parent
var layoutType = Layout;
while (layoutType != null)
{
fragment = WrapInLayout(layoutType, fragment);
layoutType = GetParentLayoutType(layoutType);
}

_renderHandle.Render(fragment);
}

private static RenderFragment WrapInLayout(Type layoutType, RenderFragment bodyParam)
{
return builder =>
{
builder.OpenComponent(0, layoutType);
builder.AddAttribute(1, LayoutComponentBase.BodyPropertyName, bodyParam);
builder.CloseComponent();
};
}

private static Type GetParentLayoutType(Type type)
=> type.GetCustomAttribute<LayoutAttribute>()?.LayoutType;
}
}
Loading

0 comments on commit 2ff6a5c

Please sign in to comment.