format the items in a selectlist - model-view-controller

i have seen other posts with answers to this but i cannot get any of them to work for me.. so. this is in my controller->
ViewData["TARGET_DATE"] = new SelectList((from n in _db.ACTION_PLANs select n).ToList(), "TARGET_DATE", "TARGET_DATE");
i want to be able to apply a format on the dates that come back from the db, my DDL looks like this
<td><%=Html.DropDownList("TARGET_DATE", "All")%></td>
does anyone know if there is a way to loop through and format each date, or apply a format to them all. or what would be the best way to do this, if u need more code i can provide, what i really want is to display the date without the time with it. thanks in advance.

I know this post is old, but I ran across it while searching for an answer to the same problem. I ended up writing a FormattableSelectList class that takes a format string as a parameter, and then formats that output of the select list. Here's a sample of how to use it.
#Html.DropDownFor(
o => o.SomeProperty,
new FormattableSelectList(ViewBag.MyItems, "ID", "Time", "{0:f}"),
"Choose a Time"
)
I wrote a short post about it at http://www.jpolete.me/2011/11/16/a-formattable-selectlist-for-net-mvc/. Here's the code for it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Collections;
using System.Web.UI;
using System.Globalization;
public class FormattableSelectList : SelectList
{
private string _FormatString;
public FormattableSelectList(IEnumerable items, string dataValueField, string dataTextField, string formatString)
: base(items, dataValueField, dataTextField)
{
_FormatString = formatString;
}
public FormattableSelectList(IEnumerable items, string dataValueField, string dataTextField, string formatString, object selectedValue)
: base(items, dataValueField, dataTextField, selectedValue)
{
_FormatString = formatString;
}
public override IEnumerator<SelectListItem> GetEnumerator()
{
return ((!String.IsNullOrEmpty(DataValueField)) ?
GetListItemsWithValueField() :
GetListItemsWithoutValueField()).GetEnumerator();
}
private IList<SelectListItem> GetListItemsWithValueField()
{
HashSet<string> selectedValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (SelectedValues != null)
{
selectedValues.UnionWith(from object value in SelectedValues select Convert.ToString(value, CultureInfo.CurrentCulture));
}
var listItems = from object item in Items
let value = Eval(item, DataValueField, null)
select new SelectListItem
{
Value = value,
Text = Eval(item, DataTextField, _FormatString),
Selected = selectedValues.Contains(value)
};
return listItems.ToList();
}
private IList<SelectListItem> GetListItemsWithoutValueField()
{
HashSet<object> selectedValues = new HashSet<object>();
if (SelectedValues != null)
{
selectedValues.UnionWith(SelectedValues.Cast<object>());
}
var listItems = from object item in Items
select new SelectListItem
{
Text = Eval(item, DataTextField, _FormatString),
Selected = selectedValues.Contains(item)
};
return listItems.ToList();
}
private static string Eval(object container, string expression, string formatString)
{
object value = container;
if (!String.IsNullOrEmpty(expression))
{
value = DataBinder.Eval(container, expression);
}
string stringValue;
if (formatString == null)
{
stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);
}
else
{
stringValue = String.Format(formatString, value);
}
return stringValue;
}
}

Here is how I implemented the class above. My client was looking for two dropdownlists containing Start time and End time. In MVC4
Model:
[Display(Name = "Time (From):")]
public int FromTimeId { get; set; }
[Display(Name = "Time (To):")]
public int ToTimeId { get; set; }
public virtual FromTime FromTime { get; set; }
public virtual ToTime ToTime { get; set; }
Controller:
public ActionResult Create()
{
ViewBag.FromTimes = db.FromTimes;
ViewBag.ToTimes = db.ToTimes;
}
View:
<div class="editor-field">
#Html.DropDownListFor(model => model.FromTimeId, new Namespace.FormattableSelectList(ViewBag.FromTimes, "FromTimeId", "FromTimeName", "{0:h:mm tt}"))<br />
#Html.ValidationMessageFor(model => model.FromTimeId)
</div>
</td>
<td>
<div class="editor-label">
#Html.LabelFor(model => model.ToTimeId, "To Time:")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ToTimeId, new Namespace.FormattableSelectList(ViewBag.ToTimes, "ToTimeId", "ToTimeName", "{0:h:mm tt}"))<br />
#Html.ValidationMessageFor(model => model.ToTimeId)
</div>

