Foreach Loop in Razor Component causes await to fail - c#

I'm new to Blazor and .NET 6, but I am working on a small app. I have LiteDB storing data on the backend, and my Razor component is fetching it. The problem is that, while 80% of the time it works as expected, the other 20% of the time the data gets populated as null in the Razor component before the database call has even processed. When debugging this, I see the code get to the database call in the backend, then it jumps back to the frontend Razor component which now has a null object, and then it jumps back to the backend where the database call has now been completed. The page populates from the null object though, so the data is not there. I get an error in the console, but it's just stating that the object was not set to an instance of an object at a foreach loop in my code. Here's my code:
#page "/origin"
#page "/origin/{id:int}"
#inject HttpClient http
#if (id == null)
{
<PageTitle>Origin Not Found</PageTitle>
<h3>Origin Not Found</h3>
}
else
{
<PageTitle>Origin: #origin.Name</PageTitle>
<h3>Origin: #origin.Name</h3>
<table class="table">
<tbody>
<tr>
<td>Description: </td>
<td>#origin.Description</td>
</tr>
<tr>
<td>Starting Health: </td>
<td>#origin.StartingHealth</td>
</tr>
<tr>
<td>Ground Movement Speed: </td>
<td>#origin.GroundMovementSpeed</td>
</tr>
#foreach(var stat in #essences)
{
<tr>
<td>Essence Increase Option: </td>
<td>#stat</td>
</tr>
}
<tr>
<td>Origin Benefit: </td>
<td>#origin.OriginBenefit</td>
</tr>
</tbody>
</table>
}
#code {
[Parameter]
public int id { get; set; }
private Origin origin = new();
private List<Essence> essences = new();
protected async override Task OnInitializedAsync()
{
var result = await http.GetFromJsonAsync<Origin>($"/api/origin/{id}");
if(result != null)
{
origin = result;
essences = result.EssenceIncreaseOptions;
}
}
}
Now if I remove the foreach loop (and the essence information I want there), then the code works 100% of the time. So there's something about that foreach loop that's causing it to fail 20% of the time, but I'm just not sure what. Any help would be appreciated. Thanks in advance.

In the posted code, the only point in the foreach(var stat ...) that could yield an NRE is <td>#stat</td>
So your Essence class probably has an override ToString() that causes the null de-reference.

What you can do is put a conditional check before the #foreach loop and when essences is null show some message saying loading etc, also, put StateHasChanged() after completion of fetching of data.
#if(essences.Count>0)
{
#foreach(var stat in #essences)
{
<tr>
<td>Essence Increase Option: </td>
<td>#stat</td>
</tr>
}
}
else {loading data}
and
protected async override Task OnInitializedAsync()
{
var result = await http.GetFromJsonAsync<Origin>($"/api/origin/{id}");
if(result != null)
{
origin = result;
essences = result.EssenceIncreaseOptions;
StateHasChanged();
}
}

Related

C# Blazor Server-Side CSS Element Leak Using InvokeVoidAsync

Trying to hack a transition experience when removing a row from a column. Something like Vue/React. Calling InvokeVoidAsync to append a class that will perform an animation. The javascript call also has a sleep call that will postpone the actual item from being removed on the server side. After the server removes the item, the class is applied to the item below it. Example of a table row appending the class to the next table row UI Side
I created a Blazor Server Side Application and updated the Index.razor page, added a couple js functions, and a css file.
Index.razor
#page "/"
<h2> #Pizzas.Count() Pizza's</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var pizza in Pizzas)
{
<tr id="#(pizza.ID)">
<td>#pizza.Name</td>
<td><button #onclick="#(async _ => await RemovePizza(pizza))">Remove</button></td>
</tr>
}
</tbody>
</table>
#inject IJSRuntime JSRuntime
#code {
public record Pizza(Guid ID, string Name);
public string[] PizzaTypes => new[] { "Pepperoni", "Cheese", "Hawaiian", "Veggie", "Bacon", "Southwestern BBQ", "Cheeseburger", "Buffalo", "Meat", "Supreme" };
public List<Pizza> Pizzas { get; set; } = new();
protected override void OnInitialized()
{
base.OnInitialized();
try
{
for (int i = 0; i < 10; i++)
AddPizza();
}
catch (Exception)
{
throw;
}
StateHasChanged();
}
public void AddPizza()
{
Pizzas.Add(new Pizza(Guid.NewGuid(), PizzaTypes[new Random().Next(0, PizzaTypes.Length - 1)]));
}
public async Task RemovePizza(Pizza pizza)
{
await JSRuntime.InvokeVoidAsync("animateTransition", pizza.ID, "fade-away", 1000);
Pizzas.Remove(pizza);
}
}
JS Functions
var sleep = async (ms) => new Promise(resolve => setTimeout(resolve, ms));
var animateTransition = async (id, className, duration) => {
const el = document.getElementById(id);
el.classList.add(className);
await sleep(duration);
};
CSS
#keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
tr.fade-away {
animation-name: fadeOut;
animation-duration: 1s;
}
Turns out I was missing the #key attribution within the loop. Such a novice mistake.
https://blazor-university.com/components/render-trees/optimising-using-key/
Updated Code
#foreach (var pizza in Pizzas)
{
<tr #key="pizza" id="#(pizza.ID)">
<td>#pizza.Name</td>
<td><button #onclick="#(async _ => await RemovePizza(pizza))">Remove</button></td>
</tr>
}

