I have a typed view as Item (one class that i created) with a form inside to add Items to my database. This Item class has one property called Categories that is a List (Category has 2 properties ID and Name)
Im using an editorfor in my view:
<div>
#(Html.EditorFor(e => e.Categories, "Categories"))
</div>
I created an EditorTemplatefor called "Categories.cshtml" render all the available categories:
#{
Layout = null;
}
#model List<Category>
#{
foreach (Category category in ((BaseController)this.ViewContext.Controller).BaseStateManager.AvailableCategories)
{
#Html.Label("test", category.Name)
<input type="checkbox" name="Categories" value="#(category.ID)" />
}
}
The checkboxes are well rendered (one for every Available category in cache), but after clicking in some, and post the form, im receiving my instance of Item but with the property Categories empty.
What i have to do to receive my List Categories completely instantiated after submit the form?
Dont loop it. Let the framework generate the code for you (then, it will know how to build it back and bind it to your controller).
Just pass the list to the editor template and mvc will do the rest. Check my blog post on something similar.
Try using an index based loop. This ensures MVC will render the item's attributes in such a way that allows the default model binder to instantiate the model on post back. Also, use the Html helper for the checkbox as well:
var categories = ((BaseController)this.ViewContext.Controller).BaseStateManager.AvailableCategories;
for (var index = 0; index < categories.Count; index ++)
{
#Html.Label("test", categories[index].Name)
#Html.Checkbox("ID", categories[index].ID)
}
Related
I am trying to use a partial view that uses a different model than the one used in the main view. The partial view has
to show a list with the products recently added. But I am stuck on how and where to implement the logic for retrieving the data I need from the database.
Home/Index.cshtml:
#Html.Partial("~/Views/Shared/_LatestProducts.cshtml", new List<Website.Models.LatestProductsList>())
Shared/_LatestProducts.cshtml:
#model List<Website.Models.LatestProductsList>
#foreach (var item in Model)
{
<a href="#" title="img">
<img src="~/Content/images/latest-product-img.jpg" alt="" /><p>#item.ProductName</p>
</a>
}
And I have the following code that I am trying to use in order to get some products for tests and show them in the partial view:
public PartialViewResult _LatestProducts()
{
List<LatestProductsList> latestProd = (from p in db.Products
where p.ID < 5
select new LatestProductsList { ProductName = p.Title }).ToList();
return PartialView(latestProd);
}
I thought that I might use it in the HomeController, but that obviously doesn't work and I am not sure if partial views should have their own controller, if I can
just call it from another class. I am still wrapping my head around ASP MVC, so any help will be appreciate it.
Just call the action that renders the partial view in Index.cshtml.
#Html.Action("_LatestProducts", "Product")
Second parameter is the name of the controller that has the _LatestProducts method.
Just a reminder: Names with _ prefix is for partial views only, not action methods. You should rename it to LatestProducts.
I have the following form:
<li>
<% using (Html.BeginForm("TestMethod", "MyController", FormMethod.Post, new {id = "TestMethod"}))
{%>
<%= Html.Hidden("model", Model.MyListOfObjects) %>
<%}%>
Test
</li>
And the javascript function for the onclick is as follows:
function SubmitForm() {
document.forms["TestMethod"].submit();
}
I am trying to pass the list of objects from the view into the controller, but i have yet managed to get this to work. My Controller function is:
[Authorize]
[HttpPost]
public ActionResult TestMethod(List<Objects> model)
{
dynamic Expando = new ExpandoObject();
Expando.test = model;
return View(Expando );
}
When I view the List of objects in the debugger it always displays "System.Collections.Generic.List`1[]" with no actual objects inside.
So my question is what should I be doing to pass a List of objects into a controller?
I have also tried:
<% using (Html.BeginForm("TestMethod", "MyWork", FormMethod.Post, new {id = "TestMethod"}))
{%>
<% int itemx = 0; %>
<% foreach (var x in Model.MyListOfObjects)
{%>
<%= Html.Hidden("model"+"["+itemx+"]", x) %>
<%itemx++; %>
<% } %>
<%}%>
You cannot just put List<object> as action parameter and expect the model binder to be able to automagically guess what object types you want to put there. You will need to write a custom model binder if you wanted to handle multiple sub-types as illustrated in this post.
And if you want to use a single type for the list such as List<MyViewModel> then simply loop through each element of the list (respecting the convention) and for each element build a hidden field for each property that you want to bind.
But since those are hidden fields, I guess that the user is not supposed to modify them. In this case those hidden fields have nothing to do in your view. Let's not reinvent the ViewState that we were all so happy to get rid of when we moved to ASP.NET MVC from classic WebForms. Simply put a hidden field containing an unique id that will allow you to refetch the corresponding list elements in the POST action given this unique id from wherever you fetched them initially (your database or something I suppose).
You need to have one hidden element for each object in the list, and named model[0], model[1], etc.
I have poked around but not found out what the ViewModel, or TempData or how my objects are being persisted for my form.
I display a custom view model in an asp.net MVC view, I edit a list of objects in that view and display them all inside a dynamic grid inside an html form, then submit the changes. When I get back to the controller I check existing objects vs the forms submitted object list and update the objects.
When I redisplay the form, objects that were deleted still show up and have values in the textbox inside the html elements, so it is asp popluating the fileds and not a browser cache. I have a checkbox that displays next to the row if it is an existing object already and those checkboxes are submitted to the controller as an array of values (the id of the object to remove).
So I delete the object, pull clean ones out of the database and set the list in the viewmodel with the newly retrieved data. However, the form shows the old object still, but there is no delete checkbox next to them so they were not retrieved from the database.
How do I fix that? I tried tweaking the methods output cache (not a browser issue as the DB ID key does not exists anymore ... no delete checkbox). I tried making a new view model an explicitly setting variables before sending to the view...no go.
My solution for now was to redirect to the get method after I edit all of the objects (simpleObject in the example) and start completely over.
A simplified example is as follows:
public class CustomViewModel {
List<SimpleObject> objects {get;set;}
}
public class SimpleObect {
public int iA {get;set;}
public int AddonHistID {get;set;}
}
Controller:
[HTTPGet] // get method and displays 2 objects by default
public ActionResult whatever( string something){
CustomViewModel form = new CustomViewModel ();
form.objects = new List<SimpleObject>();
form.objects.Add( new SimpleObect());
form.objects.Add( new SimpleObect());
return View( form)
}
[HttpPost]
public ActionResult whatever( string something, CustomViewModel form){
// adjust objects to show current objects aftering saving changes (reload and rebind to ModelView)
form.objects = getObjectsAfterChange( something); // just gets objects from db after all changes are made in this controller action
return View( form);
}
View:
<% using( Html.BeginForm()) { %>
<table width="800" id="SearchAddonsResults">
foreach( SimpleObject addonHist in Model.objects )
{
++iOdd;
string cssClass = (iOdd % 2 == 0 ? "rowOdd" : "rowEven");
%>
<tr class="<%= cssClass %>">
<td>
<%if (addonHist.AddonID > 0)
{ %>
<input type="checkbox" name="RemoveAddon" id="RemoveAddon" value="<%= addonHist.AddonID.ToString() %>" />
<% } %>
<%= addonHist.AddonHistID.ToString() %>
</td>
<td><%= addonHist.iA.ToString() %></td>
</tr>
<% } %>
</table>
<% }; //endform %>
I think this might help you get the results you expect.
Phil Haack's Blog:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Which is linked to from:
How to model bind to a List<ViewModel>?
Complex model binding to a list
How ASP.NET MVC: How can I bind a property of type List<T>?
So I delete the object, pull clean ones out of the database
and set the list in the viewmodel with the newly retrieved
data. However, the form shows the old object still,
This makes no sense. Are you absolutely sure that this line
form.objects = getObjectsAfterChange( something);
in your HttpPost method is retrieving the right information?
I would start by inspecting the value that getObjectsAfterChange( something) is returning . I suspect it's returning more than you think and that's where the problem is (rather than on the render of the view.)
Basically I have a form that I am dynamically adding objects to. I am doing this with AJAX so can just initialise the object and return it with JSON. Each new object has a unique GUID assigned to it so we can identify each object in the model collection when it is passed back into the action.
However, I need to support non JavaScript so am trying to write a solution that will post back the model and add or remove the given object from the model. There can be any number of these new objects on the model so I need to pass back several things to find out which object to delete before returning the model back to the view. This could be either
a) The GUID for the object the user has deleted.
b) The button that has been clicked to identify which object to delete.
The problem is that the partial view is generic and I would like to keep it that way so I'm trying to pass the identifying GUID back with the input button on each partial view but don't know how. I can easily do this with JavaScript because I just remove the created AJAX object from the page before posting it when the user clicks the remove link but can't figure out how to do it with a submit. Basically I want to do something like this:
#using (Project.Namespace.Infrastructure.Helpers.HtmlPrefixScopeExtensions.HtmlFieldPrefixScope _scope = Html.BeginCollectionItem())
{
<ul class="ulMedicationsControl">
#Html.ActionLink("Remove This Object", "RemoveObject", null)
#Html.Input("RemoveObject", "Remove This Object", new { Prefix = _scope.Prefix, objectGUID = IdentifyingGUID })
#Html.HiddenFor(m => m.IdentifyingGUID);
<li class="liQuestion">
#Html.MandatoryLabelFor(m => m.myField)
#Html.TextBoxFor(m => m.myField)
</li>
</ul>
<div id="#(_scope.Prefix).ajaxPlaceholder"></div>
}
In the controller:
[ActionName("FormName")]
[AcceptParameter(Name = "RemoveObject", Value = "Remove This Object")]
public ActionResult RemoveObject(MyParentModel model, string Prefix, string objectGUID)
{
Guid ID = new Guid(objectGUID);
foreach (ObjectModel object in model.objects){
if (object.IdentifyingGUID == ID)
{
model.objects.Remove(object);
break;
}
}
return View(model);
}
Any help I would really appreciate as I simple can't figure out how to do this!
EDIT
Also just to add the prefix attribute simply identifies where in the form the object sits. This will be needed for me to find which object list to go through and remove the object from as there may be several lists in different placed in the model.
An HTML input only passes "name=value" when a form post occurs so that's all you have to work with. With <input type=submit> you're further limited by the fact that the button's value is its caption (i.e. "myControl=Click Me!" is posted), so you can't stick anything programmatically meaningful in the value.
Method 1: So you're left with encoding all the information you need into the input's name - an approach that works fine, but you'll have to have to go digging into the controller action method's FormCollection parameter rather than relying on model binding. For example:
<input name="delete$#(_scope.Prefix)$#objectGUID" type="submit" value="Delete me" />
Better, have a helper class that encapsulates the string format with a ToString override and has Parse/TryParse/etc static methods, which could be used like this:
<input name="#(new DeleteToken{Prefix=_scope.Prefix, objectGUID=IdentifyingGUID})" type="submit" value="Delete me" />
In your action method:
[HttpPost]
public ActionResult Foo(FormCollection formData)
{
var deleteTokens = DeleteToken.ParseAll(formData.AllKeys);
foreach (var token in deleteTokens)
{
//...do the deletion
}
}
Method 2: An alternative approach is to group each item into its own <form> (bear in mind you can't nest forms) - so when the submit happens, only its surrounding form is posted in which you can stash hidden inputs with the necessary data. e.g.
<ul class="ulMedicationsControl">
<form ... >
<!-- hidden field and submit button and whatever else here -->
...
</form>
</ul>
I have created a DisplayTemplate for a Comment class, and placed it inside Comment/DisplayTemplates/Comment.cshtml.
Comment.cshtml is properly typed:
#model Comment
Then, I have a partial view that takes an IEnumerable<Comment> for model. In there I loop through the collection and would like to use the DisplayTemplate for the Comment class. The view, in its integrity:
#model IEnumerable<Comment>
#foreach (var comment in Model.Where(c => c.Parent == null)) {
#Html.DisplayFor(model => comment)
}
However, I get an error on the Html.DisplayFor line:
The model item passed into the dictionary is of type 'System.Int32', but this dictionary requires a model item of type 'System.String'.
How can I invoke the DisplayTemplate for each item in the foreach loop?
Instead of having a view that take an IEnumerable<Comment> and that all it does is loop through the collection and call the proper display template simply:
#Html.DisplayFor(x => x.Comments)
where the Comments property is an IEnumerable<Comment> which will automatically do the looping and render the Comment.cshtml display template for each item of this collection.
Or if you really need such a view (don't know why) you could simply:
#model IEnumerable<Comment>
#Html.DisplayForModel()
As far as the Where clause you are using in there you should simply remove it and delegate this task to the controller. It's the controller's responsibility to prepare the view model, not the view performing such tasks.
While the accepted answer works well most of the time, there are other cases in which we need to be aware of the element's index when rendering (i.e. add custom javascript that generates references to each element based on their index).
In that case, DisplayFor can still be used within the loop like this:
#model IEnumerable<Comment>
#for (int index = 0; index < Model.Count(); index++)
{
#Html.DisplayFor(model => model[index])
}