(Blazor) Select value not updating - c#

I have a <select> element in my page, of which I want to get the value. To do that, I've added the #bind= property. Here's the code:
<form target="#">
<label for="subject">Vak</label>
<select #bind="#selectedSubject" id="subject">
#foreach (Subject subject in Auth.User.Subjects)
{
<option value="#subject.Uuid">#subject.Name</option>
}
</select><br />
<!-- (more input fields) -->
<button class="button btn-primary mt-3" #onclick="addNote">Toevoegen</button>
</form>
#code {
private String selectedSubject { get; set; }
private String inputTitle { get; set; }
private String inputContent { get; set; }
private void addNote()
{
Console.WriteLine(selectedSubject);
}
}
This doesn't output anything, and trying to use selectedSubject results in a NullReferenceError.
The other values (inputTitle, inputContent) work as expected.
Yes, the <select> is properly being filled with <option>s.
I've tried switching to whatever <EditForm> is, but it kept giving me warnings and failed to build.

First of all, you don't need a form at all. The main purpose of EditForm is to validate inputs-- like, making sure a password is the correct format.
Also, unless you want to programmatically SET the value of the dropdown, you don't need to bind to it-- instead, use #onchange:
<select #onchange=ProcessSelection>
. . .
#code {
Subject SelectedSubject {get; set;}
async Task ProcessSelection (ChangeEventArgs args){
SelectedSubject = Auth.User.Subjects.Single(s=>s.uuid = args.Value.ToString();
// Process the selection.
}
}
This will (1) give you a place to breakpoint for debugging. (2) Let you do useful things with the SelectedSubject object more easily (like add / remove it from a list, pass it as a parameter to a display component, etc.).

My approach to the problem is different from the other answer.
Here's everything in one Razor component.
Separate your data out into a class. Note that the fields can be null (you may not want them to be).
Use EditForm with normal binding. You can then do validation.
Logic in select list to determine if no value is selected and display a select message.
Separate out the Select code into a separate RenderFragment block to keep the code cleaner. Not necessary if you like all your markup in one place.
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<EditForm EditContext="editContext">
<div class="col-12 mb-3">
<label class="form-label small muted">Subject</label>
<select class="form-select" #bind="model.Subject">
#this.SubjectList
</select>
</div>
<div class="col-12 mb-3">
<label class="form-label small muted">Title</label>
<input class="form-control" #bind="model.Title">
</div>
</EditForm>
<div class="alert alert-info m-3">
<div class="m-2">
Subject : #model.Subject
</div>
<div class="m-2">
Title : #model.Title
</div>
<div class="m-2">
Content : #model.Content
</div>
</div>
#code {
//private Note model = new() { Subject="Portugal"};
private Note model = new();
private EditContext? editContext;
protected override void OnInitialized()
=> this.editContext = new EditContext(model);
private IEnumerable<string> Subjects = new List<string> { "France", "Portugal", "Spain" };
private bool noSubjectSelected => !Subjects.Any(item => item == model.Subject);
// Separates out the code into a separate render block
private RenderFragment SubjectList => (__builder) =>
{
if (noSubjectSelected)
{
<option disabled selected value="">-- Select a Subject --</option>
}
foreach (var subject in Subjects)
{
<option value="#subject">#subject</option>
}
};
// Normally separate out into own class file
public class Note
{
public string? Subject { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }
}
}
I've left the subject as a string as that's what you have it as. If it's a guid you will need to adjust the Select Code to handle a Guid/String pair.

