The App: It is fed an order #, and the part ID, part #, and quantity for each part are displayed (so 3 input fields per line, per part). All those values appear as input fields that can be modified. In the PageModel, I store all those values in Lists and then iterate through them to find ones that have been modified. If I find modifications, I update the appropriate parts of the database. The app is to be used 100% in-house by people who know what they are doing. Everything I mentioned above is working swimmingly. The functionality is there.
The relevant front-end is this:
#{ int index = 0;}
#foreach (var part in Model.PartInfoList)
{
<tr class="row table-bordered">
<td class="col-4">
<input type="hidden" asp-for="OldPartIDList" value="#part.PART_ID" name="OldPartIDList"/> #*This holds the "old" part ID's so the order can still be looked up when the user wants to change the part ID.*#
<input type="text" class="form-control mb-2 mr-sm-2" asp-for="NewPartIDList" value="#part.PART_ID" id="#part.PART_ID" name="NewPartIDList[#index]" onchange="HighlightField(this)" />
#*The id here is used to determine if the field should be highlighted or not.*#
<span class="text-danger" asp-validation-for="NewPartIDList[index]"></span>
</td>
<td class="col-4">
<input type="text" class="form-control mb-2 mr-sm-2" asp-for="PartNumberList" value="#part.PART_NUMBER" id="#part.PART_NUMBER" name="PartNumberList[#index]" onchange="HighlightField(this)" />
#*The id here is used to determine if the field should be highlighted or not.*#
<span class="text-danger" asp-validation-for="PartNumberList[index]"></span>
</td>
<td class="col-4">
<input type="text" class="form-control mb-2 mr-sm-2" asp-for="QuantityList" value="#part.QUANTITY" id="#part.QUANTITY" name="QuantityList[#index]" onchange="HighlightField(this)" />
#*The id here is used to determine if the field should be highlighted or not.*#
<span class="text-danger" asp-validation-for="QuantityList[index]"></span>
</td>
</tr>
index++;
}
The lists are declared as such:
[DisplayName("Part ID"), BindProperty, Required]
//[Range(1, 999999999, ErrorMessage = "Please enter a number between 1 and 999,999,999.")]
[ResubmitSampleCenterOrderValidatorClass(FieldOptions.PART_ID, 1, 999999999, null)]
public List<int> NewPartIDList { get; set; }
[DisplayName("Part Number"), BindProperty, Required]
//[StringLength(48, MinimumLength = 1, ErrorMessage = "Please enter a part number.")]
//[ResubmitSampleCenterOrderValidatorClass(FieldOptions.PART_NUMBER, 1, 999999999, PartNumberArray: PartNumberList.ToArray())]
public List<string> PartNumberList { get; set; }
[DisplayName("Quantity"), BindProperty, Required]
//[Range(1, 99999, ErrorMessage = "Please enter a number between 1 and 99,999.")]
[ResubmitSampleCenterOrderValidatorClass(FieldOptions.QUANTITY, 1, 99999, null)]
public List<int> QuantityList { get; set; }
The comments are what I've been trying. More on that later.
The Problem: Validation, on the part number input fields specifically. I cannot use
[StringLength(48, MinimumLength = 1, ErrorMessage = "Please enter a part number.")]
because the code will try to convert the string list to a string and that obviously can't happen. I just want to:
1) Make sure each part # cannot exceed 48 characters.
2) Have the error show up below that exact input field and NOT the others (This is actually an issue with the part ID and quantity fields as well, but I care more about the part # fields right now.)
Things I've tried:
As you can see from the comments above, I tried normal validation and it did not work for the above reasons. I then decided to go to the custom validation route:
public class ResubmitSampleCenterOrderValidatorClass: ValidationAttribute
{
public FieldOptions FieldDescriptor { get; set; }
public int MinLength { get; set; }
public int MaxLength { get; set; }
public List<string> PartNumberList { get; set; }
public string[] PartNumberArray { get; set; }
public ResubmitSampleCenterOrderValidatorClass(FieldOptions FieldDescriptor, int MinLength, int MaxLength, string[] PartNumberArray)
{
this.FieldDescriptor = FieldDescriptor;
this.MinLength = MinLength;
this.MaxLength = MaxLength;
this.PartNumberArray = PartNumberArray;
PartNumberList = new List<string>();
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
switch(FieldDescriptor)
{
case FieldOptions.PART_NUMBER:
break;
}
return null;
}
}
FieldOptions is just an enum, of which PART_NUMBER is 1. Anyways, I couldn't find a way to pass the string list into the custom validator because it isn't and cannot be static. I also couldn't find a way to get my numbers out of value for the part ID and quantity, but that's a separate issue.
I also put indexes on the name attributes for my input fields on the front end, hoping that could eventually lead to a solution. It was a trick I used in a JS/jQuery app I wrote a few years ago to do something very similar. Is there a way to access specific elements from the back-end, so I could just iterate through each one of them and validate them myself?
I have googled and googled, but I can't find anyone else with this issue. Maybe I haven't found the magic search words yet. I feel that if my part # validation woes are solved, I can use that solution on the problems I'm having with the other two fields.
All ideas are welcome! Let me know if you need more clarification! Thank you and have a fantastic day!
EDIT: More information on the syntax error I get when I try to pass
public List<string> PartNumberList { get; set; }
to the custom validator class. The class itself is found above. The syntax error reads:
An object reference is required for the non-static field, method, or property 'ReviewOrderModel.PartNumberList'.
Related
I have a simple question, I would like to add a placeholder to InputNumber component. I tried this code but It didn't work.
//Code behind
public int? Hour { get; set; }
//razor page
<EditForm Model="FilteredEmployees">
<InputNumber #bind-Value="Hour" min="0" class="form-control" max="10" placeholder="Hour"/>
</EditForm>
Thanks for help.
Could be several reasons depending on how you have your code setup. Please try to add more code for a repeatable example, or show the specific error you are getting.
If this input box is displaying 0 instead of "Hour", it's most likely because you are using and int backing field instead of an int? backing field. I just double checked it and having a backing property of
public int? Hour { get; set; }
shows the placeholder text correctly when the textbox content is null.
If you are getting errors (eg null reference errors), it's most likely you are forgetting the EditForm. Please see https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-5.0
As a minimum example:
This works as expected for me, with or without the validation elements
#page "/"
<EditForm Model="#this">
#*<DataAnnotationsValidator />
<ValidationSummary />*#
<InputNumber #bind-Value="Hour" min="0" class="form-control" max="10" placeholder="Hour" />
</EditForm>
#code {
public int? Hour { get; set; }
}
I want to take only selected checkboxes. I tried this.
<input class="form-check-input" type="checkbox" name="Box" value="LDL" />
<label for="Box">LDL</label><br>
<input class="form-check-input" type="checkbox" name="Box" value="LDL" />
<label for="Box">LDL</label><br>`enter code here`
<input class="form-check-input" type="checkbox" name="Box" value="HDL" />
<label for="Box">HDL</label><br>
<div class="button-holder d-flex justify-content-center">
<button type="submit" class="btn btn-success">Send</button>
</div>
This is the Action
[HttpPost]
public IActionResult SendTest(ListParams listParams)
{
}
This is the DTO
public class CheckboxParams
{
public string Box { get; set; }
public bool IsChecked { get; set; }
}
public class ListParams
{
public ListParams()
{
this.AllCheckedParams = new List<CheckboxParams>();
}
public List<CheckboxParams> AllCheckedParams { get; set; }
}
In asp.net core, Model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix. Besides, the nested array matches the [index].property_name or property_name[index].property_name.
In your code, the name of the checkbox could not match the property in ListParams model. The correct way should be AllCheckedParams[index].box.
But for your scenario, your model design is not correct. The browser will only send the selected checkbox to the backend by default when form submit. If you choose the first two or last two(anyway,that is to say the index should be consecutive) checkbox, it works well. If you choose the first and third checkbox, it does not work any more. Because the name of them is AllCheckedParams[0].box and AllCheckedParams[2].box. They are not consecutive index of the array, so only the first checkbox could be matched and passed to the backend.
The correct way is that just change your backend received data like below:
[HttpPost]
public IActionResult SendTest(string[] box)
{
}
If you still want to use the model, you need change the model like below:
public class ListParams
{
public List<string> Box { get; set; }
}
Controller:
[HttpPost]
public IActionResult SendTest(ListParams listParams)
{
}
The HtmlHelper class provides two extension methods to generate a element in an ASP.NET MVC view. They are as follows:
CheckBox()
CheckBoxFor()
for creating form in razor page you can use
#model Student
#Html.CheckBoxFor(m => m.isActive)
The Html.CheckBox() is a loosely typed method which generates a with the specified name, isChecked boolean, and HTML attributes.
#Html.CheckBox("isActive", true)
I have a field in a form that I need to be required if a checkbox in the same form is checked.
There doesn't seem to be a way to do this with regular model validation, so I created a remote validation method in my controller.
The problem is, since the field isn't required always, the validation doesn't even fire if I put it on the field. So I tried putting the validation on the checkbox, and now I get a different problem where the validation doesn't fire when I add text to the field.
Is there a way to do what I'm needing with custom validation, or do I need to do something in JavaScript? If so, what do I need to do?
Form:
<form>
<input type="checkbox" asp-for="NotRecommended" checked=#Model.NotRecommended /> <label>Not Recommended</label>
<textarea class="form-control" id="Notes" asp-for="Notes"></textarea>
<span asp-validation-for="NotRecommended" class="text-danger"></span>
</form>
Model:
public class DetailsViewModel
{
[DisplayName("Not Recommended")]
[Remote("RequireNoteForFlag", AdditionalFields = "Notes", ErrorMessage = "Note is required when flagging someone.")]
public bool NotRecommended { get; set; }
[DataType(DataType.MultilineText)]
[MaxLength(1500)]
[DisplayName("Notes")]
public string Notes { get; set; }
}
Remote Validator
public IActionResult RequireNoteForFlag(bool NotRecommended, string Notes)
{
if (NotRecommended && String.IsNullOrWhiteSpace(Notes)) return Json("Note is required when flagging an Assessor");
else return Json(true);
}
Joe gave me pretty much the whole answer in the comments, but hasn't posted it as an answer, so I'll post it for anyone else who might need to do this.
Create the Attribute
https://github.com/joeaudette/cloudscribe/blob/master/src/cloudscribe.Web.Common/DataAnnotations/RequiredWhenAttribute.cs
Joe's Client-Side Validation
https://github.com/cloudscribe/cloudscribe/blob/master/src/cloudscribe.Web.StaticFiles/js/cloudscribe-validation-requiredwhen.js
Modified Client-Side Validation
jQuery.validator.addMethod("requiredwhen", function (value, element, param) {
var otherPropId = $(element).data('val-other');
if (otherPropId) {
var otherProp = $(otherPropId)[0].checked;
if (otherProp) {
return element.value.length > 0;
}
}
return true;
});
jQuery.validator.unobtrusive.adapters.addBool("requiredwhen");
View
<input type="checkbox" asp-for="NotRecommended" /> <label asp-for="NotRecommended"></label>
<textarea class="form-control" id="Notes" asp-for="Notes"></textarea>
<span asp-validation-for="NotRecommended" class="text-danger"></span>
Model
[DisplayName("Not Recommended")]
public bool NotRecommended { get; set; }
[DataType(DataType.MultilineText)]
[MaxLength(1500)]
[DisplayName("Notes")]
[RequiredWhen("NotRecommended", true, AllowEmptyStrings = false, ErrorMessage ="A note is required when flagging an Assessor.")]
public string Notes { get; set; }
I have implemented this with a custom validator in a similar way. However I applied the annotation to the field that would be required and included a condition in the arguments, eg:
[RequiredIf("NotRecommended = true")]
public string Notes { get; set; }
But, as you may already be aware, there is no way to cross-reference another property in data annotations so you will need to build in the javascript to handle this.
Unobtrusive validation adds a data-val="true" and data-val-required="error message" attribute to the input element so adding this in via javascript when the checkbox is checked will work.
You will also need to reinitialise the form validation so that the inputs with newly applied validation attributes are added.
I am creating a form to put data in a database. Now I want to use an array, because I want to put a form, inside a form. For example, you have a form where a user puts information about himself, like age name and so on. One question is about his friends, you must fill in the name and age off all your friends. Because you have more then one friend, you can add more than one friend. So you fill in the name and age of one friend, click on save, and then you can continue fill in the info of another friend. When you click save, you don't save the whole form.
I hope this I explaned it well.
This is my form:
public class User
{
[BsonElement("Id")]
public int Id { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
[BsonElement("Name")]
public string Name{ get; set; }
[BsonElement("Company")]
public string Company { get; set; }
[BsonElement("Array friends")]
}
View:
#model ISPInvoice.Models.Invoice
#using (Html.BeginForm("Edit", "Home"))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>New Note</legend>
<h3>New Note</h3>
<div class="editor-label">
#Html.LabelFor(model => model.InvoiceNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.InvoiceNumber)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Add another class called Friend
public class Friend
{
[BsonElement("Name")]
public string Name { get; set; }
[BsonElement("Age")]
public int Age{ get; set; }
}
In your User class add a List of Friends.
public class User
{
[BsonElement("Id")]
public int Id { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
[BsonElement("Name")]
public string Name{ get; set; }
[BsonElement("Company")]
public string Company { get; set; }
[BsonElement("Array friends")]
public List<Friend> Friends { get;set;}
}
In order to access the Friends list inside the View, you can loop through the list.
#foreach (namespace.Models.Friend friend in Model.Friends)
{
#Html.LabelFor(item => friend.Name)
}
It sounds like you'll need some client-side code.
What I'd probably do, as I understand your need, would be to have a hidden input in the form that you update with the friends that have been selected.
The trouble is that standard form encoding doesn't support hierarchical data, so you need to add support for that manually. That's not too hard, as you'll see, but it's a hassle. This would be super easy if you were using a framework like Angular, or even just posting with AJAX in general. But that's a pretty big change, that might not be worth going through and implementing just for this case.
This example depends on jQuery. It's also super simple, and doesn't support anything like removal or editing of existing friends. You should be able to add that stuff pretty easily, though. It also does absolutely no validation on anything, so take that how you will.
var friends = [];
function refreshHiddenField() {
$("#friendsListText").val(friends.map(function(d) { return d.name + "=" + d.age }).join(";"));
}
document.addEventListener("DOMContentLoaded", function(event) {
$("#btnAddFriend").click(function() {
var name = $("#newFriendName").val();
var age = $("#newFriendAge").val();
friends.push({ name : name, age: age });
refreshHiddenField();
var li = $("<li />").text(name + " (" + age + ")");
$("#friendsList").append(li);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- This will actually be an #Html.HiddenFor(m => m.FriendsText), or something -->
<input type="text" id="friendsListText" readonly placeholder="Add some friends first" />
<hr />
<input type="text" id="newFriendName" placeholder="Name" />
<input type="number" id="newFriendAge" placeholder="Age" />
<button type="button" id="btnAddFriend">Add!</button>
<br />
<ul id="friendsList">
</ul>
As you'll find playing with that (albeit very lightly tested) example, adding a friend appends the details to a user-visible list, and appends a machine-readable format into an input. Per the comment, this input is visible in this example to see what's happening, but it wouldn't be in production.
Once you've got your client-side code done, you just have to parse the hidden field on save. Assuming a Friend class with properties for string Name and int Age,
[HttpPost]
public ActionResult EditProfile(User m)
{
// Unrelated stuff
var friends = m.FriendsText.Split(';').Select(c => {
var args = c.Split('=');
return new Friend { Name = args[0], Age = int.Parse(args[1]) };
})
// Use the collection
// Unrelated stuff
}
This is just some pretty simple string manipulation with LINQ and the Split function to understand the machine-readable format from the client.
Note with this implementation that names can't have semicolons or equals signs in them.
Once you've got it all in, printing it out is easy. Just, yes, add a collection type to your model, then loop through with a foreach loop.
<ul>
#foreach (var f in Model.FriendsCollection)
{
<li>#f.Name (#f.Age)</li>
}
</ul>
I know that's not what you're talking about, but I offer this just as an example of how you would include a collection in the model, per your original question.
I have an Ajax.BeginForm() in a partial view that contains a CheckBox for a bool value. The model is as follows;
public class ViewBusinessAdd
{
[Required(ErrorMessage="Name must be supplied")]
[Display(Name = "Business Name")]
public string Name { get; set; }
[Required(ErrorMessage = "Contact must be supplied")]
[Display(Name = "Business Contact")]
public string Contact { get; set; }
[Display(Name = "Phone Number")]
public string Number { get; set; }
public string Postcode { get; set; }
public Dictionary<string, string> States { get; set; }
public string AddressRegion { get; set; }
public bool IsFacebookPost { get; set; }
public List<RecommendationViewAttribute> Attributes { get; set; }
}
The CheckBox is rendered using the Html helpers;
<div class="control-group">
<label class="control-label">
#Html.LabelFor(m => m.IsFacebookPost, "Post recommendation")
<img src="~/Content/images/f_logo.png" alt="Facebook" />
</label>
<div class="controls">
#Html.CheckBoxFor(m => m.IsFacebookPost)
</div>
</div>
This produces the following HTML when rendered;
<input data-val="true" data-val-required="The IsFacebookPost field is required." id="IsFacebookPost" name="IsFacebookPost" type="checkbox" value="true" /><input name="IsFacebookPost" type="hidden" value="false" />
When submitting the form with, it produces this error in Chrome;
Uncaught SyntaxError: Unexpected token u
If I remove the CheckBox the form submits without any error. If I convert this to a non-Ajax form, it also submits but that's not going to work with the page design unfortunately.
I'm absolutely stumped on this - I even changed this to a RadioButton and the same behavior exists. Does anyone have any ideas?
Edit: Forgot to add it's a Javascript error.
Edit: The error is coming from jQuery library on the return below;
parseJSON: function( data ) {
// Attempt to parse using the native JSON parser first
if ( window.JSON && window.JSON.parse ) {
return window.JSON.parse( data );
}
But this only happens if I use the Razor HTML helpers to generate the checkboxes.
I got bit by this and burned about 6 hours trying to figure out what the issue was. According to the Jquery developers this is intended behavior in 1.9.1. If you use the jQuery Migrate 1.1.1 plugin everything should work, other than the console warnings which I think can be turned off.
http://bugs.jquery.com/ticket/13412
See the bug report, which isn't actually a bug :)
Well it seems there was a bug introduced with jQuery 1.9.1. I've downgraded to 1.8.3 and the Razor helpers for the Checkboxes now work correctly. Steps to downgrade if anyone is interested;
Uninstall-Package jQuery -force
Install-Package jQuery -version 1.8.3