Post an Array of Models to Controller - c#

I have an MVC5 project where I am trying to Post a List of 'Permissions' back to the server.
#model List<ConnectConsole.Models.Permissions>
<form action="/products/#{#Html.Raw(Url.RequestContext.RouteData.Values["id"])}/permissions/save" method="POST">
#for (int i = 0; i < Model.Count; i++)
{
#Html.TextBoxFor(model => model[i].ConnectId)
#Html.DropDownListFor(
model => model[i].ScopeVal,
Model[i].ScopeTypes,
"-- Please Select --")
}
<button class="btn btn-full btn-submit">Save</button>
#* removed some html for brevity *#
</form>
The Data that is posted back to the server looks somthing like this (copied from chrome)
[0].ConnectId:88709d85-710c-45da-8fec-0ee661d267b1
[0].ScopeVal:e276964f-7e6e-4aaf-8bf5-cac5b02f3a8f
[1].ConnectId:78709d85-710c-45da-8fec-0ee661d267b1
[1].ScopeVal:
[2].ConnectId:68709d85-710c-45da-8fec-0ee661d267b1
[2].ScopeVal:
My C# model
public class Permissions
{
public String ConnectId { get; set; }
public List<SelectListItem> ScopeTypes;
public String ScopeVal;
}
Method signature that I am posting to
public ActionResult Save(string id, List<Permissions> permissions)
The object seems to be correctly posted to the server with all with all fields in the request but in the method the list that gets serialized from this data only contains the connect ID, it is of the correct size and I get a list that has 3 objects in it but ScopeVal is never set.
Is this a bug in MVC or am I doing something horribly wrong?

The MVC default Model Binder will only bind properties with public getters and setters.
You need to change the field
public String ScopeVal;
to be a property
public String ScopeVal { get; set; }
It's bad C# to expose class fields - this is what properties are for.

Related

Add model into model using Html helper HiddenFor C# MVC

I have a model like
public class Model
{
public int Value { get; set; }
public List<OtherModel> List { get; set; }
}
public class OtherModel
{
public int Value1 { get; set; }
public int Value2 { get; set; }
public bool IsPropTrue { get; set; }
}
I am using Model in a View where I'm looping through the List to show data in a table.
Depending on whether one of the properties (IsPropTrue) in OtherModel is true or false, I want to use the HiddenFor Html helper and send the data to the HttpPost controller.
#model Model
#foreach (var item in Model.List)
{
if (item.IsPropTrue)
{
#Html.HiddenFor(model=> item.Value1)
#Html.HiddenFor(model=> item.Value2)
}
}
I think it doesn't work because I should in some way add these properties to the OtherModel, which is inside the Model; But the way I have it now, I am adding properties to Model.
you can do it like this :
#model Model
#foreach (var item in Model.List)
{
if (item.IsPropTrue)
{
#Html.HiddenFor(model => model.List[Model.List.IndexOf(item)].Value1)
#Html.HiddenFor(model => model.List[Model.List.IndexOf(item)].Value2)
}
}
this way the binding system will bind the hidden fields with your List OtherModel in the Model
if you want send an array to server based on the Model you have to use indexer in #Html.HiddenFor .
#model WebApplication1.Models.MyModel
<form>
#if (Model != null && Model.List != null)
{
for (int i = 0; i < Model.List.Count; i++)
{
if (Model.List[i].IsPropTrue)
{
#Html.HiddenFor(model => Model.List[i].Value1)
#Html.HiddenFor(model => Model.List[i].Value2)
}
}
}
<button type="submit">submit</button>
</form>
if you want know reason of using indexer on model i recommend How does MVC 4 List Model Binding work?
Consider if it the responsibility of the view or the controller action to make the decisions - you can send everything back to the action to do the decision making.
In your Views/Shared folder, create a controller called EditorTemplates
In this folder, add a partial view called OtherModel
In this view, set the model to OtherModel and set the Layout=null
Add the three OtherModel fields in EditorFor (and HiddenFor if not displaying isPropTrue). This partial view displays just one instance of your list.
In your main view, use the above editor model like so. MVC will take care of all rendering and postback of the Model State for your complete list of items. We like one-liners...
#Html.EditorFor(model => model.OtherModel)
When the data is subsequently posted back to an action, Model State has wrapped up all of your displayed items into a list again, so you can check the isPropTrue value for each item on the server.
The only issue with MVC is that is you pass an empty list out to a view, you get a null value back, so just replace this with an empty list when null is returned

