First off, let me say that I'm new to MVC so if something seem strange then that's why. I probobly don't have the right aproach so I would like some guidelines
I have a form from which the user is suppose to enter translations to a page. The translations are handled as a list of TranslationObjects to the page object. I need a way to render input fields in the form for each language branch represented on the site and let the user fill those out and post them back to the controller.
This example is just a simplification of a more complex model, but it explains the problem pretty well.
The viewmodel:
public class Page
{
public List<TranslationObject> Translation { get; set; }
}
public class TranslationObject
{
public string LanguageBranch { get; set; }
public string PageName { get; set; }
public string PageDescription { get; set; }
}
Controller for rendering the form:
public ActionResult AddPage()
{
var model = new Page {Translation = new List<TranslationObject>()};
foreach (var languageBranch in new[] {"en", "sv", "de"}) // These are normally loaded from database
{
model.Translation.Add(new TranslationObject{ LanguageBranch = languageBranch});
}
return View(model);
}
View:
#model MvcApplication1.Models.Page
#using (Html.BeginForm("SubmitPage", "Home", FormMethod.Post))
{
foreach (var translation in Model.Translation)
{
#Html.LabelFor(x => x.Translation.FirstOrDefault(y => y.LanguageBranch == translation.LanguageBranch).PageName)
#Html.TextBoxFor(x => x.Translation.FirstOrDefault(y => y.LanguageBranch == translation.LanguageBranch).PageName)<br/>
#Html.LabelFor(x => x.Translation.FirstOrDefault(y => y.LanguageBranch == translation.LanguageBranch).PageDescription)
#Html.TextBoxFor(x => x.Translation.FirstOrDefault(y => y.LanguageBranch == translation.LanguageBranch).PageDescription)
}
<input type="submit" value="Submit"/>
}
And controller for the post:
[HttpPost]
public ActionResult SubmitPage(Page model)
{
// Save model to db
return View();
}
The translation object of the Page model is always null. I'm aware that this is probobly the wrong aproach so I'm asking for some direction to render a list of objects and returning them to the controller when posting
Use for instead of foreach.
#model MvcApplication1.Models.Page
#using (Html.BeginForm("SubmitPage", "Home", FormMethod.Post))
{
for(int i=0;i<Model.Translation.Count;i++)
{
#Html.LabelFor(x => x.Translation[i].PageName)
#Html.TextBoxFor(x => x.Translation[i].PageName)<br/>
#Html.LabelFor(x => x.Translation[i].PageDescription)
#Html.TextBoxFor(x => x.Translation[i].PageDescription)
}
<input type="submit" value="Submit"/>
}
And in Page Constructor initialize the Translation
Related
I have this Model:
public class ClassRoom
{
public List<Student> Students { get; set; }
}
public class Student
{
public int ID { get; set; }
public int Type { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
The values of the ID and Type are already full, I need to create a view where the student can add his name and last name.
So I need to loop the list of students in the view.
I am doing the following:
#model Models.ClassRoom
#{
ViewBag.Title = "Classroom";
}
#if (Model != null)
{
<form action="Controller/Method">
foreach (var item in Model.Students)
{
#Html.HiddenFor(model => model.ID)
<div>
#Html.TextBoxFor(model => model.Name)
#Html.TextBoxFor(model => model.LastName)
</div>
}
<input type="submit">
</form>
}
I want to eventually submit a model of type Classroom with a list of students filled with Name and Last Name for each ID
But this is not working.
How can I bind values From the View to item on a certain index in a list?
For each ID in the hidden input,I want to save the written name and last name.
Please help
I need to create a form and submit the ClassRoom with a full List of Students eventually. What should be the types in my Controller method and views?
If you want to send your model back to controller, then you would need to generate naming correctly. There are several ways, one of them would look like this:
#using (Html.BeginForm("Index", "Home")) {
<div style="margin-top: 100px;">
#if (Model != null) {
for (var i = 0; i <= Model.Students.Count - 1; i++) {
#Html.HiddenFor(model => model.Students[i].ID)
<div>
#Html.TextBoxFor(model => model.Students[i].Name)
#Html.TextBoxFor(model => model.Students[i].LastName)
</div>
}
}
<input type="submit" value="Go"/>
</div>
}
And in controller dont forget to add:
[HttpGet]
public async Task<IActionResult> Index() {
var classroom = new ClassRoom();
... //add some students to the classroom
return View(classroom);
}
[HttpPost]
public async Task<IActionResult> Index(ClassRoom classRoom) {
...
}
Here can be found some more reading.
I have been following a article on Visual Studio Magazine and have found that for some reason I am getting duplicates for Rules in the drop down on my page. Could someone please explain what I am missing here? Also I would like to know how I could fix this issue.
Upon closer inspection I believe the issue lies with the Validators line as it appears to be reading in 4 objects rather than 2 (a Phone validator and an Email validator). The Email validator code is essentially the same as the Phone but using a different regex.
Controller code:
[ImportMany]
public IEnumerable<Lazy<BusinessRules.IValidate<string>, BusinessRules.IValidateMetaData>> Validators { get; private set; }
[HttpGet]
public ActionResult Index()
{
var vm = new ValidationFormModel();
vm.Rules = new List<SelectListItem>(from v in Validators
select new SelectListItem()
{ Text = v.Metadata.Name,
Value = v.Metadata.Name });
return View(vm);
}
View Model code:
public class ValidationFormModel
{
public string Input { get; set; }
public List<SelectListItem> Rules { get; set; }
public string Rule { get; set; }
public string StatusLabel { get; set; }
}
Business Rules Code
[Export(typeof(IValidate<string>))]
[ExportMetadata("Name", "Phone")]
public class ValidatePhone : IValidate<string>
{
const string PHONE_PATTERN = #"^((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}$";
public ValidationResult Validate(string input)
{
var result = new ValidationResult();
if (input == null || !Regex.IsMatch(input, PHONE_PATTERN))
{
result.ErrorMessage = string.Format("{0} is not a valid phone number");
}
else
{
result.IsValid = true;
}
return result;
}
}
The View:
#model ValidationExample.ViewModels.ValidationFormModel
#{
ViewBag.Title = "MEF Demo";
}
#using (Html.BeginForm())
{
<strong>#Html.DisplayFor(m => m.StatusLabel)</strong>
#Html.ValidationSummary(false)
<fieldset>
<legend>Validation Demo</legend>
#Html.LabelFor(m => m.Input)
#Html.TextBoxFor(m => m.Input)
#Html.LabelFor(m => m.Rule)
#Html.DropDownListFor(m => m.Rule, Model.Rules)
</fieldset>
<input type="submit" />
}
From the view, try using IEnumerable
#model IEnumerable<ValidationExample.ViewModels.ValidationFormModel>
and try changing your dropdown list to a normal <select>
<fieldset>
<legend>Validation Demo</legend>
#Html.LabelFor(m => m.Input)
#Html.TextBoxFor(m => m.Input)
#Html.LabelFor(m => m.Rule)
foreach(var item in Model)
{
<select>#item.Rule </select>
}
</fieldset>
We have a list of action links
Partial View
#foreach (var item in Model.Regions) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.RegionName)
</td>
<td>
<input type="submit" value="Select" />
</td>
#Html.HiddenFor(modelItem => Model.Id)
</tr>
}
</table>
I assume that this isn't the correct way to do this, but if you could point me in the right direction it would be appreciated. I want to submit this data into an existing form
Region View
#using (Html.BeginForm()){
<fieldset>
#Html.Partial("_RegionsPartial");
<legend>Create new region</legend>
<ol>
<li>#Html.LabelFor(m => m.RegionName)</li>
<li>#Html.EditorFor(m => m.RegionName)</li>
</ol>
<input type="submit" value="Next" />
#Html.HiddenFor(model => model.RegionId)
</fieldset>
}
So you can either submit a new one or submit an existing one. Im not sure how to get the id of an existing one into my model. Here is the controller:
public ActionResult Region()
{
var model = new WizardModel();
var getRegions = _facade.FetchRegion();
model.Regions = getRegions;
return View(model);
}
[HttpPost]
public ActionResult Region(WizardModel model)
{
if (model.RegionName != null)
{
var newRegion = _facade.CreateRegion(model.RegionName);
model.RegionId = newRegion.Id;
}
else
{
model.RegionName = _facade.FetchRegion(model.RegionId).RegionName;
}
TempData["suburbModel"] = model;
return RedirectToAction("Suburb");
}
Thanks for taking the time
So heres my example of passing an instance of a model. I've got a view with many courses so I need to click a button and fire an action, thus carrying all data (including relevant ID) of the course clicked. So in the end I carry the instance I need with the hidden fields.:)
My course model...
public class CourseModel
{
public int RecordId { get; set; }
public string StudentNameField { get; set; }
public string SubjectField { get; set; }
public string CatalogField { get; set; }
public string SectionField { get; set; }
public string InstrNameField { get; set; }
public string MtgStartField { get; set; }
public string MtgEndField { get; set; }
}
My main View...Called "CourseList" in Views folder
<div id="container">
<div class="selectLabel">Select a Course:</div><br />
#foreach (var item in Model)
{
#Html.DisplayFor(model=>item)
}
</div>
My Display template - Its a view called "CourseModel" in Shared\DisplayTemplates ...For your display template, you could make a unique model for existing & new. Using your "existing" model in the displaytemplate, it results in multiple forms, each using a button type=submit to submit the form with model instance. Use CSS to model the button like a link. If you still need to use actionlink, carry the iD as one of the params.
#using LecExamRes.Helpers
#model LecExamRes.Models.SelectionModel.CourseModel
#using (Html.BeginForm("CourseList", "Home", null, FormMethod.Post))
{
<div class="mlink">
#Html.AntiForgeryToken()
#Html.EncryptedHiddenFor(model => model.RecordId)
#Html.EncryptedHiddenFor(model => model.CatalogField)
#Html.EncryptedHiddenFor(model => model.SectionField)
#Html.EncryptedHiddenFor(model => model.SubjectField)
#Html.EncryptedHiddenFor(model => model.InstrNameField)
#Html.EncryptedHiddenFor(model => model.MtgStartField)
#Html.EncryptedHiddenFor(model => model.MtgEndField)
<p>
<input type="submit" name="gbtn" class="groovybutton" value="#Model.SubjectField - #Model.CatalogField - #Model.SectionField : #Model.InstrNameField">
</p>
</div>
}
My controller, Courselist [POST] Action...
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult CourseList(SelectionModel.CourseModel model)
{
//....do something with my model
}
I have a problem while passing an object with HttpPost...
Once the form is submitted, the model is set "null" on the controller side, and I don't know where is the issue..
Here is my controller :
public ActionResult AddUser(int id = 0)
{
Group group = db.Groups.Find(id);
List<User> finalList = db.Users.ToList() ;
return View(new AddUserTemplate()
{
group = group,
users = finalList
});
//Everything is fine here, the object is greatly submitted to the view
}
[HttpPost]
public ActionResult AddUser(AddUserTemplate addusertemplate)
{
//Everytime we get in, "addusertemplate" is NULL
if (ModelState.IsValid)
{
//the model is null
}
return View(addusertemplate);
}
Here is AddUserTemplate.cs :
public class AddUserTemplate
{
public Group group { get; set; }
public User selectedUser { get; set; }
public ICollection<User> users { get; set; }
}
Here is the form which return a null value to the controller (note that the dropdown list is greatly populated with the good values) :
#using (Html.BeginForm()) {
<fieldset>
<legend>Add an user</legend>
#Html.HiddenFor(model => model.group)
#Html.HiddenFor(model => model.users)
<div class="editor-field">
//Here, we select an user from Model.users list
#Html.DropDownListFor(model => model.selectedUser, new SelectList(Model.users))
</div>
<p>
<input type="submit" value="Add" />
</p>
</fieldset>
}
Thanks a lot for your help
I tried your code and in my case the addusertemplate model was not null, but its properties were all null.
That's because of a few model binding issues: Html.HiddenFor and Html.DropDownListFor do not work with complex types (such as Group or User) (at least that's how it is by default).
Also, Html.HiddenFor cannot handle collections.
Here's how to solve these issues:
instead of #Html.HiddenFor(model => model.group) there should be one #Html.HiddenFor for each property of the group that you need bound
instead of #Html.HiddenFor(model => model.users) you need to iterate through the list of users and for each object add #Html.HiddenFor for each property of the user that you need bound
instead of #Html.DropDownListFor(model => model.selectedUser [...], create a property like int SelectedUserId {get;set;} and use that in the DropDownList (as it cannot handle complex types).
Here's the code that works:
1. The User and Group classes, as I imagined them to be:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
}
2. The adjusted AddUserTemplate class:
public class AddUserTemplate
{
public Group Group { get; set; }
public IList<User> Users { get; set; }
public int SelectedUserId { get; set; }
public User SelectedUser
{
get { return Users.Single(u => u.Id == SelectedUserId); }
}
}
The adjustments:
Users was changed from ICollection to IList, because we'll need to access elements by their indexes (see the view code)
added SelectedUserId property, that will be used in the DropDownList
the SelectedUser is not a readonly property, that returns the currently selected User.
3. The adjusted code for the view:
#using (Html.BeginForm())
{
<fieldset>
<legend>Add an user</legend>
#*Hidden elements for the group object*#
#Html.HiddenFor(model => model.Group.Id)
#Html.HiddenFor(model => model.Group.Name)
#*Hidden elements for each user object in the users IList*#
#for (var i = 0; i < Model.Users.Count; i++)
{
#Html.HiddenFor(m => m.Users[i].Id)
#Html.HiddenFor(m => m.Users[i].Name)
}
<div class="editor-field">
#*Here, we select an user from Model.users list*#
#Html.DropDownListFor(model => model.SelectedUserId, new SelectList(Model.Users, "Id", "Name"))
</div>
<p>
<input type="submit" value="Add" />
</p>
</fieldset>
}
Another option that does not require a bunch of hidden fields is to simply specify that you want the model passed to the controller. I think this is much cleaner.
#using(Html. BeginForm("action","controller", Model, FormMethod.Post)){
...
}
This is my first post so please go easy on me fellas. I am trying to implement a create form that utilizes jquery autocomplete. The create form allows users to enter data that will be saved to my database, via a submit button. Here is my code:
Controller
// GET: /Inspection1/Create
public ActionResult Create()
{
InspectionInfo model = new InspectionInfo
{
Submitted = DateTime.Now,
Contact = new Contact()
};
ViewBag.CountyName = new SelectList(db.Counties, "CountyName", "CountyName");
return View(model);
}
//
// POST: /Inspection1/Create
[HttpPost]
public ActionResult Create(InspectionInfo inspectioninfo)
{
if (ModelState.IsValid)
{
db.InspectionInfos.Add(inspectioninfo);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(inspectioninfo);
}
// this allows for autocompletion behavior
public ActionResult QuickSearchContact(string term)
{
var contacts = db.Contacts
.Where(r => r.ContactName.Contains(term))
.Take(10)
.Select(r => new { label = r.ContactName });
return Json(contacts, JsonRequestBehavior.AllowGet);
}
Models
public class InspectionInfo
{
[Key]
public int InspectionId { get; set; }
[DataType(DataType.Date)]
public virtual DateTime Submitted { get; set; }
[DataType(DataType.MultilineText)]
[MaxLength(1000)]
public string Comments { get; set; }
[Required]
public Contact Contact { get; set; }
public class Contact
{
[Key]
public string ContactName { get; set; }
View:
<div class="editor-label">
#Html.LabelFor(model => model.Contact)
</div>
<div class="editor-field">
<input type ="text" name ="q" data-autocomplete=
"#Url.Action("QuickSearchContact", "Inspection")"/>
#Html.ValidationMessageFor(model => model.Contact.ContactName)
</div>
JS
$(document).ready(function () {
$(":input[data-autocomplete]").each(function () {
$(this).autocomplete({ source: $(this).attr("data-autocomplete")});
});
The autocomplete function seems to be working fine. It will pull column data from the database as I require. However, any data entered in the autocomplete text box, appears NULL in the database after the user has saved the form. Help here would be greatly appreciated.
For model binding to work, generally input names must match property names of your model. Surprisingly, you have named your input "q"
<input type ="text" name ="q" data-autocomplete="..."/>
Just rename it according to your model
<input type ="text" name="Contact.ContactName" data-autocomplete="..."/>
You don't have your on the code above but, instead of using
<input type ="text" name ="q" data-autocomplete= "#Url.Action("QuickSearchContact", "Inspection")"/>
use:
#EditorFor(x = x.NameOfTextBox)
then either have an input button wrapped in a using tag
#using (Html.BeginForm("Create", "NameOfController", FormMethod.Post){
//all your model stuff goes here
}
or use and actionlink instead of :
#Html.ActionLink("Submit", "Create", "NameOfController", Model)
The provided information doesn't tell, but is is likely that the autocomplete part is not written within the form elements of the view:
#using (Html.BeginForm())
{
<p>
...
</p>
}
In MVC the form is defined within the brackets { .... } like above.