How do I return List or Collection to Controller from View in MVC 3? - asp.net-mvc

Someone please help me return this list properly from my view. I don't see why I'm returning null for my fieldModelList I try to pass to the controller...
Here is my view:
#model List<Regions.SOA.UI.CopyBookSchemaCreator.Models.FieldModel>
<script type="text/javascript" src="~/Scripts/jquery-ui-1.8.11.min.js"></script>
#using (Html.BeginForm("GetResponse", "TestMethods", FormMethod.Post))
{
<table id="tblMethods">
<tr>
<th>
Property Name
</th>
<th>
Request
</th>
</tr>
#foreach (FieldModel fieldModel in Model)
{
<tr>
<td>
#Html.DisplayFor(m => fieldModel.PropertyName)
</td>
<td>
#Html.TextBoxFor(m => fieldModel.PropertyValue)
</td>
</tr>
}
</table>
<div>
<input type="submit"/>
</div>
and here is my controller:
[HttpPost]
public ActionResult GetResponse(List<FieldModel> fieldModelList)
{
return GetResponse(fieldModelList);
}
I am hitting the HttpPost method but if I place a breakpoint just inside it, I am returning null for the fieldModelList right off the bat, which I was hoping would be a list of the values I entered into the texboxes on the view that is of model FieldModel...
I think something is wrong with my logic versus my syntax, or as maybe as well as my syntax, but basically what I want to do is return back a list of type FieldModel with each corresponding PropertyName and PropertyValue to the controller. I noticed I am not passing any kind of id parameter in my BeginForm statement in the view. Do I need one here?
Just in case, here is my model class for FieldModel:
namespace Regions.SOA.UI.CopyBookSchemaCreator.Models
{
public class FieldModel
{
[Display(Name = "Property")]
public string PropertyName { get; set; }
[Display(Name = "Value")]
public string PropertyValue { get; set; }
}
}

Phil Haack wrote an article some time ago explaining how to bind collections (ICollection) to view models. It goes into additional detail about creating an editor template, which you could certainly do as well.
Basically, you need to prefix the HTML elements' name attributes with an index.
<input type="text" name="[0].PropertyName" value="Curious George" />
<input type="text" name="[0].PropertyValue" value="H.A. Rey" />
<input type="text" name="[1].PropertyName" value="Ender's Game" />
<input type="text" name="[1].PropertyValue" value="Orson Scott Card" />
Then, your controller could bind the collection of FieldModel
[HttpPost]
public ActionResult GetResponse(List<FieldModel> fieldModelList)
{
return GetResponse(fieldModelList);
}
I'm not 100% sure the following would name the attributes correctly (I'd recommend using the editor template) but you could easily use the htmlAttributes argument and give it a name using the index.
#for(int i = 0;i < Model.Count;i++)
{
<tr>
<td>
#Html.DisplayFor(m => m[i].PropertyName)
</td>
<td>
#Html.TextBoxFor(m => m[i].PropertyValue)
</td>
</tr>
}
Editor Template
If you wanted to go as far as adding an editor template, add a partial view named FieldModel.ascx to /Views/Shared that is strongly typed to a FieldModel
#model Regions.SOA.UI.CopyBookSchemaCreator.Models.FieldModel
#Html.TextBoxFor(m => m.PropertyName) #* This might be a label? *#
#Html.TextBoxFor(m => m.PropertyValue)
And, then the part of your view responsible for rendering the collection would look like:
#for (int i = 0; i < Model.Count; i++) {
#Html.EditorFor(m => m[i]);
}

Related

Selecting multiple from #Html.ListBoxFor