Bear in mind that source code is an instruction of rendering intent so it isn't always clear how your code will react in all cases of operation.
You have to decide whether there will be a selection at all times, or whether no material selection is also valid. For example, if you want an option of "Select a Subject" at the top of the list which is the default, so set am option with that text an a value of Guid.Empty (all zeros). When processing values - just check that the selected Guid is not equal to Guid.Empty if you want to know if a "real" subject option is selected
I use the following two approaches with InputSelect. Note that the Microsoft renderer may set the text of the select to your bound option but may not set that option to "selected"
<InputSelect #bind-Value=MyData>
<option selected value="#Guid.Empty">Select a Subject</option>
#foreach (var i in Subjects)
{
<option value=#i.Id>#i.SubjectName</option>
}
</InputSelect>
If you want finer grained control over the select, there is this binding option. As well as a value to which you bind, you also need a function which is called on change - SelectChanged - where you can run other code. you should set the bound value within this method. If the InputSelect is inside a component, the bound value should also have an EventHandler of the same name with the "Changed" text appended - you should Invoke this in your SelectChanged function. Any parent control binding to the child will have its data automatically updated and the SetStateChanged is called for you
<InputSelect Value="#MyValue" ValueChanged="#((Guid value) => SelectChanged(value))" ValueExpression="#(() => MyValue)" >
#foreach (var i in MyValues)
{
if (TheSelectedValue == i.TheValue)
{
<option selected value=#i.TheValue>#i.ValueDisplay</option>
}
else
{
<option value=#i.TheValue>#i.ValueDisplay</option>
}
}
</InputSelect>

To solve your issue with things not changing, there are two ways you can do it:
Start with a null object for your SelectedSubject, do a null check in your click, and have a dummy select option that forces you to select an item:
.
<select #onchange=ProcessChange>
<option selected disabled>Pick a subject. . . </option>
#foreach (var subject in Auth.User.Subjects) {
<option>. . . </option>
}
</select>
<button #onclick=AddSubject />
#code {
List<Subject> SelectedSubjects = new();
Subject SelectedSubject {get; set; } = null;
async Task ProcessChange (ChangeEventArgs args){. . . }
async Task AddSubject{
if (Subject is not null) SelectedSubjects.Add (SelectedSubject);
}
}
Preselect your first option in your data initialization, and then you don't NEED the dummy <option> or the null check.
.
#code{
async Task OnInitializedAsync (bool IsFirstRender){
Auth.User.Subjects = await LoadSubjectsForUser (Auth.User);
SelectedSubject = Auth.User.Subjects.First();
}
}
In your use case, I think #1 is probably better.

Related

Dropdown won't send info to db

