BeginForm in ChildAction uses wrong id - asp.net-mvc

There is something simple I don't understand with ChildActions.
I've created a simple View for a model, that loads a child action with a form.
The child action has another model than its parent, with a different id property.
Html.HiddenFor(m => m.Id) still outputs the parents id, although #Model.id outputs the correct value!
Can't I reliably use the Helper methods in ChildActions, or is this a known bug?
HomeController
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new Models.HomeModel { id = 1, message = "bugmodel" };
return View(model);
}
[HttpGet]
[ChildActionOnly]
public ActionResult Child(int id)
{
var model = new Models.HomeChildModel { id = 100, parentId = id, childMessage = "My Child message" };
return PartialView(model);
}
[HttpPost]
[ActionName("Child")]
[ValidateAntiForgeryToken()]
public ActionResult ChildPost(Models.HomeChildModel model)
{
return RedirectToAction("Index");
}
}
Models
public class HomeModel
{
public int id { get; set; }
public string message { get; set; }
}
public class HomeChildModel
{
public int id { get; set; }
public int parentId { get; set; }
public string childMessage { get; set; }
}
Home view
#model ChildActionBug.Models.HomeModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.DisplayFor(m=>m.id)
#Html.DisplayFor(m=>m.message)
#Html.Action("Child", new { id = Model.id })
**Child view**
#model ChildActionBug.Models.HomeChildModel
<h3>Child here</h3>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m=>m.id)
#Html.HiddenFor(m=>m.parentId)
#Html.EditorFor(m=>m.childMessage)
<div>Child Model ID: #Model.id</div>
<button type="submit">Save</button>
}

Based on the answer given in the SO question I posted in the comment, you're better off explicitly creating the hidden fields
ASP.Net MVC Html.HiddenFor with wrong value
That's normal and it is how HTML helpers work. They first use the
value of the POST request and after that the value in the model. This
means that even if you modify the value of the model in your
controller action if there is the same variable in the POST request
your modification will be ignored and the POSTed value will be used.
So instead, hand craft the hidden fields:
<input type="hidden" name="Id" value="#Model.Id" />
<input type="hidden" name="ParentId" value="#Model.ParentId" />
<input type="hidden" name="ChildMessage" value="#Model.ChildMessage" />

Related

MVC Return Fields to Controller

I've got a controller to retrieve and return values for my drop down, and a second, that when an option from the dropdown is selected, uses the values (Title and ID) in an API Request.
Controllers
public ActionResult GetEpics()
{
//Code to retrieve list
Epics = new GetEpicsViewModel();
Epics.Epics = epicsList;
return View(Epics);
}
[HttpPost]
public ActionResult Build(GetEpicsViewModel epic)
{
GetEpicsViewModel epicTest = epic;
//API Request
return View();
}
This is displayed in my drop down list as below:
View
#using (Html.BeginForm("Build", "GetEpics", FormMethod.Post))
{
<label for="input_OutputType"> Process: #Html.DropDownListFor(model => model.Id, new SelectList(Model.Epics, "Id", "Title")) </label>
<input type="submit" value="Submit" />
}
This works fine, but how would I then go about passing both the Title and ID to my controller?
I can pass the ID through fine, but cant figure out how to pass the Title as well.
Screenshot
Models
public class DevOpsEpic
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
and
public class GetEpicsViewModel
{
public string Id { get; set; }
public string Title { get; set; }
public List<DevOpsEpic> Epics { get; set; }
}
Realise this is probably a really simple answer, but just cant figure it out!
You can use jQuery for that, so when your dropdown is changed, set title value in hidden file.
#using (Html.BeginForm("Build", "GetEpics", FormMethod.Post))
{
<label for="input_OutputType"> Process: #Html.DropDownListFor(model => model.Id, new SelectList(Model.Epics, "Id", "Title"),new { name = "Id" }) </label>
<input type="hidden" id="Title" name="Title" />
<input type="submit" value="Submit" />
}
$('#dropdownId').change(function(){
$('#Title').val($('#dropdownId option:selected').text());
});

Html.DropDownListFor returns null

