I am newbie in MVC Razor and I want to implement validation message on textboxes. Here I'm creating some textbox dynamically as follows:
View Code:
foreach (var items in (IEnumerable<System.Data.DataRow>)Model.UsersOfList)
{
#Html.TextBoxFor(m => m.LoginNameOfLoginInfoTab, new { #class = "textBox_LoginInfoAndPermission", #value = (Model.LoginNameOfLoginInfoTab = items["UserName"].ToString()), #id = ("txtUserLoginName" + Model.UsernameOfLoginInfoTab.Trim()) })
#Html.ValidationMessageFor(m => m.LoginNameOfLoginInfoTab, null, new { #class = "ErrorMessage" })
#Html.TextBoxFor(m => m.UsernameOfLoginInfoTab, new { #class = "textBox_LoginInfoAndPermission", #value = (Model.UsernameOfLoginInfoTab = items["FirstName"].ToString()), #id = ("txtUserName" + Model.UsernameOfLoginInfoTab.Trim()) })
#Html.ValidationMessageFor(m => m.UsernameOfLoginInfoTab, null, new { #class = "ErrorMessage" })
}
in the module I've written code for validation as follows:
[Required (ErrorMessage="*")]
public string UsernameOfLoginInfoTab
{
get;
set;
}
[Required(ErrorMessage = "*")]
public string LoginNameOfLoginInfoTab
{
get;
set;
}
Now when all textboxes has been created and when one validation message is displaying for first loop iteration textbox then it will automatically displaying in front of another textbox too which is created on second loop iteration.
Please tell me whats going wrong.
The problem is because the expression you're using in TextBoxFor and ValidationMessageFor, which is used by MVC to create a string name for the field and look up validation messages from ModelState, is always the same throughout the iteration of the loop.
Your approach here seems a bit flawed, so my answer is more comprehensive.
1) Make view models that structurally represent the info you are trying to display.
Fix your view models:
public class UserInfoViewModel
{
[Required (ErrorMessage="*")]
public string UserName { get; set; }
[Required(ErrorMessage = "*")]
public string LoginName { get; set; }
}
// I don't know if you actually need this or not, but your existing model may contain additional properties relevant to the view that I don't know about, so I'll keep it.
public class ListOfUsersViewModel
{
public IList<UserInfoViewModel> UsersOfList { get; set; }
}
Fix your action (I'm making this up here to illustrate a point):
public ActionResult ListOfUsers()
{
var users = GetUserDataRows(); // gets your collection of DataRows
var model = new ListOfUsersViewModel
{
UsersOfList = users.Select(row = new UserViewModel { UserName = row["FirstName"], LoginName = row["UserName"] }).ToList()
};
return View(model);
}
2) Now you can iterate through users in your view and create proper fields with validation messages.
Let's call this view ListOfUsers.cshtml. Include whatever other things you need in your view, but use a for loop instead.
#using(Html.BeginForm("ListOfUsers"))
{
<ul>
#for (var i = 0; i < Model.UsersOfList.Count; i++)
{
<li>
#Html.TextBoxFor(m.UsersOfList[i].LoginName, new {#class="textbox_LoginInfoAndPermission"})
#Html.ValidationMessageFor(m => m.UsersOfList[i].LoginName)
#Html.TextBoxFor(m.UsersOfList[i].UserName, new {#class="textbox_LoginInfoAndPermission"})
#Html.ValidationMessageFor(m => m.UsersOfList[i].UserName)
</li>
}
</ul>
<button type="submit">Submit changes</button>
}
This will result in HTML like this for each item (0 in the name and id will be the index of the user in the collection):
<li>
<input type="text" id="UsersOfList_0_LoginName" name="UsersOfList[0].LoginName" value="..." />
<span class="field-validation-valid" data-valmsg-for="UsersOfList_0_LoginName" ... ></span>
<input type="text" id="UsersOfList_0_UserName" name="UsersOfList[0].UserName" value="..." />
<span class="field-validation-valid" data-valmsg-for="UsersOfList_0_UserName" ... ></span>
</li>
3) Create an action to receive submitted changes. This action will automatically bind the submitted values to the model argument, and do validation for you. All you need to do is check ModelState.IsValid.
[HttpPost, ActionName("ListOfUsers")]
public ActionResult ListOfUsersPost(ListOfUsersViewModel model)
{
// at this point, model will be instantiated, complete with UsersOfList with values submitted by the user
if (ModelState.IsValid) // check to see if any users are missing required fields. if not...
{
// save the submitted changes, then redirect to a success page or whatever, like I do below
return RedirectToAction("UsersUpdated");
}
// if ModelState.IsValid is false, a required field or other validation failed. Just return the model and reuse the ListOfUsers view. Doing this will keep the values the user submitted, but also include the validation error messages so they can correct their errors and try submitting again
return View("ListOfUsers", model);
}
Related
I have an a href link to a page which adds a parameter to the link for example:
tsw/register-your-interest?Course=979
What I am trying to do is to extract the value in Course i.e 979 and display it in the view. When attempting with the below code, I only return with 0 rather than the course value expected. ideally I'd like to avoid using routes.
Here is the view:
<div class="contact" data-component="components/checkout">
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Model.Course;
}
And my controller:
public ActionResult CourseEnquiry(string Course)
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
model.Course = Request.QueryString["Course"];
return model
}
This is the View Model:
public class CourseEnquiryVM : PageContentVM
{
public List<OfficeLocation> OfficeLocations { get; set; }
public string Test { get; set; }
public string Course { get; set; }
public List<Source> SourceTypes { get; set; }
}
SOLUTION:
After some research and comments I've adjusted the code to the below which now retrieves the value as expected
#Html.HiddenFor(m => m.Course, new { Value = #HttpContext.Current.Request.QueryString["Course"]});
Thanks all
Based on the form code you provided you need to use #Html.HiddenFor(m => m.Course) instead of just #Model.Course. #Model.Course just displays the value as text instead of building a input element that will be sent back to your controller.
If your problem is with a link prior to the view you referenced above, here's what I'd expect to work:
View with link:
#model CourseEnquiryVM
#Html.ActionLink("MyLink","CourseEnquiry","CourseController", new {course = #Model.Course}, null)
CourseController:
public ActionResult CourseEnquiry(string course)
{
// course should have a value at this point
}
In your view, you are only displaying the value of Course.. which isn't able to be submitted. You need to incorporate the value of course with a form input element (textbox, checkbox, textarea, hidden, etc.).
I would highly suggest using EditorFor or Textboxfor, but because your controller action is expecting just a string parameter you could just use Editor or TextBox.
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Html.TextBox(Model.Course, null, new { #class = "form-control"});
<input type="submit" value="Submit" />
}
Then you should just be able to do this in your controller:
public ActionResult CourseEnquiry(string course) // parameter variables are camel-case
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
if(!string.IsNullOrWhiteSpace(course))
model.Course = course;
return model;
}
Let me know if this helps.
Html.CheckBox("SelectedStudents", false, new { #class = "check-item", id = x.Id, value = x.Id })
which produce
<input checked="checked" class="check-item" id="4507" name="SelectedStudents" value="4507" type="checkbox">
<input checked="checked" class="check-item" id="4507" name="SelectedStudents" value="4508" type="checkbox">
<input checked="checked" class="check-item" id="4507" name="SelectedStudents" value="4509" type="checkbox">
In mvc model I have
public IEnumerable<string> SelectedStudents { get; set; }
but when I post back, SelectedStudents are always null. Why?
In this howto http://benfoster.io/blog/checkbox-lists-in-aspnet-mvc
is written:
The ASP.NET MVC modelbinder is smart enough to map the selected items
to this property.
but in my example is always null. Why? How to write more checkboxes and bind it back
You should be using a strongly typed editor to be able to pass the result to the controller (Model binder).
I prefer to do it this way.
Model
public class YourViewModel
{
public List<SelectListItem> Students
{
get;
set;
}
}
Controller Get
Students= service.GetStudents(); //Fill the list
View
#for (var i = 0; i < Model.Students.Count; i++)
{
#Html.CheckBoxFor(m => m.Students[i].Selected)
#Html.HiddenFor(m => m.Students[i].Text)
#Html.HiddenFor(m => m.Students[i].Value)
<span>#Model.Students[i].Text</span>
}
Controller Post
[HttpPost]
public ActionResult Create(YourViewModel model)
{
foreach(var student in model.Students)
{
if(student.Selected) { // Do your logic}
}
}
Alternatively
You could use an array or List of string. A ListBox is used in this example.
public string[] SelectedStudents{ get; set; }
#Html.ListBoxFor(s => s.SelectedStudents, new MultiSelectList(Model.Students, "Value", "Text", Model.SelectedStudents), new { #class = "form-control", style = "height:250px; width:100%" })
See my answer here How to bind checkbox values to a list of ints?.
The nice thing about this is that it separates concerns between your controller and ui nicely. The html extension methods also create correct html using label and input for the checkbox. and there is no need for hidden fields.
Do you try it with CheckBoxListFor?? You need to associate the checkbox with model and should not have the same ID and name
#Html.CheckBoxListFor(model => model.SelectedSources, Model.SubscriptionSources)
You need to use a mutable type, like List<string>.
public List<string> SelectedStudents { get; set; }
In MVC I have a form that uses a lot of duplicate information. Basically they click a button and there is another "form"(text box drop down list etc) that pops up. When they click the submit button however all of that comes back with the same name. How would I go about either making the names different in the Post or be able to throw the items into a list?
My code:
#Html.TextBox("Textbox1", "", new { placeholder = "", size = "77", maxlength = "76" })
#Html.DropDownList("Country", ViewData["CountryOptions"] as SelectList,"Select", new { id = "Country"})</div>
<div class="pad5">
<span class="FieldLabel">Application *</span><span>#Html.DropDownListFor(model => model.ProductsID, Model.HowProductsAreUsedOptions, new { #class = "General", id = "General-1" })
#Html.DropDownListFor(model => model.ApplicationValue, Model.Options, "--Choose One--", new { id = "Option1", style = "display:none" })
</div>
These can be repeated up to 9 times by the time they submit. However this will give me this in the Post
FormID=8&Textbox1=This&Country=USA&ProductsID=1&ApplicationValue=2&
Textbox13=Is&Country=Canada&ProductsID=2&ApplicationValue=3&
Textbox14=A&Country=Canada&ProductsID=2&ApplicationValue=1&
Textbox15=Test&Country=Canada&ProductsID=1&ApplicationValue=8&
Textbox16=For&Country=Canada&ProductsID=2&ApplicationValue=1&
Textbox17=Stack&Country=USA&ProductsID=1&ApplicationValue=9&
Textbox18=Overflow&Country=USA&ProductsID=2&ApplicationValue=2
How can I make something so that way it will be able to seperate these into 7 different value sets instead of only giving me one of each?
If I were you I would create a strongly typed view model to represent your data.
One to represent each item with the properties within them.
public class FooModel
{
public string Text { get; set; }
public string Country { get;set;}
public int ProductsID { get; set; }
public int ApplicationValue { get; set; }
}
Then create a model to hold them and represent them
public class FooViewModel
{
public List<FooModel> Foos { get; set; }
}
You can then return an instance of FooViewModel from your controller.
Within your view you use the name indexing of the collection as follows:
#for (var i = 0; i < Model.Foos.Count; i++)
{
...
#Html.TextBoxFor(model => Model[i].Text)
#Html.DropDownListFor(model => Model[i]Country, ViewData["CountryOptions"] as SelectList,"Select")
#Html.HiddenFor(model => Model[i].ProductsID)
...
}
The HiddenFor's will post those values back too.
Now in your action you just need to change your parameter to take an instance of FooViewModel and they will all be populated server side.
For more info on it see here:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
I have a DropDownListFor control that I am wanting to show a display value that resides in a property within a model/class (this is the Rule class.) The view's model is actually a collection of these model/classes. However, when I select the item from the DropDownList, I want to send back the entire model as a parameter. I have this working perfectly with the following code, but the Name property within the parameter is coming back as null. The other properties all have appropriate values.
View Code:
#model List<StockTrader.Web.Data.Rule>
#{
ViewBag.Title = "Configure Rules";
}
<h2>#ViewBag.Title</h2>
<h4>Choose a rule to edit:</h4>
<form method="post" id="rulesform" action="SaveRules">
#Html.DropDownListFor(m => m.First().RuleID, new SelectList(Model.AsEnumerable(), "RuleID", "Name"))
<div style="margin-bottom: 15px;">
<label>Value:</label><br />
<input type="number" name="Value" style="margin-bottom: 15px;" /><br />
<button>Save Value</button>
</div>
Controller Code:
public ActionResult SaveRules(Rule model)
{
//do something
}
Rule Class:
public class Rule
{
public int RuleID { get; set; }
public string Name { get; set; }
public int Value { get; set; }
public bool IsDeleted { get; set; }
}
We do have Kendo controls, so if another control would be more appropriate, that is an option.
I would be glad to provide anymore code or information you might need.
Any thoughts or ideas?
EDIT:
So it turns out this is what I needed to do, the accepted answer got me to this point so I'm going to leave it checked.
View Code (w/script included):
#Html.DropDownListFor(m => m.First().RuleID, new SelectList(Model.AsEnumerable(), "RuleID", "Name"), new { id = "ruleid", #onchange = "CallChangefunc(this)" })
#Html.HiddenFor(m => m.First().Name, new { id = "rulename" })
function CallChangefunc(e) {
var name = e.options[e.selectedIndex].text;
$("#rulename").val(name);
}
You will need a hidden field for it,and use dropdownlist on change event on client side to update hidden field:
#Html.DropDownListFor(m => m.First().RuleID, new SelectList(Model.AsEnumerable(), "RuleID", "Name"),new { id= "ruleid" })
#Html.HiddenFor(m=>m.First().Name,new { id="rulename" })
and jquery code:
$("#ruleid").change(function(){
$("#rulename").val($(this).text());
});
Second option isif Rule collection is coming from database you can fetch RuleName by using id to by querying db in action.
it can be achieved by using UIHint
On your model class, on the RuleID property, add an annotation for UIHint. It basically lets you render a partial (cshtml) for the property. So, on the partial, you can have the template for generating the dropdwon with required styling. When Page is generated. Now you can use the same Html.DropDownListFor for RuleID and UI generates a dropdown for it.
This will avoid having additional jQuery code to get the dropdown value, and code is more concise and testable.
I am trying to understand how I should validate on the client sections of my MVC3 page independently and have come up with a simplyfied version of what I am trying to achieve.
If I use one form:
Pros: When I submit back to the "PostData" controller method I receive all data contained within the form. In this case both values "name" and "description", which means that I can instantiate "PersonHobbyModel" and assign the data I have received. I can either store in the database or I can return the same view.
Cons: I cant validate independently. So if "name" isn't completed and I complete "description" I can still submit the page. (This is a simplyfied version of what I am trying to do and I would have more fields than just "name" and "description")
With two forms:
Pros: I can validate independently.
Cons: The controller method only receives the subitted forms data which, in this case either "Persons name" or "Hobby description" which means that I can't recreate a full instance of "PersonHobbyModel".
This is the model:
public class Person {
[Display(Name = "Person name:")]
[Required(ErrorMessage = "Person name required.")]
public string Name { get; set; }
}
public class Hobby {
[Display(Name = "Hobby description:")]
[Required(ErrorMessage = "Hobby description required.")]
public string Description { get; set; }
}
public class PersonHobbyModel {
public PersonHobbyModel() {
this.Person = new Person();
this.Hobby = new Hobby();
}
public Person Person { get; set; }
public Hobby Hobby { get; set; }
}
This is the controller:
public class PersonHobbyController : Controller
{
//
// GET: /PersonHobby/
public ActionResult Index()
{
var model = new PersonHobbyModel();
return View(model);
}
public ActionResult PostData(FormCollection data) {
var model = new PersonHobbyModel();
TryUpdateModel(model.Person, "Person");
TryUpdateModel(model.Hobby,"Hobby");
return View("Index", model);
}
}
This is the view:
#model MultipleFORMStest.PersonHobbyModel
#{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
#using (Html.BeginForm("PostData", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Person.Name)
#Html.TextBoxFor(model => model.Person.Name)
#Html.ValidationMessageFor(model => model.Person.Name)
<input type="submit" value="Submit person" />
</div>
}
#using (Html.BeginForm("PostData", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Hobby.Description)
#Html.TextBoxFor(model => model.Hobby.Description)
#Html.ValidationMessageFor(model => model.Hobby.Description)
<input type="submit" value="Submit hobby" />
</div>
}
UPDATE 1
I didnt mention, as I wanted to keep the question as simple as possible, but for one of the sections I am using "jquery ui dialog". I initially used a DIV to define the dialog, which I had inside my main form. This would of caused one problem as I wouldn't have been able to validate on the client the "JQuery dialog form" independently from the rest of the form.
Saying this jquery did removed the "div jquery ui dialog" from the main form which made me include the dialog in it's own form. For this reason I have ended up with two forms. The advantage is that I can now independently validate the "jquery dialog ui form".
But I am confused as to how should I handle on the server data submited from various forms on the client as there is a chance that the user has JS disabled. If I submit from one form I can't access the data in other forms.
UPDATE 2
Thanks for the replies. I believe I do need two forms and two entities as I want to validate them independently on the client, (apart from being kind of forced to by "Jquery UI Dialog"). For instance if I have, instead of one hobby I have a list of hobbies, which I could posible display in a grid in the same view. So I could not fill in the person name, but continue to add hobbies to the grid, If I do not complete the hobby description I'd get a validation error. (Sorry as I should of included both of my updates in the initial question but for the purpose of clarity I wanted to keep it as simple as posible)
From my perspective, you have a single view model that corresponds to two entity models. In your place I would use a single form and validate the view model and not really think about it as two (dependent) entities. Receive back the view model in your action, instead of a generic form collection, and use model-based validation via data annotation attributes. Once you have a valid, posted model you can then translate that into the appropriate entities and save it to the database.
Model
public class PersonHobbyViewModel {
[Display(Name = "Person name:")]
[Required(ErrorMessage = "Person name required.")]
public string Name { get; set; }
[Display(Name = "Hobby description:")]
[Required(ErrorMessage = "Hobby description required.")]
public string Description { get; set; }
}
Controller
public class PersonHobbyController : Controller
{
//
// GET: /PersonHobby/
[HttpGet] // mark as accepting only GET
public ActionResult Create() // Index should probably provide some summary of people and hobbies
{
var model = new PersonHobbyViewModel();
return View(model);
}
[HttpPost] // mark as accepting only POST
public ActionResult Create(PersonHobbyViewModel model) {
if (ModelState.IsValid) {
var person = new Person { Name = model.Name };
var hobby = new Hobby { Description = model.Description };
person.Hobbies = new List<Hobby> { hobby };
db.Persons.Add( person );
db.SaveChanges();
}
return RedirectToAction( "details", new { id = person.Id } ); // view the newly created entity
}
}
View
#model MultipleFORMStest.PersonHobbyViewModel
#{
ViewBag.Title = "Create";
}
<h2>
Create</h2>
#using (Html.BeginForm("Create", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Person.Name)
#Html.TextBoxFor(model => model.Person.Name)
#Html.ValidationMessageFor(model => model.Person.Name)
<input type="submit" value="Submit person" />
</div>
<div>
#Html.LabelFor(model => model.Hobby.Description)
#Html.TextBoxFor(model => model.Hobby.Description)
#Html.ValidationMessageFor(model => model.Hobby.Description)
<input type="submit" value="Submit hobby" />
</div>
}
I think your ViewModel should be only only specific to that view you are representing. In this case, i would use a ViewModel like this
public class AddPersonHobbyViewModel
{
[Required]
[Display (Name="Person Name")]
public string PersonName { set;get;}
[Required]
[Display (Name="Hobby Description")]
public string HobbyDescription { set;get;}
}
And in my PostData ActionMethod, I will check for Model Validation
[HttpPost]
public ActionResult PostData(AddPersonHobbyViewModel objVM)
{
if(ModelState.IsValid)
{
// Everything is fine. Lets save and redirect to another get View( for PRG pattern)
}
return View(objVm);
}
And you use only one Form in your View which is strongly typed to AddPersonHobbyViewModel
#model AddPersonHobbyViewModel
#using (Html.BeginForm("PostData","Person"))
{
#Html.TextBoxFor(m=>m.PersonName)
#Html.ValidationMessageFor(m => m.PersonName)
#Html.TextBoxFor(m=>m.HobbyDescription )
#Html.ValidationMessageFor(m => m.HobbyDescription )
<input type="submit" value="Save" />
}