Blazor, trouble displaying data fetched from api - c#

Im having trouble displaying my data from an api request, and I cant seem to figure out why. Here's my code so far:
This div
<div>
<h4>Weather: #weatherData.location.name </h4>
</div>
Does not get updated when I have picked City, how come?
#page "/appweather"
<PageTitle>CheckWeather</PageTitle>
<h3>AppWeather</h3>
<div>
<select #bind="SelectedCountry">
<option value="">Select a city</option>
<option value="London">London</option>
<option value="Stockholm">Stockholm</option>
<option value="Paris">Paris</option>
</select>
<button #onclick="GetWeather">Get Weather</button>
</div>
<div>
<h4>Weather: #weatherData.location.name </h4>
</div>
#code {
#using Newtonsoft.Json;
private string SelectedCountry { get; set; }
private Weather weatherData;
private async Task GetWeather()
{
if (string.IsNullOrEmpty(SelectedCountry))
{
return;
}
var client = new HttpClient();
var response = await client.GetAsync($"http://api.weatherapi.com/v1/current.json?key=key&q={SelectedCountry}&aqi=no");
var jsonstring = await response.Content.ReadAsStringAsync();
var weather = JsonConvert.DeserializeObject<Weather>(jsonstring);
}
class Weather
{
public Location location { get; set; }
public Current current { get; set; }
}
class Location
{
public string name { get; set; }
public string region { get; set; }
public string country { get; set; }
public string localtime { get; set; }
}
class Current
{
public double temp_c { get; set; }
public double wind_kph { get; set; }
public string wind_dir { get; set; }
}
}
Kind regards

As there are several issues with your code, here's a simpler version that demonstrates how to make this work.
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public string? Location { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
The controller:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private static readonly string[] Locations = new[] { "London", "Stockholm", "Paris" };
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
[Route("{location?}")]
public IEnumerable<WeatherForecast> Get(string? location)
{
if (location == null)
return WeatherForecasts;
return WeatherForecasts.Where(x => x.Location == location);
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return WeatherForecasts;
}
private IEnumerable<WeatherForecast> WeatherForecasts =
Enumerable.Range(1, 100).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)],
Location = Locations[Random.Shared.Next(Locations.Length)]
});
}
The FetchData page:
#page "/fetchdata"
#using SO75326593.Shared
#inject HttpClient Http
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
<select class="form-select" value="#location" #onchange=GetFilteredList>
#if (location is null)
{
<option selected disabled value=""> -- Select a Location --</option>
}
<option value="London">London</option>
<option value="Stockholm">Stockholm</option>
<option value="Paris">Paris</option>
</select>
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Location</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.Location</td>
<td>#forecast.TemperatureC</td>
<td>#forecast.TemperatureF</td>
<td>#forecast.Summary</td>
</tr>
}
</tbody>
</table>
#code {
private IEnumerable<WeatherForecast> forecasts = Enumerable.Empty<WeatherForecast>();
private string? location;
private async Task GetFilteredList(ChangeEventArgs e)
{
location = e.Value?.ToString();
if (!string.IsNullOrWhiteSpace(location))
{
var list = await Http.GetFromJsonAsync<WeatherForecast[]>($"WeatherForecast/{location}");
forecasts = list ?? Enumerable.Empty<WeatherForecast>();
}
}
protected override async Task OnInitializedAsync()
{
var list = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
forecasts = list ?? Enumerable.Empty<WeatherForecast>();
}
}

Related

InputSelect with null reference in Blazor

