I have two models:
class ModelIn{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
class ModelOut{
public ModelOut(){ People = new List<ModelIn>();}
public List<ModelIn> People { get; private set;}
public string Country { get; set; }
}
And I have Controller editing ModelOut:
public ActionResult People()
{
...
return View(SomeModelOutInstanceWith3People);
}
[HttpPost]
public ActionResult(ModelOut items)
{
...
}
In view I have sth like:
<% using (Html.BeginForm()) { %>
<%: Html.EditorFor(m => Model.Country) %>
<% for(int i = 0; i < Model.People.Count; ++i){ %>
<%: Html.EditorFor(m => Model.People[i].FirstName) %>
<%: Html.EditorFor(m => Model.People[i].LastName) %>
<%: Html.EditorFor(m => Model.People[i].Address) %>
<% } %>
<input type="submit" />
<% } %>
It works all OK, but in post action I have empty ModelOut items.
I can see in logs that data is sent correctly.
I have tried everything, nothing works.
Have you tried simply <%: Html.EditorFor(m => m.People) %> ?
MVC should loop through the list by itself.
Also, watch out how you specify your lambdas, it should be m => m rather than m => Model.
PS. I use MVC3...
The reason for your problem could be a naming mismatch...from what I remember the default model binder does not do its job properly because of this naming mismatch....this means you need to specify more info to the model binder to do its job better... try updating your View code to use the below code for each property...
<%= Html.EditorFor(string.Format("People[{0}].FirstName", i), Model.People[i].FirstName) %>
The above view code will generate the following markup
<input id="People_0__FirstName" name="People[0].FirstName" type="text" />
I might have a syntactical problem above but I guess you can get it right with the help of Visual Studio
#Dai were right. MVC let me use items for model instance name when it is instance of List, but doesn't let me use it for ModelOut.
After renaming items to model it works fine.
Related
I have a model, MyViewModel, with some members (not all are included).
namespace ANameSpace
{
public class MyViewModel
{
public int ID { get; set }
public EditTableObject EditTable { get; set }
public List<EditTableObject> EditTables { get; set }
// some other members
}
public class EditTableObject
{
public int ID { get; set }
// some other members
}
}
In the controller there are two ActionResult Index() methods
In The first, Index(int? id), I assign a value to model.ID and also add some EditTableObjects to an EditTables list.
Then, in the second, Index(ViewModel tmpModel), I am trying to use the members that I gave values to in the first index method. However, the ID member does not contain the value I assigned it; it's null. But strangely, the EditTables list does contain the values I assigned to it.
Here is some code from the controller:
namespace AnotherNamespace
{
public class MyController
{
public ActionResult Index(int? id)
{
if (id != null)
{
model.ID = (int)id;
}
else
{
model.ID = 1000;
}
model.EditTable = new EditTableObject();
model.EditTable.ID = model.ID;
model.EditTables.Add(model.EditTable);
return View(model);
}
public ActionResult(MyViewModel tmpModel)
{
return RedirectToAction("Index", new { id = tmpModel.ID });
}
}
}
If I set a break point on the second Index method I find that all the EditTables data persists but not the model.ID data.
Why is the EditTables data persisting but not the model.ID data?
Thanks for any suggestions.
Eventhough I don't quite get your question (posting some code that doesn't work will highly improve your chances of getting an answer) I'll give it a shot.
HttpRequests are stateless. So between the calls to the different methods you need to persist your data. Usually that's been done using a database.
This is a good source to get started with MVC: http://www.asp.net/mvc
Because you are not giving the entire model.
In order to receive the model you need to set your view based on that model and pass it to the controller on httppost.
Example
Controller
public ActionResult Create()
{
List<EditTableObject> myList = ....*YourList*....
ViewBag.EditTables = myList;
ViewModel vm = new ViewModel();
return View(vm);
}
[HttpPost]
public ActionResult Create(ViewModel myViewModel)
{
if(ModelState.IsValid)
{
// DO SOMETHING
}
else
{
return View(myViewModel);
}
}
View
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<namespace.ViewModel>" %>
<% using (Html.BeginForm()) { %>
<%: Html.AntiForgeryToken() %>
<%: Html.ValidationSummary(true) %>
<%: Html.HiddenFor(model => model.ID) %>
<div class="editor-field">
<%: Html.DropDownList("TableObject", IEnumerable<SelectListItem>)ViewBag.EditTable,String.Empty)%>
</div>
<% } %>
Okay, I figured it out.
In the the Razor View I added #Html.HiddenFor(modelItem => Model.ID) to a table row.
Here it is in more context:
#for (int i = 0; i < Model.EditTables.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => modelItem.EditTables[i].ID)
#Html.HiddenFor(modelItem => Model.ID)
</td>
</tr>
}
Sorry for my lack of code in the beginning. I work for a company that would not be happy I shared code on the internet so I have to pick and choose and be careful what I post.
i was wondering if its possible to pass an object based on a checkbox selection. Ill explain myself: If there is a selected value, that really means that I need to pass the object that the selection represents in order to use it.
I have this code in my view:
<% foreach (var _client in ViewData["channels"] as List<DigiTV.Models.CANAL>) { %>
<%= Html.CheckBox(_client.NOM_CANAL) %> <%= Html.Encode(_client.NOM_CANAL) %> <br />
<% } %>
As you can see, I have a list of the object type that I want to pass to the controller (List)
Do someone has any suggestions?
thanks
I would very strongly recommend you using view models, strongly typed views and editor templates.
So as always start by defining a view model which will contain all the necessary data your view might need:
public class CanalViewModel
{
public string Name { get; set; }
public bool Selected { get; set; }
}
then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new[]
{
new CanalViewModel { Name = "canal 1", Selected = false },
new CanalViewModel { Name = "canal 2", Selected = true },
new CanalViewModel { Name = "canal 3", Selected = false },
new CanalViewModel { Name = "canal 4", Selected = false },
};
return View(model);
}
[HttpPost]
public ActionResult Index(IEnumerable<CanalViewModel> model)
{
return View(model);
}
}
and next comes the ~/Views/Home/Index.aspx view:
<%# Page
Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<AppName.Models.CanalViewModel>>"
%>
<% using (Html.BeginForm()) { %>
<%= Html.EditorForModel() %>
<input type="submit" value="OK" />
<% } %>
and finally you will need an editor template for a canal which will be executed for each element in the model (~/Views/Home/EditorTemplates/CanalViewModel.ascx):
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.CanalViewModel>"
%>
<div>
<%= Html.HiddenFor(x => x.Name) %>
<%= Html.CheckBoxFor(x => x.Selected) %>
<%= Html.LabelFor(x => x.Selected, Model.Name) %>
</div>
Now when you submit the form, inside the POST action you will get a list of all canals along with their selected property being depending on which checkboxes the user select.
As you can see we don't need any ViewData which will require you to perform some ugly casts in your views and you don't need to write any foreach loops in your views. Everything is handled automatically by the framework following well established conventions.
you can make the using javascript by collecting all checked checkboxes values(comma separated) in a hidden field then read this values from your controller and split it.
<% foreach (var _client in ViewData["channels"] as List<DigiTV.Models.CANAL>) { %>
<%= Html.CheckBox(_client.NOM_CANAL) %> <%= Html.Encode(_client.NOM_CANAL) %> <br />
<% } %>
<%=Html.HiddenField("AllValues")%>
javascript(I am using jquery)
var allvalues='';
$('input[type=checkbox]').each(function(index){
if($(this).is(':checked'))
{
allvalues+=$(this).val();
}
});
$('#AllValues').val(allvalues);
in you controller
public ActionResult MyAction(FormCollection form)
{
String[] AllValues = form["AllValues"].Split(",");
}
In my view I have the following call.
<%= Html.EditorFor(x => x.Cost) %>
I have a ViewModel with the following code to define Cost.
public decimal Cost { get; set; }
However this displays a decimal value with four digits after the decimal (e.g. 0.0000). I am aware of Decimal.toString("G") (MSDN) which appears to solve this issue, but I'm uncertain of where to apply it.
One solution seems to be create a partial view "Currency.aspx".
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Decimal>" %>
<%= Html.TextBox(Model.ToString("g"), new { #class = "currency" }) %>
And a [UIHint("Currency")] in my ViewModel.
This seems inelegant. I assume that this problem has been solved tidily somewhere in the MVC framework or C# but I am unaware of cleaner solutions.
What is the appropriate way to handle editing currency values in MVC?
[DisplayFormat(DataFormatString = "{0:F2}", ApplyFormatInEditMode = true)]
public decimal Cost { get; set; }
and in your view:
<%= Html.EditorFor(x => x.Cost) %>
and that's all.
You will probably want to apply a custom CSS class. You could do this:
<div class="currency">
<%= Html.EditorFor(x => x.Cost) %>
</div>
and then have in your css:
.currency input {
/** some CSS rules **/
}
or write a custom DataAnnotationsModelMetadataProvider which will allow you to:
[DisplayFormat(DataFormatString = "{0:F2}", ApplyFormatInEditMode = true)]
[HtmlProperties(CssClass = "currency")]
public decimal Cost { get; set; }
and then in your view:
<%= Html.EditorFor(x => x.Cost) %>
This is the appropriate way if you are using EditorFor templates.
What does "inordinately inelegant" mean?
Say you have this:
public class ShoppingCart {
public IList<CartItem> cartItems {get; set; }
}
And you do this to render the class:
<%= EditorFor( m => m.ShoppingCart, "ShoppingCart") %>
How would you do the EditorFor( ??, "CartItem") in the ShoppingCart.ascx? I would think it would look something like this:
<% foreach( CartItem myCartItem in m.cartItems) {
%><%= EditorFor( ??, "CartItem")
%><% } %>
The idea here of course is to use a UI template for an entire class, not just a property.
<% for (int count = 0; count < Model.cartItems.Count; count++ )
{ %><%=
Html.EditorFor(m => m.cartItems[count]) %><%
}
%>
Creates form names like:
name="cartItems[0].Name"
name="cartItems[1].Name"
name="cartItems[2].Name"
Which bind back to the original List view model
If the model of your ShoppingCart.ascx is the ShoppingCart class, then you should be able to do
<% foreach (CartItem myCartItem in m.cartItems) { %>
<%= EditorFor(m => myCartItem, "CartItem") %>
<% } %>
I have the following types and classes:
namespace MVC.Models
public class Page
{
public EditableContent Content {get; set; }
}
public class EditableContent
{
public TemplateSection SidebarLeft {get; set; }
public TemplateSection SidebarRight {get; set; }
}
I want to edit the Page instance in my Edit.aspx View. Because EditableContent is also attached to other models, I have a PartialView called ContentEditor.ascx that is strongly typed and takes an instance of EditableContent and renders it.
The rendering part all works fine, but when I post - everything inside my ContentEditor is not binded - which means that Page.Content is null.
On the PartialView, I use strongly typed Html Helpers to do this:
<%= Html.HiddenFor(m => m.TemplateId) %>
But because the input elements on the form that are rendered by ContentEditor.ascx does not get the Content prefix to its id attribute - the values are not binded to Page.
I tried using loosely typed helpers to overcome this:
<%= Html.Hidden("Content.TemplateId", Model.TemplateId) %>
And when I'm dealing with a property that is a List<T> of something it gets very ugly. I then have to render collection indexes manually.
Should I put both Page and EditableContent as parameters to the controller action?:
public ActionResult Edit(Page page, EditableContent content) { ... }
What am I missing?
I would suggest you to use the EditorFor helper
Model:
public class EditableContent
{
public string SidebarLeft { get; set; }
public string SidebarRight { get; set; }
}
public class Page
{
public EditableContent Content { get; set; }
}
Views/Home/Index.aspx:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ToDD.Models.Page>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Home Page
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<%--
This is the important part: It will look for
Views/Shared/EditorTemplates/EditableContent.ascx
and render it. You could also specify a prefix
--%>
<%= Html.EditorFor(page => page.Content, "Content") %>
<input type="submit" value="create" />
<% } %>
</asp:Content>
Views/Shared/EditorTemplates/EditableContent.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ToDD.Models.EditableContent>" %>
<%= Html.TextBoxFor(m => m.SidebarLeft) %>
<br/>
<%= Html.TextBoxFor(m => m.SidebarRight) %>
And finally Controller/HomeController:
public class HomeController : Controller
{
public ActionResult Edit()
{
var page = new Page
{
Content = new EditableContent
{
SidebarLeft = "left",
SidebarRight = "right"
}
};
return View(page);
}
[HttpPost]
public ActionResult Edit(Page page)
{
return View(page);
}
}