'How does cascaded parameter Task<AthenticationState> get unwrapped and exposed as "context" in AuthorizeView and AuthorizedRouteView in Blazor WASM?
There is a an object of type AuthenticationState named "context" that is available inside AuthorizeView and AuthorizedRouteView components. This object allows to access the ClaimsPrincipal via context.User.
I can see in the source code that AuthenticationState is passed down to these components as Task by the CascadingValue component implemented in the CascadingAuthenticationState component (which in turn is defined at the top of the component hierarchy in App.razor).
However, when I inspect the source of the AuthorizeRouteView I can see the cascading parameter of type Task named ExistingCascadedAuthenticationState. Yet, it is a complete mystery to me how and where does the Task gets unwrapped and exposed as "context". Does anyone know the answer?
Solution 1:[1]
You need to dig deep, and it's a little complicated.
AuthorizeView
inherits fromAuthorizeViewCore
AuthorizedRouteView
builds it's ownAuthorizeRouteViewCore
inheriting fromAuthorizeViewCore
.
Code at the bottom of AuthorizedRouteView
:
private sealed class AuthorizeRouteViewCore : AuthorizeViewCore
{
[Parameter]
public RouteData RouteData { get; set; } = default!;
protected override IAuthorizeData[]? GetAuthorizeData()
=> AttributeAuthorizeDataCache.GetAuthorizeDataForType(RouteData.PageType);
}
AuthorizedRouteView
captures any cascade into ExistingCascadedAuthenticationState
. If one exists (not null) then CascadingAuthenticationState
exists in App
, so nothing more needs doing. If it's null then it adds CascadingAuthenticationState
as the component root component into its render fragment. This guarantees that Task<AuthenticationState>
is cascaded.
AuthorizeViewCore
captures the cascaded value:
[CascadingParameter] private Task<AuthenticationState>? AuthenticationState { get; set; }
It gets "unwrapped" in OnParametersSetAsync
currentAuthenticationState = await AuthenticationState;
isAuthorized = await IsAuthorizedAsync(currentAuthenticationState.User);
and used in BuildRenderTree
to the "context" you see.
var authorized = Authorized ?? ChildContent;
builder.AddContent(0, authorized?.Invoke(currentAuthenticationState!));
The content comes from:
RenderFragment<TValue>
declared as follows where TValue
- content
- is declared as AuthenticationState
:
[Parameter] public RenderFragment<AuthenticationState>? Authorized { get; set; }
Solution 2:[2]
The comment of enet helped me to find the answer.
When we have a RenderFragment<TValue>
delegate, the <TValue>
is exposed by default as @context
.
For example, in AuthorizeRouteView
we have a parameter NotAuthorized
:
[Parameter]
public RenderFragment<AuthenticationState> NotAuthorized { get; set; }
In this case AuthenticationState
is TValue
, therefore AuthenticationState
is exposed as @context
.
This article on Blazor University was the key for me to get the concept: Passing placeholders to RenderFragments.
Edit 2022-05-29
The recently added answer of @somedotnetguy makes it even more clear how render templates work. I suggest also reading his answer to get a more complete picture.
Solution 3:[3]
I was wondering the exact same thing:
How come, that suddenly inside the inner markup of a component (between its opening and closing tag, when its consumed in a parent) we can write @context
and where does the value come from?
The provided answers helped me to figure it out and there is good explanaition on Blazor University - RenderFragements (entire chapter, this page and the 4 following)
As seen in Official Docs here and here the AuthorizeView
has a property ChildContent
of type RenderFragment<AuthenticationState>
decorated with Parameter
.
[Microsoft.AspNetCore.Components.Parameter] public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.Authorization.AuthenticationState>? ChildContent { get; set; }
Here I go:
You cannot do this with a plain (empty, newly created) component:
//Parent.razor consumes MyComp
<MyComp>Some markup as content for MyComp</MyComp>
The FRAMEWORK CONVENTION is, you need the following code inside the component (class) definition, to enable the possibility to write markup between the tags (when it is consumed). The markup then gets passed as the ChildComponent property of type RenderFragment:
//MyComp.razor
[Parameter]
public RenderFragment ChildContent { get; set; }
You can also use properties/fields of the type RenderFragment or RenderFragment with other property-names, but they won't get automatically filled from the parent like above. However you can use them inside your component definition as you please. To set the content in the consuming parents of any RenderFragment you use its property name like an html tag and write the razor content inside it. The exception here is, if you ONLY want to fill the ChildContent and don't use other RenderFragments, it can be omitted like above (since this is a special name).
//MyComp.razor
<p>Fragment1:</p>
@SomeOtherRenderFragment
<p>Fragment2:</p>
@SomeGenericRenderFragment(_instanceT)
@code {
[Parameter]
public RenderFragment SomeOtherRenderFragment{ get; set; } =
@<h1>My Render Fragment Example</h1>;
[Parameter]
public RenderFragment<T> SomeGenericRenderFragment{ get; set; } // not set here in this example
private T _instanceT = null // T must be an explicit type, it's a placeholder in this example... for instance change it to 'string'. You can get the instance from where ever you want, probably through some service that you inject with DI
}
//Parent.razor consumes MyComp
// Implicit setting ChildContent
<MyComp>Some markup as content for MyComp</MyComp>
// Explicit setting ChildContent
<MyComp><ChildContent>Some markup as content for MyComp</ChildContent></MyComp>
// Explicit setting various RenderFragments
<MyComp>
<ChildContent>Some markup as content for MyComp</ChildContent>
<SomeOtherRenderFragment>SomeContent</SomeOtherRenderFragment>
<SomeGenericRenderFragment>SomeContent with @context</SomeGenericRenderFragment>
</MyComp>
And now putting it all together. You can also use the generic RenderFragment as type for the convention-property ChildContent. However, it is your responsibility to provide an instance of the generic class inside the component definition.
//MyComp.razor
<p>Content passed from parent:</p>
@ChildContent(_instanceT1)
<p>Content passed from parent a second time:</p>
@ChildContent(_instanceT2)
@code {
[Parameter]
public RenderFragment<T> ChildContent{ get; set; }
private string _instanceT1 = "Hello World!";
private string _instanceT2 = "Good Night!";
}
Parent:
//Parent.razor
<h1>Parent:</h1>
<MyComp>
<p>I can type content here and now comes the value of T: @context</p>
<p>and again again: @context</p>
</MyComp>
Final Html will look sth like this:
<h1>Parent:</h1>
<p>Content passed from parent:</p>
<p>I can type content here and now comes the value of T: Hello World!</p>
<p>and again again: Hello World!</p>
<p>Content passed from parent a second time:</p>
<p>I can type content here and now comes the value of T: Good Night!</p>
<p>and again again: Good Night!</p>
Note: @context
is ONLY available inside a component's tag (when consumed), if it has this generic RenderFragment property in its definition:
//MyComp.razor
[Parameter]
public RenderFragment<T> ChildContent{ get; set; }
AuthorizeView can use RenderFragments:
- ChildContent (most often used implicit, also explicit possible)
- Authorized (explicit)
- NotAuthorized (explicit)
- However it is implemented in such a way, that an exception is thrown if both are specified: Unhandled exception rendering component: Do not specify both 'Authorized' and 'ChildContent'. Basically Authorized substitutes ChildContent, or in other words, when not setting any RenderFragment explicitly, the ChildContent gets treated like Authorized, like shown in the answer by MrC aka Shaun Curtis.
Final Words: I hope this helps and I hope I kept typos to a minimum :)
Solution 4:[4]
The cascading parameter is Task<AuthenticationState> context
Which tells you that context
, when awaited, will return the object of type AuthenticationState. So what you get is a Task. The task when awaited, returns the value returned by the Task. The actual syntax for accessing the User is
var state = await context;
var user = state.User;
Also, you can give any name to the cascading parameter. So
[CascadingParameter]
public Task<AuthenticationState> AuthState {get;set;}
var state = await AuthState;
var user = state.User;
is equally valid.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Neits |
Solution 2 | |
Solution 3 | |
Solution 4 | Mayur Ekbote |