There are similar questions and I have tried most of them. Instead of continuing to destroy the remaining code while conjuring demons from the similar past questions, I have decided to ask for help.
When I arrive at the AddMembership action I get a null value instead of the selected item. Below are the details.
View;
#using (Html.BeginForm("AddMembership", "WorkSpace", FormMethod.Post, new { data_ajax = "true", id = "frmAddMembership" }))
{
<div id="newMembershipDiv">
#Html.DropDownListFor(m => m.selectedID, new SelectList(Model.allInfo, "Value","Text",1), "Select!")
<input type="submit" value="Add" name="Command" />
</div>
}
Controller (I just want to see the selectedID or anything appear here.);
public ActionResult AddMembership(SelectListItem selectedID)
{
return View();
}
Model;
public class SomeModel
{
public SelectList allInfo { get; set; }
public SelectListItem selectedID { get; set; }
}
The Monstrosity which initializes the allInfo SelectList
model.allInfo = new SelectList(synHelper.getall().ToArray<Person>().Select(r => new SelectListItem {Text=r.Name, Value=r.prID.ToString() }));
synHelper.getAll() returns a List of the below class;
public class Person
{
public Guid prID { get; set; }
public string Name { get; set; }
}
The only thing that is posted to your action is a selectedID, which is a simple string. If you wander, in the request it looks as simple as:
selectedID=1234
Therefore it should be awaited for as a simple string. Adjust your action parameter:
public ActionResult AddMembership(string selectedID)

How do I get a strongly typed DropDownList to bind to a control Action

I've just started a new MVC project and I'm having trouble getting the post result from a form.
This is my Model Class :
public class User
{
public int id { get; set; }
public string name { get; set; }
}
public class TestModel
{
public List<User> users { get; set; }
public User user { get; set; }
public SelectList listSelection { get; set; }
public TestModel()
{
users = new List<User>()
{
new User() {id = 0, name = "Steven"},
new User() {id = 1, name = "Ian"},
new User() {id = 2, name = "Rich"}
};
listSelection = new SelectList(users, "name", "name");
}
}
This is my view class
#model MvcTestApplicaiton.Models.TestModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.user, #Model.listSelection)
<p>
<input type="submit" value="Submit" />
</p>
}
#if (#Model.user != null)
{
<p>#Model.user.name</p>
}
And this is my controller :
public class TestModelController : Controller
{
public TestModel model;
//
// GET: /TestModel/
public ActionResult Index()
{
if(model ==null)
model = new TestModel();
return View(model);
}
[HttpPost]
public ActionResult Test(TestModel test)
{
model.user = test.user;
return RedirectToAction("index", "TestModel");
}
}
The drop down list appears just fine but I can't see to get the ActionResult Test function to run. I thought it would just bind itself with reflection but whatever is wrong, I can't see it.
You have two main errors in your code.
As Brett said you're posting to the Index method, but you don't have Index method that supports POST verb. The easiest way to fix is to change Html.BeginForm() with Html.BeginForm("Test", "TestModel")
You're using Html.DropDownListFor in a wrong way. You could pass only a value types there, because don't forget that the View will generate an HTML page. So instead of User in your Model you should have an UserID and in your View you should have #Html.DropDownListFor(x => x.UserID, #Model.listSelection). And finally in your Action you should query your data source to get the details for the user with this ID.
Hope this helps.
Looks like you're posting back to index. Either use a GET Test() action method, or specify the ACTION parameter in BeginForm().
For example,
#using (Html.BeginForm("Test", "TestModel"))
{
#Html.DropDownListFor(x => x.user, #Model.listSelection)
<p>
<input type="submit" value="Submit" />
</p>
}
Or use a view named Test (rename index.cshtml to test.cshtml):
public ActionResult Test()
{
if(model ==null)
model = new TestModel();
return View(model);
}

MVC checkbox comeing back null