Data Binding in ASP.NET MVC

I know it would be a basic question but I'm a newbie to ASP.Net MVC. I have fetched data from database using LINQ but there is an issue. I wanna bind that data with input fields of a customized webform. (I'm using MVC). I wanna populate the input fields of webform with fetched data. I'm using EF Database first approach.
My Controller and view is attached.
Controller ActionMethod
public class HomeController : Controller
{
public ActionResult Index()
{
AutoRTGSEntities_1 dc = new AutoRTGSEntities_1();
//dc.policies.Where(cb => cb.Section_Key.Contains("SenderBIC"));
return View(dc.policies.Where(cb => cb.Policy_Section.Contains("RTGS")).ToList()); //get RTGS policy section data
}
}
View
#model IEnumerable<Swift_MT_103.policy>
#{
ViewBag.Title = "Home Page";
}
<div> #Model{ #Html.TextBoxFor(x => x.data_Value)) } </div>
<div> <input type="text" name="ReceiverBIC" id="ReceiverBIC" /> </div>
Rest is HTML and CSS. Snap is attached.
Here's a very basic example of how to this. Let's say you have following class:
public class User
{
public int Id { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "E-mailaddress")]
public string E-mail { get; set; }
}
In the controller you get the user:
public ActionResult Index(int id)
{
var user = Db.Users.FirstOrDefault(x => x.Id == id);
if(user != null)
{
return View(user);
}
//Return to the 'Error' view as no user was found
return View("Error");
}
You also need a View to show everything on screen. Make it a strongly typed view, this way you can pass a Model to it. This class will hold all data you want to pass to the view. Code of the view:
//This line lets the view know which class represents the model
#model User
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
Using the Razor syntax instead of plain HTML it is very easy to construct and bind your form elements to the corresponding data. In this case the label will show the value of the Display attribute in the User class and the values of the user will be filled in the textboxes.
More reading:
Getting started with ASP.NET MVC 5
ASP.NET MVC Overview
Update:
In case you have a list of objects, you need to enumerate them in the view:
#model IEnumerable<string>
#foreach (var value in Model)
{
<div>#value</div>
}
And if the model is a class and has a property that is a list:
//Let's say a user has lots of names
public class User
{
public int Id { get; set; }
public List<string> Names { get; set; }
}
//View:
#model User
#Html.TextBoxFor(m => m.Id)
#foreach (var name in Model.Names)
{
<div>#name</div>
}
Try to implement a correct ASP.NET MVC architecture. To get this completed, you'll need to use proper Razor (.cshtml type) Syntax in your Views. Best practice:
Create a dedicated ViewModel class in the Model directory. You might call it CustomerCreditTransferViewModel for example. It should contain all Properties you want to display/edit anywhere on the page.
Once you selected your data from your DBContext in your Action, create an instance of CustomerCreditTransferViewModel and populate all fields from the result.
Update your View to use #model CustomerCreditTransferViewModel instead of Swift_MT_103.policy (believe me, this is going to make your live much easier in future)
Copy-paste your raw HTML Code into the page and start looking for all Fields you want to bind, e.g. Text fields (<input type="text" name="accountno" value="" />) and replace them with the Razor Syntax for Data Binding (#Html.TextBoxFor(x => x.AccountNo)). If done correctly, they should be populated now.
Next step is probably the POST. Follow the base MVC Post technique from the Tutorials. Ensure that the Posted Value is of type CustomerCreditTransferViewModel) again, so you can easily validate values and map back to type of Swift_MT_103.policy.

Bind checkboxes to List in partial view

