Get quote from API not working on page load Blazor - c#

I want to get a Random Quote from an API I'm using, however it's not displaying the quote when the page loads, only after pressing a button, the function isnt binded to anything but works properly when i click the button for some reason. I tried to execute the method which loads the quote from an endpoint properly, but does not enter in the if(quote != null) only when i click the button.
`
#page "/"
#inject HttpClient Client
<PageTitle>Index</PageTitle>
<body>
<h3 class="title">Breaking bad</h3>
<div class="div1" >
<input type="text" class="accordion-button" placeholder="Personaje" #bind="nombre" />
<input type="button" #onclick="GetCharacter" class="button" />
</div>
#if (aratzfag == false){
#GetRandomQuote();
#if(quote != null){
<p>#quote[0].quote</p>
}
}
<div class="div2" style="display:#display">
#if (character != null)
{
<div class="card-body">
<br>
<Character name=#character[0].name/>
<Character nickname=#character[0].nickname />
#for (int i = 0; character[0].occupation.Length > i; i++){
string a = character[0].occupation[i];
<span>#a</span>
<br/>
}
<br />
#if(character[0].name == "Saul Goodman"){
<img src="https://media.tenor.com/pMhSj9NfCXsAAAAd/saul-goodman-better-call-saul.gif" width="250px" height="250px" />
}else if (character[0].name == "Walter White")
{
<img src="https://c.tenor.com/0VT7jilszwEAAAAC/mr-white-heisenberg.gif" width="250px" height="250px" />
}
else{
<img src="#character[0].img" width="250px" height="250px" />
}
</div>
}else if(character == null){
<div class="card-body">
<h3>El personaje introducido no existe, introduce bien el nombre</h3>
</div>
}
</div>
</body>
#code {
string nombre = "";
Class1[] character;
Class2[] quote;
string display = "none";
bool aratzfag = false;
async Task GetCharacter(){
// var task = await Client.GetFromJsonAsync<Class1>(Endpoints.GetCharacter(nombre));
// var jsonString = await task.Content.ReadAsStringAsync();
// Class1[] result = System.Text.Json.JsonSerializer.Deserialize<Class1[]>(task);
// Rootobject characterData = await Client.GetFromJsonAsync<Rootobject>(Endpoints.GetCharacter(nombre));
// characters = System.Text.Json.JsonSerializer.Deserialize<Class1>(Endpoints.GetCharacter(nombre));
// var a = System.Text.Json.JsonSerializer.Deserialize<List<Class1[]>>(Endpoints.GetCharacter(nombre));
character = await Client.GetFromJsonAsync<Class1[]>(Endpoints.GetCharacter(nombre));
if (character.Length == 0){
character = null;
}
display = "flex";
//aa = System.Text.Json.JsonSerializer.Deserialize<Class1>(characters);
// var personaje = Endpoints.GetCharacter(nombre);
// characters = await Client.GetFromJsonAsync<Rootobject>(Endpoints.GetCharacter(characters1.Property1[0].name));
}
async Task GetRandomQuote()
{
quote = await Client.GetFromJsonAsync<Class2[]>(Endpoints.GetRandomQuote());
}
}
`
I also tried
`
protected override async Task OnAfterRenderAsync(bool firstRender)
{
quote = await Client.GetFromJsonAsync<Class2[]>(Endpoints.GetRandomQuote());
}
`
protected override async Task OnInitializedAsync()
but won't enter in the if statement and show the quote, only when i interact the button. I made sure to debug it and gets the quote from the api but won write it on the page.
Page loaded
After I click the button on the right of the input box

You want the random quote to be shown when opening the page and not change the quote everytime you rerender something, use OnInitializedAsync(). Your first piece of code shows you calling GetRandomQuote (which is async Task) in the UI part without await, this should be called with await in the #code section.
protected override async Task OnInitializedAsync()
{
await GetRandomQuote();
}

Related

Blazor - object displayed in a table not updating after changing a value