Component attributes do not support complex content (mixed C# and markup)

I am trying to use a Razor argument and pass it into Blazor for further processing, but I get this error message "Component attributes do not support complex content (mixed C# and markup)" on the #onclick event I am trying to build on the img tag below:
<tr>
#{
for (int j = 0; j < Candidates.Length; j++)
{
<th>
<div class="grow">
<img src="/Candidates/#(Candidates[j].ToString()).jfif" alt="#Candidates[j].ToString()" #onclick="IncrementScore(#j)" />
</div>
</th>
}
}
</tr>
Any suggestions would be greatly appreciated!
The main issue with your code because of which you've got a compiler error is the way you call the IncrementScore method. You should realize that #onclick is not an Html attribute to which you should assign a value, in this case a method that gets a value.
The #onclick attribute is a compiler directive, instructing the compiler how to form an event handler that should be invoked when the element is clicked, the target of the event, etc. In your case, you wish to call a method and pass it a value. This can only be done by using a lambada expression as follows:
#onclick="#(()=> IncrementScore(<value to pass to the method>))"
The following code snippet illustrates how to call the IncrementScore method properly when you're using a for loop or foreach loop. The distinction is very important regarding local variables that are passed to methods in loops
You can put the following code in the Index component, and run it as is:
#*#for (int j = 0; j < Candidates.Count; j++)
{
int localVariable = j;
<img src="#Candidates[j].Src" #onclick="#(()=>
IncrementScore(localVariable))" />
}*#
#foreach (var candidate in Candidates)
{
Random random = new Random();
<img src="#candidate.Src" #onclick="#(()=>
IncrementScore(random.Next(0,9)))" />
}
<p>#Scores.ToString()</p>
#code {
List<Candidate> Candidates = Enumerable.Range(1, 10).Select(i => new
Candidate { CandidateName = i }).ToList();
private int Scores;
private Task IncrementScore(int score)
{
Scores = score;
return Task.CompletedTask;
}
public class Candidate
{
public int CandidateName { get; set; }
public string Src => $"{CandidateName}.jfif";
}
}
Hope this helps...
You may also encapsulate the complex content in a code method:
<NavLink href="#GetLink()" class="button is-link">
Details
</NavLink>
#code {
[Parameter]
public string? Id { get; set; }
private string GetLink()
{
return this.Id != null ? $"/details/{this.Id}" : "#";
}
}
I came to this page in search for a solution for the same error:
Component attributes do not support complex content (mixed C# and markup)
In my case was all about the href link construction. I followed your solution on the accepted answer comment and it worked. Thanks #Øyvind Vik
Just in case it help others i'll leave my code as an example.
foreach (List<string[]> lss in searchResults)
{
var link = $"genericform/{entidadeSelecionada}/{lss.FirstOrDefault()[1]}";
<tr>
#foreach (string[] st in lss)
{
if (camposAListar.Contains(st[0].ToLower()))
{
<td>#st[1]</td>
}
}
<td>
<NavLink href="#link">
<span class="oi oi-list-rich" aria-hidden="true"></span> Utilizador
</NavLink>
</td>
</tr>
}

IAsyncEnumerable view component return in asp.net core

I have this project that is working fine:
service class:
public async Task<IAsyncEnumerable<ContratoDTO>> ContratoServiceGetAll()
{
List<ContratoDTO> listaDeContrato = new List<ContratoDTO>();
listaDeContrato = await ContratoRepository.GetAllAsync().Result.Select(u => new ContratoDTO(u)).ToList();
return listaDeContrato.ToAsyncEnumerable();
}
ViewComponent Class:
public async Task<IViewComponentResult> InvokeAsync()
{
ContratoViewModel listaContrato = new ContratoViewModel
{
Contratos = await ContratoSerivce.ContratoServiceGetAll()
};
return View(listaContrato);
}
and my component on the Shared View folder:
<tbody>
#if (Model.Contratos != null)
{
#foreach (var item in Model.Contratos.ToEnumerable())
{
<tr>
<th scope="row">#item.NuContrato</th>
<td>#item.VlContrato</td>
<td>36</td>
<td>#item.DtEmissao</td>
<td>#item.DtRetorno</td>
<td>Rio de Janeiro</td>
</tr>
}
}
</tbody>
It works but the Model.Contratos.ToEnumerable() is this the best approach ? If i take the ToEnumerable() method out it will throw an error:
'Error CS0202: foreach requires that the return type 'IAsyncEnumerator<ContratoDTO>' of 'IAsyncEnumerable<ContratoDTO>.GetEnumerator()' must have a suitable public MoveNext method and public'
ps: The ContratoViewModel has a property of IAsyncEnumerable generic type of T my DTO.
For all the years that I've been using Stack Overflow, this is my first time responding to anyone as I think it's time I should contribute to the community. I hope this is helpful.
IAsyncEnumerable<T> can be awaited on similarly like Task<T>. It doesn't need to be wrapped. A comment to your question shows that you may lose the benefit of IAsyncEnumerable<T> if you use .Result.Select(u => new ContratoDTU(u)).ToList();. I agree as .ToList() is synchronous and will block until it's completed.
While using the variables in your service class, the following is a shorthanded revision:
public async IAsyncEnumerable<ContratoDTO> ContratoServiceGetAll()
{
foreach (var u in await ContratoRepository.GetAllAsync())
yield return new ContratoDTO(u);
}
Also, the .ToEnumerable() statement in your component (assuming you meant .AsEnumerable() from extension method Enumerable.AsEnumerable<TSource>(this IEnumerable<TSource> source)) will cause the view to block at that moment to execute the Model.Contractos query (again, assuming ContractoRepository.GetAllAsync() is returning a IQueryable<T>).
It's redundant to have .ToEnumerable() as the the iteration will begin once #foreach (var item in Model.Contractos) is evaluated.

In MVC 4 How do I Add multiple Collections to a session?

I'm using MVC 4 with Razor Syntax to create a collection based on a class that was created using scaffolding (Database first based development) and I can add the first collection to the Session and return it to the Index view and display it on the page.
When I attempt to add a second collection to the Session Variable it gives me a error.
Unable to cast object of type
'System.Collections.Generic.List`1[EagleEye.Models.tblTask]' to type
'EagleEye.Models.tblTask'.
What am I doing wrong - how do I add 2 collections to the session?!
Index.cshtml (My Index view using Razor syntax)
#model List<myApp.Models.tblTask>
<table>
#{
foreach (var tblTask in Model)
{
<tr>
<td>
TaskName: #tblTask.Name
</td>
<td>
Desc: #tblTask.Description
</td>
<td>
Schedule: #tblTask.Freq #tblTask.FreqUnit
</td>
<td>
Reocurring?: #tblTask.ReocurringTask.ToString()
</td>
</tr>
}
}
</table>
Here's the "ActionResult" portion of the code from my HomeController.cs:
[HttpPost]
public ActionResult CreateTask(tblTask newTask)
{
var TaskCollection = new List<tblTask>();
if (Session["TaskCollection"] != null)
{
TaskCollection.Add((tblTask)Session["TaskCollection"]);
}
TaskCollection.Add(newTask);
Session["TaskCollection"] = TaskCollection;
return RedirectToAction("Index");
}
public ActionResult Index()
{
var TaskCollection = new List<tblTask>();
if (Session["TaskCollection"] != null)
{
TaskCollection = (List<tblTask>)Session["TaskCollection"];
}
return View(TaskCollection);
}
When I add the first entry it works fine and shows up on my index view. When I try to add the second collection of tasks, it tells me:
Unable to cast object of type
'System.Collections.Generic.List`1[EagleEye.Models.tblTask]' to type
'EagleEye.Models.tblTask'.
I've been fighting this for a few days now and have been developing for a while, but am just beginning to learn the power of asking questions when I'm stumped (instead of just continuing to beat my head against the wall until something caves in (often my head), so if my question is not well formed, please let me know.
Thanks!
Dan
Because, inside your if condition, you are casting the Session["TaskCollection"](which is a collection of tblTask to a single instance of tblTask.
This should work.
[HttpPost]
public ActionResult CreateTask(tblTask newTask)
{
var TaskCollection = new List<tblTask>();
//Check whether the collection exist in session, If yes read it
// & cast it to the tblTask collection & set it to the TaskCollection variable
if (Session["TaskCollection"] != null)
{
TaskCollection= (List<tblTask>) Session["TaskCollection"];
}
if(newTask!=null)
TaskCollection.Add(newTask);
//Set the updated collection back to the session
Session["TaskCollection"] = TaskCollection;
return RedirectToAction("Index");
}
I finally see the light -- Note the change in the HomeController.cs "TaskCollection = (List)Session["TaskCollection"]; "
[HttpPost]
public ActionResult CreateTask(tblTask newTask)
{
var TaskCollection = new List<tblTask>();
if (Session["TaskCollection"] != null)
{
//Here is the line that changed -- the following line works~
TaskCollection = (List<tblTask>)Session["TaskCollection"];
}
TaskCollection.Add(newTask);
Session["TaskCollection"] = TaskCollection;
return RedirectToAction("Index");
}

Getting a single object from SQL Server to a view

I have a an object of type Picture that contains a List Comments see source code here http://pastebin.me/21148a93065bddb7302e160f5a0ac1fb
I then want to take an Picture.ID and show the image that has that unique ID from my database
So I made a ActionResult Comment in my GalleryController http://pastebin.me/21148a93065bddb7302e160f5a0acb02 with a HttpGet
I then have this view that should display the requested image and the comments in its list http://pastebin.me/21148a93065bddb7302e160f5a0acb02
But I am not sure if its the List that is not initialized or if I am just doing it wrong but I get an error
Server Error in '/' Application.
Object reference not set to an
instance of an object.
Description: An
unhandled exception occurred during
the execution of the current web
request. Please review the stack trace
for more information about the error
and where it originated in the code.
Exception Details:
System.NullReferenceException: Object
reference not set to an instance of an
object.
Source error: http://pastebin.me/21148a93065bddb7302e160f5a0ad965
Let's examine the Action method in question. First of all, I see this and this should almost never exist:
catch
{
}
So if an exception was thrown, you're just swallowing it up and will never know about it. This would result in returning a view with no model passed in, thus the model referenced in your strongly-typed view is null. This seems like a very good possibility of what's happening.
Also, why do you keep overwriting ViewBag.Picture inside your foreach loop? Is that really what you intended to do?
Hopefully this will get you on the right path and you'll quickly get to the underlying issue.
as I cant get it to only parse a single object (And I know there will always be max one result as it searches in the database on the primary key)
So my code is now
[HttpGet]
public ActionResult Comment(string ID)
{
int id = Convert.ToInt32(ID);
if (ID != null)
{
try
{
var model = from r in _db.Gallery
where r.ID == id
select r;
return View(model);
}
catch
{
}
}
return View();
}
And
#model IEnumerable<firstweb4.Models.Picture>
#{
ViewBag.Title = "Comment";
}
#if (Model != null)
{
foreach (var item in Model)
{
<img src="#item.Path" alt="#item.Title" width="250px" />
<table>
<tr>
<th>
Posted by
</th>
<th>
Comment
</th>
<th>
Posted on
</th>
</tr>
#foreach (var comment in item.PictureComments)
{
<tr>
<td>
#comment.Auther
</td>
<td>
#comment.Text
</td>
<td>
#comment.PostedTime
</td>
</tr>
}
</table>
}
}
So I itterate through a the singleitem collection I parse and it now works.

Categories