I have added #Html.ListBoxFor i am able to bind it properly but unable to get multiple selected items after submit. Am i missing something ?
// Below is the code for binding
public ActionResult Create()
{
var cities = So.BL.City.GetCities();
SelectList cityList = new SelectList(cities, "Id", "Name", cityId);
TempData["Cities"] = cityList;
return View("Create");
}
[HttpPost]
public ActionResult Create([Bind(Include="Keywords,Cities")] So.Entities.Approval filter)
{
if (ModelState.IsValid)
{
}
return View(filter);
}
Below is the view file code. I dont have a view model just entities
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table style="width: 100%">
<tr>
<td>
Cities:
</td>
</tr>
<tr>
#* <td>
#Html.DropDownList("Cities", (SelectList)TempData["Cities"])
</td>*#
</tr>
<tr>
<td>
#Html.ListBoxFor(model => model.Id, new SelectList(TempData["Cities"] as MultiSelectList, "Value","Text",new { style = "width:250px" })))
</td>
</tr>
<tr>
<td>
#Html.LabelFor(model => model.Keywords)
</td>
</tr>
<tr>
<td>
#Html.EditorFor(model => model.Keywords)
</td>
</tr>
<tr>
<td>
</td>
</tr>
<tr>
<td>
<input type="submit" value="Create" class="button" />
</td>
</tr>
</table>
Assuming you have changed the ID property to typeof int[] as per the comments, you still have problems, the main one being that your POST method has
[Bind(Include="Keywords,Cities")]
which excludes binding of the ID property so it will always be null on post back. When ever you use the [Bind] attribute you should reconsider what you doing and use a view model to display/edit just the properties you want, including a property for the SelectList.
You also have some pointless code in the #Html.ListBoxFor() method. TempData["Cities"] is already a SelectList so new SelectList(TempData["Cities"] as MultiSelectList, "Value","Text" is converting the SelectList to a MultiSelectList and then creating a new SelectList form it. All you need is
#Html.ListBoxFor(model => model.ID, (SelectList)TempData["Cities"], new { style = "width:250px" })
In addition, the 3rd parameter in this
SelectList cityList = new SelectList(cities, "Id", "Name", cityId);
is not required (not sure where you declared cityId because as it is, your code does not compile). The ListBoxFor() method selects the options based on the value of your ID property, and ignores the selectedValue parameter in the SelectList constructor.
Finally, in your POST method, if the model is not valid you return the view. You need to reassign the value of TempData["Cities"] or this will be null in the view and throw an exception.
Like Andrei is suggesting, you need to bind the selected value to an array type.
Front End:
<div class="DualListBoxDIV">
#Html.ListBoxFor(model => model.RelatedNewsArticlesSelected, new MultiSelectList(Model.AssignedRelatedNewsArticles, "Value", "Text", Model.RelatedNewsArticlesSelected), new { #class = "SelectedBox", Size = 10 })
</div>
Back End:
public string[] RelatedNewsArticlesSelected { get; set; }
public IEnumerable<SelectListItem> AssignedRelatedNewsArticles { get; set; }
public IEnumerable<SelectListItem> UnassignedRelatedNewsArticles { get; set; }

Postback on partially loaded view brings null model