I am developing a Server-Side Blazor app in which a user inputs excel files with data. The app goes over each excel file, checks if it is valid and creates output files (based on excel file content) for user to download.
There is a table that displays IEnumerable<IExcelFile>, a list of objects with following parameters: FileName, FileType, Package (not for display). I would like to call a method that iterates over each IExcelFile and take some actions for each excel (check if valid, process). When the method is running I would like FileType of each IExcelFile to indicate, on which step of the process a particular file is. e.g. When method starts all IExcelFiles have a "Queued" FileType, when one of IExcelFiles is processed it's FileType changes to "Processing", when an exception is thrown for one excel file it's FileType changes to "Error" and so on.
I am able to change those parameters just before and after running this method but not when the method is running. Below is the code:
Table:
<table class="table table-hover">
<thead>
<tr>
<th>FileName</th>
<th>FileType</th>
<th style="width: 10px;"></th>
</tr>
</thead>
<tbody>
#if (excelFiles != null && excelFiles.Count() > 0)
{
foreach (var excelFile in excelFiles)
{
<tr>
<td>#excelFile.FileName</td>
<td>#excelFile.FileType.ToString()</td>
<td>
#if (!IsLoading)
{
<button class="btn btn-close" #onclick="() => DeleteExcelFile(excelFile)"></button>
}
</td>
</tr>
}
}
else
{
<tr>
<td colspan="3">
No files provided
</td>
</tr>
}
</tbody>
</table>
Processing excel file starts when the user clicks a "Start" button:
#if (!IsLoading)
{
<div class="mb-3">
<label for="formFileMultiple" class="form-label">Select excel files from your drive</label>
#for (int i = 0; i < numberOfInputFiles; i++)
{
<InputFile #key="i" OnChange="UploadExcelFiles" multiple style="#GetInputFileStyle(i)" class="form-control" type="file" id="formFileMultiple"></InputFile>
}
</div>
#if (IsDownloadAvailable)
{
<button type="button" class="btn btn-success" #onclick="DownloadDubCards">Download</button>
<button type="button" class="btn btn-primary" #onclick="ClearExcelFileList">Clear</button>
}
else
{
<button type="button" class="btn btn-primary" #onclick="GenerateDubCards">Start</button>
<button type="button" class="btn btn-primary" #onclick="ClearExcelFileList">Clear</button>
}
}
else
{
<div class="spinner-border text-primary" role="status" style="left: 50%; position: absolute; width: 3rem; height: 3rem;">
<span class="visually-hidden">Loading...</span>
</div>
}
And the method that is processing the excel files:
private void GenerateDubCards()
{
foreach (var excelFile in excelFiles)
{
excelFile.FileType = IExcelFile.Type.Queued;
}
foreach (var excelFile in excelFiles)
{
try
{
excelFile.FileType = IExcelFile.Type.Processing;
List<IDubCardSet> tempDubCardSets = dubCardGenerator.CalculateDubCardSetsFromExcelFile(excelFile);
dubCardSets = dubCardGenerator.AddDubCardSets(tempDubCardSets);
foreach (var dubCardSet in dubCardSets.Where(dcs => dcs.DubCards.Count == 0))
{
dubCardGenerator.CreateDubCards(dubCardSet);
}
excelFile.FileType = IExcelFile.Type.Completed;
}
catch (Exception ex)
{
excelFile.FileType = IExcelFile.Type.Error;
AppLogger.GetInstance().Info(ex.Message);
Modal.Open("Something went wrong!", ex.Message);
}
}
IsDownloadAvailable = true;
}
So basically after this method runs I just see all excel files have a FileType "Complete", but when the method runs I see no other values in between (even when debugging step by step).
Additional context that I hope is irrelevant for this issue:
This is a child component but no parameters are passed from the parent to child nor are any parameters from child shared with or dependent on parent.
This components has other methods that use StateHasChanged() like deleting or adding list elements that work properly.
I tried changing method from private void to async Task, call StateHasChanged() anywhere but with no success. I tried pretty much any related solutions on Stack Overflow, but it didn't work. Any help would be much appreciated, Thanks.
I've simplified your code into a single page which I believe demonstrates what you're trying to achieve. It demonstrates the principles of using async coding and using StateHasChanged to drive re-renders. The key bit is that the backend process that gets the data has to be a yielding async process for this to work properly. The Renderer needs thread time to update the UI. Blocking processes stop that happening.
#page "/"
<h3>Async File Processing</h3>
<div class=m-2>
<button class="btn btn-primary" #onclick=GenerateDubCards>Process</button>
</div>
#foreach(var file in excelFiles)
{
<div class="m-2 p-2 #GetCss(file)">#file.Name : #file.State</div>
}
#code {
private List<ExcelFile> excelFiles = new() { new ExcelFile { Name="UK"}, new ExcelFile { Name="Spain"}, new ExcelFile { Name="Portugal"}, new ExcelFile { Name="Australia"}};
private string GetCss(ExcelFile file)
=> file.State switch
{
FileState.Complete => "bg-success text-white",
FileState.Processing => "bg-warning text-white",
FileState.Error => "bg-danger text-white",
_ => "bg-primary text-white"
};
private async Task GenerateDubCards()
{
List<Task> tasks = new List<Task>();
foreach(var file in excelFiles)
{
tasks.Add(ProcessAFile(file));
}
await Task.WhenAll(tasks.ToArray());
}
private async Task ProcessAFile(ExcelFile file)
{
file.State = FileState.Processing;
StateHasChanged();
// emulate the file work with a variable length Task Delay
await Task.Delay(Random.Shared.Next(5000));
file.State = FileState.Complete;
if (file.Name.StartsWith("S"))
file.State = FileState.Error;
StateHasChanged();
}
public class ExcelFile
{
public string Name { get; set; } = string.Empty;
public FileState State { get; set; } = FileState.None;
}
public enum FileState
{
None,
Processing,
Complete,
Error
}
}