In one of my previous questions, I asked how I would approach populating a dropdown on my razor page by pulling info from the database. See my question here: Populating dropdown via foreign key
I was advised to do this by adding the following to my PageModel:
public List<Data.Vendor> Vendores { get; set; }
public void OnGet()
{
List<SelectListItem> test = new List<SelectListItem>();
//List<Data.File> files = new List<Data.File>();
Vendores = _context.Vendors.ToList();
foreach (var item in Vendores)
{
test.Add(new SelectListItem { Text = item.VendorName, Value = item.Id.ToString() });
}
ViewData["demo"] = test;
}
and on the .cshtml adding the following:
<select asp-for="Files.VendorId" asp-items="#((List<SelectListItem>)ViewData["demo"])"></select>
But when submitting the form in which I want to save the Dropdown selection, when it gets to my Postasync:
[BindProperty]
public Data.File Files { get; set; }
public Data.Vendor Vendors { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Files.Add(Files);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
The data for that field doesn't get passed through.
If you need to see the full PageModel and .cshtml files I can add them.
I've recorded a video of me going through the process and showing that Vendor Id is null which means nothing is passing through:
https://drive.google.com/file/d/1CxObYQ9czs4OOJYCyr5OMmu-64X9iThz/view?usp=sharing
After I checking the code in Create.cshtml, I find the reason why VendorId can't be bind to the model.
In your Create.cshtml, you have the code:
//......
<div class="form-group">
<label asp-for="Files.VendorId" class="control-label" style="color: white"></label>
<input asp-for="Files.VendorId" class="form-control" />
<span asp-validation-for="Files.VendorId" class="text-danger"></span>
</div>
//......
<select asp-for="Files.VendorId" asp-items="#((List<SelectListItem>)ViewData["demo"])"></select>
//........
Because they all use asp-for="Files.VendorId" to bind value, So they will generate the same name : Files.VendorId in html.
If there are multiple input attributes with the same name in the form, It will only bind the value of the first input attribute.
In your video, you don't write any value in VendorId input tag, So VendorId property in Files will not bind any value
You can just delete this input attribute and then Files.VendorId will bind the value of dropdown list successfully.

ValueChanged and ValueEpression in blazor?

I have seen the use of ValueChanged and ValueExpression in blazor component but does not have any idea wy they are used and what does ValueChanged and ValueEpression do in blazor?
If any body can explain them in simple language, it would be really a great help/
Please check the InputBase Class Properties:
ValueChanged: Gets or sets a callback that updates the bound value.
ValueExpression: Gets or sets an expression that identifies the bound value.
Generally, Razor components provide data binding features via an HTML element attribute named #bind with a field, property, or Razor expression value, we could use it to bind values to the html elements. If we are not using the bind attribute, we could use the ValueExpression and ValueChanged to update the bound value. Please refer to the following sample:
<EditForm Model="form">
<p>Current value: #form.MyProperty</p>
<InputSelect ValueChanged="#((string s) => DoThing(s))"
ValueExpression="#(()=> form.MyProperty)">
<option value="0">0</option>
<option value="100">100</option>
<option value="200">200</option>
<option value="300">300</option>
</InputSelect>
</EditForm>
#code {
MyForm form = new MyForm(); //create an model instance.
void DoThing(string s) //value changed event.
{
form.MyProperty = s; //get the current selected value and assign to the Model.
}
//define a class to get/set the dropdownlist selected value.
public class MyForm
{
public string MyProperty { get; set; }
}
}
If using the #bind attribute, the sample code as below:
<p>Current Season: #model.Season</p>
<InputSelect #bind-Value="model.Season">
#foreach (var value in Enum.GetValues(typeof(Season)))
{
<option>#value</option>
}
</InputSelect>
#code {
Model model = new Model();
class Model
{
public Season Season { get; set; }
}
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
}
The screenshot as below:
Reference:
ASP.NET Core Blazor data binding

How to store data in a hidden field and the pass it between pages in razor pages

So I am trying to select a value from dropdown list and passing it as query string in a another view this works fine on get but for post I want to store it in a hidden field
<div style="margin: 30px">
<label asp-for="DropDown" class="control-label">DropDownList</label>
<select asp-items="Model.dropdown"
asp-for="targetvalue"
value ="targetvalue"
type="hidden" class="form-control">
</select>
Not sure if this is correct how do I retrieve the hidden field to use it in post
IMHO, you should bind the value to a page model field, then just generate a hidden input in your form;
<input type="hidden" asp-for="targetvalue" />
You can use any of below methods, But the recommended way is "Model Binding".
You can access the value by passing the key as an indexer to the Request.Form collection, like below:
var number = Request.Form["targetvalue"];
Add a suitable property to the PageModel and to allow model binding to apply the posted value to the property: like below:
public IndexModel : PageModel
{
[BindProperty]
public int targetvalue { get; set; }
public void OnPost()
{
// posted value is assigned to the targetvalue property automatically
}
}
For this example the Drop-down list can be rendered in different ways, But once it rendered into HTML it would look like below:
<select name="targetvalue">
<option value="">Select a number</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>

onChange event not firing Blazor InputSelect