I have a checkbox, but the form is being submitted the value ticked are not being submited...
Html:
#foreach (var radiobutton in Model.InterestedIn)
{
<span > #Html.CheckBox("selected", radiobutton)
<label>#radiobutton</label></span>
<br />
}
Model:
[Display(Name = "Would you be interested in receiving *")]
public IList<string> InterestedIn { get; set; }
Controller:
IList<string> lists = new List<string>();
lists.Insert(0, "Latest News");
lists.Insert(1, "Special Offers");
lists.Insert(1, "New Products");
model.InterestedIn = lists;
PostMethod:
[HttpPost]
public ActionResult Index(Competition model)
{
if (ModelState.IsValid)
{
I don't that your code will compile at all. The CheckBox helper expects a boolean as second argument whereas you are passing it a string.
Try like this:
#model MyViewModel
#using (Html.BeginForm())
{
foreach (var value in Model.InterestedIn)
{
<span>
<input type="checkbox" name="interestedin" value="#Html.AttributeEncode(value)" />
<label>#value</label>
</span>
<br />
}
<button type="submit">OK</button>
}
This assumes that you have the following view model:
public class MyViewModel
{
[Display(Name = "Would you be interested in receiving *")]
public IList<string> InterestedIn { get; set; }
}
and the following controller:
public class HomeController : Controller
{
public ActionResult Index()
{
IList<string> lists = new List<string>();
lists.Insert(0, "Latest News");
lists.Insert(1, "Special Offers");
lists.Insert(1, "New Products");
var model = new MyViewModel();
model.InterestedIn = lists;
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
If you want to use the CheckBox or even better the CheckBoxFor helper you will have to adapt your view model so that it no longer has an IList<string> property but an IList<CheckBoxItemViewModel> property where CheckBoxItemViewModel is another view model that will contain the label and a boolean property indicating whether this value has been selected or not.

Is it possible to pass a ViewModel object encapsulating Person to a Create view but only receive a Person object from a POST Create action method?

I have a domain model and a view model as follows:
Domain Model:
namespace MvcApplication1.Models
{
public enum Sex { Male, Female };
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[Required(ErrorMessage="Please select either Female or Male.")]
public Sex? Sex { get; set; }
}
}
View Model:
namespace MvcApplication1.ViewModels
{
public class HomeCreateVM
{
public HomeCreateVM()
{
}
public HomeCreateVM(Person p)
{
Person = p;
SelectList = p.Sex.GetSelectList();
}
public Person Person { get; set; }
public SelectList SelectList { get; set; }
}
}
The auxiliary extension method is defined as follows:
namespace MvcApplication1.Models
{
public static class Utilities
{
public static SelectList GetSelectList<XXX>(this XXX? obj) where XXX : struct
{
var values = from XXX x in Enum.GetValues(typeof(XXX))
select new { Text = x.ToString(), Value = x };
return new SelectList(values, "Value", "Text", obj);
}
}
}
Controller:
public ActionResult Create()
{
var p = new Person();
return View(new HomeCreateVM(p));
}
[HttpPost]
public ActionResult Create(Person hc)// the source of problem!
{
if (ModelState.IsValid)//always false!
{
TempData["status"] = hc;
return RedirectToAction("Confirm");
}
else
return View(new HomeCreateVM(hc));
}
HomeCreateVM.cshtml:
#model MvcApplication1.ViewModels.HomeCreateVM
<div>
Name: #Html.EditorFor(model => model.Person.Name)</div>
<div>
Sex: #Html.DropDownListFor(model => model.Person.Sex, Model.SelectList, "--Select--")</div>
Create View:
#model MvcApplication1.ViewModels.HomeCreateVM
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>HomeCreateVM</legend>
#Html.EditorForModel()
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Question:
There is no problem if the POST Create action method accepts a HomeCreateVM object as the argument.
However, if I change the POST Create action method argument from HomeCreateVM to Person (as shown in the code above), ModelState.IsValid always returns false.
The question is: "Is it possible to pass a ViewModel object to a Create view but only accept a DomainModel object from a POST Create action method?"
Because your view is strongly typed to the view model your form fields will look like this:
<input type="text" name="Person.Name" />
and if you want to bind correctly you need to specify the prefix:
[HttpPost]
public ActionResult Create([Bind(Prefix = "Person")]Person hc)
{
...
}

Resources