so...Im working on a Blazor Server app and having troubles with anderstanding of how a cascading parameters work and why my MainLayout returns always null.
Im doing it like this:
MainLayout.razor:
<CascadingValue Value="this">
<PageTitle>Global</PageTitle>
<div id="wrapper">
<SfToolbar CssClass="dockToolbar">
<ToolbarEvents Clicked="#Toggle"></ToolbarEvents>
<ToolbarItems>
<ToolbarItem PrefixIcon="e-icons e-menu" TooltipText="Menu"></ToolbarItem>
<ToolbarItem>
<Template>
<div class="e-folder">
<div class="e-folder-name">Global</div>
</div>
</Template>
</ToolbarItem>
</ToolbarItems>
</SfToolbar>
<div id="main-content container-fluid col-md-12" class="maincontent">
<div>
<div class="content">#Body</div>
</div>
</div>
</div>
and Pages that behaves
SignIn.razor:
#page "/sign-in"
<ComponentLibrary.Components.AuthPage backUrl="/"></ComponentLibrary.Components.AuthPage>
#code {
[CascadingParameter]
public MainLayout? Layout { get; set; }
protected override void OnInitialized()
{
Layout.userModel = null;
Layout.RefreshSideBar();
base.OnInitialized();
}
}
But when im getting on SignIn page my Layout for some reason is null, any advices where need to look at?
i tried to just create a new example of Layout in other pages but that is tottaly not what i needed
Yes, you guys were right. I just noticed that there was 2 MainLayout files i miss that part, sorry. removing from project extra layout solves the problem
So let's try and recreate your problem.
Start with a standard Blazor server project.
#inherits LayoutComponentBase
<PageTitle>SO74799583</PageTitle>
<CascadingValue Value=this>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
About
</div>
<article class="content px-4">
#Body
</article>
</main>
</div>
</CascadingValue>
#code {
public string RenderTime = DateTime.Now.ToLongTimeString();
protected override bool ShouldRender()
{
RenderTime = DateTime.Now.ToLongTimeString();
return true;
}
}
And Index:
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<div class="bg-dark text-white p-2 m-2">
Main Component last rendered at: #(this.mainLayout?.RenderTime ?? "Not Rendered")
</div>
#code {
[CascadingParameter] private MainLayout? mainLayout { get; set; }
}
Which works: mainLayout is not null. So your not showing us everything.
However, you should NEVER cascade or pass around a reference to a Comnponent.
You are not in control of it's lifecycle: the Renderer is. It will "Dispose" it when it no longer needs it.
There's functionality that you should never attempt to use from another component.
If you want to communicate between components create a state class and either:
Register it as a service and use it to raise and register for events.
Cascade it.
Here's a simple example.
public class MyState
{
public string LastUpdateTime { get; private set; } = "Not Set";
public event EventHandler<string>? Updated;
public void SetTime()
{
this.LastUpdateTime = DateTime.Now.ToLongTimeString();
this.Updated?.Invoke(this, this.LastUpdateTime);
}
}
Layout:
#inherits LayoutComponentBase
<PageTitle>SO74799583</PageTitle>
<CascadingValue Value=myState>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
About
</div>
<article class="content px-4">
<div class="alert alert-info">#RenderTime</div>
#Body
</article>
</main>
</div>
</CascadingValue>
#code {
public string RenderTime = DateTime.Now.ToLongTimeString();
private MyState myState = new();
protected override void OnInitialized()
=> myState.Updated += OnUpdated;
private void OnUpdated(object? sender, string value)
=> StateHasChanged();
public void Dispose()
=> myState.Updated -= OnUpdated;
}
And Index:
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<div class="p-2 m-2">
<button class="btn btn-primary" #onclick=Update>Update Time</button>
</div>
#code {
[CascadingParameter] private MyState? myState { get; set; }
private Task Update()
{
myState?.SetTime();
return Task.CompletedTask;
}
}
Related
I have created a simple blazor application and I need to show a loading screen on the index.razor page ,I am not needed to include it on the index.razor page.i am using some indirect way to do it .
first I have created a one class called Appsettings.cs and brought the loading logic inside it
Appsettings.cs
public class AppSettings
{
public static bool _IsProcessing { get; set; } = false;
public static MainLayout _layout { get; set; } = new MainLayout();
public static void Loading(bool isProcessing)
{
_IsProcessing = isProcessing;
if(_layout !=null)
_layout .LoadingScreenShowing(_IsProcessing);
}
}
then my index.razor file is like this ,when press the below Load button ,I need to show the Loading screen .
index.razor
<button onclick="PageLoading">Load</button>
#code{
protected override void Oninitialized(){}
private void PageLoading(){
AppSettings.Loading(true);
this.StateHasChanged();
//do something
AppSettings.Loading(false);
this.StateHasChanged();
}
after I have included loading part into the MainLayout.razor not explicitly to the index.razor
MainLayout.razor
#inherits LayoutComponentBase
<PageTitle>BlazorApp1</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
About
</div>
<article class="content px-4">
<Loading IsVisible="#IsLoading" #ref="load"/> //loading component
#Body
</article>
</main>
</div>
And I've created a partial class to place the functional part of the main layout
MainLayout.razor.cs
public partial class MainLayout{
private bool IsLoading;
Loading load ;
public async void LoadingScreenShowing(bool isLoading)
{
load = new Loading();
IsLoading = isLoading;
this.StateHasChanged();//exception is throwing from this line
await Task.CompletedTask;
}
}
when execute the this.StateHasChanged() line ,I am getting the exception called
System.InvalidOperationException: 'The render handle is not yet
assigned.'
but I don't know why It's occurring ,pls help me
You can't do it the way your are trying to. Component lifecycles are managed by the Renderer: you can't create a component instance and somehow shoehorn it into a page. That's the reason for the error.
Here's a heavily refactored version of your code that I think does what intend.
The scoped service that maintains the App State (your AppSettings):
public class AppStateService
{
private bool _isLoaded;
public event Action? LoadStateChanged;
public bool IsLoaded
{
get => _isLoaded;
set
{
if (_isLoaded != value)
{
_isLoaded = value;
LoadStateChanged?.Invoke();
}
}
}
}
registered in program:
builder.Services.AddScoped<AppStateService>();
The Loading component - I've used a simple bootstrap alert to display the loading message.
#inject AppStateService appStateService
#implements IDisposable
#if (!this.appStateService.IsLoaded)
{
<div class="alert alert-danger">
Page Loading...
</div>
}
#code {
protected override void OnInitialized()
=> this.appStateService.LoadStateChanged += this.OnStateChanged;
private void OnStateChanged()
=> this.InvokeAsync(StateHasChanged);
public void Dispose()
=> this.appStateService.LoadStateChanged -= this.OnStateChanged;
}
MainLayout:
#inherits LayoutComponentBase
<PageTitle>BlazorApp3</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4 auth">
<LoginDisplay />
About
</div>
<article class="content px-4">
<Loading />
#Body
</article>
</main>
</div>
And finally the demo page:
#page "/"
#inject AppStateService appStateService;
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
#code{
protected async override Task OnInitializedAsync()
{
this.appStateService.IsLoaded = false;
// emulate an async loading event
await Task.Delay(2000);
this.appStateService.IsLoaded = true;
}
}
I have a blazor app that gets some inputs, calculates, and displays some outputs via Edit Form and single model.
Some of the inputs:
<EditForm Model="#model" OnValidSubmit="hundlevalidsubmit">
<div class="form-group">
<label>Full Name</label>
<InputText style="width: 25%" #bind-Value="model.name" class="form-control">
</InputText>
</div>
<div class="form-group">
<label>Type of Vessel</label>
<InputSelect style="width: 25%" #bind-Value="model.typeofvessel" class="form-control">
<option value="">Select the type of Vessel</option>
<option value="5">General Cargo Ship</option>
<option value="10">General Cargo Ship</option>
<option value="15">Roll on-roll off Ship</option>
<option value="20">Bulk Carrier</option>
</InputSelect>
</div>
<div class="form-group">
<label>Gross Tonnage</label>
<InputNumber style="width: 25%" #bind-Value="model.gt" class="form-control" />
</div>
</EditForm>
I then bind the results at the end of the page with
<EditForm Model="#model" OnValidSubmit="hundlevalidsubmit">
<div class="card-body">
<div class="row">
<div class="col">Full Name:</div>
<div class="col text-right">#model.name</div>
</div>
<div class="row">
<div class="col">EEXI Value:</div>
<div class="col text-right">#eexi.ToString("N")</div>
</div>
<div class="row">
<div class="col">Compliance:</div>
<div class="col text-right">#model.compliance</div>
</div>
</div>
</EditForm>
And display them with a calculate button at the end of the page with this:
<EditForm Model="#model" OnValidSubmit="hundlevalidsubmit">
<center>
<div class="checkbox">
<label>
<input name="cbPrivacy" id="cbPrivacy" type="checkbox"> I accept Dromon Bureau of Shipping (Privacy Policy).
</label>
</div>
<div class="col-lg-10 #*col-lg-offset-2*#" style="margin-bottom:20px;">
<button class="btn btn-default btn-medium">Cancel</button>
<button href="/Results" #onclick="#(() => { SendEmail(); })" style="background-color:green;" class="btn btn-primary btn-medium">
Calculate
</button>
#*href="/Results"*#
#* <p>Calculate</p>*#
</div>
</center>
</EditForm>
This is the code that does the calculations
#code {
public infomodel model = new infomodel();
public double eexi;
public void hundlevalidsubmit()
{
eexi = (int.Parse(model.typeofvessel) + model.gt);
if (eexi > 50)
{
model.compliance = "Yes";
}
else
{
model.compliance = "No";
}
}
Now I would like to display the results on a new page (results).
Please note this is my first app so limited technical knowledge.
Method 1:
You can pass parameters to page being navigated like this, if the values are few and not sensitive and are used only in one place
NavigationManager.NavigateTo($“NextPage/{Value1}/{Value2}”);
NextPage:
#page “/nextpage/{value1}/{value2}
[Parameter]
Public string Value1 {get; set;}
[Parameter]
Public string Value2 {get; set;}
Method 2:
It involves using DI to hold global data
Register service like this in program.cs
builder.Services.AddScoped<DataService>();
In first page:
#inject DataService dataService;
dataService.model = new model with calculated values
In second page:
#inject DataService dataService;
Use dataService.model to retrieve values
DataService.cs
Public class DataService{
public infomodel model{get; set;}
}
You can use startup.cs file for DI
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddScoped<DataService>();
}
This one is Blazor server-side .NET 6.0 project
Program.cs
using FinalExiiCalculator.Data;
using FinalExiiCalculator.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddSingleton<DataService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
**DataService.cs**
using FinalExiiCalculator.Models;
namespace FinalExiiCalculator.Services
{
public class DataService
{
public infomodel infomodel{get; set;} = new();
}
}
**infomodel.cs**
public class infomodel
{
public string compliance { get; set; } = "";
public string typeofvessel { get; set; } = "";
}
counter.razor
#page "/counter"
#using FinalExiiCalculator.Services
#inject DataService dataService
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
dataService.infomodel.typeofvessel = "My Vessel";
dataService.infomodel.compliance = "Yes";
}
}
FetchData.razor
#page "/fetchdata"
<PageTitle>Weather forecast</PageTitle>
#using FinalExiiCalculator.Data
#inject WeatherForecastService ForecastService
#using FinalExiiCalculator.Services
#inject DataService dataService
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
#if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
#foreach (var forecast in forecasts)
{
<tr>
<td>#forecast.Date.ToShortDateString()</td>
<td>#forecast.TemperatureC</td>
<td>#forecast.TemperatureF</td>
<td>#forecast.Summary</td>
</tr>
}
</tbody>
</table>
<div>
<span>#dataService.infomodel.typeofvessel</span>
<span>#dataService.infomodel.compliance</span>
</div>
}
#code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}
I am using ASP.NET Core 5 Blazor server-side. Assume port was 5011, as you know, log in page is
https://localhost:5011/Identity/Account/Login
When run app, or press F5 for debugging, entry point is
https://localhost:5011
For checking authentication in web-app, I use something like these
File MainLayout.razor
#inherits LayoutComponentBase
#implements IDisposable
#inject Microsoft.AspNetCore.Components.NavigationManager NavigationManager
#inject IJSRuntime JSRuntime
#using Microsoft.JSInterop;
<AuthorizeView>
<Authorized>
<div class="sidebar">
<div class="top-row logo-container pl-4 navbar-dark bg-light text-body">
<button class="navbar-toggler" #onclick="#ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
<span class="logo-image" />
#*<div class="#($"bg-light text-dark d-inline-block theme-settings { ThemeSwitcherShown }")">
<a class="nav-item nav-link" #onclick="#ToggleThemeSwitcherPanel" href="javascript:void(0);">
<span class="demo-theme-icon"></span>
</a>
</div>*#
</div>
<div class="#NavMenuCssClass">
<NavMenu />
</div>
</div>
<div class="main">
<div class="top-row bg-light text-body px-4" id="navbar">
<div class="logo">
<h5 class="caption">NumBooks</h5>
</div>
<div class="#($"theme-settings { ThemeSwitcherShown }")">
<a class="nav-item nav-link" #onclick="#ToggleThemeSwitcherPanel" href="javascript:void(0);">
<span class="demo-theme-icon"></span>
</a>
</div>
</div>
<div class="content px-4">
#Body
</div>
</div>
<ThemeSwitcher #bind-Shown="#ThemeSwitcherShown"></ThemeSwitcher>
</Authorized>
<NotAuthorized>
Log in
</NotAuthorized>
</AuthorizeView>
#*#if(!Context.User.Identity.IsAuthenticated)
{
Log in
}*#
#code {
string NavMenuCssClass { get; set; } = "";
void ToggleNavMenu()
{
NavMenuCssClass = string.IsNullOrEmpty(NavMenuCssClass) || NavMenuCssClass.Contains("d-none") ? "d-block d-xl-none" : "d-none d-xl-flex";
ThemeSwitcherShown = false;
}
bool themeSwitcherShown = false;
bool ThemeSwitcherShown
{
get => themeSwitcherShown;
set
{
themeSwitcherShown = value;
InvokeAsync(StateHasChanged);
}
}
void ToggleThemeSwitcherPanel()
{
ThemeSwitcherShown = !ThemeSwitcherShown;
}
string UriFragment { get; set; } = "";
void OnLocationChanged(object sender, LocationChangedEventArgs args)
{
if (!string.IsNullOrEmpty(NavMenuCssClass))
{
NavMenuCssClass = "";
InvokeAsync(StateHasChanged);
}
}
protected override void OnInitialized()
{
base.OnInitialized();
NavigationManager.LocationChanged += OnLocationChanged;
}
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
JSRuntime.InvokeAsync<string>("ScrollToTarget");
}
return base.OnAfterRenderAsync(firstRender);
}
void IDisposable.Dispose()
{
NavigationManager.LocationChanged -= OnLocationChanged;
}
}
Let's focus at these lines
<AuthorizeView>
<Authorized>
#* web-app for authenticated user(s). *#
</Authorized>
<NotAuthorized>
#* Press text link to go to log-in page. *#
Log in
</NotAuthorized>
</AuthorizeView>
Then, when web-app run, I press button Log in to go to https://localhost:5011/Identity/Account/Login if not authenticated. This solution is really not good. I want show Login page when user not authenticated, how to archive that?
I'm pretty new to blazor and have gotten myself in some doubt on adding roles to the database.
I have implemented to Identity role management and have a working system.
But now i want to add new roles trough the GUI instead of editing the database.
I have a razor page called RolesOverview.razor
On this page i have a input field and a button.
When i click this button i want to add the text to the roles manager and save it to the database.
This is my razor component
#page "/admin/roles"
#using Microsoft.AspNetCore.Identity
#inject RoleManager<IdentityRole> roleManager
<div class="jumbotron">
<!-- Roles Overview Group Box -->
<div class="row mb-5">
<div class="col-12">
<h1 class="display-6">Roles Options</h1>
<hr class="my-4" />
<div class="row" style="background-color:white; margin-bottom:10px; margin-top:10px;">
<div class="col-12">
<div class="card w-100 mb-3" style="min-width:100%;">
<div class="card-body">
<h5 class="card-title">Roles</h5>
<p class="card-text">
<div class="row">
<div class="col-1">
Role Name:
</div>
<div class="col-10">
<input type="text" style="min-width:100%;" placeholder="Role Type" />
</div>
<div class="col-1">
Add Role
</div>
</div>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Not Getting Saved...
#code {
private string CurrentValue { get; set; }
private async void AddRole()
{
if (CurrentValue != string.Empty)
{
if (!await roleManager.RoleExistsAsync(CurrentValue))
{
await roleManager.CreateAsync(new IdentityRole
{
Name = CurrentValue
});
}
}
}
}
I have no clue on what todo next.
I's it posible todo it with razor component or do i need to do it trough razor page?
Example would be perfect.
Regards Me!
Answer :
<div class="col-10">
<input value="#CurrentValue" #onchange="#((ChangeEventArgs __e) => CurrentValue =__e.Value.ToString())" />
#*<input type="text" style="min-width:100%;" placeholder="Role Type" />*#
</div>
<div class="col-1">
<a #onclick="AddRole" class="btn btn-primary" style="min-width:90px;">Add Role</a>
</div>
#code {
private string CurrentValue { get; set; }
private async void AddRole()
{
if (CurrentValue != string.Empty)
{
if (!await roleManager.RoleExistsAsync(CurrentValue))
{
await roleManager.CreateAsync(new IdentityRole
{
Name = CurrentValue
});
}
}
}
}
You can use RoleManager to create a new role by using the CreateAsync method:
if (!await roleMgr.RoleExistsAsync("RoleName"))
{
await roleManager.CreateAsync(new IdentityRole
{
Name = "RoleName"
});
}
I have created a simple form which can be filled out and saved to the database but I don't know how I can implement any kind of success message in a blazor component.
This is my form:
<EditForm Model=#Input OnValidSubmit="Speichern">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-row">
<div class="form-group col-md-6">
<label>Vorname</label>
<InputText class="form-control" #bind-Value="Input.FirstName" />
<ValidationMessage For="() => Input.FirstName" />
</div>
<div class="form-group col-md-6">
<label>Nachname</label>
<InputText class="form-control" #bind-Value="Input.LastName" />
<ValidationMessage For="() => Input.LastName" />
</div>
</div>
<div class="form-group">
<label>Username</label>
<InputText class="form-control" #bind-Value="Input.Username" />
<ValidationMessage For="() => Input.Username" />
</div>
<div class="form-group">
<label>E-Mail</label>
<InputText class="form-control" #bind-Value="Input.Email" />
<ValidationMessage For="() => Input.Email" />
</div>
<div class="form-group">
<label>Telefonnummer</label>
<InputText class="form-control" #bind-Value="Input.PhoneNumber" />
<ValidationMessage For="() => Input.PhoneNumber" />
</div>
<button type="submit" class="btn btn-primary">Speichern</button>
</EditForm>
The method Speichern() saves the changes to the SQL-Database
public async void Speichern()
{
Mitarbeiter.UserName = Input.Username;
Mitarbeiter.FirstName = Input.FirstName;
Mitarbeiter.LastName = Input.LastName;
Mitarbeiter.Email = Input.Email;
Mitarbeiter.PhoneNumber = Input.PhoneNumber;
Mitarbeiter.EmailConfirmed = true;
await UserManager.UpdateAsync(Mitarbeiter);
}
After the await statement I want to set a custom message. I know I can do it with a string property and set it to any kind of text but I want to be more flexible here. Is it possible to even display a custom component? Perhaps with a custom Alert component?
Edit
I thought it is wasm, but you can try it anyway for blazor server-side.
There are numerous ways how to do it. I'll show how I implemented toasts in blazor.
I created ToastContainer as Component. It may contain html for toast or if you use UI library such Kendo or Syncfusion it will contain their toast component. I'll use Syncfusion. Then, I added this component to App.razor, because I wanted to use it on every page.
Then I created an interface IToastService and ToastService with its implementation. ToastService class contains reference to toast object, which is initialized in ToastContainer.
IToastService.cs
public interface IToastService
{
SfToast SfToast{ get; set; }
void ShowMessage(string title, string content = null);
}
ToastService.cs
public class ToastService : IToastService
{
public SfToast SfToast { get; set; }
public void ShowError(string title, string content = null)
{
SfToast.Show(new ToastModel
{
Title = title,
Content = content
});
}
}
ToastContainer.razor
#using Syncfusion.Blazor.Notifications
<SfToast #ref="#_sfToast" TimeOut="5000" >
<ToastPosition X="Right"></ToastPosition>
<ToastAnimationSettings>
<ToastAnimationSettingsShow Effect="#ShowEffect" Easing="#ShowEasing" Duration="#ShowDuration"></ToastAnimationSettingsShow>
<ToastAnimationSettingsHide Effect="#HideEffect" Easing="#HideEasing" Duration="#HideDuration"></ToastAnimationSettingsHide>
</ToastAnimationSettings>
</SfToast>
#code {
[Inject] private Client.Services.Contracts.IToastService ToastService { get; set; }
private SfToast _sfToast;
public string ShowEasing { get; set; } = "ease";
public string HideEasing { get; set; } = "ease";
public string ShowEffect { get; set; } = "SlideRightIn";
public string HideEffect { get; set; } = "SlideRightOut";
public double ShowDuration = 400;
public double HideDuration = 400;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
ToastService.SfToast = _sfToast;
}
}
}
Lastly, I registered this service as singleton in Program.cs using DI.
Program.cs
builder.Services.AddSingleton<IToastService, ToastService>();
Usage in WhatEverPage.razor
#inject IToastService toastService
#code {
protected override async Task OnInitialized() {
toastService.ShowMessage("title", "content");
}
}