Blazor cascading component preventing StateHasChanged - c#

So I'm new to Blazor and I've just found out about CascadingValue and their possible use for showing error messages. I am creating an Outlook Add In for saving mails and attachments in Sharepoint, so I'm dynamically retrieving the current Outlook mail using Office-js and JSInterop, such that my Add In always uses the selected mail.
But now that I'm trying to implement a cascading component for error messages I've run into problems with StateHasChanged.
Following code is my cascading error component:
<CascadingValue Value=this>
#ChildContent
</CascadingValue>
<div class="error-container #(ErrorMessage == null ? "hidden" : "")">
<span class="error-message">
#ErrorMessage
</span>
<a class="dismiss-error" #onclick="DismissError">x</a>
</div>
#code {
[Parameter]
public RenderFragment ChildContent { get; set; }
public string ErrorMessage { get; set; }
public void ProcessError(Exception ex)
{
ErrorMessage = ex.Message;
StateHasChanged();
}
public void DismissError()
{
ErrorMessage = null;
StateHasChanged();
}
}
And the component I'm having troubles with (atleast the important parts):
<div>
{Showing details from OutlookMail here}
</div>
#code {
[CascadingParameter] public Error Error { get; set; }
public OutlookMail OutlookMail { get; set; }
private static Action<OutlookMail> _receiveMailAction;
protected override async Task OnInitializedAsync()
{
_receiveMailAction = UpdateMail;
await base.OnInitializedAsync();
}
private void UpdateMail(OutlookMail mail)
{
InvokeAsync(() =>
{
OutlookMail = mail;
StateHasChanged();
});
}
[JSInvokable]
public static void ReceiveMail(OutlookMail mail)
{
_receiveMailAction?.Invoke(mail);
}
public async Task MethodWithError()
{
try
{
await DoStuffWithPossibleError();
}
catch (Exception e)
{
Error.ProcessError(e);
}
}
}
Everything works fine until Error.ProcessError gets called and the error message is shown, after that StateHasChanged stops rerendering my component which gives inconsistent data to the users.
I am probably doing something wrong, since I can't really find anyone else with the same problem, but I'm not seeing it, so I'm hoping someone on here has a better understanding of where things are going wrong, or maybe even a better way of going about showing error messages if the problem is with using CascadingValue like this.
All the help is appreciated.
Edit 1: After some more looking into this, it seems that it's actually not updating OutlookMail, even when I move it outside of InvokeAsync. So there might be something weird happening with instances of my component

After my first edit, I found out it was indeed a problem with instances, since I'm overwriting a static Action within OnInitialized, each time it creates a new instance, the old instances no longer works. And the code which returned the error happened to have a auth dialog with a redirect callback to the same page, it closes directly after, but loading the page actually creates a new instance of my component, which in turn invalidates the one my user works with. So I'll probably just have to create a singleton service that handles the incoming mail data, so that my component can always access the correct data.

Related

Blazor templating component with inheritance