How to fix this simple ElementNotFoundException in Blazor?

I am unit testing a blazor app. I get a ElementNotFoundException. I think the cause for this is an if statement in the the index.razor page. see code below:
<div class="row">
<div class="col-12">
#if ((challenges != null) && (challenges.Count > 0))
{
<MultiStepComponent Id="MultiStepContainer" Challenges="#challenges">
<div class="row p-3">
<div class="col-6" id="challengeContainer">
#foreach(var c in challenges)
{
<MultiStepNavigation Name="#c.Title">
<h1>#c.Title</h1>
<img class="float-left" src="#c.ImagePath" width="200" />
#foreach(var sentence in c.Description)
{
<p>#sentence</p>
}
</MultiStepNavigation>
}
</div>
<div class="col-6">
<textarea rows="26" cols="120" #bind="input" id="input"></textarea>
<button class="btn" id="runBtn" #onclick="RunAsync">Run</button>
<br />
<textarea rows="10" cols="120" id="output" readonly>#((MarkupString)Output)</textarea>
</div>
</div>
</MultiStepComponent>
}
</div>
</div>
The code behind of this page (index.razor.cs) has the following initialization code:
protected override async Task OnInitializedAsync()
{
jsonRepository = new JSONChallengeRepository();
challenges = await jsonRepository.GetChallengesAsync();
}
The test for this page is here:
[Test]
public async Task Compile_code_Success()
{
_codingChallengeService.Setup(c => c.SendInputToCompilerAsync("50+50")).ReturnsAsync("100");
_testContext.Services.AddScoped(x => _codingChallengeService.Object);
var razorComponent = _testContext.RenderComponent<Index>();
razorComponent.Instance.challenges = GetChallenges();
if ((razorComponent.Instance.challenges != null) && (razorComponent.Instance.challenges.Count > 0))
{
var runBtn = razorComponent.FindAll("button").FirstOrDefault(b => b.OuterHtml.Contains("Run"));
var input = razorComponent.Find("#input");
input.Change("50+50");
runBtn.Click();
var outputArea = razorComponent.Find("#output");
var outputAreaText = outputArea.TextContent;
Assert.AreEqual("100", outputAreaText);
}
Assert.IsNotNull(razorComponent.Instance.challenges);
}
The #input is missing..Why??
Thanks in advance!
I am guessing the problem is that you do not cause the component under test to re-render when you assign razorComponent.Instance.challenges property/field, and if the component does not re-render, then the markup inside #if ((challenges != null) && (challenges.Count > 0)) block in the component is not displayed.
In general, dont mutate properties (parameters) of components through the razorComponent.Instance. If you really have to do so, make sure to trigger a render after.
Instead, pass parameters to the component through the RenderComponent or SetParametersAndRender methods, or through services injected into components. That will cause the component to go through its normal render life-cycle methods.

Blazor doesn't re-render on class change

