I have this function that returns the type IEnumerable<SelectListItem>.
public IEnumerable<SelectListItem> GetItems()
{
IEnumerable<SelectListItem> results = null;
results =*(some logic)*
return results;
}
I try to bind this to dropdown in a view using
<% foreach (IEnumerable<SelectListItem> schdItem in Model.GetItems())
{%>
<%= Html.DropDownList("xxx", schdItem)%>
<%} %>
But it breaks with the error message
Unable to cast object of type 'System.Web.Mvc.SelectListItem' to type 'System.Collections.Generic.IEnumerable`1[System.Web.Mvc.SelectListItem]'.
How do I fix this?
Based on the comment from asawyer I modified it to
<%= Html.DropDownList("xxx", Model.GetScheduleItems())%>
now it works!
No loop needed, just this.
<%= Html.DropDownList("xxx", Model.GetItems())%>
I would make the SelectList a property on your model rather than a getter method.
<%= Html.DropDownList("xxx", Model.MySelectList)%>
<%= Html.DropDownList("xxx", Model.GetItems())%>
Althought this is a terrible way to go about it. Instead, you should be using Html.DropDownListFor such as this:
<%= Html.DropDownListFor(Model.SelectedItem, Model.Items) %>
Where Model.SelectedItem is the type of item, and Model.Items is a property that returns a collection of SelectListItems.
Related
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.
Is there any way to access any attributes (be it data annotation attributes, validation attributes or custom attributes) on ViewModel properties from the view? One of the things I would like to add a little required indicator next to fields whose property has a [Required] attribute.
For example if my ViewModel looked like this:
public class MyViewModel
{
[Required]
public int MyRequiredField { get; set; }
}
I would want to do something in the EditorFor template like so:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<int?>" %>
<div class="label-container">
<%: Html.Label("") %>
<% if (PROPERTY_HAS_REQUIRED_ATTRIBUTE) { %>
<span class="required">*</span>
<% } %>
</div>
<div class="field-container">
<%: Html.TextBox("") %>
<%: Html.ValidationMessage("") %>
</div>
The information you're looking for is in ViewData.ModelMetadata. Brad Wilson's blog post series on Templates should explain it all, especially the post on ModelMetadata.
As far as the other ValidationAttributes go, you can access them via the ModelMetadata.GetValidators() method.
ModelMetadata.IsRequired will tell you if a complex type (or value type wrapped in Nullable<T>) is required by a RequiredAttribute, but it will give you false positives for value types that are not nullable (because they are implicitly required). You can work around this with the following:
bool isReallyRequired = metadata.IsRequired
&& (!metadata.ModelType.IsValueType || metadata.IsNullableValueType);
Note: You need to use !metadata.ModelType.IsValueType instead of model.IsComplexType, because ModelMetadata.IsComplexType returns false for MVC does not consider to be a complex type, which includes strings.
I would suggest not doing that way because you're adding logic in the view which is a bad practice.
Why don't you create a HtmlHelper or LabelExtension, you can call ModelMetaProvider inside the method and find out whether the property has Required attribute decorated?
The standard MVC example to draw an item with the appropriate View Template is:
Html.DisplayFor(m => m.Date)
If the Model object has a property named Date of type DateTime, this returns a string with the HTML from the Display/DateTime.ascx template.
Suppose you wanted to do the same thing, but couldn't use the strongly-typed version - you didn't know the Model's type for this View at compile time. You use the older:
Html.Display("Date");
So here's the hard part.
Suppose the Model is IEnumerable. You don't know what those objects are at compile-time, but at run-time they happen to be objects with a Date of type DateTime again, like:
public class ModelClass
{
public DateTime Date { get; set; }
}
Now suppose you want your View to iterate over those objects and render each out. If all you cared about was the value you could do this:
<%
StringBuilder sb = new StringBuilder();
foreach(object obj in (IEnumerable<object>)Model)
{
Type type = obj.GetType();
foreach(PropertyInfo prop in type.GetProperties())
{
// TODO: Draw the appropriate Display PartialView/Template instead
sb.AppendLine(prop.GetValue(obj, null).ToString());
}
}
%>
<%= sb.ToString() %>
I'm obviously taking some shortcuts to keep this example focused.
Here's the point - how do I fulfill that TODO I've written for myself? I don't just want to get the value - I want to get it nicely formatted like Html.Display("Date"). But if I just call Html.Display("Date"), it inspects the Model, which is an IEnumerable, for a property named Date, which it of course does not have. Html.Display doesn't take an object as an argument to use as the Model (like Html.Display(obj, "Date"), and all the classes and methods I can find that lie underneath appear to be internal so I can't tweak and call into them directly.
There must be some simple way to accomplish what I'm trying to do, but I can't seem to find it.
Just to make sure I'm being clear - here's an example of the code of DateTime.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.DateTime>" %>
<%= Model.ToString("MM/dd/yyyy") %>
And so, ideally, the output from this View that can take any Model, but in this case a list of 3 of these ModelClass objects above, would be:
11/10/2001
11/10/2002
11/10/2003
Because the code would find the Display PartialView for DateTime and render it appropriately for each.
So - how do I fulfill the TODO?
Have a look at the template code in this excellent post from Phil Haack. It seems to come close to what you are looking for: http://haacked.com/archive/2010/05/05/asp-net-mvc-tabular-display-template.aspx
I've found one potential solution to this but I'm not in love with it; it requires using several file-based templates, meaning you can't abstract this easily into a code library for use in multiple projects.
The View:
<%
StringBuilder sb = new StringBuilder();
Type itemType = Model.GetType().GetGenericArguments()[0];
sb.AppendLine("<table>");
// Pass in the Model (IEnumerable<object>)'s generic item type as
// the Model for a PartialView that draws the header
sb.Append(Html.Partial("DisplayTableHead", itemType));
foreach(object item in (IEnumerable<object>)Model)
{
sb.Append(Html.Partial("DisplayTableRow", item));
}
sb.AppendLine("</table>");
%>
<%= sb.ToString() %>
Views/Shared/DisplayTableHead.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Type>" %>
<tr>
<%
foreach (PropertyInfo prop in Model.GetProperties())
{
%>
<th><%= prop.Name %></th>
<%
}
%>
</tr>
Views/Shared/DisplayTableRow.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<tr>
<%
Type modelType = Model.GetType();
foreach (PropertyInfo modelField in modelType.GetProperties())
{
%>
<td><%= Html.Display(modelField.Name) %></td>
<%
}
%>
</tr>
But I now see the major flaw in this solution, which is that Clicktricity's posted solution acknowledges details in the ModelMetadata - like whether that particular property is set for display, whether it's complex or not, etc.
in the aspx page i am getting this error while binding dropdown list
Unable to cast object of type
'System.Web.Mvc.SelectList' to type
'System.Collections.Generic.IList`1[System.Web.Mvc.SelectListItem]'.
I have written:
<p>
<label for="categoryId">Category:</label>
<%= Html.DropDownList("categoryId", (IList<SelectListItem>)ViewData["categoryId"])%>
<%= Html.ValidationMessage("categoryId", "*")%>
</p>
please tell me the correct way of writing.
thanks
ritz
Here is a nice example of exactly what you are trying to accomplish:
How to bind IList with MVC Dropdownlist box
It looks like you will have to add some code-behind code to build the compatible list type.
what is the code in the controller action that you use to generate viewdata["categoryId"], here is what i normally do in the action code:
ArrayList categoryList=New ArrayList;
foreach (category c In YourcategoryCollection)
{ categoryList.Add(New With {.Item = c.categoryName, .value = c.categoryID})
}
Viewdata("categoryId")=New SelectList(categoryList, "Value", "Item", itemToEdit.categoryID)}
and then in your view, you just need:
<%= Html.DropDownList("categoryId", ViewData["categoryId"])%>
I have a database menu structure which I would like to add to the site.master file.
I’ve looked at other questions on StackOverflow but cannot get this to work on my website.
How do I add a User Control to the Site.Master file?
Menu.ascx
<%foreach (MainMenuSort mainMenuSort in (List<MainMenuSort>)ViewData["MainMenuSortListDisplay"])
{ %>
<li><%= Html.Encode(mainMenuSort.MainMenuId.MainMenuName)%></li>
<%foreach (SubMenuSort subMenuSort in (List<SubMenuSort>)ViewData["SubMenuSortListDisplay"])
{%>
<%if (mainMenuSort.MainMenuId.Id == subMenuSort.SubMenuId.MainMenu.Id)
{ %>
<li><%= Html.Encode(subMenuSort.SubMenuId.SubMenuName)%></li>
<%} %>
<%} %>
<%}%>
You need to use the Html.RenderPartial method in your master page.
You will need to set the MainMenuSortListDisplay and SubMenuSortListDisplay view data keys in whatever action is calling the view that uses your master page.
In your master use this
<% Html.RenderPartial("~/Views/Shared/Menu.ascx");
The path needs to be the app relative path to the control's folder. Typically these go under Shared. You can make the structure how you want below the Shared folder.
To make this technique stronger, use a strongly typed partial. In the question you would perhaps make a new class (MenuModel) with two generic collections as properties and place it in the models folder of the application. Then in the model's constructor call a method that populates the lists.
public class MenuModel
{
public IEnumerable<MainMenuSort> OuterList {get; set;}
public IEnumerable<SubMEnuSort> InnerList {get; set;}
public MenuModel()
{
VoidThatFillsTheInnerAndOuterList();
}
This will mean that you can do this in your controller
public ActionResult ShowAForm()
{
ViewData["MenuPartialData"] = new MenuModel();
return View();
}
Having set this key, your master page can use the overload of RenderPartial, like this
<% Html.RenderPartial(
"~/View/Shared/Menu.ascx",
(MenuModel)ViewData["MenuPartialData"]); %>
This assumes that your partial is strongly typed to the MenuModel class. Then in the partial you can use the model which rewrites your code slightly
<% foreach (MainMenuSort mainMenuSort in Model.OuterList) { %>
<li><%= Html.Encode(mainMenuSort.MainMenuId.MainMenuName)%></li>
<% foreach (SubMenuSort subMenuSort in Model.InnerList) {%>
<%if (mainMenuSort.MainMenuId.Id == subMenuSort.SubMenuId.MainMenu.Id)
{ %>
<li><%= Html.Encode(subMenuSort.SubMenuId.SubMenuName)%></li>
<%} %>
<%} %>
<%}%>
Hope that helps
Try something like
<% Html.RenderPartial("Menu") %>
EDIT: Corrected a typo
You could also do it as a HTMLHelper and in the MasterPage just call <%= Html.Menu() %>. Then in your HTMLHelper you have the code to get the database records and loop through them. Here is a link I found to get you started. Note my comments as there is a bug in the code example provided. I'm still having issues handling subitems of menus, I guess I need a recursive function or something??
With the help of this link. I was able to display a menu in the site.master page.