I have a base component PetTemplate and a second PetDog that inherits and uses the template of PetTemplate. PetTemplate has a method named ToggleDisplay. My goal is when I click the button on the Index page that invokes the PetDog.ToggleDisplay method and show/hide the PetDog details on the page.
The "Inside" button in the sample code below works but "Outside" button don't. How can I invoke the ToggleDisplay method from a page or a parent component correctly?
Index.razor
#page "/"
<button #onclick="ShowPetDetails">Show Details (Outside)</button>
<PetDog #ref="dog" />
#code {
PetDog dog;
void ShowPetDetails()
{
dog.ToggleDisplay();
}
}
PetDog.razor
#inherits PetTemplate
<PetTemplate Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
PetTemplate.razor
<div class="mt-3">
<button #onclick="ToggleDisplay">Show Details (Inside)</button>
<h3>Pet Name: #Name</h3>
<div style="display:#display">
#ChildContent
</div>
</div>
#code {
string display = "none";
[Parameter]
public string Name { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
public void ToggleDisplay()
{
display = display == "none" ? "block" : "none";
StateHasChanged();
}
}
When you use
<PetDog #ref="dog" />
#code {
PetDog dog;
void ShowPetDetails()
{
dog.ToggleDisplay();
}
}
You actually create a reference to the PetDog component, and then try to call a derived method, dog.ToggleDisplay(), on object you have no reference to ( the instance of the PetTemplate). In order to make it work, you'll have to get a reference to the parent component (PetTemplate), and provide it to the derived component (PetDog), like this:
PetTemplate.razor
<div class="mt-3">
<button #onclick="ToggleDisplay">Show Details (Inside)</button>
<h3>Pet Name: #Name</h3>
<div style="display:#display">
#ChildContent
</div>
</div>
#code {
string display = "none";
string val;
[Parameter]
public string Name { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
public void ToggleDisplay()
{
display = display == "none" ? "block" : "none";
InvokeAsync(() => StateHasChanged());
}
}
PetDog.razor
#inherits PetTemplate
<PetTemplate #ref="petTemplate" Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
#code
{
PetTemplate petTemplate;
public PetTemplate PetTemplateProp { get; set; }
protected override void OnAfterRender(bool firstRender)
{
if(firstRender)
{
PetTemplateProp = petTemplate;
}
base.OnAfterRender(firstRender);
}
}
Index.razor
#page "/"
<button #onclick="ShowPetDetails">Show Details (Outside)</button>
<PetDog #ref="dog" />
#code {
PetDog dog;
void ShowPetDetails()
{
dog.PetTemplateProp.ToggleDisplay();
}
}
Note: Though Razor components are C# classes, you cannot treat them as normal classes. They behave differently. As for instance, you can't define a variable instance, and set its parameters, etc. outside of the component. At best, you can capture a reference to a component as well as call public methods on the component instance, as is done in the current sample. In short, component objects differ from normal classes.
It's also important to remember that each component is a separate island that can render independently of its parents and children.
But just wondering how can I change a component parameter value from outside of it, that inherited/uses a template. I tried the methods in the documentation or the resources I found, but it didn't work for my case
You should not (it was a warning) and probably cannot ( it may be now an error) change a component parameter's value outside of the component. As for instance, you can't capture a reference to a component and assign a value to its parameter property:
<PetTemplate #ref="petTemplate">
<div>Someone's best friend!</div>
</PetTemplate>
PetTemplate petTemplate;
This is not allowed: petTemplate.Name="Dog" as this is changing the parameter outside of its component. You can only do that like this:
<PetTemplate Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
Furthermore, modification of a parameter property from within the component itself is deprecated ( currently you should get a warning, at least that is what Steve Sanderson suggested to the Blazor team).
To make it clear, you should not modify the parameter property Name from within the PetTemplate component. A parameter property should be automatic property; that is, having a get and set accessors like this: [Parameter] public string Name { get; set; }
And you should not use it like this:
private string name;
[Parameter]
public string Name
{
get => name;
set
{
if (name != value)
{
name = value;
// Code to a method or whatever to do something
}
}
}
This is deprecated as it may have side effects. Component parameters should be treated as DTO, and should not be modified. If you wish to perform some manipulation of the parameter value, then copy it to a local variable, and do your thing.
As pointed out by #enet Blazor component inheritance doesn't behave exactly as one would intuitively expect. This is a cleaner approach when you want to control a UI functionality that can be controlled both internally and externally:
Declare an event in the base component that is raised when the UI state is changed from within the component. Also let the variable that controls the state be a parameter. In you case, something like
PetTemplate.razor:
[Parameter]
public EventCallback OnToggleRequested {get;set;}
[Parameter]
public string Display {get;set;}
protected async Task RaiseToggle()
{
await OnToggleRequested.InvokeAsync();
}
In your PetDog, simple call the toggle method when inside click is raised
PetDog.razor:
<button #onclick="RaiseToggle">Show Details (Inside)</button>
In your container (in this case, index.razor) listen to the event and make changes. Also wire the outside button to the same method:
Index.razor:
<button #onclick="ToggleDisplay">Show Details (Outside)</button>
<PetDog OnToggleRequested="ToggleDisplay" Display="#display"/>
string display = "block";
void ToggleDisplay()
{
display = display == "none" ? "block" : "none";
}
Note that the event can be used at level of hierarchy and you don't need to capture any references anywhere.

Blazor #onclick event to close a single item inside a loop

I'm using a Razorcomponent with a Blazor server app. The app polls for alertmessages on the server.
The server might send back several messages, which I loop over.
The class on the div has a "show" and "hidden" and that takes care of hiding elements.
The problem I have is that I want to be able to close each alertmessage and not all- which happens as described in the below simplified code:
--snip
#if(alert.valid == true){
#foreach(var alert in alerts){
#if(alert.type == "alert")
<div id="alertmessage" class="#show">
<button type="button" #onclick="#show">Hide this element</button>
</div>
}
}
#code{
private string value { get; set;} = "show";
private void Show() {
value = "hidden";
}
}
As per the above example, if there are several alerts, the method Show() will close all the boxes, and it produces x count of <div id="alertmessage" I get this, but is there a way to grab that specific element like alert.id or something? Appreciate all feedback.
Thanks.
In blazor you work everytime with objects , you should do a class for the alert and change its attribute on the for each.
The page has to contain a list of alert objects as attribute.
More less this:
#if(alert.valid == true){
#foreach(var alert in alerts){
<div id="alertmessage" class="#show">
<button type="button" hidden="#alert.hidden" #onclick="()=>show(alert)">Hide this element</button>
</div>
}
}
#code{
private string value { get; set;} = "show";
private List<Alert> alerts = new();
private void Show(Alert alert) {
alert.hidden = true;
alert.message= "whatever"
}
public class Alert{
public String message = "whatever"
public bool hidden = false;
//other stuff
}
}
If you want to separate the logic from the presentation you can declare alert logic in its own class -file .

