Blazor resets bind value - binding

I'm having trouble understanding blazor binding. I have created a basic sample to illustrate the problem I'm facing:
Suppose I want to create a component to choose a day and time like this:
, with a Value property of type DateTime.
First, I create a component to hold the time input:
#* TimeInput.razor *#
<input type="time" #onchange=Changed value=#Value.ToString("HH:mm") required>
#code {
[Parameter] public DateTime Value { get; set; }
[Parameter] public EventCallback<DateTime> ValueChanged { get; set; }
public void Changed(ChangeEventArgs e)
{
Value = DateTime.Parse((string)e.Value!);
ValueChanged.InvokeAsync(Value);
}
}
Then I create a component with a select input and the TimeInput component created in the previous step:
#* DateTimeSelector.razor *#
<select #onchange=DateChanged>
#for (int i = 0; i < 7; i++)
{
var date = new DateTime(2022, 1, 1).AddDays(i);
<option value=#date.ToString("yyyy-MM-dd")
selected=#(Value.Date == date)>
#date.ToString("dddd")
</option>
}
</select>
<TimeInput Value=Value ValueChanged=TimeChanged />
#code
{
[Parameter] public EventCallback<DateTime> ValueChanged { get; set; }
private void DateChanged(ChangeEventArgs arg)
=> Value = DateTime.Parse((string)arg.Value!).Add(Value.TimeOfDay);
private void TimeChanged(DateTime time)
=> Value = Value.Date.Add(time.TimeOfDay);
private DateTime value;
[Parameter]
public DateTime Value
{
get => value;
set
{
Console.WriteLine($"Value is {value}"); // FOR DEBUGGING
if (this.value != value)
{
this.value = value;
ValueChanged.InvokeAsync();
}
}
}
}
Finally, I test it in a page:
#page "/"
<p><DateTimeSelector #bind-Value=#dateTime /></p>
<p>Debug: <input type="text" value=#dateTime /></p>
#code {
private DateTime dateTime = new DateTime(2022, 1, 3, 17, 0, 0);
}
When the page is loaded, the component shows with value 2022-01-03 17:00 as expected:
But as soon as the user change a value (for example, from 5 pm to 6 pm), then the value resets to 0001-01-01 00:00:
If i take a look at the console I see this:
So why is this happening? Who's calling (twice) the setter for property Value with a default date? How can I fix it?

I Have a Similar Issue And didn't Find the appropiate solution, so i was looking for other options and discover Blazorise, it's a component library which has modules like DateEdit or TimeEdit and solve the problem that you have, it's an alternative, could be not what you expect but it's pretty simple to use!
<Field>
<FieldLabel>Date</FieldLabel>
<DateEdit TValue="DateTime?" Placeholder="Select Date" #bind-Date="#input.date"></DateEdit>
</Field>

Well, just a small omission. In your DateTimeSelector, you have to invoke with the value.
ValueChanged.InvokeAsync(); => ValueChanged.InvokeAsync(value);

Related

DateOnly field not being populated from form .NET6 EF6 Razor Pages No MVC