I have the following Code for an InputSelect
<InputSelect class="form-control form-control form-control-sm"
placeholder="Role"
disabled="#IsReadOnly"
#bind-Value="Model.Role"
#onchange="#RoleChanged">
<option value="Member">Member</option>
<option value="Admin">Admin</option>
<option value="Pioner">Pioneer</option>
<option value="Retailer">Retailer</option>
</InputSelect>
And for the Code:
bool ShowCreated;
bool ShowUpdated;
bool IsReadOnly;
string SelectedRole;
public EditForm AccountForm;
public Multi.Dtos.RegistrationDto Model = new Dtos.RegistrationDto() { Role = "Member" };
public void RoleChanged(ChangeEventArgs e)
{
SelectedRole = e.Value.ToString();
Console.WriteLine(e.Value);
}
The RoleChange function is not invoked when i try to select a new item. Can someone point out what wrong with this. The value of the proper has changed but the Event was not called.
Note:
InputSelect is a component element, not HTML element, which is why you cannot
apply to it the #onchange compiler directive. This directive is applied to
elements, usually but not necessarily with the value attribute to form the so-
called two way data-binding.
#bind-Value is a compiler directive directive instructing the compiler to
produce code that enable two-way data binding between components. Applying
#bind-Value to the InputSelect component requires you (already done in this
case by the Blazor team) to define a parameter property named Value and an
EventCallback 'delegate', conventionally named ValueChanged. Value and
ValueChanged are properties of the InputSelect component.
There are a couple of ways to do this:
<InputSelect ValueExpression="#(()=>comment.Country)"
Value="#comment.Country"
ValueChanged="#((string value) => OnValueChanged(value ))">
<option value="">Select country...</option>
<option value="USA">USA</option>
<option value="Britain">Britain</option>
<option value="Germany">Germany</option>
<option value="Israel">Israel</option>
</InputSelect>
And
private Task OnValueChanged(string value)
{
// Assign the selected value to the Model
comment.Country = value;
return Task.CompletedTask;
}
You can also implement INotifyPropertyChanged.PropertyChanged Event in your Model, as dani herrera has suggested. You do that if you want to do away with the Forms components, including the EditForm, and use the normal HTML tags instead, in which case you'll be able to do something like:
<input type="text" value="#MyValue" #onchange=OnChange"/>
Of course your model class is going to be thick, and it should communicate with the EditContext....
Hope this helps...
The request is not that old, so you may consider the following which works with EditForm
(basically hook up onto another event) => at oninput eventhandler you pass The ChangeEventArgs var, and so you can get the value to process
<InputSelect #bind-Value="#labBook.StockID" #oninput="OnLabbookStockChange" class="form-control">
#if (lstStocks != null)
{
<option value="">select...</option>
#foreach (var stock in lstStocks)
{
<option value="#stock.StockID">#stock.TestArticle.TAName</option>
}
}
</InputSelect>
The problem is the #bind attribute makes use of the #onchange event and Blazor will not allow multiple #onchange event handlers. Workarounds in the code below:
Method 1: This is the vanilla example. It shows how to wire up a dropdown using an HTML select tag when you do not require an onchange event handler.
Method 2: This is, I think, the simplest if you need 2-way data binding AND an event handler. It uses the HTML select tag (not a Blazor component) with 1-way data binding using the "value" attribute. Since the #bind attribute is not used, we are free to attach a handler to the #onchange event. This handler, as well as handling the event, also needs to populate the property in order to achieve 2-way data binding.
Method 3: If you are using the whole Blazor EditForm and InputText/InputSelect/etc components infrastructure, this method may be best for you. Without the EditContext, the example shows 2-way binding using #bind-Value. In order to handle the onchange event for any component, we add an event handler (EditContext.OnFieldChanged) for the entire form. The handler checks to see which property was changed (based on the FieldName) and fires the required event handler accordingly.
I created this page to remind me of the options for this. Hope it helps anyone who finds their way here.
#page "/dropdown"
<h2 class='#(hightlight ? "bg-info" : "")'>
User Id: #UserId
</h2>
<hr />
<h3>Using Select Tag</h3>
<p>
<label>
Method 1 (2-way binding but do not need to handle an onchange event):<br />
<select #bind="UserId">
<option value=""></option>
#foreach (var u in users)
{
<option value="#u.Key">#u.Value</option>
}
</select>
</label>
</p>
<p>
<label>
Method 2 (need to handle the onchange event; 2-way binding via the control onchange event handler):<br />
<select value="#UserId" #onchange="OnChangeUserId">
<option value=""></option>
#foreach (var u in users)
{
<option value="#u.Key">#u.Value</option>
}
</select>
</label>
</p>
<h3>Using InputSelect Tag</h3>
<EditForm EditContext="EditContext">
<p>
<label>
Method 3 (2-way binding; event handling via the EditForm EditContext.OnFieldChanged)<br />
<InputSelect #bind-Value="#UserId">
<option value=""></option>
#foreach (var u in users)
{
<option value="#u.Key">#u.Value</option>
}
</InputSelect>
</label>
</p>
</EditForm>
#code {
private EditContext EditContext;
private Dictionary<int, string> users = new Dictionary<int, string>();
private int? UserId { get; set; }
private bool hightlight { get; set; }
protected override void OnInitialized()
{
EditContext = new EditContext(users);
EditContext.OnFieldChanged += EditContext_OnFieldChanged;
}
protected override void OnParametersSet()
{
// simulate getting current data from the db (which would use async version of this event handler)
users.Clear();
users.Add(1, "Bob");
users.Add(2, "Sue");
users.Add(3, "R2D2");
UserId = 2;
base.OnParametersSet();
}
private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
{
if (e.FieldIdentifier.FieldName == "UserId")
{
DoStuff();
}
}
private void OnChangeUserId(ChangeEventArgs args)
{
// select tag's "value" attribute provide one-way data binding; to get 2-way,
// we need to manually set the value of the UserId based on the ChangeEventArgs
if (args.Value == null)
UserId = null;
else
UserId = int.Parse(args.Value.ToString());
DoStuff();
}
private void DoStuff()
{
hightlight = (UserId == 3);
}
}
Instead of using the onchange event. You may want to attempt using oninput event instead.
May be late to the party, but I found the easiest thing to do is use the #bind-Value="MyProperty" and then for the property call the function in the setter. Therefore, no need to use #onchanged.
so in the c# model:
private T _myProperty;
public T MyProperty
{
get => _myProperty;
set =>
{
_myProperty = value;
MyFunction();
}
}
I solved this simply using the #onclick event. It fires multiple times, but the last time it fires, its the selected item.
<div>
<EditForm Model="model">
<InputSelect #bind-Value="model.SimulatorType" #onclick="SelectionChanged">
#foreach (var value in Enum.GetValues(typeof(SimulatorType)))
{
<option>#value</option>
}
</InputSelect>
</EditForm>
</div>
#code {
class Model
{
public SimulatorType SimulatorType
{
get;set;
}
}
Model model = new Model();
private async void SelectionChanged()
{
await OnSelectionChanged.InvokeAsync(model.SimulatorType.ToString());
}
[Parameter]
public EventCallback<string> OnSelectionChanged { get; set; }
}