<div>
<div>
<div class="#(Base64Images.Count == 0 ? "block" : "hidden")">
<label for="file-upload">
<span>Upload a file</span>
<InputFile OnChange="HandleChange" id="file-upload" name="file-upload" class="sr-only" />
</label>
</div>
<div class="#(Base64Images.Count > 0 ? "block" : "hidden")">
#foreach(var image in Base64Images)
{
<img src="#image" />
}
</div>
</div>
</div>
#code {
public IReadOnlyList<IBrowserFile> BrowserFiles { get; protected set; } = new List<IBrowserFile>();
private List<string> Base64Images { get; set; } = new List<string>();
private async Task<bool> HandleChange(InputFileChangeEventArgs e)
{
IReadOnlyList<IBrowserFile> fileList;
BrowserFiles = new List<IBrowserFile> { e.File };
await BrowserFilesToBase64Images();
return true;
}
private async Task<bool> BrowserFilesToBase64Images()
{
foreach(var image in BrowserFiles)
{
if(image != null)
{
var format = "image/png";
var buffer = new byte[image.Size];
await image.OpenReadStream().ReadAsync(buffer);
Base64Images.Add($"data:{format};base64,{Convert.ToBase64String(buffer)}");
}
}
return true;
}
}
So I have this code, it's pretty simple. I want to display a preview of what the use uploads, but the preview must only be displayed after the file was selected. Likewise, I want to hide the input (but not remove it from the DOM) when there is an image loaded. But no matter what I do, Blazor won't re-render.
Base64Images.Count
Changes and I have been able to debug it. The conditions should be hit, but the HTML won't change. Is there any way to tell Blazor to re-render?
I know of StateHasChanged(), but not only that one is supposedly called in after every event, but even calling it multiple times doesn't force the re-render.
You'll have to explain what you want to happen. You have Lists, but when you handle the FileInput's OnChange, you're only getting one File (maybe).
If you want multiple files, then you'll have to set your FileInput like this:
<InputFile OnChange="HandleChange" id="file-upload" name="file-upload" class="sr-only" multiple />
And to get the collection of IBrowserFile objects, this:
BrowserFiles = e.GetMultipleFiles(maxAllowedFiles);
Here's my test code based on what you've given us. It works, so we're missing something obvious.
#page "/Images"
<div class="#(Base64Images.Count > 0 ? "block" : "hidden")">
#foreach (var image in Base64Images)
{
<h4>Images goes here</h4>
<img src="#image" />
}
</div>
#if (!_hasImages)
{
<div>
<InputFile OnChange="#OnInputFileChange" multiple />
</div>
}
else
{
<div>
#foreach (var image in Base64Images)
{
<h4>More Images goes here</h4>
<img src="#image" />
}
</div>
}
<button class="btn btn-dark" #onclick="() => Click()"> Click</button>
#code {
List<string> Base64Images = new List<string>();
private bool _hasImages => Base64Images != null && Base64Images.Count > 0;
void Click()
{
Base64Images.Add("Bye");
}
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
await Task.Delay(1000);
Base64Images.Add("Bye");
}
}

How do I properly use .NET Core 5.x and Blazor to allow a user to perform a search, then display results efficiently, appropriately, and Blazor-like?

