I'm facing a very weird problem, every time I update my data (without changing the price) the price will be updated as well (1200,00 => 120000,00). Is there any solution to this? The controller and view are built using the scaffold.
I'm using custom tag helper (asp-for-invariant="Price") from ASP.NET Core Localization Decimal Field Dot and Coma. I have noticed that with or without a custom tag helper the weird problem still occurs.
Here is my model
[Required]
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
Here is my controller (edit)
public async Task<IActionResult> Edit(int id, [Bind("AlbumId,GenreId,ArtistId,Title,Price,ImageFile")] Album album)
{
System.Diagnostics.Debug.WriteLine(album.ImageFile != null);
if (id != album.AlbumId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
if (album.ImageFile != null)
{
if (album.ImageName != null)
{
// delete old image
DeleteImage(ImagePathGenerator(album.ImageName));
}
// save new image
album.ImageName = ImageNameGenerator(album.ImageFile.FileName);
string path = ImagePathGenerator(album.ImageName);
using (var fileStream = new FileStream(path, FileMode.Create))
{
await album.ImageFile.CopyToAsync(fileStream);
}
}
_context.Albums.Update(album);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!AlbumExists(album.AlbumId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["ArtistId"] = new SelectList(_context.Artists, nameof(Artist.ArtistId), nameof(Artist.Name));
ViewData["GenreId"] = new SelectList(_context.Genres, nameof(Genre.GenreId), nameof(Genre.Name));
return View(album);
}
Here is my edit.cshtml
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input class="form-control" asp-for-invariant="Price" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
Here is my index.cshtml
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
In your GUI, type 1200.00 causes the value 120000 . Check your Region settings, or try 1200 or 1200,00 .
Simple solution, try
<input class="form-control" asp-for-invariant="Price" asp-is-invariant="false"/>
or
<input class="form-control" asp-for-invariant="Price" asp-is-invariant="true"/>
Set culture https://stackoverflow.com/a/8744037/3728901
I found it surprisingly difficult to dynamically create and use Radio Buttons and Checkboxes in an asp.net Blazor (Server-side) page, mostly because:
I could not figure out how to bind in a <input type="radio"> field
I came across the EditForm rather late
I did not find (m)any examples
one has to use a loop-local variable to avoid closures (and index out of bound errors)
<InputCheckbox> seems to be unable to bind to a List<bool> and a list of complex objects is required and throws an ArgumentException: The provided expression contains a InstanceMethodCallExpression1 which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object. error.
So I am posting this question and answering it at the same time with a working solution. The following code first demonstrates the use of regular <input> fields and later uses the EditForm component. As far as I can tell, the latter one is the preferred solution.
I still have one question: How can one use a List<bool> instead of a list of complex objects? It just doesn't feel right having to use a separate class to encapsulate a singe property. It also complicates things if formModel is persisted using EF Core. The same issue was discussed in a similar context e.g. here.
I think your answer over complicates this.
The code below demonstrates a basic setup (it's demo code not production).
It uses the EditForm with a model. There are radio buttons and checkboxes linked into a model that get updated correctly. Selected has a setter so you can put a breakpoint in ans see who's being updated.
#page "/"
#using Blazor.Starter.Data
<EditForm EditContext="this.editContext">
#foreach (var model in models)
{
<h3>#model.Value</h3>
<h5>Check boxes</h5>
foreach (var option in model.Options)
{
<div>
<InputCheckbox #bind-Value="option.Selected" />#option.Value
</div>
}
<h5>Option Select</h5>
<div>
<InputRadioGroup #bind-Value="model.Selected">
#foreach (var option in model.Options)
{
<div>
<InputRadio Value="option.Value" /> #option.Value
</div>
}
</InputRadioGroup>
<div>
Selected: #model.Selected
</div>
</div>
}
</EditForm>
<button class="btn btn-dark" #onclick="OnClick">Check</button>
#code {
private EditContext editContext;
private List<Model> models;
protected override Task OnInitializedAsync()
{
models = Models;
editContext = new EditContext(models);
return Task.CompletedTask;
}
public void OnClick(MouseEventArgs e)
{
var x = true;
}
public List<Model> Models => new List<Model>()
{
new Model() { Value = "Fred"},
new Model() { Value = "Jon"},
};
public class ModelOptions
{
public string Value { get; set; }
public bool Selected
{
get => _Selected;
set
{
_Selected = value;
}
}
public bool _Selected;
}
public class Model
{
public string Value { get; set; }
public string Selected { get; set; }
public List<ModelOptions> Options { get; set; } = new List<ModelOptions>()
{
new ModelOptions() {Value="Tea", Selected=true},
new ModelOptions() {Value="Coffee", Selected=false},
new ModelOptions() {Value="Water", Selected=false},
};
}
}
Here's what it looks like:
Here is the code example mentioned in the question with more detailled comments. First the version without EditForm, which is not the recommended way, but nicely demonstrates the closure problem and why a loop-local variable is required. It also demonstrates my failure to use an <input> field of type="radiobutton" as it seems impossible to bind a value to it:
#page "/"
<h1>Use Checkboxes and Radio Buttons in asp.net Blazor w/o EditForm</h1>
<h2>Checkboxes using a for-loop and a List of bool</h2>
Don't tick the left column, as it generates an index out of bounds error.
#for (int i = 0; i < CheckboxList.Count(); i++)
{
// Gotcha: This is a closure, so the function call is stored together with the environment.
// Since i resides outside of the loop, it is a single variable which is stored with the function call, so
// index out of bound occurs because the last value is used. Instead, use a local variable, so for each
// iteration, a new variable is used and bound to the function. This is a compiler gotcha.
// Since C# 5.0, the loop variable of a foreach lop is inside the loop, so using foreach is save, but it
// does not work with a List<bool> but requires a List of objects than contains a bool as in
// foreach(bool items in CheckboxList), item is the iteration variable and cannot be assigned.
// https://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/
// https://stackoverflow.com/questions/58843339/getting-argumentoutofrangeexception-using-for-loop-in-blazor-component
int ii = i;
<div class="form-check">
<input type="checkbox" #bind=#CheckboxList[i] /><!-- does not work, index out of range -->
<input type="checkbox" #bind=#CheckboxList[ii] />
<label>Answer #ii</label>
</div>
}
Checkbox selection for-loop: #OutTextCheckboxFor
<hr />
<h2>Checkboxes using a foreach-loop and a List<Item></h2>
#foreach (CheckboxItem item in CheckboxItems)
{
<div class="form-check">
<input type="checkbox" #bind=#item.IsChecked />
<label>#item.Title</label>
</div>
}
Checkbox selection foreach-loop: #OutTextCheckboxForEach
<hr />
<h2> Does not work: Radio Buttons using a foreach-loop and a List<Item></h2>
Somehow it seems as if binding a radio button does not work the same way as binding a checkbox. I would expect
that item.Ischecked is true/false, depending on the selection of the radio button.
#foreach (RadioItemBool item in RadioItems)
{
<div class="form-check">
#* would need to add value=, but can't as it is already assigned by bind?? *#
<input type="radio" #bind=#item.IsChecked />
<label>#item.Title</label>
</div>
}
Radio selection foreach-loop: #OutTextRadioForEach
<hr />
<button #onclick="OnSubmit">
Evaluate all the above
</button>
<hr />
#code {
// could also use an array as a direct replacement for the list
// could also be a field instead of a property
public List<bool> CheckboxList { get; set; } = new List<bool> { true, false, true };
public List<CheckboxItem> CheckboxItems = new List<CheckboxItem>() {
new CheckboxItem(true, "Checkbox 1"), new CheckboxItem(false, "Checkbox 2"), new CheckboxItem(true, "Checkbox 3") };
public List<RadioItemBool> RadioItems = new()
{
new RadioItemBool(false, "Radio 1"),
new RadioItemBool(false, "Radio 2"),
new RadioItemBool(false, "Radio 3")
};
string OutTextCheckboxFor;
string OutTextCheckboxForEach;
string OutTextRadioForEach;
public class CheckboxItem
{
public bool IsChecked;
public string Title;
public CheckboxItem(bool isChecked, string title)
{
IsChecked = isChecked;
Title = title;
}
}
public class RadioItemBool
{
public bool IsChecked;
public string Title;
public RadioItemBool(bool isChecked, string title)
{
this.IsChecked = isChecked;
this.Title = title;
}
}
public void OnSubmit()
{
OutTextCheckboxFor = "";
for (int i = 0; i < CheckboxList.Count(); i++)
{
OutTextCheckboxFor += " " + (CheckboxList[i] ? "1" : "0");
}
OutTextCheckboxForEach = "";
foreach (CheckboxItem item in CheckboxItems)
{
OutTextCheckboxForEach += " " + (item.IsChecked ? "1" : "0");
}
OutTextRadioForEach = "";
foreach (RadioItemBool item in RadioItems)
{
OutTextRadioForEach += " " + (item.IsChecked);
}
}
}
And here is the preferred version with EditForm, also demonstrating data validation for a text input. It also shows that it seems impossible to bind the checkboxes to a list of simple types (List<bool>) and a list of a complex type (List<myBool>) is required to prevent the ArgumentException: The provided expression contains a InstanceMethodCallExpression1 which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object. exception (c.f. here). Often, the text shown in the checkboxes might also be stored in the separate class (myBool), but to demonstrate the shortcoming of not beeing able to bind to a list of bool, only the bool is inside a separte class.
#page "/"
#using System.ComponentModel.DataAnnotations
<h2>EditForm input with validation</h2>
#* https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-validation *#
<EditForm Model="#formModel" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group row">
<label for="ShortText" class="col-sm-2 col-form-label">Short Text</label>
<div class="col-sm-6">
<InputText #bind-Value="formModel.ShortText" class="form-control" id="ShortText" aria-describedby="ShortTextHelp"
placeholder="Enter short text" />
<small id="ShortTextHelp" class="form-text text-muted">This is a required field.</small>
</div>
</div>
<div class="form-group row">
<label for="Checkboxes" class="col-sm-2 col-form-label">Checkbox</label>
<div id="Checkboxes" class="col-sm-10">
#for (int i = 0; i < formModel.IsCheckedComplex.Count; i++)
{
// prevent closures (IsCheckedComplex[ii] needs to be a loop-local variable)
int ii = i;
<div class="col-sm-10">
#* Unfortunately, using #bind-value with a List<bool> does not work (see comment below *1) *#
<InputCheckbox class="form-check-input" id="#ii" #bind-Value="#formModel.IsCheckedComplex[ii].IsChecked" />
<label class="form-check-label" for=#ii>"Text for item"</label>
</div>
}
</div>
</div>
<div class="form-group row">
<label for="RadioButtons" class="col-sm-2 col-form-label">Radio buttons</label>
<div id="RadioButtons" class="col-sm-10">
<InputRadioGroup id="RadioButtons" #bind-Value=#formModel.SelectedRadio>
#foreach (var item in formModel.RadioItems)
{
<div class="col-sm-10">
<InputRadio class="form-check-input" id=#item.Index Value=#item.Index />
<label class="form-check-label" for=#item.Index>#item.Title</label>
</div>
}
</InputRadioGroup>
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
<p>Inspect formModel in HandleValidSubmit() to see user inputs.</p>
</EditForm>
#code {
private FormModel formModel = new();
public class FormModel
{
public string Text { get; set; }
[Required]
[StringLength(10, ErrorMessage = "Short Text is too long.")]
public string ShortText { get; set; }
// === for the checkboxes
// unfortunately, binding to a List<bool> is not possible; try replacing IsCheckedComplex with IsChecked above *1
public List<bool> IsChecked { get; set; } = new() { true, false, false };
public List<myBool> IsCheckedComplex { get; set; } = new() { new(true), new(false), new(false) };
// a complex object is required as using List<bool> directly, throws an ArgumentException: The provided expression
// contains a InstanceMethodCallExpression1 which is not supported. FieldIdentifier only supports simple member
// accessors (fields, properties) of an object. See e.g. https://github.com/dotnet/aspnetcore/issues/12000
public class myBool
{
public bool IsChecked { get; set; }
// store the text shown in the checkbox items here as well
public myBool(bool init)
{
IsChecked = init;
}
}
// === for the radio buttons
public int SelectedRadio = 2;
public class RadioItem
{
public int Index;
public string Title;
public RadioItem(int index, string title)
{
this.Index = index;
this.Title = title;
}
}
public List<RadioItem> RadioItems = new()
{
new RadioItem(1, "Radio 1"),
new RadioItem(2, "Radio 2"),
new RadioItem(3, "Radio 3")
};
}
private void HandleValidSubmit()
{
// HandleValidSubmit called
}
}
Sorry for my english.
Data binding doesn't work.
All data correctly serializing and displayed, but if i try to change some value - nothing happens.
Klik() method working correctly, conditions works correctly.
Please, help.
HTML code
<div id="app">
<div class="areaInfo " v-for="area in mainObjects" v-on:click="klik(area)">
<div class="trDiv areaData">
<div class="tdDiv" v-for="(prop, key) in area" v-if="key != 'ChildData'">
{{key}}
<template v-if="key.includes('Start') || key.includes('End') ">
{{ ConvertJsonDateString(prop) }}
</template>
<template v-else-if="!key.includes('Id')">
{{ prop }}
</template>
</div>
<div class="tdDiv" > {{area.childSeen}}</div>
</div>
</div>
</div>
Script:
var mainObjects = #(Html.Raw(result.Content));
for (var i = 0; i < mainObjects.length; i++) {
mainObjects[i].childSeen = false;
for (var j = 0; j < mainObjects[i].ChildData.length; j++) {
mainObjects[i].ChildData[j].childSeen = false;
}
}
console.log(mainObjects);
var app = new Vue({
el: "#app",
data: mainObjects,
methods: {
klik: function (region) {
console.log(region.childSeen)
if (region.childSeen == false) {
console.log('wasFalse');
return region.childSeen = true;
}
return region.childSeen = false;
}
},
});
Model example:
public class Test
{
public string FirstName {get;set;}
public string LastName {get;set;}
public List<Rebenok> ChildData {get;set;}
}
public class Rebenok
{
public string FirstName {get;set;}
public string LastName {get;set;}
public List<Diagnosis> Diagnoses {get;set;}
}
public class Diagnosis
{
public string Name {get;set;}
public string Description {get;set;}
}
mainObjects reference is not changed. You need to deep copy to make Vue reactive
<div id="app">
<div class="areaInfo " v-for="(area, index) in mainObjects" v-on:click="klik(index)">
<div class="trDiv areaData">
<div class="tdDiv" v-for="(prop, key) in area" v-if="key != 'ChildData'">
{{key}}
<template v-if="key.includes('Start') || key.includes('End') ">
{{ ConvertJsonDateString(prop) }}
</template>
<template v-else-if="!key.includes('Id')">
{{ prop }}
</template>
</div>
<div class="tdDiv" > {{area.childSeen}}</div>
</div>
</div>
</div>
var app = new Vue({
el: "#app",
data () {
return {
mainObjects
}
},
methods: {
klik: function (index) {
const mainObjects = JSON.parse(JSON.stringify(mainObjects)) // deep copy
const region = mainObjects[index]
console.log(region.childSeen)
if (region.childSeen == false) {
console.log('wasFalse');
region.childSeen = true;
}
region.childSeen = false;
this.mainObjects = mainObjects // assign again
}
},
});
I am new in MVC asp.net i am developing a project where i want to add floors sequence wise. Want to start sequence from 0.
Message=LINQ to Entities does not recognize the method 'NTB.Floor LastOrDefault[Floor](System.Linq.IQueryable1[NTB.Floor], System.Linq.Expressions.Expression1[System.Func`2[NTB.Floor,System.Boolean]])' method, and this method cannot be translated into a store expression.
Controller:
public ActionResult Create(int id)
{
var cfloorno = 0;
//if (bid > 0)
//{
var lastfloor = db.Floors.Last(x => x.BuildingId == id);
if (lastfloor != null)
{
cfloorno = lastfloor.FloorNo.GetValueOrDefault() + 1;
}
//}
var building = db.Buildings.ToList();
ViewBag.Building = building;
var type = db.FloorTypes.ToList();
ViewBag.Type = type;
ViewBag.CurrentFloor = cfloorno;
ViewBag.buildingid = id;
return View();
}
[HttpPost]
public ActionResult Create(Floor f)
{
using (db)
{
db.Floors.Add(f);
db.SaveChanges();
}
return RedirectToAction("List");
}
View:
<input type="hidden" name="BuildingId" value="#ViewBag.buildingid" />
<div class="row">
<label class="col-sm-2 control-label">Floor #</label>
<div class="col-sm-10">
<input class="form-control" name="FloorNo" value="#ViewBag.CurrentFloor" disabled type="number">
#Html.ValidationMessageFor(model => model.FloorNo)
</div>
</div>
I'm trying to implement some custom EditorTemplates but they're only being rendered by my Create view, and not the Edit one.
Model
public class Page {
public int PageID { get; set; }
[DataType(DataType.Html)]
[AllowHtml]
// I tried including [UIHint("Html")] but this made no difference
public string Content { get; set; }
...
}
/Views/Shared/EditorTemplates/Html.cshtml
#model string
#Html.TextArea("", Model, new { #class = "html"})
/Views/Shared/EditorTemplates/Object.cshtml
#if (ViewData.TemplateInfo.TemplateDepth > 1)
{
#ViewData.ModelMetadata.SimpleDisplayText
} else {
#Html.ValidationSummary(false)
foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit
&& !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml) {
#Html.Editor(prop.PropertyName)
#prop.DataTypeName
} else {
<div class="form-field">
#if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())) {
#Html.Label(prop.PropertyName)
}
#Html.Editor(prop.PropertyName)
</div>
}
}
}
/Views/Page/Create.cshtml ( This correctly renders Html.cshtml )
#model MvcDisplayTemplates.Models.Page
#using (Html.BeginForm()) {
#Html.EditorForModel(Model)
<p><input type="submit" value="Create" /></p>
}
/Views/Page/Edit.cshtml ( This simply renders the default single line text editor )
#model MvcDisplayTemplates.Models.Page
#using (Html.BeginForm()) {
#Html.EditorForModel(Model)
<p><input type="submit" value="Save" /></p>
}
Interestingly, if I use EditorFor on Edit.cshtml then Html.cshtml is actually rendered. e.g.
#Html.EditorFor(model => model.Content)
UPDATE: If I delete object.cshtml then Html.cshtml is also rendered correctly. So this does seem to be an issue in Object.cshtml. It just seems odd that it works on one view but not another
I fixed by explicitly setting the template in Object.cshtml
#Html.Editor(prop.PropertyName, prop.TemplateHint ?? prop.DataTypeName)
Still not clear why it previously worked in one view but not the other though.