How to select some column in huge table which contain null data in some row and use distinict in column

I have huge table which contains some column null data but I don't want to change anything in table.
In one of my view I want to select just two column to display in the page, first is ID and second is Area but in Area, I should use distinct and prevent to display null value
there is my code but it not work and tell me how can I do it.
public class ClsArea
{
public int id { get; set; }
public string AreaName { get; set; }
}
public ActionResult Index()
{
return View(DB.school.ToList());
}
----------
#model IEnumerable<school.Models.ClsArea>
<div class="container">
<div class="row" style="margin-top:30px">
<div class="col-lg-12 col-xs-12">
<select class="form-control">
#foreach (var itemArea in Model.Select(c => new { c.IdSMP, c.Area }).ToList() as IEnumerable<ClsArea>)
{
<option value=#itemArea.id>
#itemArea.areaname
</option>
}
</select>
</div>
</div>
</div>
Error Display
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[<>f__AnonymousType1`2[System.Int32,System.String]]', but this dictionary requires a model item of type 'System.Collections.Generic.IList`1[PMIS_V01.Models.ClsArea]'.
Distinct will not prevent NULL values. It was not designed to do so. It will pull one NULL value with others.
If you want to skip the NULL values then why don't you just ignore those? Something like:
DB.school.where(s => s.Area != null).ToList()
Update:
You are expecting list of school.Models.ClsArea in the view but you are sending list of DB.school type from action. DB.school is the entity model type right?

Categories

Resources