MvvmBlazor
MvvmBlazor copied to clipboard
Parameters with non-public accessors are not set
Question
Hi, How can I get AuthenticationState in ViewModel? I'm trying with:
[CascadingParameter] Task<AuthenticationState> AuthenticationState { get; set; }
but it is always null.
Thanks.
Code sample
No response
Version
6.0.6
Are you using Blazor WASM or Blazor Server?
Blazor WASM
Cascading parameters are supported. Could you create a minimal reproduction to show the exact issue? Keep in mind that you need to declare such parameters in both the component and the view model in order to make this work. Have a look at the Readme for some examples regarding parameters.
Hi, Here is my component code:
@attribute [Authorize]
@layout MainLayout
@using System.Collections.ObjectModel
@inherits MvvmComponentBase<TeamViewModel>
<div id="main-content">
@if (@BindingContext.DataSource == null)
{
<Placeholder />
}
else
{
<TeamMembers [email protected]/>
}
</div>
@code{
[CascadingParameter] Task<AuthenticationState> authenticationState { get; set; }
[Parameter] public ObservableCollection<User> DataSource { get; set; }
}
and here is my ViewModel code:
public class TeamViewModel : ViewModelBase
{
private readonly ITeamService _teamService;
private readonly IJSRuntime _jsRuntime;
[CascadingParameter] Task<AuthenticationState> authenticationState { get; set; }
[Parameter] public ObservableCollection<User> DataSource { get; set; }
public MetaData Paging { get; set; }
public TeamViewModel(ITeamService teamService, IErrorHelper errorHelper, IJSRuntime jsRuntime)
{
_teamService = teamService;
_jsRuntime = jsRuntime;
}
public override async Task OnInitializedAsync()
{
var a = authenticationState.Result;
//var response = await _teamService.GetTeamAsync(new QueryParameters() { AccountId = 2 });
//DataSource = new ObservableCollection<User>(response.Items);
//Paging = response.MetaData;
}
}
In this line of code: var a = authenticationState.Result, authenticationState is null.
Can you confirm that the property has a value in the component itself?
I have added initialization in my component, like this:
@attribute [Authorize]
@layout MainLayout
@using System.Collections.ObjectModel
@inherits MvvmComponentBase<TeamViewModel>
<div id="main-content">
@if (@BindingContext.DataSource == null)
{
<Placeholder />
}
else
{
<TeamMembers [email protected]/>
}
</div>
@code{
[CascadingParameter] Task<AuthenticationState> authenticationState { get; set; }
[Parameter] public ObservableCollection<User> DataSource { get; set; }
protected override void OnInitialized()
{
var auth = authenticationState.Result;
}
}
In component, I receive result from authenticationState.Result, however, now my BindingContext is null. I'm guessing that it is because I have OnInitialize in both component and ViewModel. When I leave OnInitialize methot only on component it works fine, but I need it inside ViewModel. Do you have any idea how to resolve this.
If you override OnInitialized()
in your component you need to call base.OnInitialized()
. Otherwise the binding will be lost. You don't need to override it anyways, simply setting a breakpoint into the setter of the authenticationState
property should be enough.
I would still assume that your authentication state is simply null in the first place. Please review the docs and ensure that you properly set up your router.
@bojankostic81 try changing the visibility to public (on both the component and view model), then I believe it should work.
[CascadingParameter] public Task<AuthenticationState> authenticationState { get; set; }
@hrannzo good catch, does parameter injection into components work without a public setter? If so, such a use case should be supported by this library.
@klemmchr It seems that a property can be private but it must have a setter defined when receiving a cascading value. The setter can be private and init. If the setter is missing then an exception is thrown from the belly of the Blazor.
CascadingComponent.razor
Name1=@Name1
<br/>
Name2=@Name2
<br/>
Name3=@Name3
<br/>
Name4=@Name4
<br/>
Name5=@Name5
@code {
[CascadingParameter(Name = nameof(Name1))]
public string? Name1 { get; init; }
[CascadingParameter(Name = nameof(Name2))]
public string? Name2 { get; private set; }
[CascadingParameter(Name = nameof(Name3))]
private string? Name3 { get; init; }
[CascadingParameter(Name = nameof(Name4))]
private string? Name4 { get; set; }
// Attribute commented out since it would crash the app
// Runtime error: Cannot provide a value for property 'Name' on type '...' because the property has no setter.
//[CascadingParameter(Name = nameof(Name5))]
public string? Name5 { get; }
// Does not compile: BL0004 - Component parameter should be public
// [Parameter] private string Name6 { get; set; }
}
WrapperComponent.razor
<CascadingValue Value="@("Hello")" Name="Name1">
<CascadingValue Value="@("World")" Name="Name2">
<CascadingValue Value="@("Foo")" Name="Name3">
<CascadingValue Value="@("Bar")" Name="Name4">
<CascadingValue Value="@("Crash")" Name="Name5">
<CascadingComponent></CascadingComponent>
</CascadingValue>
</CascadingValue>
</CascadingValue>
</CascadingValue>
</CascadingValue>
Rendered output:
@hrannzo thanks for the investigation, this actually is a bug in the library