I am VERY new at C#, .NET Core, and Blazor and I am trying to get a site to work while "properly" utilizing Blazor's functionality.
How I need this to work is like this:
User navigates to the site: 192.168.1.100
Enters a query into an input field
My code contacts a Swagger-powered API to retrieve the data
A "new" page is generated with the user's requested data
Simple, yes ... and I have done it hundreds of times with standard HTML, PHP, JavaScript, Java, but never with Blazor.
I have a section at the top of the site that I want to remain "static" no matter what page the user goes to. Like this:
User Starts Here
Results Displayed Here
My _Hosts.cshtml page calls MainLayout.razor, which is structured like this:
#inherits LayoutComponentBase
<div>
<div>
<TopBanners />
</div>
<div>
<QuickStatusIndicators />
</div>
<div>
<div>
#Body
</div>
</div>
</div>
#Body loads Index.razor which is structured like this:
#page "/"
<div class="text-center">
<HeaderLarge />
<form method="POST" action="#">
<DefaultSearch />
<SearchOptions />
</form>
</div>
#code {
}
And, DefaultSearch.razor looks like this:
<div id="mainSearchContainer">
<div id="mainSearchFormContainer">
<input id="mainSearchInputField" name="queryString" type="text" />
</div>
</div>
#code {
}
I have been doing research on how to hit an API endpoint with a POST request in Blazor, and the example I have doesn't seem to use a "normal" form. This works well, and generally, looks like this:
#page "/search"
...
#inject IHttpClientFactory clientFactory
<h3>Search</h3>
<div>
<input type="text" id="queryString" name="queryString" #bind="queryString" />
<button #onclick="GetSearchResults">Get It</button>
</div>
<div id="search_response_area">
#if (fetchedResults != null)
{
#foreach (var result in fetchedResults.documents)
{
// ...
}
}
</div>
#code {
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(#"http://192.168.1.234:8080");
string searchstr = JsonConvert.SerializeObject(search);
StringContent content = new StringContent(searchstr, Encoding.UTF8, "application/json");
using HttpResponseMessage httpResponse = await client.PostAsync(("/api/...", content);
httpResponse.EnsureSuccessStatusCode();
if (httpResponse.StatusCode == System.Net.HttpStatusCode.OK)
{
string response = await httpResponse.Content.ReadAsStringAsync();
fetchedResults = JsonConvert.DeserializeObject<SearchResultSet>(response);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
So, my question is this. Do I go the <FORM> route OR stick with the way I have found that works successfully using a method like this:
#page "/"
...
#inject IHttpClientFactory clientFactory
<div class="text-center">
<HeaderLarge />
#if (fetchedResults == null)
{
<DefaultSearch />
<SearchOptions />
} else {
<SearchResultsPage />
}
</div>
#code {
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(#"http://192.168.1.234:8080");
string searchstr = JsonConvert.SerializeObject(search);
StringContent content = new StringContent(searchstr, Encoding.UTF8, "application/json");
using HttpResponseMessage httpResponse = await client.PostAsync(("/api/...", content);
httpResponse.EnsureSuccessStatusCode();
if (httpResponse.StatusCode == System.Net.HttpStatusCode.OK)
{
string response = await httpResponse.Content.ReadAsStringAsync();
fetchedResults = JsonConvert.DeserializeObject<SearchResultSet>(response);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
And have the new SearchResultsPage.razor contain the #foreach (var result in fetchedResults.documents) code?
If I continue on with the <FORM>, how do I go about proceeding?
The goal is to get back JSON serialized data, so you can deserialize it to an object for presentation. I don't see any particular benefit in trying to package and process a form post, like you might in a normal (read: not Blazor) page.
I think you're on the right track with the latter example. It feels much more "Blazor-y" to me.
#Brian,
like in your latter code snippet, something like this perhaps:
#page "/search"
#inject HttpClient Http
<div>
<input type="text" id="queryString" name="queryString" #bind="queryString" />
<button #onclick="GetSearchResults">Get It</button>
</div>
<div id="search_response_area">
#if (fetchedResults != null)
{
#foreach (var result in fetchedResults.documents)
{
// ...
}
}
</div>
#code {
private FetchedResultsModel fetchedResults = null;
private async Task GetSearchResults()
{
...
fetchedResults = await Http.GetJsonAsync<FetchedResultsModel>(<...url to get search results...>);
}
}
In addition to your existing code, I'd suggest "separating" UI and "serverside", i.e
you have your search.razor page - serves as UI with input fields and search results,
your search.razor page "inherits" via #inherits SearchBase statement,
where SearchBase is something like
public class SearchBase:ComponentBase
{
[Inject] HttpClient Http { get; set; }
[Inject] IJSRuntime JSRuntime { get; set; }
....
....
}

ASP.NET WebAPI change file name?

I am trying to change the file name of images to the value that I posted in the input box username. The files are getting uploaded to the server and also, after overriding GetLocalFileName the file name is changed from "BodyPart_(xyz)" to the original one. How do I rename them to the value that I provided in the input box?
<form name="form1" method="post" enctype="multipart/form-data" action="api/poster/postformdata">
<div class="row-fluid fileform">
<div class="span3"><strong>Username:</strong></div>
<input name="username" value="test" type="text" readonly/>
</div>
<div class="row-fluid fileform">
<div class="span3"><strong>Poster:</strong></div>
<div class="span4"><input name="posterFileName" ng-model="posterFileName" type="file" /></div>
</div>
<div class="row-fluid fileform">
<div class="span8"><input type="submit" value="Submit" class="btn btn-small btn-primary submitform" /></div>
</div>
</form>
I have stored the value that I received in the newName variable but I am confused on how to rename the file in the server.
public async Task<HttpResponseMessage> PostFormData()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
await Request.Content.ReadAsMultipartAsync(provider);
// Show all the key-value pairs.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
newName = val;
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
public class MyMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
public MyMultipartFormDataStreamProvider(string path)
: base(path)
{
}
public override string GetLocalFileName(System.Net.Http.Headers.HttpContentHeaders headers)
{
string fileName;
if (!string.IsNullOrWhiteSpace(headers.ContentDisposition.FileName))
{
fileName = headers.ContentDisposition.FileName;
}
else
{
fileName = Guid.NewGuid().ToString() + ".data";
}
return fileName.Replace("\"", string.Empty);
}
}
One way is to override the ExecutePostProcessingAsync method like the following:
public override async Task ExecutePostProcessingAsync()
{
await base.ExecutePostProcessingAsync();
// By this time the file would have been uploaded to the location you provided
// and also the dictionaries like FormData and FileData would be populated with information
// that you can use like below
string targetFileName = FormData["username"];
// get the uploaded file's name
string currentFileName = FileData[0].LocalFileName;
//TODO: rename the file
}

Categories