How does Blazor handle sharing data among multiple components and nested components?

I have a Blazor app with many components, and nested components that I would like to share data among, but I cannot figure out how to do this efficiently. Here is, sort of, what I have:
MyProject.Pages.Index.razor:
#page "/"
<div>
#if(some_state) {
<ComponentAlpha #bind-something=Something />
else if(some_other_state){
<ComponentBeta Something="Something" />
} else {
<ComponentGamma Something="Something" />
}
</div>
#code {
String Something { get; set; }
}
MyProject.Shared.ComponentAlpha.razor:
<div>
Stuff here ...
</div>
#code {
[Parameter]
public String something { get; set; }
[Parameter]
public EventCallback<String> somethingChanged { get; set; }
private async Task MyTask() {
await somethingChanged.InvokeAsync(something);
}
}
This all works fantastic for getting data from ComponentAlpha.razor back to Index.razor, and from Index.razor to ComponentBeta.razor and ComponentGamma.razor. My question comes in for beyond ComponentBeta.razor and ComponentGamma.razor.
MyProject.Shared.ComponentBeta.razor:
<div>
Stuff here ...
<ComponentDelta />
<ComponentEpsilon />
</div>
#code {
[Parameter]
public String Something { get; set; }
}
MyProject.Shared.ComponentGamma.razor:
<div>
Stuff here ...
<ComponentZeta />
<ComponentEta />
</div>
#code {
[Parameter]
public String Something { get; set; }
}
MyProject.Shared.ComponentDelta.razor:
MyProject.Shared.ComponentEpsilon.razor:
MyProject.Shared.ComponentZeta.razor:
MyProject.Shared.ComponentEta.razor:
<div>
Stuff here ...
<MoreAndMoreComponents />
</div>
#code {
// I want to use variable "something" here as well.
}
In order to be able to share the string something amongst all my components and embedded components, do I need to jump through all the elaborate hoops that I did for just Index.razor, ComponentAlpha.razor, and ComponentBeta.razor or is there some better way?
I saw THIS out there and thought option 3. State Container would be my best bet, However, when I follow their example, I always end up with this Exception:
Error CS0119 'AppState' is a type, which is not valid in the given context
So, what is the way we are supposed to use to efficiently share data amongst all components and nested components?
You should consider using a service, probably Scoped. You then inject the service into each component, you can use an Interface and/or base abstract class to boilerplate the code. You can also use this same service for events - signalling that data has changed.
See the MS Docs here on services and how to use/provision them.
One option you can consider is using cascading parameters, which will allow a top level component to pass itself down to any child components regardless of component tree depth. It's very easy to set up.
In your top level component:
<CascadingValue Value="this">
#*Note Component Alpha placement*#
<ComponentAlpha />
</CascadingValue>
#code {
private string something = "initial value";
// this is a getter only for the property, but you can use
// a setter for 2 way binding also. Just make sure
// to modify the setter to run 'StateHasChanged()'
// after updating the value, and then
// you can then set the property directly
public string Something => something;
// This will allow child components to set the new value
public Task SetNewValue(string value)
{
something = value;
StateHasChanged();
return Task.CompletedTask;
}
}
Note that the CascadingValue is passing this down the tree to it's children, so you can capture it where needed.
In the mid level (alpha) component
<div>
<div #onclick="SetNewValue">#ValueFromTopLevel</div>
#*Note Component Beta placement*#
<ComponentBeta />
</div>
#code {
// Capture the top level component here
[CascadingParameter]
public Component TopLevelComponent { get; set; }
// pull out the value you need here
public string ValueFromTopLevel => TopLevelComponent.Something;
// use this to set a new value
void SetNewValue()
{
TopLevelComponent.SetNewValue("Hello from Alpha component!");
}
}
Notice the beta component nested inside the alpha component.
In the low level (beta for my example) component
<div>
<div #onclick="SetNewValue">#ValueFromTopLevel</div>
</div>
#code {
// still capture the top level component
[CascadingParameter]
public Component TopLevelComponent { get; set; }
// use the value the same as Alpha
public string ValueFromTopLevel => TopLevelComponent.Something;
// set the value the same as Alpha
void SetNewValue()
{
TopLevelComponent.SetNewValue("Hello from Beta component!");
}
}
With this setup, by clicking on the text of either the Alpha or the Beta component, the value of something is updated at the top level and then the new value is cascaded down again with a fresh render. All the state management is at the top and the child components are just hooking in where needed.This setup will allow you to use methods and properties from a top level component as you see fit. For example I created a top level Toast Notification component once that wraps the whole app and can be captured and used anywhere. Simply pass it a message and a type (info, error, warning) and it displays the flash message for a few seconds and then hides again.
There are some downsides though, notably that your top level component needs to be responsible for all state management. This works for simpler scenarios, but as the app and the interactions become more complex this can get out of hand. However, if you combine this with Enet's answer and get the state management happening at the top, you can then hook in where needed down the component tree.
Also note that any child components become coupled to the top level parent as they will require being up the tree somewhere to work properly. Maybe Ok for you, maybe not. Either way, it's a good tool to be aware of.
Official docs for cascading parameters can be found here
It depends how permanent you need your info to be, and to what degree you're willing to hold global variables on the parent.
1. One trick is to pass the parent control to the children-- give them full access to its variables:
ParentControl
<CascadingValue Value="this">
<ChildControl />
</CascadingValue>
ChildControl
[Parameter]
ParentControl Parent;
#code {
Parent.SomeVariable = Something;
}
2. If you don't want to do that, then you can pass data to its parent using an EventCallback<T>
ChildControl
#code
{
[Parameter]
EventCallBack<MyCustomClass> OnDataReady { get; set; }
MyCustomClass ActiveObject { get; set; }
void DoSomething()
{
OnDataReady.InvokeAsync(ActiveObject);
}
}
And on ParentControl:
<ChildControl OnDataReady=HandleData />
#code {
async Task HandleData (MyCustomClass data){
// Do stuff
}
}
3. If you REALLY want highly persistent data, then consider saving state in a database. Since Blazor requires no postbacks, there's really no penalty for saving or loading information from a database whenever you want.
4. Using a service as per #enet's answer

