I'm trying to use the WebGrid html helper in ASP.NET MVC 3 to autogenerate the columns according to the information found in the ModelMetadata. For example the code in a view that accepts a list of objects would be:
var grid = new WebGrid(Model);
#grid.GetHtml(columns: ViewData.ModelMetadata.Properties.Single.Properties
.Select(p => grid.Column(
columnName: p.PropertyName,
header: p.ShortDisplayName
)));
This actually works like a charm (I was surprised it was that easy actually). What happens here is that from the properties of the model I use the ShortDisplayName as the column's header.
The problem? I need to apply a default format to all columns. Basically I want to use the Html.Raw extension for all the data that my grid will display. An attempt would be something like that :
var grid = new WebGrid(Model);
#grid.GetHtml(columns: ViewData.ModelMetadata.Properties.Single.Properties
.Select(p => grid.Column(
columnName: p.PropertyName,
header: p.ShortDisplayName,
format: (item) => Html.Raw(GetPropertyValue(item, p.PropertyName))
)));
where the method GetPropertyValue would read the value of the property using reflection or whatever (I need to remind here that item is dynamic and its value is actually the object that is being displayed in the current row).
Is there any better way to do this?
Thanks,
Kostas
I suggest you looking into MVCContrib Grid project: http://mvccontrib.codeplex.com/wikipage?title=Grid
Don't know if you still need some help with this question, but I had a problem just like yours and that's what I did to solve it:
I Used a Foreach loop to iterate through the properties
Filled a variable with the name of the property
Formated the column
The code that I got was something like this:
var columns = List<WebGridColumn>();
foreach (var p in ViewData.ModelMetadata.Properties.Single.Properties) {
String propertyName = p.PropertyName;
columns.Add(grid.Column(p.PropertyName, p.ShortDisplayName, format: item => Html.Raw(GetPropertyValue(item.Value, propertyName))));
}
#grid.GetHtml(columns: columns.ToArray());
And that's how I get the property's value:
public static object GetPropertyValue(object obj, String propertyName)
{
if (propertyName.Contains("."))
{
int index = propertyName.IndexOf(".");
object prop = obj.GetType().GetProperty(propertyName.Substring(0, index)).GetValue(obj, null);
return GetPropertyValue(prop, propertyName.Substring(index + 1));
}
return obj.GetType().GetProperty(propertyName).GetValue(obj, null);
}
I really don't know if this is the best way, but it's working pretty good for me.
Related
I have a MVC 3 applicaiton in which I pass a vewmodel from the controller to the view. The vewmodel contains a couple of List<> properties.
public ActionResult MainView()
{
var model = GetViewModel();
return View("SignificantEventsView", model);
}
private SignificantEventsViewModel GetViewModel()
{
var viewModel = new SignificantEventsViewModel();
List<County_Codes> countyCodes = GetCountyCodeList();
List<String> stateNames = countyCodes.OrderBy(o=>o.County_st).Select(o => o.County_st ).Distinct().ToList();
viewModel.selectedState = stateNames.FirstOrDefault();
viewModel.CountyCodesList = countyCodes;
viewModel.StateNames = stateNames;
viewModel.SelectedCounties = new String[]{};
viewModel.SelectedCountyCodes = new String[] { };
viewModel.UnSelectedCounties = new String[] { };
viewModel.UnSelectedCountyCodes = new String[]{};
return viewModel;
}
The View looks like this:
#model ServicingPortal.ViewModels.SignificantEventsViewModel
#{
ViewBag.Title = "Significant Events";
}
<h2>SignificantEvents</h2>
#using (Html.BeginForm("RefreshCounties", "SignificantEvents", FormMethod.Post, new { id = "significantEventsForm", Model }))
{
<fieldset>
<span class="SpanTextboxEdit">
#Html.Label("states", "States")
<br />
<br />
#Html.DropDownListFor(o => #Model.selectedState
, new SelectList(Model.StateNames)
, new { id = "stateDropDown", onchange = "submit()", name = "test" })
</span>
</fieldset>
...
}
When the StateDropdownList is changed the veiwmodel is passed back to the controller, but the countyCodes list is always null.
I tried adding #Html.HiddenFor(o => #Model.CountyCodesList) in the view, but it still returns null. The only values that don't seem to be null are the primitive types such as String or String[]. Even the List stateNames is null.
I don't want to rebuild the county code list on each post back because there is substantial overhead involved. I have to create the list from all active loans in the database, of which there are thousands.
How can I get a List<> to persist from the view to the controller?
I should explain what I'm trying to acheive here.
I have a dropdown and a multiselect list box. The dropdown contains states and the listbox contains counties filtered by the selected state.
I need to filter the listbox contents when the selected state changes.
It would make sense to perform this task on the client side, but I have not found a good solution.
I will admit my javascript skills are quite limited.
All the solutions I researched one way or another involved filtering the county list on the server side.
I can accomplish this on the server side easy enough, but I thought that since I have already built the list, why not keep it intact instead of going to the backend each time.
The short answer is that you can't really do what you're trying to do. You're kind of trying to solve the wrong problem. You should look at using caching on the server side to prevent going back to the database to construct the county list every time.
I solved this by using TempData. On the postback action I can get the County List from temp data and set the ViewModel CountyCodeList to this value.
I have a selectlistitem that gathers data from the database and inserts it into a dropdownlist. All of that works but I can't seem to make the INT work for instance if I write this.. etc. The issue is with the ArticleID
IEnumerable<SelectListItem> items = db.Articles.Where(c=>c.RegistrationID==getid)
.Select(c => new SelectListItem
{
Value =c.ArticleID +"/"+ c.title,
Text = c.ArticleID + "/" + c.title
});
ViewBag.articles = items;
I get the error Unable to cast the type 'System.Int32' to type 'System.Object' and
then my dropdownlist gets highlighted which is
#Html.DropDownList("article1",(IEnumerable<SelectListItem>)ViewBag.articles)
I obviously can't do c.ArticleID.Tostring() , so how can I fix this issue ? If I take the ArticleID out everything works correctly but I need that in there..
You would actually use c.ArticleID.ToString(). The reason is that you're concatenating a string together, therefore need string values to do so. Value and Text are both string types so it makes sense that you need strings.
I have a drop down list in C# asp.net MVC3 razor engine.
I need to load values to that dropdownlist from one of my tables in my database and some values are hard coded.
So I need to get both kind of values into one dropdown list.
I can do them separately.
This is how my view is :
#Html.DropDownListFor(model => model.MyTransaction.Status, new MultiSelectList(ViewBag.MyStatusId, "ID", "Name"))
My Model where enums are created :
public enum Ntypes{
halfday
casual
}
My Controller :
ViewBag.MyTransaction = db.LeaveTypes.ToList(); //get the table values to drop down
//then even I can get the hard coded values separately ............
ViewBag.MyTansaction = (from NewLeaveTypes t in Enum.GetValues(typeof(Ntypes))
select new { ID = t, Name = t.ToString()).ToList();
But cant get both values into one dropdownlist.
Plzzzz Help.
Thanks...........
You could concatenate the 2 lists together:
var nTypes = Enum
.GetValues(typeof(Ntypes))
.Select(t => new LeaveType { ID = t, Name = t.ToString())
.ToList();
ViewBag.MyTransaction = db.LeaveTypes.ToList().Concat(nTypes);
and then in the view:
#Html.DropDownListFor(
model => model.MyTransaction.Status,
new SelectList(ViewBag.MyTransaction, "ID", "Name")
)
According to http://msdn.microsoft.com/en-us/library/dd470803.aspx, the MultiSelectList(IEnumerable, IEnumerable) constructor takes in two parameters: items and selectedValues.
The documentation is not completely explicit so I just want to clarify two points:
How exactly does selectedValues work? Does this constructor merely iterate through the collection and set .Selected = True for each element?
Must selectedValues be a subset of items? How is this defined, precisely (i.e. as long as their ToString values match)?
Specifically, I am playing with a jQuery multiselect plugin and am trying to do essentially what is being done in the demo of that plugin (i.e. the "selected" list is already populated by certain elements upon initialization).
The HTML:
<%=Html.ListBoxFor(model => model.tempCategories, (MultiSelectList)(ViewData["Categories"]), new {#size = "5" })%>
Controller code:
List<Categories> categoriesList = categories.Select();
ViewData["Categories"] = GenCategoryMultiList(categoriesList);
private System.Web.Mvc.MultiSelectList GenCategoryMultiList(List<TemplateCategories> entity)
{
entity = entity.OrderBy(e => e.CategoryName).ToList();
System.Web.Mvc.MultiSelectList selectList = new System.Web.Mvc.MultiSelectList(entity, "CategoryID", "CategoryName");
return selectList;
}
I am using this with JQuery Multiselect. Working code...
Am testing out MvcContrib's grid for sorting.
Am using LightSpeed as my ORM
Problem: getting compile error on: listOfRfidTags = ...
The type arguments for method 'System.Linq.Enumerable.OrderBy(System.Collections.Generic.IEnumerable, System.Func, System.Collections.Generic.IComparer)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
public ActionResult Index(GridSortOptions sort)
{
IEnumerable<RfidTag> listOfRfidTags = uow.RfidTags;
if(sort.Column != null) {
listOfRfidTags = listOfRfidTags.OrderBy(sort.Column, sort.Direction);
}
ViewData["sort"] = sort;
return View(listOfRfidTags);
}
view:
#Html.Grid(Model).Columns(column =>{
column.For(a => Html.ActionLink("Edit", "Edit", new { id = a.Id })).Named("Edit");
column.For(a => a.TagCode).Named("TagCode").Sortable(true);
column.For(a => a.Number);
})
You are getting this compiling error because you are trying to use an OrderBy extension method that is only defined in MvcContrib and not in System.Linq.
In order to fix it, you just need to use the following line:
using MvcContrib.Sorting;
And then you can use the OrderBy method as in your original code:
listOfRfidTags = listOfRfidTags.OrderBy(sort.Column, sort.Direction);
Although itowlson answer works, he just reimplements what the OrderBy extension method in MvcContrib already does (see SortExtensions.cs).
The OrderBy extension method takes a delegate for getting the sort key, not a column and direction. So this line:
listOfRfidTags = listOfRfidTags.OrderBy(sort.Column, sort.Direction);
needs to look something like this:
listOfRfidTags = listOfRfidTags.OrderBy(r => r.SomeProperty);
(or OrderByDescending depending on the sort.Direction). The trouble is that SomeProperty can't be determined at compile time because you want it to come from sort.Column. This means that if you want to use LINQ then you'll probably need to use Dynamic LINQ or Reflection to extract the property you want to sort on e.g.
PropertyInfo property = typeof(RfidTag).GetProperty(sort.Column);
listOfRfidTags = listOfRfidTags.OrderBy(r => property.GetValue(r));
However, since you are using LightSpeed as your ORM, you can bypass LINQ and use the core API, which does allow dynamic column names:
Order order = Order.By(sort.Column);
if (sort.Direction == SortDirection.Descending))
order = order.Descending();
IList<RfidTag> listOfRfidTags = uow.Find<RfidTag>(new Query { Order = order });
This has the side benefit that the sorting will happen on the database instead of in the Web application.