been trying to fix this for a few days now and just cant get it to work!
i have a radio button list which determines the output of a form - one of the radio buttons is to download X amount of files. this radio button has a text box for the user to enter the amount (X) they wish to download.
i only need this textbox to validate if the radio button that corresponds to it is selected - this is what i have so far but cannot get it to work. any help would be appriciated.
MODEL
public class myClass
{
[Required(ErrorMessage = "Please select the type of output you wish to generate")]
public int providerType { set; get; }
public int? numOutput { set; get; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (providerType == 2 && numOutput == null)
yield return new ValidationResult("Description must be supplied.");
}
}
CONTROLLER
[AcceptVerbs(HttpVerbs.Post)]
[HttpPost, ValidateInput(false)]
public ActionResult Spin(myClass theData)
{
int? ProviderType = Convert.ToInt16(Request.Form["providerType"]);
if (ModelState.IsValid)
{
//other random processing
}
}
VIEW
<ul>
<li>
<%=Html.RadioButton("ProviderType","1")%><label>Output A Single Article (With Visible HTML Tags)</label>
</li>
<li>
<%=Html.RadioButton("ProviderType","4")%><label>Output A Single Article (With HTML Pre Rendered - Not Recommended For Articles With Videos)</label>
</li>
<li>
<%=Html.RadioButton("ProviderType", "3")%><label>Output For Mass Submission</label>
</li>
<li>
<%=Html.RadioButton("ProviderType", "2")%><label>Download Several Copies Of Article (With Visible HTML Tags)</label>
How Many Artilces (Max 20)
<%= Html.TextBoxFor(Model => Model.numOutput)%>
<%= Html.ValidationMessageFor(Model => Model.numOutput)%>
im still new to MVC so im probably doing something stupid here - any help is much appriciated. The error im getting is that when i select the radio button and dont enter anything in the textbox "Input string was not in a correct format." so obviously the validation is not firing - cant quite figure out why.
The reason why its not hitting Validation is because your model is not implementing IValidatable Object.
e.g. public class MyClass:IValidatableObject
Let me know if that works for you. I was able to get the above code working locally and can send you the code if it still doesnt work for you.
Related
My project is VS-2022, .Net 6, C#, Blazor WASM hosted. For CRUD, we designed a razor page showing a list of "records" but needed a way to "page" for a list of 12 records on each page.
This post is NOT a question, but may be used by others seeking simple paging that works in similar projects. I hope this post helps others.
From this list screen, we could ADD or EDIT (delete in the future requirement). I had researched the web for how to do paging but the solutions were older and some used TagHelper which is no longer available in Blazor (or my knowledge is too weak to morph it).
So from these un-usable (in my case) solutions, I created a simple set of code to be placed on each LIST-for-CRUD-page. This process relies on threeList<> elements to hold the vehicle-data fetched from the DB once in OnParametersSetAsync() method. The paging-list is a subset of the main list of All-Vehicle-data (from the DB) and Filtered-Vehicle-data. The paging is extremely fast since paging takes place in the client-project after a single fetch of DB-data.
I show you the list page with the paging shown at the bottom of the list.
Below the image is the PagingInfo.cs class that holds the relevant paging variables. The "number of records per page" is a constructor value for this class so each CRUD-model-page can have a different number of records per page.
Followed by the pertinent HTML code that renders the paging buttons (only when there is more than a single page of 12 records).
Followed by the C#-code that drives the paging. What is NOT shown is typical Blazor code to fetch DB-data for the Vehicles and managing the Active/In-Active select control's events.
For each CRUD-model, in this case Vehicle CRUD, the C# code has a List<Vehicle> _AllVehicles variable that fetches DB-data-records for all vehicles.
A List<Vehicle> _FilteredVehicles variable that is filtered by CUSTOMER and by Active/In-Active vehicles (see the Active/In-Active select-control in the page-image).
AND a List<Vehicles> _PagedVehicles variable that contains the records to be displayed in the HTML-table (no code provided -- but see the page-image below) that is "computed/filtered" in the PageClicked() -method using Skip and Take filters for the _FilteredVehicles list.
Here is the paging-info object that is used by the page's code with information needed for the paging operations.
public class PagingInfo {
public PagingInfo(int pItemsPerPage = 8) {
ItemsPerPage = pItemsPerPage;
}
public int TotalItems { get; set; }
public int ItemsPerPage { get; set; }
public int CurrentPage { get; set; }
public int TotalPages {
get {
return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage);
}
}
}
Here is the HTML paging code below the table-body the list-"table" ( not shown here).
</tbody>
#if (_PagingInfo.TotalItems > _PagingInfo.ItemsPerPage) {
<tr class="bg-warning m-0 py-0">
<td colspan="3">
<div style="height: 3.1rem; text-align: center;" class="justify-content-center">
<div class="btn-group justify-content-center" role="group" aria-label="Basic outlined example">
<button id="btnPrev" type="button" class="btn btn-outline-dark border-3 fw-bold" #onclick="PagingClickPrevious">< Previous</button>
<span id="btnPage" type="button" class="btn btn-outline-dark disabled fw-bold">Page #_PagingInfo.CurrentPage of #_PagingInfo.TotalPages</span>
<button id="btnNext" type="button" class="btn btn-outline-dark border-3 fw-bold" #onclick="PagingClickNext">Next ></button>
</div>
</div>
</td>
</tr>
}
</table>
The paging-code follows:
// Paging Functions /////////////////////////////////////////////////////////////////////
PagingInfo _PagingInfo = new PagingInfo(12); // Initialize here: number of items-per-page.
private IEnumerable<Vehicle> _PagedVehicles { get; set; }
private void initializePagingInfo(int pPageNumber) { ////////////////////////////////////////////////////////////
_PagingInfo.CurrentPage = pPageNumber;
if (_FilteredVehicles is not null && _FilteredVehicles.Count() > 0)
_PagingInfo.TotalItems = _FilteredVehicles.Count();
}
private void PagingClicked(int pItemNumber = -1) { ////////////////////////////////////////////////////////////
_PagingInfo.TotalItems = _FilteredVehicles.Count(); // Get the count of items.
switch (pItemNumber) {
case < 1: // Goto previous page.
_PagingInfo.CurrentPage = _PagingInfo.CurrentPage < 2 ? 1 : --_PagingInfo.CurrentPage;
break;
case > 99: // Goto next page.
if (_PagingInfo.CurrentPage < _PagingInfo.TotalPages)
_PagingInfo.CurrentPage++;
break;
default:
_PagingInfo.CurrentPage = pItemNumber;
break;
}
_PagedVehicles = _FilteredVehicles.Skip((_PagingInfo.CurrentPage - 1) * _PagingInfo.ItemsPerPage).Take(_PagingInfo.ItemsPerPage).ToList();
}
private void PagingClickPrevious() { ////////////////////////////////////////////////////////////
// Event from the HTML-paging "previous" paging button -- pass -1 for previous page.
PagingClicked(-1);
}
private void PagingClickNext() { ////////////////////////////////////////////////////////////
// Event from the HTML-paging "next" paging button -- pass a large number for next page.
PagingClicked(999);
}
// end of paging functions /////////////////////////////////////////////////////////////////////////////////////
I would like to load a list of students at moment I have a signalr connection and the request gets all students. I'd like to scroll down the list and it would load a 100 students at a time?
<ul>
#if (Students != null)
{
#foreach (var student in Students)
{
<li>
<div class="treeview__item__header">
#plan.Name
</div>
</li>
}
}
</ul>
#code
{
private List<StudentsData> Students { get; set; }
protected override async Task OnInitializedAsync()
{
Students = await StudentsConnection.GetStudents();
}
}
I have used this example with success:
https://github.com/amuste/DnetVirtualScrolling
in alternative, as Alexander stated, a simple pager calculated on the number of rows to show and seems to be the best "pure blazor" solution
Edit: Looks like Virtualize could be perfect for this use:
https://www.syncfusion.com/blogs/post/asp-net-core-blazor-component-virtualization-in-net-5.aspx
I believe that you should consider solution on store procedure level with
Select top 100 where and have a parameter #next
With witch you could control from app with button or event when you scroll down to the end of list
you can use pagination and implement a pager component. Look at these two articles, they may help you: Pager component and part of Pager component 2
I am creating questionnaire application, in which I have to load random questions and get answers from the user.
I want to know, How I should iterate the records using the button.
I know how to display the data using foreach/for loop on the view, but the requirement is I have to display one question at a time, get the answer, store it in list, and when user press the "Next" button, I have to present the next question record to the user.
I would appreciate any help in this regard.
Thanks.
I have attached my sample code here:
VIEW MODEL CLASS:
public class ExamViewModel
{
public IEnumerable<Question> QuestionsList { get; set; }
public string Heading { get; set; }
public Question GetQuestionAtIndex(IEnumerable<Question> questionsList, int index = 0)
{
return questionsList.ElementAt(index);
}
}
VIEW:
<h2>#Model.Heading</h2>
<div class="panel-body">
#{
int index = 0;
var question = Model.GetQuestionAtIndex(Model.QuestionsList, index);
}
<div class="form-group">
<div class="col-sm-8">
<ul class="list-group">
<li class="list-group-item list-group-item-heading list-group-item-warning">
#question.QuestionText
</li>
</ul>
</div>
</div>
</div>
Here is a basic outline: https://dotnetfiddle.net/xKdwlK
In that example I just load all of the answers into an array and used ajax to send it to the controller. I didn't write the controller save method because I have no idea what you need there but you can fill that in.
This is just one way to do it but you could also use a form submission instead of ajax.
I have created a function for doing on validations on textbox entered value based on selection in drop downlist .. ..suppose If the dropdownlist selected item is amount then the entered value in textbox must be in between 10 t0 20 like this i have got two more validations.. for that purpose, I have got one textbox and one dropdwonlist and one button in my view
when i enter value in textbox as 30 and select the drop downlist item as "Amount" and then click on the submit button, the view is not showing any error message and ( if i left textbox as empty and then press submit button its should show error msg but its not showing) (I have written Custom functions for validating these ones on server side)
I have put a breakpoint at a postValues method but its not hitting ...
for that I have done like this (controller part)
public class CrossFieldsTxtboxesController : Controller
{
public ActionResult Index()
{
var itemsforDropdown = new List<SelectListItem> {
new SelectListItem{ Text = "Amount" , Value = "Amount"},
new SelectListItem{Text= "Pound", Value ="Pound"},
new SelectListItem {Text ="Percent", Value ="percent"}
};
ViewData["Listitems"] = itemsforDropdown;
return View("DdlCrossFields");
}
[HttpPost]
public ActionResult PostValues(CrossFieldValidation model)
{
if (!ModelState.IsValid)
{
return View(model);
}
else
{
return RedirectToAction("DdlCrossFields");
}
}
}
and this is my view part
#model MvcSampleApplication.Models.CrossFieldValidation
#{
ViewBag.Title = "DdlCrossFields";
}
<h2>DdlCrossFields</h2>
#using (Html.BeginForm())
{
<div class ="editor-field">
#Html.TextBoxFor(m => m.TxtCrossField)
#Html.ValidationMessageFor(m=>m.TxtCrossField)
</div>
#Html.DropDownList("Listitems",ViewData["Listitems"] as SelectList)
<input id="PostValues" type="Submit" value="PostValues" />
}
and this is my model part
public class CrossFieldValidation
{
public string DDlList1
{ get; set; }
[Required(ErrorMessage = "Quantity is required")]
[Display(Name= "Quantity:")]
[ValueMustbeInRange]
[NumericAttributes]
public string TxtCrossField
{ get; set; }
}
would any one have any idea why its not working for button click , any suggestions also would be grateful
many thanks..
I don't see any place where you specify an action that should handle POST request (PostValues in your case). You can use an overload of Html.BeginForm and specify POST action name explicitly:
Html.BeginForm("PostValues", "CrossFieldsTxtboxes")
If I'm right, your POST requests go to Index action and therefore ModelState.IsValid is not checked there.
You can use client side validation using jQuery Unobtrusive validation plugin. Please check if you have the following keys in your web.config file:
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
But note that custom validation attributes require additional JavaScript code to work on client.
I dont see where you are actually calling the method when you do the post - do you have a javscript click handler?
You need to have the method and controller you want to go to in your Html.BeginForm() or you can have something like the below in your view:
#Html.ActionLink("Post Values", "PostValues", "CrossFieldsTxtboxesController", null, new { #class = "postValuesButton", onclick = "return false;" })
Scenario
I have a parent/child model (to be exact a small questionnaire form and a one or more number of contacts). For historic reasons, all of this would have been done on the same form so user would have a form for the parent and one child and they would hit a button to add more children. Child has a few standard fields and the same with the parent, nothing fancy. Main requirement is that the data must not touch the database until all is valid and setup while I would have to go back to server for adding deleting children.
Implementation
It was very quick to get this working in ASP.NET MVC (using MVC 2 with VS 2010). I got two models, one for parent and one for the child and got only one controller. Controller has a Create Method which is a get and gets a default view with a fresh brand new parent containing one child. I use editor template for the child model which works nicely.
I have one HTML form which has a "save" and "add child" and I have "delete" button for each form. Since this cannot be stored in database, I store the temp model in the form itself and it goes back and forth between browser and server. Perfromance is not much of an issue here but the cost of development since there are quite a few of these forms - so please do not get distracted too much by suggesting an alternative approach although I appreciate comments anyway.
In order to find out which child to delete, I create temp GUID Ids and associate them with the child. This will go onto the HTML input's value for delete button (usual trick when you have multiple actions and the same form).
I have disabled caching.
Issue
Please have a look at the snippets below. I have debugged the code and I have seen always correct GUID being passed, correct item removed from the list in the controller and correct items being rendered in the template. BUT ALWAYS THE LAST ONE GETS DELETED!! I usually click the first delete and can see that the last gets deleted. I carry on and first item is the last being deleted.
Controller
public ActionResult Create()
{
EntryForm1 entryForm1 = new EntryForm1();
entryForm1.Children.Add(new Child("FILL ME", "FILL ME"){ TempId = Guid.NewGuid()});
return View("EntryForm1View", entryForm1);
}
[HttpPost]
public ActionResult Create(EntryForm1 form1, FormCollection collection, string add)
{
if (add == "add")
form1.Children.Add(new Child("FILL ME", "FILL ME") {TempId = Guid.NewGuid()});
var deletes = collection.AllKeys.Where(s => s.StartsWith("delete_"));
collection.Clear();
if (deletes.Count() > 0)
{
string delete = deletes.FirstOrDefault();
delete = delete.Replace("delete_", "");
Guid g = Guid.Parse(delete);
var Children = form1.Children.Where(x => x.TempId == g).ToArray();
foreach (Child child in Children)
{
form1.Children.Remove(child);
}
// HERE CORRECT ITEM IS DELETED, BELIEVE ME!!
}
if (ModelState.IsValid)
{
return Redirect("/");
}
return View("EntryForm1View", form1);
}
View snippet
<% for (int i = 0; i < Model.Children.Count;i++ )
{%>
<h4> <%: Html.EditorFor(m=>m.Children[i])%></h4>
<%
}%>
<p>
<input type="submit" value="Create" name="add" />
<input type="submit" value="add" name="add" />
</p>
Child Editor template snippet
<%: Html.HiddenFor(x=>x.TempId) %>
</span>
<input type="submit" name='delete_<%: Html.DisplayTextFor(m => m.TempId) %>' value="Delete" />
Many thanks for your time and attention
UPDATE
I was asked for model classes and I am sharing them as exactly as they are.
Entryform1 is the parent and Somesing is the child.
public class Somesing
{
public Somesing()
{
}
public Somesing(string o, string a) : this()
{
OneSing = o;
AnozerSing = a;
}
[StringLength(2)]
public string OneSing { get; set; }
[StringLength(2)]
public string AnozerSing { get; set; }
public Guid TempId { get; set; }
}
public class EntryForm1
{
public EntryForm1()
{
Sings = new List<Somesing>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public List<Somesing> Sings { get; set; }
}
I believe that problem lies with ModelState. When the view gets rendered, which I assume is where the issue lies, after the POST, the last value is not displayed i.e. removed from the view.
The issue is that Model.Children.Count will return the correct number of elements to display.
Lets break this down...
So if you have initially had 5 then removed the first one which is at index 0 based on the Guid, you now have items 4 items left with indexes 1 to 4.
However, when rendering the view after the post, the HtmlHelpers do not look at the values in model posted, but rather the values contained within the ModelState. So in the ModelState, item with index 0 still exists and since the loop is now looping to 4, the last element will not be displayed.
The solution, use ModelState.Clear()
OK, as Ahmad pointed out, ModelState is the key to the issue. It contains the collection as such:
FirstName
LastName
...
Sings[0].OneSing
Sings[0].AnozerSing
Sings[1].OneSing
Sings[1].AnozerSing
Sings[2].OneSing
Sings[2].AnozerSing
Now if I delete item 0 from the list, now the items will move up in the list and the data in the ModelState will go out of sync with the model. I had expected ASP.NET MVC to be clever enough to find out and re-order, but well that is asking for too much.
I actually implemented PRG (post-redirect-get) and by keeping the model in session, I was able to display correct information but again, this will remove all the validation in the collection and if model itself is valid, it will happily save and redirect back to home "/". Clearly this is not acceptable.
So one solution is to remove all items in the ModelState and then add a new entry for the model itself (with key of EmptyString). This can actually work alright if you populate it with error "Item deleted" as this will be displayed in the validation summary.
Another solution is to manually change the items in the model state and re-arrange them based on the new indexes. This is not easy but possible.
ModelState.Clear() will Solved this problem.
ModelState.Clear() is used to clear errors but it is also used to force the MVC engine to rebuild the model to be passed to your View.