My application have page with few search boxes. When user clicks on respective search button a result grid is expected to load. That grid have few editable controls in which user can modifications and hit save button if needed to save the data.
We have implemented the functionality using AjaxForm and partial view.
Search area containing search boxes and search button is created using Ajax.BeginForm, on submit of which a postback Search method is called from controller. SearchModel is passed to this method.
Partial View is created to show results and Ajax form in step 1 loads it successfully from controller postback method. SearchModel.Results property is passed to view as its model.
This partial view showing results have a Save button (again a Ajax form) which invokes another method in controller but gets model null in controller.
have tried lot of tricks but was unsuccessful. Any working example demonstrated anywhere or suggestion to make this working ? There are lot of examples on web where usage of AjaxForm to load data is explained but didn’t found any for multiple (or nested?)
Thanks in advance.
Edit - Feb 24
Here is the sample I created using Visual Studio default MVC template, which is similar to the actual criteria explained above and have the same problem on Submit of Partial page ..
Views:
Index.cshtml
#using MvcApplication1.Models
#model SearchModel
#{
ViewBag.Title = "Home Page";
}
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" ></script>
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
</p>
#Ajax.BeginForm("Search", "Home", new AjaxOptions { HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,UpdateTargetId = "SearchResults"})
{
<table>
<tr>
<td>
First Name:
</td>
<td>
#Html.TextBoxFor(m => m.SearchString)
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Submit" />
</td>
</tr>
</table>
}
<div id="SearchResults" style="color: Green;"></div>
Partial View - _SearchResult.cshtml
#using MvcApplication1.Models
#model MvcApplication1.Models.SearchModel
#{
ViewBag.Title = "Result Partial";
}
<h2>
testPartial</h2>
#Ajax.BeginForm("SearchResult", "Home", new AjaxOptions
{
HttpMethod = "Post"
})
{
<table>
#foreach (ResultModel item in Model.Result)
{
<tr>
<td>
Name:
</td>
<td>
#Html.DisplayFor(m => item.Name)
</td>
</tr>
<tr>
<td>
Address:
</td>
<td>
#Html.TextAreaFor(m => item.Address)
</td>
</tr>
}
<tr>
<td colspan="2">
<input type="submit" value="Submit" />
</td>
</tr>
</table>
}
Models:
SearchModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1.Models
{
public class SearchModel
{
public string SearchString { get; set; }
public List<ResultModel> Result { get; set; }
}
}
ResultModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1.Models
{
public class ResultModel
{
public string Name { get; set; }
public string Address { get; set; }
}
}
Controller:
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
[HttpPost]
public ActionResult Search(SearchModel model)
{
//dummy search for result
List<ResultModel> result = new List<ResultModel>();
ResultModel res1 = new ResultModel();
res1.Name = model.SearchString + " 1";
res1.Address = "Dummy address";
result.Add(res1);
ResultModel res2 = new ResultModel();
res2.Name = model.SearchString + " 2";
res2.Address = "Rummy address";
result.Add(res2);
//assign seach results to model
model.Result = result;
return PartialView("_SearchResult", model);
}
[HttpPost]
public ActionResult SearchResult(SearchModel model)
{
//do something with results
List<ResultModel> res = model.Result; // null here !!
return RedirectToAction("Index");
}
}
}
SO, above sample will
Show you a Search box on home page - Index.cshtml
When you click on submit it will load a partial view displaying results below search
When you edit search results and hit submit on result form, please have a breakpoint set up there, you will see model being returned is null.
Hope, this explain my problem.
Now that you have shown your actual code with a complete example your question can be answered.
The reason you are getting null is because you are not respecting the standard naming convention for your input fields that the default model binder expects. Please read the following article from Phil Haack to familiarize yourself with those conventions.
The problem with your code is the fact that you used a foreach loop inside your partial to render the results instead of using an editor template. So replace your code inside _SearchResult.cshtml with the following:
#using MvcApplication1.Models
#model SearchModel
#{
ViewBag.Title = "Result Partial";
}
<h2>testPartial</h2>
#using(Ajax.BeginForm("SearchResult", "Home", new AjaxOptions()))
{
<table>
#Html.EditorFor(x => x.Result)
<tr>
<td colspan="2">
<input type="submit" value="Submit" />
</td>
</tr>
</table>
}
and then define a custom editor template for the ResultModel type (~/Views/Shared/EditorTemplates/ResultModel.cshtml - warning, the name and location of your editor template is important because it works by convention):
#using MvcApplication1.Models
#model ResultModel
<tr>
<td>
Name:
</td>
<td>
#Html.DisplayFor(m => m.Name)
#Html.HiddenFor(m => m.Name)
</td>
</tr>
<tr>
<td>
Address:
</td>
<td>
#Html.TextAreaFor(m => m.Address)
</td>
</tr>
Things to notice:
I have wrapped the Ajax.BeginForm helper in a using statement. You should do the same inside your Index.cshtml view
I have added a hidden field for the Name property inside the custom editor template (#Html.HiddenFor(m => m.Name)) in order to send this value to the server when the form is submitted because you only had a TextArea for the Address field meaning that the Name would never have been sent to your server.

Binding input model in partial view help needed

I want to pass an input model from a partial view to a controller. I'm rather new to MVC so still trying to understand how the default model binder works.
Via AJAX (listBox) a controller passes back a partial view and inserts into table id=searchResults.
#model ViewModels.LocationViewModel
#using (Ajax.BeginForm("ProcessSearch", "SearchR", new AjaxOptions
{
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "searchResults",
}))
{
<div>#Html.ListBoxFor(xxx)</div>
<input id="Search" type="submit" value="Search" />
}
Here is the controller and ViewModel that populates the partial view
public class OrderViewModel
{
public string Description { get; set; }
public string Status { get; set; }
}
public ActionResult ProcessSearch(SearchViewModel search)
{
select new OrderViewModel{
Status=f.STATUS,
Description=f.DESCRIPTION}).ToList();
return PartialView(model);
}
In the same main view I have this form that I want I want to bind to yet another view model. I simply don't understand how to implement the default binder from the model of the partial view. I apologize if I didn't explain this correctly. I hope it makes sense.
#using (Html.BeginForm("Testing", "SearchR", FormMethod.Post))
{
<div>#Html.DropDownListFor(yyy)</div>
<input id="Reshop" type="submit" value="Reshop" />
}
<table id="searchResults"></table>
public ActionResult Testing(RSOrderViewModel rOrder)
{
return Content("hey");
}
public class RSOrderViewModel
{
public string yyy { get; set; }
public IEnumerable<OrderViewModel> sovm { get; set; }
}
#model List<ViewModels.OrderViewModel>
#{ViewData.TemplateInfo.HtmlFieldPrefix = "sovm";
}
<table id="searchResults">
<tr>
<th>Order Id</th>
<th>Order Detail</tr>
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(x => x[i].OrderId)
</td>
<td>
#Html.DisplayFor(x => x[i].OrderDetail)
</td>
</tr>
}
</table>
The table is outside the second form. So when you POST to the Testing action all that is sent to the controller is the value of the dropdown list. If you want to send the collection that's stored in this table you will have to either use AJAX or put the table inside the form:
#using (Html.BeginForm("Testing", "SearchR", FormMethod.Post))
{
<div>#Html.DropDownListFor(yyy)</div>
<table id="searchResults"></table>
<input id="Reshop" type="submit" value="Reshop" />
}
Now of course putting the table inside the form doesn't mean that it will send anything to the server when you submit the form. You need to put input fields (hidden if you don't want them to be visible to the user) that will contain the values that will be POSTed back. Also those input field names must follow the standard convention for binding to a list.
You haven't actually shown how does the partial view look like but here's an example of how it might look so that the convention is respected. For example let's suppose that you have 2 properties in your OrderViewModel that you want to be bound: OrderId and OrderDetail:
#model IEnumerable<OrderViewModel>
#{
// We set the name prefix for input fields to "sovm"
// because inside your RSOrderViewModel the collection
// property you want to bind to this table is called sovm
// and in order to respect the convention the input names
// must be in the following form "sovm[0].OrderId", "sovm[1].OrderId", ...
ViewData.TemplateInfo.HtmlFieldPrefix = "sovm";
}
<thead>
<tr>
<th>Order Id</th>
<th>Order detail</th>
</tr>
</thead>
<tbody>
#Html.EditorForModel()
</tbody>
and then you could have an editor template which will be rendered for each element of the model (~/Views/Shared/EditorTemplates/OrderViewModel.cshtml):
#model OrderViewModel
<tr>
<td>#Html.EditorFor(x => x.OrderId)</td>
<td>#Html.EditorFor(x => x.OrderDetail)</td>
</tr>
The name of the template is the name of the type used in the collection you want to bind to (IEnumerable<OrderViewModel> sovm { get; set; } => OrderViewModel.cshtml). It must also be placed inside the ~/Views/Shared/EditorTemplates folder if it can be reused between multiple controllers or if it is specific to the current controller inside the ~/Views/XXX/EditorTemplates folder where XXX is the current controller.