I have a CreateViewModel.
public class CreateViewModel
{
public AttributesViewModel AttributesInfo { get; set; }
}
The AttributesViewModel is sent to a partial view.
public class AttributesViewModel
{
public AttributesViewModel()
{
ChosenAttributes = new List<int>();
}
public List<Attributes> Attributes { get; set; }
public List<int> ChosenAttributes { get; set; }
}
The List of Attributes is outputted in the partial view. Each one has a checkbox.
foreach (var attribute in Model.Attributes)
{
<input type="checkbox" name="ChosenAttributes" value="#attribute.ID" /> #Attribute.Name
}
When I post CreateViewModel, AttributesInfo.ChosenAttributes is always empty even though I checked some boxes. How do I properly name each checkbox so that it binds to the ChosenAttributes List?
My Solution
I took Stephen Muecke's suggestion to do the two way binding. So, I created a CheckboxInfo class that contained Value, Text, and IsChecked. I created a EditorTemplate for it:
#model Project.CheckboxInfo
#Html.HiddenFor(model => model.Text)
#Html.HiddenFor(model => model.Value)
#Html.CheckBoxFor(model => model.IsChecked) #Model.Text
One GIANT caveat. To get this to work properly, I had to create an EditorTemplate for the AttributesViewModel class. Without it, when CreateViewModel is posted, it cannot link the checkboxes to AttributesInfo.
Your naming the checkbox name="ChosenAttributes" but CreateViewModel does not contain a property named ChosenAttributes (only one named AttributesInfo). You may be able make this work using
<input type="checkbox" name="AttributesInfo.ChosenAttributes" value="#attribute.ID" /> #Attribute.Name
but the correct approach is to use a proper view model that would contain a boolean property (say) bool IsSelected and use strongly typed helpers to bind to your properties in a for loop or using a custom EditorTemplate so that your controls are correctly names and you get 2-way model binding.
I had a similar scenario, but this was how I did it. The solution is not perfect so please excuse if I have left something out, but you should be able to relate. I tried to simplify your solution as well :)
I changed the Attribute class name to CustomerAttribute, rename it to whatever you like, use a singular name, not plural. Add a property to your CustomerAttribute class, call it whatever you like, I called mine IsChange.
public class CustomerAttribute
{
public bool IsChange { get; set; }
// The rest stays the same as what you have it in your Attributes class
public string Name { get; set; } // I'm assuming you have a name property
}
Delete your AttributesViewModel class, you don't really need it, I like simplicity.
Modify your CreateViewModel class to look like this:
public class CreateViewModel
{
public CreateViewModel()
{
CustomerAttributes = new List<CustomerAttribute>();
}
public List<CustomerAttribute> CustomerAttributes { get; set; }
}
Your controller will look something like this:
public ActionResult Create()
{
CreateViewModel model = new CreateViewModel();
// Populate your customer attributes
return View(model);
}
Your post controller action method would look something like this:
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
// Do whatever you need to do
}
In your view, you will have something like this:
<table>
<tbody>
#for (int i = 0; i < Model.CustomerAttributes.Count(); i++)
{
<tr>
<td>#Html.DisplayFor(x => x.CustomerAttributes[i].Name)</td>
<td>#Html.CheckBoxFor(x => x.CustomerAttributes[i].IsChange)</td>
</tr>
}
<tbody>
</table>
Create a sample app and try out the code above and see if it works for you.

Handling nested collections with mvc4 view to controller

I've been struggling for a few hours on a concept that I feel should be simple. I have a Model that is essentially a Quiz with a Collection of Questions and that collection has a collection of Answers. Here is a example of my model (simplified):
public class QuizModel
{
public List<Question> Questions { get; set; }
}
public class Question
{
public string TheQuestion { get; set; }
public List<Answer> Answers { get; set; }
}
public class Answer
{
public string Value { get; set; }
}
And the troublesome part, my view:
#using (Html.BeginForm("SubmitQuiz", "Quiz", FormMethod.Post, new { role = "form" }))
{
<ol>
#{
#Html.Hidden("Id", Model.Id, new { Id="pQuizModel"})
for (int vQIndex = 0; vQIndex < Model.Questions.Count; vQIndex++)
{
<li>
#Model.Questions.ElementAt(vQIndex).Question
<ul class="list-unstyled">
#{
for (int vAIndex = 0; vAIndex < Model.Questions.ElementAt(vQIndex).Answers.Count; vAIndex++)
{
<li>#Html.RadioButtonFor(pModel => pModel.Questions.ElementAt(vQIndex).SelectedAnswer, Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Value) #Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Value</li>
//<li>#Html.RadioButton(Model.Questions.ElementAt(vQIndex).Id.ToString() + ":" + Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Id, Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Id)
// #Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Value</li>
}
}
</ul>
</li>
}
}
</ol>
<button type="submit" class="btn btn-default">Submit</button>
}
My controller, just trying to debug it and make sure the Model is filled out properly:
public ActionResult SubmitQuiz(QuizModel pQuizModel)
{
return View();
}
I've literally tried a ton of different suggestions on my view. It's pretty easy to bind directly to a single value in a Model, I can even get my pQuizModel to have the correct Hidden piece you see in the View. But nothing else in the Model gets populated and I can't figure out why.\
Edit: To clarify my problem, the view is good but the controller does not receive any values in the pQuizModel parameter. I don't have the binding setup properly, need some help there.
The problem is not with the Razor rendering of your initial page, it's with the model binder on the resulting POST (after they push the button). The model binder makes certain assumptions about how to map ids into nested items. Because you're not following the convention as you form your Razor code, it isn't filling the method's input properties. See http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx for a similar example.

