I have created a partial view (even though the editor template), I pass a sub model to the view, however, when I clicked "submit", I always get "null" from the partial view. I can get Main model's properties values except the sub model one.
Main Model
public class PetModel
{
public string name {get; set;}
public long SpeciesID {get; set;}
public long BreedID {get; set;}
public Calendar DOB {get; set;}
}
Sub Model
public class Calendar
{
public string Year{get; set;}
public string Month{get; set;}
public string Day{get; set;}
}
Main View
#model Application.Models.PetModel
#using (Html.BeginForm("CatchPetContent", "Quote",Model))
{
#Html.TextBoxFor(x => x.Name)
#Html.DropDownListFor(x=>x.SpeciesID,new List<SelectListItem>(),"select")
#Html.DropDownListFor(x=>x.BreedID,new List<SelectListItem>(),"select")
#Html.EditorFor(Model => x.DOB)
<input type="submit" value="submit" />
}
Editor template
#model Application.Models.Calendar
#Html.DropDownListFor(Model => Model.Day, new List<SelectListItem>())
#Html.DropDownListFor(Model => Model.Month,new List<SelectListItem>())
#Html.DropDownListFor(Model => Model.Year, new List<SelectListItem>())
"CatchPetContent" action
[HttpPost]
public ActionResult CatchPetContent(PetModel Model)
{
PetModel pet = new PetModel();
pet.Name = Model.Name;
pet.SpeciesID = Model.SpeciesID;
pet.BreedID = Model.BreedID;
pet.DOB = Model.DOB;// always null
RouteValueDictionary redirectTargetDictionary = new RouteValueDictionary();
redirectTargetDictionary.Add("Controller", "Home");
redirectTargetDictionary.Add("Action", "Index");
return new RedirectToRouteResult(new RouteValueDictionary(redirectTargetDictionary));
}
When I debugged it, "Model.DOB" is always null
You should add the sub-property as an extra parameter on your action:
[HttpPost]
public ActionResult CatchPetContent(PetModel Model, Calendar Bob)
{
** snip **
}
The default ModelBinder doesn't nest the objects. It does however find the values if you include it as a second parameter.
If you want to nest them, you'd have to create your own modelbinder.
The following question had a similar issue: List count empty when passing from view to model in ASP.Net MVC
The default model binder will bind nested objects, you do not need to create your own model binder for this scenario.
See:
http://lostechies.com/jimmybogard/2011/09/07/building-forms-for-deep-view-model-graphs-in-asp-net-mvc/
and on SO
DefaultModelBinder not binding nested model
I suspect your problem is that your editor template is not returning anything via the post back so the nested object is null. The lines in your editor template:
#Html.DropDownListFor(Model => Model.Day, new List<SelectListItem>())
#Html.DropDownListFor(Model => Model.Month,new List<SelectListItem>())
#Html.DropDownListFor(Model => Model.Year, new List<SelectListItem>())
Will give you three controls with empty drop downs, you will not be able to select anything from these drop downs as you have not supplied any values to them. If there is no value then they are not sent across the wire and cannot be model bound. To start of with change you editor template to:
#Html.TextBoxFor(Model => Model.Day)
#Html.TextBoxFor(Model => Model.Month)
#Html.TextBoxFor(Model => Model.Year)
This will allow you to actually enter some data onto to form. Once this is working you can then change the text boxes back to drop downs but you will need to supply values for the select lists, again, just creating new empty lists will not allow you to select any values.
instead of
#Html.EditorFor(Model => x.DOB)
try render partial view
#Html.Partial("partialViewName", Model.DOB)
I think in your Main View you have a typo, it looks like you should change
#Html.EditorFor(Model => x.DOB)
to
#Html.EditorFor(x => x.DOB)
Related
I have a .net MVC page that has a form used to edit an entity from database. The form has some dynamic select list fields that need to be populated from Database also.
I pass a PageView Model that contains the Data for the entity, and a list of entities to populate the select list like so:
public class EditPortalPage
{
public PortalViewModel Data { get; set; }
public IEnumerable<SelectListItem> Cultures { get; set; }
}
in my View I have
#model Website.Models.Portals.EditPortalPage
at the top of the page
I can easily populate my form doing this:
#Html.EditorFor(model => model.Data.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.LabelFor(model => model.Data.Name)
#Html.ValidationMessageFor(model => model.Data.Name, "", new { #class = "text-danger" })
#Html.DropDownListFor(model => model.Data.DefaultCultureId, new SelectList(Model.Cultures, "Value", "Text"), new { #class = "mdb-select colorful-select dropdown-info" })
#Html.LabelFor(model => model.Data.DefaultCultureId)
#Html.ValidationMessageFor(model => model.Data.DefaultCultureId, "", new { #class = "text-danger" })
The problem is this produces messed up Id and Name attributes for my form fields. In the above example it produces:
Name Field ID: Data_Name
Name Field Name: Data.Name
What is the correct way to set it up so I the name and ID does not include the 'Data' prefix
Can I pass the Data model directly to teh form?
The HtmlHelper methods generate the name and id attribute based on the name of the property. In your case, its a nested property so it generates the Data. prefix for the name attribute, and in the case of the id attribute, it replaces the .(dot) with an _(underscore) so that the id can be used in jquery selectors (otherwise the dot and the following text would be interpreted as a class selector.
Basing the id attribute on the property name ensures a unique id (and therefore valid html), and I'm not sure why you think its 'messed up', but you can always override the value by using
#Html.EditorFor(m => m.Data.Name, new { htmlAttributes = new { id = "xxx", #class = "form-control" } })
which will render <input id="xxx" .... /> if you want to use xxx in a css or javascript/jquery selector (which is the only purpose of the id attribute)
However the name attribute is certainly not 'messed up' and if it were changed, it would not bind to your model when you submit. Under no circumstance should you ever attempt to change the name attribute when using the HtmlHelper methods.
I'm guessing that your (incorrectly) using your data model as a parameter in the POST method, in which case you could use the Prefix property of the [Bind] attribute (although a [Bind] attribute should never be used with a view model)
public ActionResult Edit([Bind(Prefix = "Data")]PortalViewModel model)
to strip the "Data." prefix and bind to PortalViewModel.
However, the real issue here is you incorrect use of a view model. View models, especially when editing data, should not contain data models. They contain only the properties of your data model that you need in the view. Some of the benefits of view models include Separation of Concerns, protection against under and over posting attacks, ability to add display attributes which are not applicable in data models, and the ability to add additional view specific properties. In your case, you have negated all but the last point. You view model should be
public class EditPortalPage
{
public string Name { get; set; }
[Display(Name = "Culture")]
[Required(ErrorMessage = "Please select a culture")]
public int? SelectedCulture { get; set; } //nullable and Required to protect against under-posting attacks
... // other properties of PortalViewModel that you need in the view
public IEnumerable<SelectListItem> Cultures { get; set; }
}
and in the POST method, you map your view model to the data model and save the data model. Refer also What is ViewModel in MVC?.
As a side note, using new SelctList(..) in your DropDownList() method is pointless extra overhead - its just creating another identical IEnumerable<SelectListItem> from the first one, and your code should just be
#Html.DropDownListFor(m => m.SelectedCulture, Model.Cultures, "Please select", new { ... })
If I understand the Q correctly, you could use a partial "_data.cshtml":
#model PortalViewModel
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
Pass data to the partial from your view:
#Html.Partial("_data",model.Data)
First, sorry for my bad English. I am from Brazil. And I am a beginner in .NET MVC5.
I had a model class Task with ID plus 4 editable columns. When creating a new Task, the user must fill out only 2 of them (TaskType and Subject). The remaining two columns (UserID and CreationDate) must be populated with information obtained from the system: user ID and current date.
Then, in the Create Get, I put this two information in the ViewBag:
public ActionResult Create()
{
ViewBag.TaskTypeID = new DAO.TaskTypesDAO().ListOfTypes();
ViewBag.ApplicationUserId = User.Identity.GetUserId();
ViewBag.CreationDate = DateTime.Now;
return View();
}
And in View Create I tried to include this information first, without showing it:
First, I tried HiddenFor() and after with DisplayFor()
#Html.LabelFor(model => model.CreationDate, "Creation Date")
#Html.DisplayFor(model => model.CreationDate)
#Html.HiddenFor(model => model.CreationDate)
#Html.LabelFor(model => model.ApplicationUserId, "Creator")
#Html.DisplayFor(model => model.User.Id)
#Html.HiddenFor(model => model.ApplicationUserId)
DisplayFor() doesn't show anything.
And when I submit the Post Create method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,TaskTypeID,Subject,CreationDate,ApplicationUserId")] Task task)
{
if (ModelState.IsValid)
{
db.Tasks.Add(task);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TaskTypeID = new DAO.TaskTypesDAO().ListOfTypes();
ViewBag.ApplicationUserId = User.Identity.GetUserId();
ViewBag.CreationDate = DateTime.Now;
return View(tarefa);
}
The ModelState is invalid and task.CreationDate and task.ApplicationUserId are empty.
How can include this two information before the create post bind????
or else, How can include after and revalidate ModelState??
or .....
I don't understand why are you sending CreationDate and ApplicationUserId to the view through ViewBag and posting it back, this doesn't make any sense. Someone could easily edit the html and forge the ApplicationUserId or CreationDate.
I think your life would be much easier if you just use a strong typed view for this. My 2 cents for you would be:
Put everything you want to display inside a view model (dropdowns, lists, etc.).
Here you could propably use fill a SelectListItem to show your tasks types on a dropdown (or a enum if it's simple enough).
[HttpGet]
public ActionResult Create()
{
var viewModel = new CreateTaskViewModel
{
// [...] Put info that you need to display on the view here
}
return View(viewModel);
}
Get current date and user id when creating the task, that way the user can't forge this information. After creating the task, if you want to show it to the user, just use a Show view.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateTaskViewModel createTaskViewModel)
{
// If validation fails return to the view showing the errors
if (!ModelState.IsValid) { return View(createTaskViewModel); }
// [...] Create task object using info from viewModel
// and what else is necessary
db.Tasks.Add(task);
db.SaveChanges();
// [...] Fills view model to show info and redirect
return RedirectToAction("Show", showViewModel);
}
Use a model on the view and don't put sensitive information hidden on your html.
#model CreateTaskViewModel
#Html.LabelFor(model => model.TaskType)
// Or a dropdown list
#Html.TextBoxFor(model => model.TaskType)
#Html.LabelFor(model => model.Subject)
#Html.TextBoxFor(model => model.Subject)
Another thing that can be really usefull for you to study is the PRG (Post Redirect Get) pattern. It comes in handy when you have more complex action/ views.
I'm stucked at creating dropdownlist in ASP.NET MVC.
ViewModel:
public MultiSelectList users { get; set; }
I set the values in controller:
var allUsers = db.Users.Select(u => new {
id = u.UserId,
name = u.Name
}).ToList();
model.users = new MultiSelectList(allUsers, "id", "name");
so selectbox values are set.
In view:
#Html.DropDownListFor(m => m.users, Model.users, new { #class = "form-control" })
The problem is that if I select the value and click submit i get this error:
No parameterless constructor defined for this object.
I think the problem is in the way how I create the dropdownlist in view, I'm not sure how to set it, thanks.
EDIT: If I dont choose any user from dropdown all goes well, but if I choose then the error appears.
You're trying to post to the MultiSelectList property. That's not going to work regardless, but the specific error is related to the fact that MultiSelectList has no parameterless constructor, and there's no way for the modelbinder to new up a class with parameters. Anything involved in the modelbinding process must have a parameterless constructor.
What you should be doing is have an additional property like:
public List<int> SelectedUserIds { get; set; }
And, then bind to that in your view:
#Html.ListBoxFor(m => m.SelectedUserIds, Model.Users)
Also, as you'll notice, I changed DropDownListFor to ListBoxFor. If you're wanting to have a select multiple, you need ListBoxFor.
Looks like it is failing when trying to bind, so to prevent it from binding:
[HttpPost]
public ActionResult YourMethod([Binding(Exclude = "users")] SomeViewModel model)
The post back should go to an IEnumerable to capture the selected items.
Add to view model
public IEnumerable UserList { get; set; }
Change view to
#Html.DropDownListFor(m => m.UserList, Model.users, new { #class = "form-control" })
If you want get selected user id from a dropdownlist you must add a property to your model
public MultiSelectList users { get; set; }
public int SelectedUser { get;set;}
And in view
#Html.DropDownListFor(m => m.SelectedUser, Model.users, new { #class = "form-control" })
When I post the form by clicking on the save button, it hits the post method but the model parameter is always null.
Controller:
[HttpPost]
public ActionResult Edit(QuestionMaster question)
{
if (questionLogic.Update(model))
{
return RedirectToAction("List");
}
return View();
}
View:
#using (Html.BeginForm()) {
<fieldset>
<legend>QuestionMaster</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Question)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Question)
#Html.ValidationMessageFor(model => model.Question)
</div>
</fieldset>
<p><input type="submit" value="Save" /></p>
}
You have not posted your model for QuestionMaster but from the view code it appears to contain a property named Question which is typeof string. The problem is that your POST method parameter is also named question which causes model binding to fail and the object is null.
Rename the parameter to anything but a name of a property in your model, for example
[HttpPost]
public ActionResult Edit(QuestionMaster model)
The reason why your model was null on postback is because of how model binding works
The DefaultModelBinder initializes a new instance of
QuestionMaster
The posted form's name/value pairs are then checked. If a matching
property name is found, the value of that property is set.
In your case your posting back Question="The text you entered" The
model binder finds the parameter named question (i.e. a match) and
sets it to "The text you entered", but question is typeof
QuestionMaster (a complex object, not a string) so binding fails
and the model becomes null.
Pass HtmlFieldPrefix in your EditorFor.
#Html.EditorFor(model => model.Question, new ViewDataDictionary() { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "QuestionMaster" }})
This way the names of the fields will be correct and the model binder will be able to bind them.
From the code you've given above, your View is missing a model.
You can add the model to the view as below:
#model QuestionMaster
This code is typically the first line in your view.
Other than that, can you explain the scope of model in your controller action? Where is model defined? If it isn't defined, you should understand that using #Model.someValue in the view is fine, but accessing model in your controller won't work unless your posted model parameter is called model.
Assuming that may be another reason for your form being "null", try changing your controller to:
[HttpPost]
public ActionResult Edit(QuestionMaster question)
{
if (questionLogic.Update(question))
{
return RedirectToAction("List");
}
return View();
}
You can do it what vortex told you or just change your parameter name to anything but "question"
[HttpPost]
public ActionResult Edit(QuestionMaster question2)
or
[HttpPost]
public ActionResult Edit(QuestionMaster model)
or
[HttpPost]
public ActionResult Edit(QuestionMaster anything)
I have a generic "Index" page which lists all the entries for a given table and there is a side-bar which allows filtering the data in the grid. My model is as follows:
public class GenericFormIndexModel
{
public IEnumerable<IGenericForm> Entries { get; set; }
public FormSearchQueryModel Query { get; set; }
}
In the razor file I have an html like this:
#using (Html.BeginForm("Search", controllerName, FormMethod.Post, new { id = "fSearch" }))
{
#Html.HiddenFor(m => m.Query.PageIndex)
#Html.HiddenFor(m => m.Query.PageSize)
#Html.HiddenFor(m => m.Query.SortBy)
...etc
#Html.TextBoxFor(m => m.Query.SerialNumber, null, new { #class = "inputbox right-search-field" })
...etc
and I have defined an action as follows:
[HttpPost]
public virtual ActionResult Search(FormSearchQueryModel queryModel)
{
//Implementation ommited
}
Now, the problem is that the values from the form are indeed submitted, but do not bind to my "queryModel" argument in the action. I can see them in Request.Form["Query.Something"].
I do not wish to submit the entire Model, as it is not necessary to post all the entries and whatever else back. Is it possible to get MVC to bind to a nested property or am I stuck with using Reqest.Form[""] ?
Did you try setting the Prefix property as below,
[HttpPost]
public virtual ActionResult Search([Bind(Prefix="Query")]FormSearchQueryModel queryModel)
{
//Implementation ommited
}
The Bind attribute has other properties like Include, Exclude through which you can control what are the posted values need to be binded.