Related

MVC populate dropdown from foreign key

I have been struggling with this for several days. I need to populate a dropdownlistfor with genres.
My MovieRepository to grab the genres:
public IQueryable<Movies> MoviesAndGenres
{
get { return db.Movies.Include(m => m.parentGenre); }
}
My movie model
public virtual Genres parentGenre { get; set; }
Genre Model:
public class Genres
{
public Genres()
{
this.movies = new HashSet<Movies>();
}
[Key]
public int genreId { get; set; }
[Required(ErrorMessage = "A genre name is required")]
[StringLength(25)]
public String genreName { get; set; }
public ICollection<Movies> movies { get; set; }
}
I am trying to pass in the genres with a select list, but I am getting a LINQ to Entities does not recognize the System.String To String() Method, and this method cannot be translated to a stored expression.
Movies Controller, addMovie action:
ViewBag.Genres = movieRepository.MoviesAndGenres.Select(m => new SelectListItem
{
Text = m.parentGenre.genreName,
Value = m.parentGenre.genreId.ToString()
}).ToList();
return View();
View:
#Html.DropDownListFor(m => m.parentGenre, (SelectList)ViewBag.Genres)
Any help would be greatly appreciated!
Update:
Repository:
public IQueryable<Genres> MoviesAndGenres
{
get { return db.Genres; }
}
Controller:
var x = movieRepository.MoviesAndGenres.Select(m => new
{
Text = m.genreName,
Value = m.genreId
});
ViewBag.Genres = new SelectList(x);
return View();
View:
#Html.DropDownListFor(m => m.parentGenre, (SelectList)ViewBag.Genres)
Since you're retrieving all of the records anyways, you can just do this.
ViewBag.Genres = movieRepository.MoviesAndGenres.AsEnumerable()
.Select(m => new SelectListItem
{
Text = m.parentGenre.genreName,
Value = m.parentGenre.genreId.ToString()
});
You would also need to change your view to:
#Html.DropDownListFor(m => m.parentGenre, new SelectList(ViewBag.Genres))
Actually, a better approach would probably be this, since then it only retrieves the specific columns you need:
var x = movieRepository.MoviesAndGenres.Select(m => new
{
Text = m.parentGenre.genreName,
Value = m.parentGenre.genreId
});
ViewBag.Genres = new SelectList(x)
Also, the ToList() is no longer required because it's already in a an immediate state.

MultiSelectList does not highlight items in the selectedValues list