Render Blazor child component with parameters

I'm new to Blazor and I'm currently working on a Blazor Webassembly .net 5.0 application.
I try to figure out the correct way to
render a child component
from a parent component
on button click (form submit)
pass parameters from the parent component to => the child component
My current solution seems to work, but unfortunately it ends in an infinite rendering loop: I use the OnParametersSetAsync method in the child component to handle the data loading.
Side note: I use Telerik Blazor components, but it should have no impact.
My parent component looks like this:
View (parent)
// I want to submit a form to set a bool = true, and then to rend the child component - is that ok?
<EditForm OnValidSubmit="#(async () => await StartEverything())">
<label for="OrderNumber">OrderNumber: </label>
<TelerikTextBox #bind-Value="OrderNumber" Id="OrderNumber" />
<TelerikButton ButtonType="#ButtonType.Submit">Start Everything</TelerikButton>
</EditForm>
#if (ShowChild)
{
<MyChildComponent OrderNumber="OrderNumber"/>
}
else
{
<div>Please enter an order number.</div>
}
Code Behind (parent)
public class MyParentComponent : ComponentBase {
protected int OrderNumber { get; set; }
protected bool ShowChild { get; set; }
protected async Task StartEverything()
{
if (OrderNumber > 0)
{
await Task.FromResult(ShowChild = true);
}
}
}
My child component looks like this:
View (child)
#if (Customer != null)
{
<p>#Customer.CustomerName</p>
<p>#Customer.AgencyName</p>
}
Code Behind (child)
public class MyChildComponent : ComponentBase {
// I need this Parameter sent from my parent component
[Parameter]
public int OrderNumber { get; set; }
protected CustomerViewModel Customer { get; set; }
protected override async Task OnParametersSetAsync()
{
var parameterForQuery = OrderNumber; // this should hold the value sent from the parent component
// Load Customer ViewModel Data here - is this the correct event? What is the best approach?
}
}
Item ViewModel
public class CustomerViewModel
{
public string CustomerName { get; set; }
public string AgencyName { get; set; }
}
Do you know how to correctly render a Child Component within a Parent Component and pass parameters from the Parent Component to the child component - then render the child component ONLY ON BUTTON CLICK (form submit, no infinite render loop)?
Do you know how to solve this problem?
I recommend going through https://blazor-university.com. It's the site that kind of got me kick-started when I first started with Blazor.
In regard to your question, I recommend the following:
https://blazor-university.com/components/component-lifecycles/
In particular, the following statement should prove useful in your case (from that link):
OnInitialized / OnInitializedAsync
This method is only executed once when the component is first created.
If the parent changes the component’s parameters at a later time, this
method is skipped.
It seems likely that simply changing which method you override will solve your problem, since OnParametersSetAsync behaves as you've described, and 'OnInitializedAsync' behaves as you want. :D