Update a Model and a Collection of Models on a Single View (MVC4)

I've been working on an MVC 4 Application and have run into a problem when attempting to update Models in a ViewModel.
My ViewModel (detailed below) contains one ComplexObjectOne and a List<ComplexObjectTwo>.
My GET ActionResult successfully populates the ViewModel from a database and everything displays correctly on my View.
The problem is encountered when attempting to pass the ComplexObjectOne and List<ComplexObjectTwo> to the POST ActionResult.
The ComplexObject is passed correctly but everything I've tried fails pass the List<ComplexObjectTwo> collection.
My ComplexModelOne Model
public class Test
{
public int Id {get;set;}
public string Result {get;set;}
public virtual ICollection<TestResult> TestResults {get;set;}
}
My ComplexModelTwo Model
public class TestResult
{
public int Id {get;set;}
public string Result {get;set;}
public string Comment {get;set;}
public virtual Test Test{get;set;}
}
My ViewModel
public class TestingViewModel
{
public TestingViewModel()
{
if(TestResults == null)
{
TestResults = new List<TestResult>();
}
}
public Test Test {get;set;}
public IEnumerable<TestResult> TestResults {get;set;}
}
My Edit() GET ActionResult
public ActionResult Edit(int id = 0)
{
var viewModel = new TestingViewModel();
Test test = testRepo.GetTestById(id);
var results = test.TestResults;
viewModel.Test = test;
viewModel.TestResults = results;
return View(viewModel);
}
My Edit() POST ActionResult
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(TestingViewModel model)
{
// do update - left out for brevity
}
My Edit.cshtml View
#model Namespace.Models.ViewModels.TestingViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.EditorFor(model => model.Test, "TestHeader")
<table>
<tr>
<th>Test</th>
<th>Result</th>
<th>Comment</th>
</tr>
#Html.EditorFor(model => model.TestResults, "TestResults")
</table>
<input type="submit" value="Update"/>
}
Within my View I do use a couple of EditorTemplates to display the property fields.
Any assistance, comments, or suggestions will be much appreciated. I'd like to be able to accomplish updating these entities on a single page instead of multiple pages which I resorted to in the Create() steps.
Thank you,
Patrick H. (stpatrck)
Replace:
#Html.EditorFor(model => model.TestResults, "TestResults")
with:
#Html.EditorFor(model => model.TestResults)
and then rename your EditorTemplates/TestResults.cshtml editor template to EditorTemplates/TestResult.cshtml (notice the missing s) and inside replace the model declaration from:
#model IEnumerable<TestResult>
to:
#model TestResult
Now obviously this will lead to getting rid of any for or foreach loops you might have written in this editor template because now ASP.NET MVC will automatically invoke the template for each element of the collection.
So for example:
#foreach (var item in Model)
{
#Html.EditorFor(x => item.SomeProperty)
}
will simply become:
#Html.EditorFor(x => x.SomeProperty)
Now look at the generated markup and notice the difference in the names of your input fields. Before you had:
<input type="text" name="item.SomeProperty" value="foo" />
and now you have:
<input type="text" name="TestResults[0].SomeProperty" value="foo" />
Now when you submit the form to the POST action the default model binder will be able to successfully bind the collection because now the naming convention is respected. You can read more about this convention in the following blog post.
Also you have circular references in your object graph which cannot be successfully serialized and model bound. You should use view models in order to break this circular dependency.

Categories

Resources