In a ASP.NET MVC (Razor) project, I'm using a ListBox with Multi Select option in a Edit View, there was a problem in highlighting the previously selected items by using selectedValues in MultiSelectList, so I asked a question on SO previously. According to the answers given for that question I decided to use a ViewModel (with AutoMapper) for passing the data to the View, without using the ViewBag, but still I have the same problem.. It does not select the items given in the selectedValues list
this is my new code
MODELS
public class Post
{
public int Id { get; set; }
...
public string Tags { get; set; }
}
public class PostEditViewModel
{
private DocLibraryContext db = new DocLibraryContext();
public int Id { get; set; }
..
public MultiSelectList TagsList { get; set; }
}
Controller
public ActionResult Edit(int id)
{
Post post = db.Posts.Find(id);
PostEditViewModel postEditViewModel = Mapper.Map<Post, PostEditViewModel>(post);
var tagsQuery = from d in db.Tags
orderby d.Name
select d;
postEditViewModel.TagsList = new MultiSelectList(tagsQuery, "Id", "Name", post.Tags.Split(','));
return View(postEditViewModel);
}
VIEW
<div class="editor-field">
#Html.ListBoxFor(model => model.Tags, Model.TagsList as MultiSelectList)
</div>
What am I doing wrong here? Please help....
UPDATE 1 :
changed controller to
public ActionResult Edit(int id)
{
Post post = db.Posts.Find(id);
PostEditViewModel postEditViewModel = Mapper.Map<Post, PostEditViewModel>(post);
var tagsQuery = from d in db.Tags
orderby d.Name
select d;
var selectedIds = post.Tags.Split(',').Select(n => tagsQuery.First(t => t.Name == n));
postEditViewModel.TagsList = new MultiSelectList(tagsQuery, "Id", "Name", selectedIds);
return View(postEditViewModel);
}
but I get the same results.
UPDATE 2:
I tried changing code (as in this tutorial), which worked, But I need to use previous method..
MODELS
public Post Post { get; set; }
public MultiSelectList TagsList { get; set; }
public PostEditViewModel(Post post)
{
Post = post;
var tagsQuery = from d in db.Tags
orderby d.Name
select d;
TagsList = new MultiSelectList(tagsQuery, "Name", "Name", post.Tags.Split(','));
}
Controller
public ActionResult Edit(int id)
{
Post post = db.Posts.Find(id);
return View(new PostEditViewModel(post));
}
VIEW
<div class="editor-field">
#Html.ListBox("Tags", Model.TagsList as MultiSelectList)
</div>
What makes the difference...??
The problem is with the construction of your MultiSelectList:
new MultiSelectList(tagsQuery, "Id", "Name", post.Tags.Split(','));
You are specifying that the values for the elements will be taken from each tag's Id property, but then for the actual selected values you are passing in an array of strings which presumably corresponds to the Names of the tags. It doesn't matter that you also specify Name to be the property from which the display text will be determined; the selectedValues parameter matches against values, not display text.
To fix this, project each tag name into its corresponding Id:
var selectedIds = post.Tags.Split(',').Select(n => tagsQuery.First(t => t.Name == n).Id);
new MultiSelectList(tagsQuery, "Id", "Name", selectedIds);
Update:
Oops, there was a mistake in the code above.
I edited the answer to add a required .Id at the end of the selectedIds initialization -- the previous version was selecting tags, not ids (and of course they were comparing unequal, apples and oranges).
I had the same problem, I used my own extention method to generate the html and problem solved
public static MvcHtmlString ListBoxMultiSelectFor<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
object htmlAttributes)
{
return ListBoxMultiSelectFor(helper, expression, selectList, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString ListBoxMultiSelectFor<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
IDictionary<string, object> htmlAttributes)
{
string name = ExpressionHelper.GetExpressionText(expression);
TagBuilder selectTag = new TagBuilder("select");
selectTag.MergeAttributes(htmlAttributes);
selectTag.MergeAttribute("id", name, true);
selectTag.MergeAttribute("name", name, true);
foreach (SelectListItem item in selectList)
{
TagBuilder optionTag = new TagBuilder("option");
optionTag.MergeAttribute("value", item.Value);
if (item.Selected) optionTag.MergeAttribute("selected", "selected");
optionTag.InnerHtml = item.Text;
selectTag.InnerHtml += optionTag.ToString();
}
return new MvcHtmlString(selectTag.ToString());
}

CheckBoxList does not update the model

I defined a Person entity:
public partial class Person
{
public string persID { get; set; }
public string last_name { get; set; }
public string driving_licence { get; set; }
}
where the driving licence is as follows:
public class DrivingLicence
{
public string drivingLicenceValue { get; set; }
public string drivingLicenceText { get; set; }
public DrivingLicence(string paValue, string paText)
{
drivingLicenceValue = paValue;
drivingLicenceText = paText;
}
}
having a repository where is defined this function:
public List<DrivingLicence> GetAll()
{
try
{
var drivingLicenceList = new List<DrivingLicence>();
DrivingLicence oneDrivingLicence = new DrivingLicence("A", "A");
drivingLicenceList.Add(oneDrivingLicence );
oneDrivingLicence = new DrivingLicence("B", "B");
drivingLicenceList.Add(oneDrivingLicence );
oneDrivingLicence = new DrivingLicence("C", "C");
drivingLicenceList.Add(oneDrivingLicence );
oneDrivingLicence = new DrivingLicence("D", "D");
drivingLicenceList.Add(oneDrivingLicence );
return drivingLicenceList;
}
catch (Exception)
{
throw new Exception("An error occured. Failed to Get the list.");
}
}
Now: I want the driving licences displayed as a CheckBoxList and on submit I want the person to get assigned the checked driving licence categories, e.g.: the "A" and "C" categories are selected, the resulting person.driving_licence must be "AC".
The problem is that this does not happen, the person is created but the driving_licence property is empty. I payed attention that the check boxes name be identical to that of the corresponding property (Person.driving_licence).
Is that an error in the present code? Or should I modify the Person entity?
Thank you for your advice.
Here is the view model:
public class PersonFormViewModel
{
// Properties
public Person person { get; set; }
public SelectList DrivingLicenceList { get; set; }
public string ActionToPerform { get; set; }
public PersonFormViewModel() { }
// Constructor
public PersonFormViewModel(Person pPerson, SelectList pDrivingLicenceList)
{
person= pPerson;
DrivingLicenceList = pDrivingLicenceList;
if (String.IsNullOrEmpty(person.persID))
{
ActionToPerform = "Create";
}
else
{
ActionToPerform = "Edit";
}
}
}
The controller:
//
// GET: /Person/Create
[Authorize]
public ActionResult Create()
{
Person person = new Person();
SelectList drvLicenceList = new SelectList(drvLicenceRepository.GetAll(), "drivingLicenceValue", "drivingLicenceText");
return View("Create", new PersonFormViewModel(person, drvLicenceList));
}
//
// POST: /Person/Create
[HttpPost, Authorize]
public ActionResult Create(PersonFormViewModel model)
{
Person person = model.person;
SelectList drvLicenceList = new SelectList(drvLicenceRepository.GetAll(), "drivingLicenceValue", "drivingLicenceText");
if (ModelState.IsValid)
{
try
{
db.Entry(person).State = EntityState.Added;
db.SaveChanges();
return RedirectToAction("Details");
}
catch (...)
{
...
}
}
return View("Create", new PersonFormViewModel(person, drvLicenceList));
}
And the view:
#model MyApp.ViewModels.PersonFormViewModel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(false, "Errors occured.")
<fieldset>
<legend>Fill in your details</legend>
#Html.LabelFor(model => model.person.last_name)
#Html.TextBoxFor(model => model.person.last_name)
#Html.ValidationMessageFor(model => model.person.last_name, "*")
#Html.HiddenFor(model => model.person.persID)
#foreach (var ctg in (Model.DrivingLicenceList))
{
<input type="checkbox" name="driving_licence" value=ctg.value />#ctg.Text
}
<input type="submit" value="Sauvegarder" class="submit" />
</fieldset>
}
I would use a collection property in order to store the selected driving licence categories (multiple checkboxes can be selected => collection):
public partial class Person
{
public string persID { get; set; }
public string last_name { get; set; }
public string[] driving_licence { get; set; }
}
and then you will need to fix the name of the checkbox in order for it to bind correctly:
#foreach (var ctg in Model.DrivingLicenceList)
{
<input type="checkbox" name="person.driving_licence" value="#ctg.Value" />
#ctg.Text
}
and if you wanted to preserve the selected values you will need to set the checked property accordingly:
#foreach (var ctg in Model.DrivingLicenceList)
{
<input type="checkbox" name="person.driving_licence" value="#ctg.Value" #((Model.person.driving_licence ?? Enumerable.Empty<string>()).Contains(ctg.Value) ? "checked=\"checked\"" : "") />
#ctg.Text
}
This being said, we now have a working solution but it is far from anything I would content myself with and stop here. From now on we could start refactoring this mess in order to comply with C# naming conventions (things like property names start with capital letter, ...), introduce real view models (which do not reference domain models), custom HTML helpers that will generate this checkbox lists to avoid writing loops in the views and hardcoding checkboxes, ...