Interpret Data produced by View Component ASP Razor MVC

I am using a View Component to create a portion of an MVC Page. The View component goes to a database, and produces a table of results if the query produces results. The query producing results means there's a problem, so I've set up the view component to only display if there were results. This query is meant to be re-usable in different parts of the site, too, because its results can impact multiple pages.
Works great for showing the error only when it's an issue!
However, the main page has a form handler for sending an E-Mail message, and I want to disable that capability when the data is invalid. I use ViewData.ModelState.IsValid to check whether the model is valid or not. However, because my Component is indicating whether or not the data is valid, I've run into an issue!
I have no idea how to check whether the component is populated or not, without somehow hardcoding it back to the original page. I can't seem to pass view data from the Component to the calling page (although the other direction works great), I can't even subscribe the page to an event because I see no way to associate the Component instance with the page!
I don't need to check the contents of the component before invalidating the page, I just need to know whether or not the component produced anything at all.
Can anybody help me do this?
Here is the code for the Component:
namespace Reports.Shared.Validation
{
public class IdentifierValidationViewComponent : ViewComponent
{
private readonly IdentifierValidationDB _IdentifierValidationContext;
public IdentifierValidationViewComponent(IdentifierValidationDB IdentifierValidationContext)
{
_IdentifierValidationContext = IdentifierValidationContext;
}
public List<IdentifierValidation> InvalidIdentifiers { get; set; }
public async Task<IViewComponentResult> InvokeAsync(string date)
{
InvalidIdentifiers = await _IdentifierValidationContext.IdentifierValidations.FromSqlRaw("EXEC Reports.IdentifierValidation {0}", date).ToListAsync();
return View(InvalidIdentifiers);
}
}
}
Here is the Partial View that the Component is rendering:
#model List<Reports.Models.Shared.Validation.IdentifierValidation>
#if (Model.Count() > 0)
{
<div id="InvalidIdentifiers" class="alert-danger">
<h2>Values are missing Identifiers!</h2>
<ul>
#foreach (var invalid in Model)
{
<li>
#invalid.SecName has no Identifier.
</li>
}
</ul>
</div>
}
Finally, I invoke the component on the main page with this line
#await Component.InvokeAsync("IdentifierValidation", new { date = Model.Date })
All I want to do is have the main page check if the component is actually producing any HTML or not, and then invalidate ViewData.ModelState.IsValid if so.
I figured it out!
In my case, because I only need to check during the initial load of the page whether or not the table has produced results, and because the status doesn't need to be retained between refreshes, I can use ViewContext.HttpContext.Items[] to store a flag indicating whether or not the table has been populated. I needed to make changes to the Component and the main page, but with the following changes, it seems to work!
Controller:
namespace Reports.Shared.Validation
{
public class IdentifierValidationViewComponent : ViewComponent
{
private readonly IdentifierValidationDB _IdentifierValidationContext;
public IdentifierValidationViewComponent(IdentifierValidationDB IdentifierValidationContext)
{
_IdentifierValidationContext = IdentifierValidationContext;
}
public List<IdentifierValidation> InvalidIdentifiers { get; set; }
public async Task<IViewComponentResult> InvokeAsync(string date)
{
InvalidIdentifiers = await _IdentifierValidationContext.IdentifierValidations.FromSqlRaw("EXEC Reports.IdentifierValidation {0}", date).ToListAsync();
if(InvalidIdentifiers.Count() > 0)
{
ViewContext.HttpContext.Items["InvalidIdentifier"] = "Detected";
}
return View(InvalidIdentifiers);
}
}
}
And now, we can invoke the controller as such:
#await Component.InvokeAsync("IdentifierValidation", new { date = Model.Date })
#if (ViewContext.HttpContext.Items["InvalidIdentifier"] != null)
{
ViewData.ModelState.AddModelError("InvalidIdentifier", "An Invalid Identifier has been Detected!");
}
When we do this, we are invalidating the model state only if the partial produced results when it was loaded on page refresh!
Of course, if you're doing something fancier with it, like using javascript/ajax to refresh the content of the partial on the client side, this will not work.

Categories