ASP.NET MVC FormCollection TextArea

I have a textarea that represents a description field. The descriptions have commas so when trying to split the field's descriptions the data is not parsed correctly. How can I get each row's description correctly.
var DescList = FormValues["Item.Description"].Split(',').Select(item => item).ToList<string>();
//will not work for obvious reasons. Comma delimited FormCollection has commas to identify separate row data.
It seems like Microsoft designed the FormsCollection without the textarea control in mind. A text area with commas will not work when trying to access each value. What is interesting is that the _entriestables property has it in the perfect format but they chose to make it a private property. Very frustrating.
`
Here is the important part of my viewmodel.
public class TenantViewModel
{
public Tenant Tenant { get; set; }
public Site Site { get; set; }
}
My view is populated like this:
if (Model != null && Model.Tenant != null && Model.Tenant.Site != null && Model.Tenant.Site.Count() > 0)
{<div class="detailsbox_view">
<table id="tblTenantSites">
<tr>
<th>#Html.LabelFor(item => item.Site.Title)</th>
<th>#Html.LabelFor(item => item.Site.Description)</th>
</tr>
#foreach (var Item in Model.Tenant.Sites)
{
<tr>
#Html.HiddenFor(modelItem => Item.SiteId)
<td>
#Html.EditorFor(modelItem => Item.Title)
</td>
<td>
#Html.TextAreaFor(modelItem => Item.Description, new {#width="400" })
</td>
</tr> }
</table>
As you see this site table is a child of Tenant object. This child record does not get automatically updated using this method but the Tenant data does automatically get updated. This is the reason I tried the FormColelction instead.
Is there something I am missing to make this work?
try with this useful function
ValueProviderResult Match=FormCollection.GetValue("ValueProvider");
When you have multiple fields with the same name attribute, they'll come back into your FormCollection as an array. So upon posting a view like this:
<form action="/Home/MyAction">
<textarea id="row_one_description" name="description">
First row's description
</textarea>
<textarea id="row_two_description" name="description">
Second row's description
</textarea>
<input type="submit" value="Submit" />
</form>
you could do something like this in your action
[HttpPost]
public ActionResult MyAction(FormCollection collection)
{
var descriptionArray = collection["description"];
string firstRowDescription = descriptionArray[0];
string secondRowDescription = descriptionArray[1];
}
I must note that this is not the recommended way of dealing with posted data. You should instead be building your view using data from a view model and using strongly typed html helpers to render your controls. That way when you post, your action can take the ViewModel as a parameter. Its properties will be automatically bound and you will have a nice object to play with.
[HttpPost]
public ActionResult MyAction(MyViewModel viewModel)
{
foreach (var row in viewModel.Rows)
{
string description = row.Description;
}
}
EDIT
I'm still assuming a lot about your ViewModel but perhaps try this:
<table id="tblTenantSites">
<tr>
<th>#Html.LabelFor(model => model.Site.Title)</th>
<th>#Html.LabelFor(model => model.Site.Description)</th>
</tr>
#for (var i = i < Model.Tenants.Sites.Count(); i++) {
<tr>
#Html.HiddenFor(model => model.Tenants.Sites[i].SiteId)
<td>
#Html.EditorFor(model => model.Tenants.Sites[i].Title)
</td>
<td>
#Html.TextAreaFor(model => model.Tenants.Sites[i].Description, new { #width="400" } )
</td>
</tr>
}
</table>
You could also try ,
string Match=FormCollection.GetValue("ValueProvider").AttemptedValue;

Asp.net MVC Razor more than one form on a page

Yo
I have a registration page on my site - at the top of the page is a login form for existing users. In the main area there is the registration form.
The login are is a partial view with #model ViewModels.LoginViewModel
The registration are is also a partial with #model ViewModels.RegViewModel
The main page which houses these partials is a view with #model ViewModels.RegPageViewModel
This viewmodel looks like:
public class RegViewModel
{
public RegisterVm RegisterVm { get; set; }
public LoginVm LoginVm { get; set; }
}
When I submit the registration part of the page (it's action is register/capture - the receiving action expects a RegisterVm) to it's controller it complains about being passed the wrong viewmodel
What's the deal with subviews and their viewmodel? Is there a standard approach to dealing with this?
Should I have one submit URL for this page which figures out if it's a login request or a register request and then handles the post accordingly? That seems messy to me though...
http://monobin.com/__d33cf45a4 - RegisterVm.cs (LoginVm.cs is pretty much the same as this)
http://monobin.com/__m69132f76 - RegPageVm.cs
Register.cshtml:
#model xxxx.ViewModels.RegPageVm
#{
View.Title = "Register";
Layout = "~/Views/Shared/_BareBones.cshtml";
}
<link rel="stylesheet" href="#Url.Content("~/Public/Css/signup.css")" type="text/css" />
<div id="sign-up-container">
<div id="sign-up-box">
<div id="sign-up-box-left">
<img src="#Url.Content("~/Public/Images/Signup_176x81.png")" />
</div>
<div id="sign-up-box-right">
#Html.Partial("_Register")
</div>
</div>
</div>
<div class="clear">
</div>
_Register.cshtml:
#model xxxx.ViewModels.RegisterVm
#using (Html.BeginForm("Capture", "Register", FormMethod.Post))
{
<table class="sign-up-box-inner">
<tr>
<td class="label-area">
#Html.LabelFor(x => x.Email)
</td>
<td class="field-area">
#Html.TextBoxFor(x => x.Email, new { #class = "login-input", title = "Enter Name" })
</td>
</tr>
<tr>
<td class="label-area">
#Html.LabelFor(x => x.Password)
</td>
<td class="field-area">
#Html.PasswordFor(x => x.Password, new { #class = "login-input", title = "Enter Name" })
</td>
</tr>
<tr>
<td class="label-area">
#Html.LabelFor(x => x.UserName)
</td>
<td class="field-area">
#Html.TextBoxFor(x => x.UserName, new { #class = "login-input", title = "Enter Name" })
</td>
</tr>
<tr>
<td colspan="2">
<input type="image" src="../../Public/Images/Submit_150x47.png" class="submit-button" />
</td>
</tr>
</table>
#Html.AntiForgeryToken()
}
And finally RegisterController.cs:
public class RegisterController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Capture(RegisterVm registerVm)
{
if (!ModelState.IsValid)
{
return View("index", new RegPageVm()
{
LoginVm = new LoginVm(),
RegisterVm = registerVm
});
}
return RedirectToAction("index", "Event");
}
}
w://
You need to ensure that the form elements (like the textbox etc) should have the same id as the RegisterVM and LoginVM properties. Your theory is right but I think you might be making a mistake in the naming convention of MVC.
If you can share your view code + the VM classes, then we'll be able to help better.
EDIT:
Looking at your code I think you should be passing the view model to your partial view. Like for example the following line believe should be like this >
#Html.Partial("_Register", Model.RegisterVm)
According to your answer to nEEbz:
You are using:
Html.TextBoxFor(x=>x.LoginVM.Email) // i guess
this would turn into <input name="LoginVM.Email" ...>
Notice the LoginVM. part
Your login action probably looks like:
public ActionResult Login(LoginVM model) { }
so it expect field names like Email and Password, not LoginVM.Email and LoginVM.Password.
So you could could use Html.Textbox instead (so that the field name doesn't get autocreated).

Resources