Cascading dropdown lists MVC3

Relatively new to MVC and trying to get a cascading dropdown list working for train times.
After looking at a lot of posts, people say that you should stay away from ViewBag/ViewData and instead focus on ViewModels, but I just can't seem to get my head round it, and it's driving me up the wall. Any tutorial seems to be either to complex or too easy and the whole viewModel idea just hasn't clicked with me yet.
So here is my scenario: I have an admin system where staff can add individual train journeys. For each train time, I have an input form where the user can choose a Company, and from there, I'd like the dropdownlist underneath to populate with a list of journey numbers, which indicate routes. Once they have chosen a number, they can carry on with the rest of the form, which is quite large, including time of travel, facilities on the train etc.
I've created a viewmodel like so:
public class JourneyNumbersViewModel
{
private List<SelectListItem> _operators = new List<SelectListItem>();
private List<SelectListItem> _journeys= new List<SelectListItem>();
[Required(ErrorMessage = "Please select an operator")]
public string SelectedOperator { get; set; }
[Required(ErrorMessage = "Please select a journey")]
public string SelectedJourney { get; set; }
public List<SelectListItem> Journeys
{
get { return _journeys; }
}
public List<SelectListItem> Operators
{
get
{
foreach(Operator a in Planner.Repository.OperatorRepository.GetOperatorList())
{
_operators.Add(new SelectListItem() { Text = a.OperatorName, Value = a.OperatorID.ToString() });
}
return _operators;
}
}
}
In my controller, I have this for the Create view:
public ActionResult Create()
{
return View(new JourneyNumbersViewModel());
}
And this is where it isn't really working for me - if I change my model at the top of the Create view to: #model Planner.ViewModels.JourneyNumbersViewModel, then the rest of my form throws errors as the model is no longer correct for the rest of the form. Is this the way it is supposed to work - what if you need to reference multiple view models with a single view?
I know this is a simple thing and as soon as it clicks I'll wonder how on earth I could have struggled with it in the first place, but if anyone can point out where I'm going wrong, I'd be very grateful.
I have done something similar. Here is some of the code (apologies upfront for this being quite long, but I wanted to make sure you could re-create this on your side):
View looks like this:
using Cascading.Models
#model CascadingModel
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Cascading Forms</h2>
<table>
#using(Html.BeginForm("Index", "Home"))
{
<tr>
<td>#Html.LabelFor(m=>m.CategoryId)</td>
<td>#Html.DropDownListFor(m => m.CategoryId, new SelectList(Model.Categories, "Id", "Name"), string.Empty)</td>
</tr>
<tr>
<td>#Html.LabelFor(m=>m.ProductId)</td>
<td>#Html.CascadingDropDownListFor(m => m.ProductId, new SelectList(Model.Products, "Id", "Name"), string.Empty, null, "CategoryId", "Home/CategorySelected")</td>
</tr>
<tr>
<td> </td>
<td><input type="submit" value="Go"/></td>
</tr>
}
</table>
the Model looks as follows:
public class CascadingModel
{
public int CategoryId { get; set; }
public List<Category> Categories { get; set; }
public int ProductId { get; set; }
public List<Product> Products { get; set; }
}
the real "clever" part of the system is the Html.CascadingDropDownListFor which looks as follows:
public static class MvcHtmlExtensions
{
public static MvcHtmlString CascadingDropDownListFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
string optionLabel,
IDictionary<string, Object> htmlAttributes,
string parentControlName,
string childListUrl
)
{
var memberName = GetMemberInfo(expression).Member.Name;
MvcHtmlString returnHtml = Html.SelectExtensions.DropDownListFor(htmlHelper, expression, selectList, optionLabel, htmlAttributes);
var returnString = MvcHtmlString.Create(returnHtml.ToString() +
#"<script type=""text/javascript"">
$(document).ready(function () {
$(""#<<parentControlName>>"").change(function () {
var postData = { <<parentControlName>>: $(""#<<parentControlName>>"").val() };
$.post('<<childListUrl>>', postData, function (data) {
var options = """";
$.each(data, function (index) {
options += ""<option value='"" + data[index].Id + ""'>"" + data[index].Name + ""</option>"";
});
$(""#<<memberName>>"").html(options);
})
.error(function (jqXHR, textStatus, errorThrown) { alert(jqXHR.responseText); });
});
});
</script>"
.Replace("<<parentControlName>>", parentControlName)
.Replace("<<childListUrl>>", childListUrl)
.Replace("<<memberName>>", memberName));
return returnString;
}
private static MemberExpression GetMemberInfo(Expression method)
{
LambdaExpression lambda = method as LambdaExpression;
if (lambda == null)
throw new ArgumentNullException("method");
MemberExpression memberExpr = null;
if (lambda.Body.NodeType == ExpressionType.Convert)
{
memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
}
else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
{
memberExpr = lambda.Body as MemberExpression;
}
if (memberExpr == null)
throw new ArgumentException("method");
return memberExpr;
}
}
Controller Logic for those looking for it:
public ActionResult CategoriesAndProducts()
{
var viewModel = new CategoriesAndProductsViewModel();
viewModel.Categories = FetchCategoriesFromDataBase();
viewModel.Products = FetchProductsFromDataBase();
viewModel.CategoryId = viewModel.Categories[0].CategoryId;
viewModel.ProductId = viewModel.Products.Where(p => p.CategoryId).FirstOrDefault().ProductId;
return View(viewModel);
}

Sorting a List of Class with LINQ

I have a List<MyClass> and I want to sort it by DateTime CreateDate attribute of MyClass.
Is that possible with LINQ ?
Thanks
To sort the existing list:
list.Sort((x,y) => x.CreateDate.CompareTo(y.CreateDate));
It is also possible to write a Sort extension method, allowing:
list.Sort(x => x.CreateDate);
for example:
public static class ListExt {
public static void Sort<TSource, TValue>(
this List<TSource> list,
Func<TSource, TValue> selector) {
if (list == null) throw new ArgumentNullException("list");
if (selector == null) throw new ArgumentNullException("selector");
var comparer = Comparer<TValue>.Default;
list.Sort((x,y) => comparer.Compare(selector(x), selector(y)));
}
}
You can enumerate it in sorted order:
IEnumerable<MyClass> result = list.OrderBy(element => element.CreateDate);
You can also use ToList() to convert to a new list and reassign to the original variable:
list = list.OrderBy(element => element.CreateDate).ToList();
This isn't quite the same as sorting the original list because if anyone still has a reference to the old list they won't see the new ordering. If you actually want to sort the original list then you need to use the List<T>.Sort method.
Here is a sample:
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
public class Test
{
public void SortTest()
{
var myList = new List<Item> { new Item { Name = "Test", Id = 1, CreateDate = DateTime.Now.AddYears(-1) }, new Item { Name = "Other", Id = 1, CreateDate = DateTime.Now.AddYears(-2) } };
var result = myList.OrderBy(x => x.CreateDate);
}
}
public class Item
{
public string Name { get; set; }
public int Id { get; set; }
public DateTime CreateDate { get; set; }
}
}
Sure the other answers with .OrderBy() work, but wouldn't you rather make your source item inherit from IComparable and just call .Sort()?
class T {
public DateTime CreatedDate { get; set; }
}
to use:
List<T> ts = new List<T>();
ts.Add(new T { CreatedDate = DateTime.Now });
ts.Add(new T { CreatedDate = DateTime.Now });
ts.Sort((x,y) => DateTime.Compare(x.CreatedDate, y.CreatedDate));

Resources