simnilar to the answer of this question
Html.BeginForm with html attributes asp.net mvc4
I have a viewmodel for a view that contains collections that are used to populate drop downs and lists. so i dont watn to return them, i just want to return the model object. Well actually i just want to return 4 fields in that model - but that's the next problem.
I've dodged that rpeviously by doing this appraoch but im having no luck unless i submit the entire viewmodel which on this form is ridiculous as 95% of info is discarded.
Anyway the problem i get here is that i cannot get the game event that is returned in the create post to be anything other than null. The gameEvent parameter on create is NULL.
Also kinda suprised i haven't been able to find a ton of info on this.
The controller:
public ActionResult Create()
{
...
var createEventViewModel = new CreateEventViewModel()
{
Places = places,
Characters = characters,
Event = new GameEvent()
};
return this.View(createEventViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Description,EventType,Duration")] GameEvent gameEvent)
{
...
}
The View:
#model Sisyphus.Web.Models.CreateEventViewModel
#{
ViewBag.Title = "Create Event";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create Event</h2>
<div class="row">
<div class="col-lg-8">
<section id="createEvent">
#using (Html.BeginForm("Create", "Event",
new
{
GameEvent = Model.Event
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Event.Name, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Event.Description, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextAreaFor(m => m.Event.Description, 10, 30, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.Duration, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Duration, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Duration, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.EventType, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EnumDropDownListFor(m => m.Event.EventType)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create Event" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
The Model:
public class GameEvent
{
public string Name { get; set; }
public string Description { get; set; }
public int Duration { get; set; }
public EventType EventType { get; set; }
}
The viewmodel: (edited down have removed members that are irrelevant
public class CreateEventViewModel
{
public GameEvent Event { get; set; }
}
Edit:
Ok i just tried this
#using (Html.BeginForm("Create", "Event",
new RouteValueDictionary()
{
{"GameEvent", Model.Event}
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
Game event is now not null (All values in it are) - so not really any closer
Your inputs for postback are based on class CreateEventViewModel, for example
#Html.TextBoxFor(m => m.Event.Name, ...
#Html.TextAreaFor(m => m.Event.Description, ...
which would generate the following html
<input id="Event_Name" name="Event.Name" value=....
However the parameter of your post action method is typeof GameEvent, not CreateEventViewModel. If you inspect the Response.Form.Keys you will see Event.Name, Event.Description etc, but class GameEvent has properties Name, Description etc so the values cant be matched up by the ModelBinder
You need to change your post method to
public ActionResult Create(CreateEventViewModel model)
{
GameEvent event = model.GameEvent;
// do whatever with GameEvent
You should also remove new {GameEvent = Model.Event} from theHtml.BeginForm` method
Note I excluded the BindAttibute because I don't think its necessary in this case - you appear to want all the properties of GameEvent, and unless you create inputs for properties of Places and Characters, they will be null anyway, and since you are not accessing the other properties there is no mass assignment vulnerability.
Other alternative are to create the inputs manually so that the properties are correctly mapped, either direct html
<input name="Name" value=#Model.Event.Name />
<input name="Description" value=#Model.Event.Desciption />
or using helpers
var Description = Model.Event.Description;
#Html.TextBoxFor(m => Description)
Related
This question already has answers here:
MVC5 - How to set "selectedValue" in DropDownListFor Html helper
(5 answers)
Closed 4 years ago.
I'm quite new to ASP.Net and I'm trying to make a reservation to a specified branch. When I click the reservation link of a branch, it comes to a drop-down list for me to choose a branch from those already existed in the database. What I want is the branch has already been set based on the reservation link of that branch I clicked. In the database, the reservation entity has an attribute referencing to the branchId in the branch entity. I have viewed some related questions such as MVC5 - How to set “selectedValue” in DropDownListFor Html helper, but neither solved my question properly. I still have no idea of this for now.
The following images are what it looks like now.
This is the create action in my reservation controller:
// GET: Reservations/Create
public ActionResult Create()
{
ViewBag.branchId = new SelectList(db.Branches, "branchId", "name");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "reservationId,branchId,customerId,date")] Reservation reservation)
{
reservation.customerId = User.Identity.GetUserId();
ModelState.Clear();
TryValidateModel(reservation);
if (ModelState.IsValid)
{
db.Reservations.Add(reservation);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.branchId = new SelectList(db.Branches, "branchId", "name", reservation.branchId);
return View(reservation);
}
This is my reservation create view:
#model Mel_Medicare_Location_Reservation_System.Models.Reservation
#{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Reservation</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group ">
#Html.LabelFor(model => model.branchId, "branchId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("branchId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.branchId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.customerId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.customerId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.customerId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
You should have the dropdown options as a get-only property of your model, then use the #Html.DropDownListFor() HTML helper to bind model.branchId to that list, which will set the value appropriately.
I would re-write your Create() controller method to return the view with an instance of the Reservation view model bound to it. This will involve adding a constructor to the view model class which accepts a branch id as a parameter. Something like:
// GET: Reservations/Create
public ActionResult Create(int branchId)
{
return View(new Reservation(branchId));
}
And in your Reservation model:
public class Reservation
{
public int BranchId { get; set; }
// Other properties...
public SelectList BranchOptions
{
get
{
return new SelectList(db.Branches);
}
}
public Reservation(int branchId)
{
this.BranchId = branchId;
}
}
Then in your view:
#Html.DropDownListFor(m => m.BranchId, Model.BranchOptions, htmlAttributes: new { #class = "form-control" })
My Controller Method:
[HttpGet]
public ActionResult NewInventory()
{
return View();
}
[HttpPost]
public async Task<ActionResult> NewInventory(string bookID, string ttlin, string lowin, string Outnow)
{
// test values passed, etc.....
}
So far only the "lowin" value is being passed correctly. All the other values are set to "0" (I believe due to the datatypes being set to "not null" in SQL DB). Why is this?
I assume because only one value is passed correctly and no exceptions are thrown, then the view page code is missing the other the fields to pass.
View Code :
#model LibraryMS.Inventory
#{
ViewBag.Title = "newinventory";
}
<h2>newinventory</h2>
#using (Html.BeginForm("NewInventory","BookInfo", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Inventory</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.BookID, "BookID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.BookID, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.BookID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TotalIn, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TotalIn, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TotalIn, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LowIn, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.LowIn, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.LowIn, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Out, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Out, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Out, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
By looking, the values are being passed.
Turns out that the controller method's parameters needs to be spelled the same as what is going to be passed to it. For example:
With the autogenerated form using html helper #Beginform, all the fields are present. But the parameters in the controller method are not the same as the inventory's class fields.
public partial class Inventory
{
public string BookID { get; set; }
public short TotalIn { get; set; }
public short LowIn { get; set; }
public short Out { get; set; }
public virtual BookInfo BookInfo { get; set; }
}
Compared to the paramters:
[HttpPost]
public async Task<ActionResult> NewInventory(string bookID, string ttlin, string lowin, string Outnow)
{
// test values passed, etc.....
}
The fix was to ofcoarse make the params the same, capitalization does not matter.
[HttpPost]
public async Task<ActionResult> NewInventory(string bookID, string totalin, string lowin, string Out)
{
// test values passed, etc.....
}
It was a simple mistake but took me some time to figure this out. Hopefully this helped someone else!
I've just started my journey with MVC, but I have a problem and for two days I can't find answer.
I have EditorFor and ListBoxFor in a one site and I want to Post info from EditorFor about Name and from ListBoxFor about EmployeeId.
In listbox I have list of my employees. I can write name in Editor and I can choose one of employee from list.
Everything is ok, but when I mark anything inside ListBox it makes that text inside EditorFor disappears.
View:
#model Models.CreateClient
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div>
<h4>Client</h4>
<hr />
<div class="col-md-6">
#Html.ValidationSummary(true, "", new {#class = "text-danger"})
<div class="form-group">
#Html.LabelFor(model => model.Client.Name, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.Client.Name, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.Client.Name, "", new {#class = "text-danger"})
</div>
</div>
</div>
<div class="col-md-6">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SelectedEmployee, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBoxFor(model => model.SelectedtEmployee, Model.EmployeeListItem(), new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SelectedEmployee, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group col-md-6">
<input type="submit" value="CreateNew" class="btn btn-default " />
#Html.ActionLink("Back to List", "Index")
</div>
</div>
}
Model:
public class CreateClient
{
public Client Client { get; set; }
public List<Employee> Employees { get; set; }
public IEnumerable<int> SelectedtEmployee { get; set; }
public IEnumerable<SelectListItem> EmployeeListItem()
{
List<SelectListItem> employeeSelectedListItem = new List<SelectListItem>();
foreach (var employee in Employees)
{
SelectListItem selectListItem = new SelectListItem()
{
Text = employee.FullName,
Value = employee.Id.ToString(),
};
employeeSelectedListItem.Add(selectListItem);
}
return employeeSelectedListItem;
}
}
Controller:
public ActionResult CreateNew()
{
CreateClient createClient = new CreateClient();
createClient.Employees = db.EmployeeContet.ToList();
return View(createClient);
}
I made mistake and my Client's name was int (instead of string).
I have an object called Job and one of the properties is a List of Steps:
public class Job
{
[Display(Name = "Id")]
public int? JobId { get; set; }
[Required]
public string Name { get; set; }
public List<Step> Steps { get; set; }
public Job()
{
Steps = new List<Step>();
}
}
public class Step
{
public int? StepId { get; set; }
public int JobId { get; set; }
[Required]
public string Name { get; set; }
}
I have a JobController with the following action to perform the update:
// PUT: /Job/Edit/5
[HttpPut]
public ActionResult Edit(Job model)
{
// Logic to update model here
}
Based on a the answer to this question I updated my UI (using the Bootstrap template that comes with MVC5) to:
#using (Html.BeginForm())
{
#Html.HttpMethodOverride(HttpVerbs.Put)
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.JobId)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<h3>Steps</h3>
<div>
#foreach (var item in Model.Steps)
{
<div class="form-group">
#Html.Hidden("Steps[" + stepIndex + "].StepId", item.StepId)
#Html.LabelFor(modelItem => item.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input class="form-control text-box single-line" data-val="true" data-val-required="The Name field is required."
id="#String.Format("Steps_{0}__Name", stepIndex)" name="#String.Format("Steps[{0}].Name", stepIndex)" type="text" value="#item.Name" />
#Html.ValidationMessageFor(modelItem => item.Name, "", new { #class = "text-danger" })
</div>
</div>
stepIndex += 1;
<hr />
}
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
As you can see I have to manually build the input tag opposed to using Html.EditorFor. The reason is that I need to control name of the id so that it passes the Index into the id and name. I would assume there is a better approach that would allow MVC to render the correct values using labelFor, EditorFor and ValidationMessageFor.
The questions I have are:
Is there a set of controls I can use with MVC5 that allows me to render complex child objects without going through these extra steps?
If no on 1, then is there a better approach than manually create input tag?
Option 1: Replace the foreach loop with for:
#for (int i = 0; i < Model.Steps.Count; i++)
{
<div class="form-group">
#Html.HiddenFor(m => m.Steps[i].StepId)
#Html.TextBoxFor(m => m.Steps[i].Name, new { #class = "form-control text-box single-line" })
...
</div>
}
Option 2: Create an editor template called Step.chtml for the Step class and use EditorFor:
Step.chtml
#model Step
<div class="form-group">
#Html.HiddenFor(m => m.StepId)
#Html.TextBoxFor(m => m.Name, new { #class = "form-control text-box single-line" })
...
</div>
Main View
<h3>Steps</h3>
<div>
#Html.EditorFor(m => m.Steps)
<div>
In both these ways the framework will give the inputs correct names and ids.
Looks like complicated the things, try the below.
1. Create a new editor template (which is a view) named 'Step.cshtml' under the EditorTemplates folder with the model Step.
2. In that do the below code, Step.cshtml
#model Step
<div class="form-group">
#Html.HiddenFor(model => model.StepId)
#Html.LabelFor(modelItem => modelItem.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.Name, htmlAttributes: new { #class = "form-control text-box single-line" })
#Html.ValidationMessageFor(modelItem => modelItem.Name, "", new { #class = "text-danger" })
</div>
3. Remove the foreach statement from your view, and instead call the editor template as,
#Html.EditorFor(model => model.Steps)
I have a viewmodel that contains a list of another viewmodel. As demonstrated here:
public class PrizeViewModel
{
public Guid PrizeId { get; set; }
public string Description { get; set; }
public List<CategoryViewModel> Categories { get; set; }
}
CategoryViewModel is defined as such:
[Serializable]
public class CategoryViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Selected { get; set; }
}
The Prize view looks like this:
#model PrizeViewModel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Prize</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Categories, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
Create New Category
#Html.EditorFor(model => model.Categories, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
I then have a CategoryEditorTemplate:
#model CategoryViewModel
<div>
<span><input id="#Model.Id" name="#Model.Id" type="checkbox" checked="#(Model.Selected)" value="#Model.Name" /></span>
<label for="#Model.Id">#Model.Name</label>
</div>
The Create method in the controller takes a PrizeViewModel, problem that I am having is that when I get the PrizeViewModel back, Categories is null. Any suggestions?
First, I don't understand how your Category template is supposed to work. You're mixing up your Boolean and id and somehow expecting them to bind... Here's how I think you want it to work.
Change your category editor template to this (it should be called CategoryViewModel.cshtml) The key is that you need to hidden values in order to post them back to the server. And, like Stephen mentions, you were overriding the Editor Template automatic collection naming by not using a helper for your input fields.
#model CategoryViewModel
<div>
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Name)
<Label>#Html.CheckBoxFor(x => x.Selected) #Model.Name</label>
</div>
Your prize view should be fine exactly as you posted it.
Do not. I repeat, do NOT use any form of foreach or for statement with an editor template and collections.
If you loop through categories with for loop when posting to actionresult it will be able to bind model to list here is post about binding model to list
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
here is example of how to make it:
#for(int i = 0; i < Model.Categories.Count(); i++)
{
#Html.EditorFor(model => Model.Categories[i])
}
In your CategoryEditorTemplate, you are overriding the default naming required for correct binding when you do ...name="#Model.Id"...
The html for your categories should look like
<input type="text" name=Categories[0].Name ...
<input type="checkbox" name=Categories[0].Selected...
<input type="text" name=Categories[1].Name ...
....
Either use helpers in your Template, for example
#Html.TextBoxFor(m => m.Name)
#Html.CheckBoxFor(m => m.Selected)
or delete the template and use a for loop to generate the html
#for(int i = 0; i < Model.Categories.Count; i++)
{
#Html.TextBoxFor(m => m.Categories[i].Name)
#Html.CheckBoxFor(m => m.Categories[i].Selected)
}