ASP.NET MVC: Accessing ModelMetadata for items in a collection - asp.net-mvc

I'm trying to write an auto-scaffolder for Index views. I'd like to be able to pass in a collection of models or view-models (e.g., IEnumerable<MyViewModel>) and get back an HTML table that uses the DisplayName attribute for the headings (th elements) and Html.Display(propertyName) for the cells (td elements). Each row should correspond to one item in the collection.
When I'm only displaying a single record, as in a Details view, I use ViewData.ModelMetadata.Properties to obtain the list of properties for a given model. But what happens when the model I pass to the view is a collection of model or view-model objects and not a model or view-model itself?
How do I obtain the ModelMetadata for a particular item in a collection?

A simple extension method might do the job:
public static class MyExtensions
{
public static ModelMetadata GetMetadata<TModel>(this TModel model)
{
return ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TModel));
}
}
And in your view:
<%# Page
Language="C#"
Inherits="System.Web.Mvc.ViewPage<System.Collections.Generic.IEnumerable<MyViewModel>>" %>
<%-- Get metadata for the first item in the model collection --%>
<%= Html.Encode(Model.ElementAt(0).GetMetadata().ModelType) %>

Related

MVC Moving from one Model View to Another Model View

I am trying to move from one Model's view to another Model's view. I have a Person model and a BurnProject model. From my Person's Index view I have a "Select" link in which I would like for it to go the BurnProject's Index view. I have tried a couple of things neither have worked.
public ActionResult BurnProject()
{
//return View("~/Views/BurnProject.cshtml");
return RedirectToAction("Index", BurnProject);
}
From my Person's Index view I have a "Select" link in which I would
like for it to go the BurnProject's Index view
Why not create a link which navigates to the index action method of BurnProjectsController ?
So in your Person's index view, you may use the Html.ActionLink helper method.
#model Person
<h1>This is persons index view<h1>
#Html.ActionLink("Select","Index","BurnProjects")
This will generate html markup for an anchor tag which has href attribute set to "BurnProjects/Index".
If you want to pass some data from the person's index view to your BurnProject index action, you can use another overload of Html.ActionLink
#model Person
#Html.ActionLink("Select","Index","BurnProjects",new {#id=Model.Id},null)
Assuming your Person entity has an Id property(which you want to pass the value for) and your BurnProjects index action accepts an id param
public ActionResult Index(int id)
{
// return something.
}

asp.net mvc ViewModel in a View with List of objects has old list items displayed after removing in controller

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.)

asp.net mvc 3 Model with complex property and checkboxes

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)
}

How to model bind to a List<ViewModel>?

I'm trying to accomplish something like this. I feel like it's possible, and if not probably an oversight in the MVC framework?
View:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<List<MyViewModel>>" %>
...
<% foreach (MyViewModel vm in Model) {
Html.RenderPartial("MyViewModelPartial", vm);
} %>
The partial view being an editable form, strongly typed to a single MyViewModel, and use the DataAnnotations on the MyViewModel class to validate
Controller:
public ActionResult FooController(List<MyViewModel> vml)
{
...
}
Is this possible? This seems like the most logical way to build grid/table structures in MVC(with each partial view being a table row) but I can't seem to get it to work and I end up using FormCollection in my controller to loop through the whole dang form, and it's just messy.
See:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Which is linked to from:
Complex model binding to a list
How ASP.NET MVC: How can I bind a property of type List<T>?

MVC View - how to display the "category" of objects collection, not just the objects themselves

Using ASP MVC and Entity Framework. In the view, you have a page declaration that specifies the model for this view will be a collection implementing IEnumerable. Let's say that collection holds Car objects, that are only from Ford (Ford being the Category).
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<detelete.Models.Car>>" %>
This list of Ford cars only, was generated via a LINQ to Entities query. The EF object thingee is aware of the relationship of Cars to Manufacturers (which I call the category)
var dat = ent.CarSet.Where(m => m.Manufacturer.Name == nm);
List<Car> cars = dat.ToList<Car>();
return View("ListingByManufacturer", cars);
So, in the view I display the list of cars that are all Ford's. I have the view displaying all the cars properties correctly, but there isn't a way to show what category (manufacturer in this example) the cars are from. I have seen some EF/MVC examples that have two foreach loops, and the top one displays the manufacturer - but that feels klugey.
Seems like it should be simple, but I am stuck...
You can just pass the Manufacturer name via ViewData in the controller :
ViewData["Manufacturer"] = nm;
Or use a another object as your model that holds both cars and the manufacturer name
public class CategoryViewModel {
public string ManufacturerName { get; set; }
public List<Car> Cars { get; set; }
}
And pass an instance of that class to the view.

Resources