I'm using an InputSelect in my razor component, but in the console I always see this error:
System.NullReferenceException: Object reference not set to an instance
of an object. at
VidaConfortoApplication.Client.Pages.ServiceTypes.Add.b__0_7(RenderTreeBuilder
__builder3) in C:\repos\sources\VidaConfortoTese\src\VidaConfortoApplication\VidaConfortoApplication\Client\Pages\ServiceTypes\Add.razor:line
18 at
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddContent(Int32
sequence, RenderFragment fragment) at
Microsoft.AspNetCore.Components.Forms.InputSelect`1[[System.Int64,
System.Private.CoreLib, Version=6.0.0.0, Culture=neutral,
PublicKeyToken=7cec85d7bea7798e]].BuildRenderTree(RenderTreeBuilder
builder) at
Microsoft.AspNetCore.Components.ComponentBase.<.ctor>b__6_0(RenderTreeBuilder
builder) at
Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder
batchBuilder, RenderFragment renderFragment, Exception&
renderFragmentException)
I do not understand what I am doing wrong.
Component:
<EditForm Model="#_model" OnValidSubmit="OnValidSubmit">
<DataAnnotationsValidator />
<div class="form-row">
<div class="form-group col">
<label>Serviço pai</label>
<InputSelect class="form-control" #bind-Value="#_model.ParentId">
<option value="">--</option>
#foreach (var service in _services)
{
<option value="#service.Id">#service.Name</option>
}
</InputSelect>
<label>#_serviceHierarchy</label>
<ValidationMessage For="#(() => _model.ParentId)"/>
</div>
</div>
<br />
<div class="form-group">
<button class="btn btn-primary">
Save
</button>
</div>
</EditForm>
#code {
private readonly AddServiceTypeViewModel _model = new();
private IEnumerable<ServiceType> _services;
protected override async Task OnInitializedAsync()
{
_services = await ServiceTypeService.GetAll();
}
}
Class AddServiceTypeViewModel
public class AddServiceTypeViewModel
{
[Required]
public string Name { get; set; }
public long? ParentId { get; set; }
public string? Description { get; set; }
}
Class ServiceType
public class ServiceType
{
public string Name { get; set; }
public long? ParentId { get; set; }
public ServiceType? Parent { get; set; }
public string? PathToService { get; set; }
public string? Description { get; set; }
public DateTime? AddedWhen { get; set; }
public DateTime? UpdatedWhen { get; set; }
public long Id { get; set; }
}
Your template is trying to access the "_services" property to render some piece of html before it's initialized. To avoid this,
You can initialize it to an empty list:
private IEnumerable<ServiceType> _services = new List<ServiceType>();
or
You can put a guard on the template code:
if (_services is not null)
{
#foreach (var service in _services)
{
<option value="#service.Id">#service.Name</option>
}
}

How to create a custom Blazor multiple select HTML component? Error: MultipleSelect requires a value for the 'ValueExpression' parameter

I am working to create a custom Blazor multiple select HTML component. It works until I add the validation. Also if I disable multiple select and leave the validation on, it works.
When multiple select with validation is on I get this error:
InvalidOperationException: MultipleSelect requires a value for the 'ValueExpression' parameter. Normally this is provided automatically when using 'bind-Value'.
I haven't been able to use the 'bind-Value' property because I get this error.
The documentation I have been able to find so far only address building a custom component from an HTML <select> element when the multiple select option is not in use.
How do I go about creating a <select> element with multiple select enabled?
Custom multiple select component
MultipleSelect.razor
#using CustomComponents.DataModels
#using System.Linq.Expressions
#using System
#using System.Collections.Generic
#inherits InputBase<string>
<div class="row">
<div class="col-3">
<select id="#Id" #bind=#CurrentValue class="form-control #CssClass" multiple="multiple" size="#BoxHieght" style="width:#BoxWidth">
#foreach (var option in Options)
{
<option #onclick="#(() => SelectOption(option))" value="#option.Value">#option.Text</option>
}
</select>
</div>
</div>
#code {
[Parameter]
public string Id { get; set; }
[Parameter]
public List<Option> Options { get; set; } = new List<Option>();
[Parameter]
public Option SelectedOption { get; set; } = new Option { Text = " ", Value = " " };
[Parameter]
public int BoxHieght { get; set; } = 5;
[Parameter]
public string BoxWidth { get; set; } = "auto";
[Parameter, EditorRequired]
public Expression<Func<string>> ValidationFor { get; set; } = default!;
private void SelectOption(Option option)
{
SelectedOption = option;
}
protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)
{
try
{
result = value;
validationErrorMessage = null;
return true;
}
catch (Exception exception)
{
result = null;
validationErrorMessage = exception.Message;
return false;
}
}
}
Option data model object
Option.cs
namespace CustomComponents.DataModels
{
public class Option
{
public string Text { get; set; }
public string Value { get; set; }
}
}
Web Form Model
FormModel.cs
using CustomComponents.Data.DataModels;
namespace BlazorApp2.Pages.PageModels
{
public class FormModel
{
public Option Option { get; set; } = new Option();
}
}
Data Model
State.cs
using System.ComponentModel.DataAnnotations;
namespace BlazorApp2.Data.DataModels
{
public class State
{
[Required]
public string Name { get; set; }
[Required]
public string Abbreviation { get; set; }
}
}
Web Form
Index.razor
#page "/"
#using CustomComponents.Components
#using CustomComponents.Data.DataModels
#using CustomComponents.Pages.PageModels
<PageTitle>Mutiple Select Component</PageTitle>
<EditForm Model="#model" OnValidSubmit="ValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<MyComponent Options="#options" #bind-Value="#model.Option" ValidationFor="() => State.Name"></MyComponent>
Selected option:
<div class="row">
<div class="col">
#model.Option.Value #model.Option.Text
</div>
</div>
<div class="row">
<div class="col">
<button class="btn btn-primary" type="submit">Submit</button>
</div>
</div>
<div class="row">
<div class="col">
#if (formSubmitted) #FormSubmitted
</div>
</div>
<div class="row">
<div class="col">
#if (formSubmitted) #StateSubmitted
</div>
</div>
</EditForm>
#code {
private List<Option> options = new List<Option>();
public FormModel model = new FormModel();
public State State { get; set; } = new State();
private List<State> states = new List<State>
{
new State { Name = "Utah", Abbreviation = "UT" },
new State { Name = "Texas", Abbreviation = "TX" },
new State { Name = "Florida", Abbreviation = "FL" }
};
public string FormSubmitted { get; set; } = "Form submitted.";
public string StateSubmitted { get; set; } = string.Empty;
private bool formSubmitted = false;
protected override void OnInitialized()
{
foreach(State state in states)
{
options.Add(new Option{ Value = state.Abbreviation, Text = state.Name});
}
model.Option = options[0];
}
public void ValidSubmit()
{
State.Abbreviation = model.Option.Value;
State.Name = model.Option.Text;
formSubmitted = true;
StateSubmitted = $"{State.Abbreviation} {State.Name}";
}
}
because you inherit the component InputBase, you must use bind-value.
I edited a lot of the code to make it work
#using BlazorApp2.Client.Components
#using System.Linq.Expressions
#using System
#using System.Collections.Generic
#using System.Diagnostics.CodeAnalysis
#inherits InputBase<Option>
<div class="row">
<div class="col-3">
<select id="#Id" class="form-control" size="#BoxHieght" style="width:#BoxWidth"
#bind="OptionValueSelected" #bind:event="oninput">
#foreach (var option in Options)
{
<option value="#option.Value">#option.Text</option>
}
</select>
<p>Selected option:#SelectedOption.Value </p>
</div>
</div>
#code {
[Parameter]
public string Id { get; set; }
[Parameter]
public List<Option> Options { get; set; } = new List<Option>();
[Parameter]
public Option SelectedOption { get; set; } = new Option { Text = " ", Value = " " };
[Parameter]
public int BoxHieght { get; set; } = 5;
[Parameter]
public string BoxWidth { get; set; } = "auto";
[Parameter, EditorRequired]
public Expression<Func<string>> ValidationFor { get; set; } = default!;
private string OptionValueSelected
{
get => CurrentValue.Value;
set
{
CurrentValue = Options.Find(o => o.Value == value);
}
}
protected override bool TryParseValueFromString(string value,
[MaybeNullWhen(false)] out Option result, [NotNullWhen(false)] out string validationErrorMessage)
{
try
{
result = Options.First(o => o.Value == value.ToString());
validationErrorMessage = null;
return true;
}
catch (Exception exception)
{
result = null;
validationErrorMessage = exception.Message;
return false;
}
}
}
After very long research, Here are some important changes I made:
Inherits from InputBase of type Option not string. Reason: so the form context knows the type and binds correctly
Bind value with a property with setters. Reason: To convert from string to option
Set the value of SelectedOption from the Input base CurrectValue. Reason: To alert the form context about the change so that it updates the main view
I Tested the component using this page on new project:
#page "/"
#using BlazorApp2.Client.Components
<PageTitle>Index</PageTitle>
#code {
List<Option> options = new List<Option>
{
new Option{Text = "Test1", Value = "Test1"},
new Option{Text = "Test2", Value = "Test2"}
};
ExampleModel model;
protected override void OnInitialized()
{
model = new ExampleModel();
}
}
<h1>Hello, world!</h1>
<EditForm Model="#model">
<MyComponent Options="#options" #bind-Value="#model.Option"></MyComponent>
</EditForm>
<p>#model.Option.Text : #model.Option.Value</p>
Example Model:
public class ExampleModel
{
public Option Option { get; set; } = new Option();
}
Resources helped me with my research :
Blazor components
Blazor form components binding
Blazor Custom Binding

C# Html.ValidationMessageFor not working when form post

I have an issue with validation messages.
Controller:
[HttpGet]
[Authorize]
public ActionResult Others(string bid)
{
return View(ret);
}
[HttpPost]
public ActionResult OthersUser(Others info)
{
return RedirectToAction("Others", "User", new { bid = "1" });
}
[HttpPost]
public ActionResult OthersPass(Others info)
{
if (ModelState.IsValid)
{
}
return RedirectToAction("Others", "User", new { bid = "2" }); ;
}
Password Confirm Class:
public class PassConfirm
{
[Key]
public int ID { get; set; }
public User User { get; set; }
public virtual int UserID { get; set; }
public string Key { get; set; }
[Required]
public string Password { get; set; }
[NotMapped]
[Compare("Password", ErrorMessage = "Passwords not matching!")]
public string ConfirmPassword { get; set; }
}
Others Class:
public class Others
{
public int BID { get; set; }
public PassConfirm PassChg { get; set; }
public String OldPsw { get; set; }
public User User { get; set; }
public string UserCheck { get; set; }
}
View:
<div style="display: flex; align-items: center;">
#using (Html.BeginForm("OthersPass", "User", FormMethod.Post))
{
<table>
<tr>
<td><label for="oldpsw">Eski Şifre:</label></td>
<td>#Html.TextBoxFor(x => x.OldPsw ,new { #id = "oldpsw", #type = "password" })</td>
</tr>
<tr>
<td><label for="newpsw">Yeni Şifre:</label></td>
<td>#Html.TextBoxFor(x => x.PassChg.Password, new { #id = "newpsw", #type = "password" })</td>
<td>#Html.ValidationMessageFor(x => x.PassChg.Password)</td>
</tr>
<tr>
<td><label for="confpsw">Şifreyi Doğrula:</label></td>
<td>#Html.TextBoxFor(x => x.PassChg.ConfirmPassword, new { #id = "confpsw", #type = "password"})</td>
<td>#Html.ValidationMessageFor(x => x.PassChg.ConfirmPassword)</td>
</tr>
</table>
<button class="btn btn-success" formmethod="post">Onayla</button>
}
</div>
When the user clicked the button with the wrong values, it returns nothing. I think the problem is caused by 2 post method. Maybe because of the values ​​returned by ActionResult elements. So what could be the source of the problem?

binding button to child component blazor

here is my fiddle https://blazorfiddle.com/s/d15hrars
I have a select component that changes the class of a table. there is also a button that can set the selected value of the select component. but pressing the button does not update the selected value in the drop down (the select component)
so pressing the button highlights the row San Jose but does not update the drop down. not sure why
parent component
#page "/"
<table class="table table-sm table-bordered table-striped">
<thead>
<tr>
<th>City</th>
</tr>
</thead>
<tbody>
#foreach (string c in Cities)
{
<tr class="#GetClass(c)">
<td>#c</td>
</tr>
}
</tbody>
</table>
<SelectFilter values="#Cities"
title="#SelectTitle"
#bind-SelectedValue="SelectedCity"/>
<button class="btn btn-primary"
#onclick="#(() => SelectedCity = "San Jose")">
Change
</button>
#functions {
string[] Cities = new string[] { "New York", "Los Angeles", "Denver", "San Jose" };
public string SelectedCity { get; set; }
public string GetClass(string city) =>
SelectedCity == city ? "bg-info text-white" : "";
[Parameter]
public string SelectTitle { get; set; }
}
child component
<div class="form-group">
<label for="select-#Title">#Title</label>
<select name="select-#Title"
class="form-control"
#onchange="HandleSelect"
value="#SelectedValue"
#attributes="Attrs">
<option disabled selected>Select #Title</option>
#foreach (string val in Values)
{
<option value="#val" selected="#(val == SelectedValue)">#val</option>
}
</select>
</div>
#code {
[Parameter]
public IEnumerable<string> Values { get; set; } = Enumerable.Empty<string>();
public string SelectedValue { get; set; }
[Parameter]
public string Title { get; set; } = "Placeholder";
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> Attrs { get; set; }
[Parameter]
public EventCallback<string> SelectedValueChanged { get; set; }
public async Task HandleSelect(ChangeEventArgs e)
{
SelectedValue = e.Value as string;
await SelectedValueChanged.InvokeAsync(SelectedValue);
}
}
Your child component is missing a Parameter attribute.
[Parameter]
public string SelectedValue { get; set; }

Entity Framework Eager Loading - pass data to ViewModel

In my ASP.NET MVC Core app, from an action method shown below, I'm passing Blogs data and its related data from Posts table to a view as return View(await _context.Blogs.Include(p => p.Posts).ToListAsync()); Since I'm passing data from two tables, I need to use a ViewModel shown below. Question: How can I use ViewModel to pass the related data from my Controller Action method
Test() to view shown below?
In the code below I'm getting the obvious error:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List'1[ASP_Core_Blogs.Models.Blog]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IList'1[ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel]'.
Model:
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int PostYear { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
Controller:
[HttpGet]
public async Task<IActionResult> Test(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View(await _context.Blogs.Include(p => p.Posts).ToListAsync());
}
ViewModel:
public class BlogsWithRelatedPostsViewModel
{
public int BlogID { get; set; }
public int PostID { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int PostYear { get; set; }
}
View:
#model IList<ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel>
<div class="row">
<div class="col-md-12">
<form asp-controller="DbRelated" asp-action="EnterGrantNumbers" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post">
<table class="table">
<thead>
<tr>
<th></th>
<th></th>
<th>Url</th>
<th>Title</th>
<th>Content</th>
</tr>
</thead>
<tbody>
#for (int t = 0; t < Model.Count; t++)
{
<tr>
<td><input type="hidden" asp-for="#Model[t].BlogID" /></td>
<td><input type="hidden" asp-for="#Model[t].PostID" /></td>
<td>
<input type="text" asp-for="#Model[t].Url" style="border:0;" readonly /> <!--Not using /*Html.DisplayFor(modelItem => Model[t].Url)*/ since it does not submit stateName on Post. Not using <label asp-for=.....> since Bootstrap bold the text of <label> tag-->
</td>
<td>
<input asp-for="#Model[t].Title" />
</td>
<td>
<input asp-for="#Model[t].Content" />
</td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
You need to project your query using your BlogsWithRelatedPostsViewModel class:
return View( _context.Blogs
.Include(p => p.Posts)
.SelectMany(e=> e.Posts.Select(p=> new BlogsWithRelatedPostsViewModel
{
BlogId= e.BlogId,
PostId=p.PostId,
Url=e.Url,
...
})
.ToList());
SelectMany extension method allows you flatten each projection from e.Posts into one sequence, so at the end you will get a List<BlogsWithRelatedPostsViewModel>
On top of Octavioccl's, answer there is a nice little extension method I have been using (I don't know of the author to this but if anyone else knows, I will happily update my answer to give credit). This way, you don't have to write out each property.
public static T Cast<T>(this object myobj)
{
var target = typeof(T);
var x = Activator.CreateInstance(target, false);
var d = from source in target.GetMembers().ToList()
where source.MemberType == MemberTypes.Property
select source;
var memberInfos = d as MemberInfo[] ?? d.ToArray();
var members = memberInfos.Where(memberInfo => memberInfos.Select(c => c.Name)
.ToList().Contains(memberInfo.Name)).ToList();
foreach (var memberInfo in members)
{
var propertyInfo = typeof(T).GetProperty(memberInfo.Name);
if (myobj.GetType().GetProperty(memberInfo.Name) == null) continue;
var value = myobj.GetType().GetProperty(memberInfo.Name).GetValue(myobj, null);
propertyInfo.SetValue(x, value, null);
}
return (T)x;
}
Usage:
var ViewModelList = ModelList.Select(model => model.Cast<ViewModel>()).ToList();
There is also a well supported framework built for this specific problem. Called AutoMapper (http://automapper.org/).
For passing data from Action to view as ViewModel. Create a new instance of your View Model first and assign value to each propery by calling your context query(whatever your Linq query is) and return the list of view as your View model variable.
var blogWithRelatedPost = new BolblogWithRelatedPost();
// your logic here for assigning value to property or LINQ query
return View(blogWithRelatedPost);

Categories

Resources