For some reason the dates that I enter in the date-type inputs of the form are not getting into the database. Instead, after all the different methods I researched and tried, to no avail, the dates default to 01/01/01, which I understand to be the defualt min value yes? And in the postgres database, the date fields show "-infinity". I can use pgadmin query tool to update the dates successfully, but when I try to create or edit a record from the form, the above occurs.
When I get to the ModelState.IsValid line in the debugger, it is showing the data I entered correctly in the other fields, but the date fields are showing 01/01/01.
I have been through the docs and other forum posts but none of the fixes I attempted worked.
Any help would be greatly appreciated.
Here is the model
public class ToDo
{
[Key]
public int Id { get; set; }
public DateOnly CreateDate { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
public DateOnly DueDate { get; set; }
public bool Complete { get; set; }
}
... the post
public async Task<IActionResult> OnPost()
{
if (!ModelState.IsValid)
{
var errors = ModelState.SelectMany(x => x.Value.Errors.Select(z => z.Exception));
}
if (ModelState.IsValid)
{
await _db.Todo.AddAsync(Todo);
await _db.SaveChangesAsync();
TempData["success"] = "ToDo created successfully.";
return RedirectToPage("Index");
}
return Page();
}
... and the form
<form method="post">
<input hidden asp-for="Todo.Id" />
<div class="p-3 mt-4">
<div class="row pb-2">
<h2 class="text-primary pl-3">Create ToDo</h2>
<hr />
</div>
<div asp-validation-summary="All"></div>
<div class="mb-3">
<label asp-for="#Model.Todo.CreateDate"></label>
<input asp-for="#Model.Todo.CreateDate" class="form-control" type="date"/>
<span asp-validation-for="Todo.CreateDate" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="#Model.Todo.Name"></label>
<input asp-for="#Model.Todo.Name" class="form-control" />
<span asp-validation-for="Todo.Name" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="#Model.Todo.Description"></label>
<input asp-for="#Model.Todo.Description" class="form-control" />
<span asp-validation-for="Todo.Description" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="#Model.Todo.DueDate"></label>
<input asp-for="#Model.Todo.DueDate" class="form-control" type="date"/>
<span asp-validation-for="Todo.DueDate" class="text-danger"></span>
</div>
<div class="form-check m-4">
<input asp-for="#Model.Todo.Complete" class="form-check-input" type="checkbox"/>
<label class="form-check-label ms-3" asp-for="#Model.Todo.Complete">
Complete
</label>
</div>
<button type="submit" class="btn btn-outline-primary rounded-pill" style="width:150px;">Update</button>
<a asp-page="Index" class="btn btn-outline-secondary rounded-pill text-white" style="width:150px;">Back To List</a>
</div>
</form>
Thanks Mike that did the trick, here's what I did.
I applied your first solution,
[DataType(DataType.Date)]
public DateTime CreateDate { get; set;}
but I got this error:
"Cannot write DateTime with Kind=Local to PostgreSQL type 'timestamp
with time zone', only UTC is supported"
So I applied the following fix from this thread:
.NET6 and DateTime problem. Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone'
Here's what I did.
I created an "Extensions" folder in my project and within that I created a "UtcDateAnnotation.cs" file and pasted the following.
namespace ToDoRazorNoMvcPostgres.Extensions
{
public static class UtcDateAnnotation
{
private const string IsUtcAnnotation = "IsUtc";
private static readonly ValueConverter<DateTime, DateTime> UtcConverter = new ValueConverter<DateTime, DateTime>(convertTo => DateTime.SpecifyKind(convertTo, DateTimeKind.Utc), convertFrom => convertFrom);
public static PropertyBuilder<TProperty> IsUtc<TProperty>(this PropertyBuilder<TProperty> builder, bool isUtc = true) => builder.HasAnnotation(IsUtcAnnotation, isUtc);
public static bool IsUtc(this IMutableProperty property)
{
if (property != null && property.PropertyInfo != null)
{
var attribute = property.PropertyInfo.GetCustomAttribute<IsUtcAttribute>();
if (attribute is not null && attribute.IsUtc)
{
return true;
}
return ((bool?)property.FindAnnotation(IsUtcAnnotation)?.Value) ?? true;
}
return true;
}
/// <summary>
/// Make sure this is called after configuring all your entities.
/// </summary>
public static void ApplyUtcDateTimeConverter(this ModelBuilder builder)
{
foreach (var entityType in builder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (!property.IsUtc())
{
continue;
}
if (property.ClrType == typeof(DateTime) ||
property.ClrType == typeof(DateTime?))
{
property.SetValueConverter(UtcConverter);
}
}
}
}
}
public class IsUtcAttribute : Attribute
{
public IsUtcAttribute(bool isUtc = true) => this.IsUtc = isUtc;
public bool IsUtc { get; }
}
}
I corrected all the errors by adding the appropriate using statements etc.
Then I added the following in my DbContext file right after the public DbSet statement
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyUtcDateTimeConverter();//Put before seed data and after model creation
}
That did the trick, but it still showed the time as well, I just wanted to display the date.
I tried the below but it didn't work.
[DataType(DataType.Date)]`
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime CreateDate { get; set; }
Instead I added this to the view:
<td>#Convert.ToString(string.Format("{0:MM/dd/yyyy}", obj.CreateDate))</td>
That did the trick. Thanks again for the help!
You are using the new DateOnly type. The ASP.NET Core model binder does not support binding to DateOnly at the moment (https://github.com/dotnet/aspnetcore/issues/34591). Until .NET 7, you can use a DateTime type and data annotations to control the input type that gets rendered (https://www.learnrazorpages.com/razor-pages/forms/dates-and-times):
[DataType(DataType.Date)]
public DateTime CreateDate { get; set; }
Alternatively, you can extract the value from Request.Form yourself and assign it to the relevant property:
if(DateTime.TryParse(Request.Form["Todo.CreateDate"].ToString(), out var created))
{
Todo.CreateDate = DateOnly.FromDateTime(created);
}
else
{
ModelState.AddModelError("Todo.CreateDate", "A created date must be a date");
}

How to get unobtrusive client-side validation on a Required List<> of objects in MVC?

I have a model in which an employee contains a list of functions. The employee should at least have one function.
public class Employee
{
[Required(ErrorMessage = "Name is Required")]
public string Name { get; set; }
[Required(ErrorMessage = "Email is Required")]
[RegularExpression(#"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}" +
#"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
#".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
ErrorMessage = "Email is not valid")]
public string Email { get; set; }
[Required(ErrorMessage = "At least one function is required")]
public List<Function> Functions { get; set; }
}
public class Function
{
[Required(ErrorMessage = "Name is Required")]
public string Name { get; set; }
}
I've created an EditorTemplate for a Function
#model MvcClientSideValidation.Models.Function
<fieldset>
<legend>Functie</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
The Index view uses the EditorFor and a ValidationMessageFor.
#Html.EditorFor(m => m.Functions)
#Html.ValidationMessageFor(m => m.Functions)
The view also contains code to add or delete a function.
When the view is submitted, client-side validation does not check if a function is present. Server-side validation does. The problem is that when the list is empty, no input elements are rendered for the Function property, so there are no tags to which the validation tags can be added.
So I'm looking for an easy way to have unobtrusive client-side validation for a List with the [Required] attribute.
Edit: I just realized the [Required] attribute will probably only verify that Function is not null. It will not check if it contains any items. This is fine by me, as the property will become null automatically on postback.
Try this:
[RequiredEnumerable(ErrorMessage = "At least one function is required")]
[UIHint("FunctionCollection")] // -> EditorTemplate partial view, it ensures that the input is generated.
public List<Function> Functions { get; set; }
RequiredEnumerable class:
public class RequiredEnumerableAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
var enumerable = value as IEnumerable;
if (enumerable == null) return false;
IEnumerator enumerator = enumerable.GetEnumerator();
if (enumerator.MoveNext())
return true;
return false;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule { ValidationType = "requiredenumerable", ErrorMessage = ErrorMessageString };
}
}
FunctionCollection partial view (/Views/Shared/EditorTemplates/FunctionCollection.cshtml):
#model IEnumerable<Function>
#{
Function[] models = Model != null ? Model as Function[] ?? Model.ToArray() : new Function[0];
string value = Newtonsoft.Json.JsonConvert.SerializeObject(models.Select(x => x.Name));
}
#Html.Hidden(String.Empty, value)
#Html.ValidationMessage(String.Empty)
#if (models.Any())
{
<ul>
#foreach (Function f in models)
{
<li>#f.Name</li>
}
</ul>
}
#Ajax.ActionLink("Edit functions", "Index", "Function", new { selectedFunctions = value }, new AjaxOptions { })
in JavaScript:
<script>
// create and append method 'requiredenumerable' to $.validator (before $.ready)
$.validator.addMethod("requiredenumerable", function (value) {
value = $.trim(value);
if (!value.length) return false;
//assumes that value is either a json array or a string of comma separated values​​
var arr = value.indexOf("[") === 0 ? $.parseJSON(value) : value.split(",");
return arr && arr.length > 0;
});
$.validator.unobtrusive.adapters.addBool("requiredenumerable");
// as #Functions input is hidden so we need adapt the validator object (inside $.ready)
$(document).ready(function () {
var elemSelector = "#Functions";
var validator = $(elemSelector).closest("form").data("validator");
if (validator) {
validator.settings.ignore = ":hidden:not(" + elemSelector + ")";
}
});
</script>
It's been a few years and this post comes up for searches related to .NET Core, so I hope it's okay that I post an answer related to .NET Core... The idea should be the same in the stack in question.
We have several validations like this as well. Without much custom code you can add a property to your model like so:
public class Employee
{
/* other properties */
public List<Function> Functions { get; } = new List<Function>();
// add a "Count Validation" property against any required lists
[Range(1, int.MaxValue)]
public int FunctionsCount => Functions.Count;
}
HTML:
#model Employee
<ul>
#for (var i = 0; i < Model.Functions.Count; i++)
{
<li>
<input type="hidden" asp-for="Functions[i].Name" />
#f.Name
</li>
}
</ul>
#* Add the count input and validator *#
<input type="hidden" asp-for="FunctionsCount" />
<span asp-validation-for="FunctionsCount"></span>
Now unobtrusive-validation kicks in automatically to validate that FunctionsCount is greater or equal to 1. The only JS you will need is to set the count by hand whenever a function is added or removed:
$("#FunctionsCount").val($("li").length); // whatever selector gets you the number of functions. I used li since in my example the number of li elements is equivalent to the number of functions the user setup.
I think your problem is that you're allowing the list to be empty. If it truly requires a function to be present, then start with 1 item in the list, with nothing selected in the fields. Do not allow your code that adds or removes items to remove the last item, thus there will always be at least one and validation should work.

Get dynamic radio buttons values

I'm doing survey system on asp.net mvc 4. How can i get all radio buttons data in Controller ?
Example
$(document).ready(function(){
var AnswerCounter = 0;
$("#answer-add").click(function(){
var answertext = $("#answer-text").val();
var answerContent = $('<p>', {
class:'external',
});
var inputAnswer = $('<input>',{
id:'answer-'+AnswerCounter,
name:'rb',
class:'answer-radio'
}).attr("type","radio").appendTo(answerContent);
var labelAnswer = $('<label>').attr('for','answer-'+AnswerCounter).text(answertext).appendTo(answerContent);
$('#answers').append(answerContent);
AnswerCounter++;
});
});
You can see in the example buttons created by jquery. so i can use model binding.
Thanks.
The HTML typically geneated for MVC3 radiobuttons look like this
<input name="FeedingTime" id="FeedingTime" type="radio" value="Morning"/>
<input name="FeedingTime" id="FeedingTime" type="radio" value="Afternoon" checked="checked"/>
and when the radio button group posts, it will bind to the variable that matches the name.
[HttpPost]
public ActionResult Index (string FeedingTime){
//feeding time here is "Afternoon"
}
so to make your code bind correctly,
set the value of the input 'value' attribute in your script, and
have a variable in the ActionResult that matches the 'name' attribute in the html input element
EDIT: This answer is no longer relevant to the question being asked.
to get the value from radio buttons you can bind to them like you would a string variable; suppose in your view you have
#Html.LabelFor(m => m.FeedingTime,"Morning")
#Html.RadioButtonFor(m => m.FeedingTime, "Morning")<br />
#Html.LabelFor(m => m.FeedingTime,"Afternoon")
#Html.RadioButtonFor(m => m.FeedingTime, "Afternoon")<br />
#Html.LabelFor(m => m.FeedingTime,"Night")
#Html.RadioButtonFor(m => m.FeedingTime, "Night")
when you post back, you would capture this in a string variable
public ActionResult(..., string FeedingTime, ...){
}
normally this variable is embedded in a viewmodel such as
public class AnimalViewModel
{
public string Name { get; set; }
public string Type { get; set; }
public string FavoriteColor { get; set; }
public string FeedingTime { get; set; }
}
so that when you post
[HttpPost]
public ActionResult Index(AnimalViewModel viewModel){
...
}
it binds the viewmodel to the data automagically.
To answer your question more directly. I dont think theres a way to detect all radiobutton inputs on a page, you can only detect the results from that are posted from the radio button. There is no indication that the string posted was once the answer to a radiobutton. So, if you want to know the values of all radiobuttons on the page, you will simply have to check the values of the strings which are posted.

how to read date from calendar in MVC into Model property

I have Model properties
public DateTime SelectedDate { get; set; }
public DateTime SelectedDateTo { get; set; }
The view has
<input id="Testdate-Field" type="text" style="width: 125px;" value="#Html.DisplayTextFor(m=>m.SelectedDate)" /> To
<input id="TestdateTo-Field" type="text" style="width: 125px;" value="#Html.DisplayTextFor(m=>m.SelectedDateTo)" />
To show the calendar in the view I have
$("#Testdate-Field").bind("click", function(event) {
$( "#Testdate-Field").datepicker();
});
$("#TestdateTo-Field").bind("click", function(event) {
$( "#TestdateTo-Field").datepicker();
});
Now, 2 problems
When I click in the input box for the first time, the calendar doesn't appear till I click somewhere else and then in the input box for date.
When I pick a date from the calendars my model properties SelectedDate and SelectedDateTo do not change and remain the default values when I read them. What do I need to do to read the input dates?
---Update---
Added this in ready as per the suggestion
$("#Testdate-Field").datepicker();
$("#TestdateTo-Field").datepicker();
and the first problem is solved! Now how do I bind what I have selected in the datefield to my model properties?
For question 1, do not put your .datepicker() code in the bind event. Simply execute those on document ready:
<script>
jQuery(document).ready(function ($) {
$( "#Testdate-Field").datepicker();
$( "#TestdateTo-Field").datepicker();
});
</script>
For your second question, use Html helpers to generate your inputs and then in your controller you can easily bind them: instead of using DisplayTextFor inside of input tags YOU generate, use TextBoxFor to generate the input tags for you:
ViewModel:
public class SampleViewModel
{
// other properties here
public DateTime SelectedDate { get; set; }
public DateTime SelectedDateTo { get; set; }
// other properties here
}
View:
#model SampleClassViewModel
#using(Html.BeginForm())
{
#Html.TextBoxFor(m => m.SelectedDate, new { class = "datepickers" })
#Html.TextBoxFor(m => m.SelectedDateTo, new { class = "datepickers" })
<input type="submit" />
}
<script>
jQuery(document).ready(function ($) {
$( ".datepickers").datepicker();
});
</script>
There are many way to bind your properties, here are a couple...
Controller option 1:
[HttpPost]
public ActionResult SomeControllerAction(SampleViewModel model)
{
// do work...
}
Controller option 2:
[HttpPost]
public ActionResult SomeControllerAction()
{
var model = SomeServiceMethodToBuildModel();
this.TryUpdateModel(model);
// do work...
}
Thanks!

Why does the selected radio button at runtime differ from my programmatic setup?

I have data model classes as follows:
public class QuizItem
{
public int QuizItemId { get; set; }
public string Question { get; set; }
public IEnumerable<Choice> Choices { get; set; }
}
and
public class Choice
{
public int ChoiceId { get; set; }
public string Description { get; set; }
public bool IsCorrect { get; set; }
}
I made a setup in a controller action as follows:
public class HomeController : Controller
{
public ActionResult Index()
{
IEnumerable<Choice> choices = new Choice[]
{
new Choice{ChoiceId=1,Description="Black",IsCorrect=true},
new Choice{ChoiceId=2,Description="Red",IsCorrect=false},
new Choice{ChoiceId=3,Description="Yellow",IsCorrect=false}
};
QuizItem qi = new QuizItem { QuizItemId = 1,
Question = "What color is your hair?",
Choices = choices };
return View(qi);
}
The last, here is my view:
#model MvcApplication1.Models.QuizItem
#{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>QuizItem</legend>
<div class="display-label">
Question</div>
<div class="display-field">#Model.Question</div>
#foreach (var x in Model.Choices)
{
<text>#Html.RadioButtonFor(y => Model.QuizItemId, x.Description, new { #checked = x.IsCorrect })
#x.Description<br /></text>
}
</fieldset>
At runtime, the selected option should be Black. But Yellow gets selected. How to resolve this issue?
You need to set the #checked attribute to the string "checked", not true/false.
new { #checked = x.IsCorrect ? "checked" : string.Empty }
JK is right at the same time he is wrong.
The checked attribute should really be used with the "checked" value, the correct and valid W3C html markup for a checked radio is:
<input type="radion" name="something" value="1" checked="checked">
But, when you output this:
<input type="radion" name="something" value="1" checked="">
The browser still renders it as a checked radio. Your own solution is the best so far.
I find that the radio button helper is too much trouble to get to work correctly.
You are best off with writing the raw HTML for the the radio buttons yourself. This is especially true when you have multiple options:
<input type="radio" id="ques1_choice1" name="quizQuestion1" value="1" #(x.IsCorrect ? "checked=\"checked\"" : null) />
<input type="radio" id="ques1_choice2" name="quizQuestion1" value="2" #(x.IsCorrect ? "checked=\"checked\"" : null) />
I found the solution as follows:
<fieldset>
<legend>#Model.Question</legend>
#foreach (var x in Model.Choices)
{
<text>#Html.RadioButton(Model.QuizItemId.ToString(), x.Description, x.IsCorrect)
#x.Description<br /></text>
}
</fieldset>

Resources