How would I generate a select list, where the text field, is made up of two or more text columns, eg: Where I have a Description and Rate field in my database, I want to combine these to show:
Large--£200
Medium--£150
Small--£100
Controller code is:
var stands = db.Stands.Where(s => s.ExhibitorID == null).ToList();
ViewBag.StandID = new SelectList(stands,"StandID", "Description" + "-- £" + "Rate");
...and my view is (currently):
<div class="editor-field">
#Html.DropDownList("StandID", "--Select--")
</div>
...but the "Description" + "-- £" + "Rate"); won't run:
DataBinding:
'System.Data.Entity.DynamicProxies.Stand_63F8C9F623B3C0E57D3008A57081AFCD9C39E1A6B79B0380B60840F1EFAE9DB4'
does not contain a property with the name 'Description--£Rate'.
Thanks for any help,
Mark
You could create a new anonymous class using a simple LINQ projection, and then use the SelectList(IEnumerable, string, string) constructor overload to specify the value and text fields to be used for the <option> elements i.e.:
var stands =
db.Stands
.Where(s => s.ExhibitorID == null)
.Select(s => new
{
StandID = s.StandID,
Description = string.Format("{0}-- £{1}", s.Description, s.Rate)
})
.ToList();
ViewBag.StandID = new SelectList(stands, "StandID", "Description")
Edit
In C#6 and later, string interpolation makes for better reading than string.Format
...
Description = $"{s.Description}-- £{s.Rate}"
If you project to a strong ViewModel class name (instead of to an anonymous class), you will undoubtedly want to replace the magic strings with the safety of the nameof operator:
ViewBag.StandID = new SelectList(stands, nameof(Stand.StandID), nameof(Stand.Description));
var stands = db.Stands.Where(s => s.ExhibitorID == null).ToList();
IEnumerable<SelectListItem> selectList = from s in stands
select new SelectListItem
{
Value = s.StandID,
Text = s.Description + "-- £" + s.Rate.ToString()
};
ViewBag.StandID = new SelectList(selectList, "Value", "Text");
You can create a partial Model class
public partial class Stand
{
public string DisplayName
{
get
{
return this.Description + "-- £" + this.Rate.ToString();
}
}
}
Then in your View
var stands = db.Stands.Where(s => s.ExhibitorID == null).ToList();
ViewBag.StandID = new SelectList(stands,"StandID", "DisplayName");
The Format of the constructor that you are using is
SelectList(IEnumerable items, string dataValueField, string dataTextField).
So when you use it the way you have you are actually telling it to bind to the TextField called "Description-- £Rate" and if this is not what the field is called coming in the from the DB it won't know what you are indicating.
Either of the two methods described above will work as long as the value you have in your dataValueField matches the name of the property you put the Value in and the dataTextField matches the property name of where you put the Text, perhaps a mix of the two solutions above. (Only because I prefer lambda expressions over linq.) and using a selectlist item prevents it from have to do a ToList on the collection after the transform. you are actually creating the objects that naturally bind to a select list.
You also may want to put in checks on the description or rate to make sure they aren't empty before putting them into the list
var stands = db.Stands.Where(s => s.ExhibitorID == null)
.Select(s => new SelectListItem
{
Value = s.StandID.ToString(),
Text = s.Description + "-- £" + s.Rate.ToString()
});
ViewBag.StandID = new SelectList(stands, "Value", "Text");
I did this by modifying my View Model, here are my code:
The View Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MvcEsosNew.Models;
using System.Web.Mvc;
namespace MvcEsosNew.ViewModels
{
public class EntitlementViewModel
{
public int EntitlementCount { get; set; }
public Entitlement Entitlement { get; set; }
public SelectList Member { get; set; }
public SelectList Job_Grade { get; set; }
public SelectList Department { get; set; }
public SelectList Esos_Batch { get; set; }
}
public class department_FullName
{
public int deptID { get; set; }
public string deptCode { get; set; }
public string deptName { get; set; }
public string fullName { get { return deptCode + " - " + deptName; } }
}
}
The Controller
public void getAllDepartment(EntitlementViewModel entitlementVM)
{
var department = from Department in db.Departments.Where(D => D.Status == "ACTIVE").ToList()
select new department_FullName
{
deptID = Department.id,
deptCode = Department.department_code,
deptName = Department.department_name
};
entitlementVM.Department = new SelectList(department, "deptID", "fullName");
}
The View
<div class="form-group row">
<div class="col-sm-2">
#Html.LabelFor(model => model.Entitlement.department_id)
</div>
<div class="col-sm-10">
#Html.DropDownListFor(model => model.Entitlement.department_id, Model.Department, new { #class="form-control" })
#Html.ValidationMessageFor(model => model.Entitlement.department_id)
</div>
</div>
The result:
Related
Using MySQL with EF6 throws an exception when I sum values from an empty child collection as the DefaultIfEmpty is not supported well with MySQL as related in bug #80127.
public class Foo
{
public int Id { get; set; }
public decimal Total { get; set; }
public virtual IList<Bar> Bars { get; set; }
}
public class Bar
{
public int Id { get; set; }
public int FooId { get; set; }
public virtual Foo Foo { get; set; }
public decimal Received { get; set; }
}
Using the recommended approach with DefaultIfEmpty throws an exception with invalid where clausule 'Project1'.'Id'. This is an old bug of MySQL.
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Select(b => b.Received).DefaultIfEmpty().Sum()
});
I'm using an inline if that works fine but generates an very ugly SQL with lots of inner queries and repetitions of select statements.
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Any() ? f.Bars.Sum(b => b.Received) : 0
});
Is there a better way to avoid DefaultIfEmpty?
The alternative of DefaultIfEmpty which I usually prefer is using cast operator to promote the non nullable type to nullable, which works (even) with MySQL connector.
Then the solution depends of your receiver class property type.
The best is if you can receive a nullable result, in which case the query is simple:
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Sum(b => (decimal?)b.Received)
});
If it needs to be a non nullable type, you can use null coalescing operator
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Sum(b => (decimal?)b.Received) ?? 0
});
but the generated SQL query is ugly and inefficient.
The best you can do in such case is to use (a quite annoying) double select trick:
var result = db.Foo.Select(f => new {
f.Total,
Received = f.Bars.Sum(b => (decimal?)b.Received)
})
.Select(r => new {
r.Total,
Received = r.Received ?? 0
};
or (a quite better) query syntax with let clause:
var result =
from f in db.Foos
let received = f.Bars.Sum(b => (decimal?)b.Received)
select new { f.Total, Received = received ?? 0 };
Tested on latest EF6.1.3 with MySQL Connector/Net 6.9.8
In one of my first MVC projects I encountered the following part of code in my "Create" view.
<div class="form-group">
#Html.LabelFor(model => model.Attend, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.Attend)
#Html.ValidationMessageFor(model => model.Attend, "", new { #class = "text-danger" })
</div>
</div>
</div>
This was scaffolded based on the following property in my model:
public virtual bool? Attend { get; set; }
What this will show for me on the "Create" view is a dropdown with the following values: Not Set; True; False;
Is there a way I can change these values in the dropdownlist for my bool so that it says something more readable/understandable for a user like: Yes; No;?
I tried to search for a solution and I think it has something to do with the following line:
#Html.EditorFor(model => model.Attend)
As #Stephen Muecke said you can create your own selectlist. You can do it with your own editor template .
Here is how i have done it:
Create EditorTeplates folder in Views/Shared
Create YesNoNotSelected.cshtml in this folder with this code:
#model bool?
#Html.DropDownList("", new SelectListItem[]
{
new SelectListItem()
{
Text = "Not set",
Value = String.Empty,
Selected = !Model.HasValue
},
new SelectListItem()
{
Text = "True",
Value = "true",
Selected = Model.HasValue && Model.Value
},
new SelectListItem()
{
Text = "False",
Value = "false",
Selected = Model.HasValue && !Model.Value
}
})
In your model add UIHintAttribute above Attend property like this:
[UIHint("YesNoNotSelected")]
public virtual bool? Attend { get; set; }
Now EditorFor helper should render your property according to this template.
I like the best of all worlds scenario:
I don't want to always require a UIHint
I don't want to use UIHint at all, it's not descriptive
I want bools to be extensible; yes/no, true/false, etc
Here is how I implemented it:
/Models/BooleanType.cs
public enum BooleanDisplay
{
undefined = 0,
YesNo = 1,
TrueFalse = 2,
GoodEvil = 3
}
/MVC/UIBooleanType.cs
[AttributeUsage(AttributeTargets.Property)]
public class UIBooleanAttribute : UIHintAttribute
:base("boolean", "MVC")
{
public UIBoolean(BooleanType displayAs)
{
DisplayAs = displayAs;
}
public BooleanDisplay DisplayAs { get; set; }
}
/Views/Shared/EditorTemplates/boolean.cshtml
#model boolean?
#{
// default
var displayAs = BooleanDisplay.TrueFalse;
var uiBoolean = ViewData
.ModelMetadata
.ContainerType
.GetProperty(ViewData.ModelMetadata.PropertyName)
.GetCustomAttributes(typeof(UIBooleanAttribute))
.Select(ca => ca as UIBooleanAttribute)
.FirstOrDefault(ca => ca != null);
if (uiBoolean != null)
{
displayAs = uiBoolean.DisplayAs;
}
var values = new List<SelectListItem>()
{
new SelectListItem()
{
Text = "Not set",
Value = String.Empty,
Selected = !Model.HasValue
}
};
switch(displayAs)
{
default:
throw new NotImplementedException(displayAs.ToString()
+ " is not implemented in boolean.cshtml";
YesNo:
values.Add(new SelectListItem()
{
Text = "Yes",
Value = "true",
Selected = Model.HasValue && Model.Value
});
values.Add(new SelectListItem()
{
Text = "No",
Value = "false",
Selected = Model.HasValue && !Model.Value
});
TrueFalse:
// etc
}
}
#Html.DropDownList("", values)
usage:
ViewModel:
public class Person
{
// defaults to True/False
public bool AreYouHappy { get; set; }
[UIBoolean(BooleanDisplay.GoodEvil)]
public bool IsInherently { get; set; }
}
View:
#model Person
#Html.EditorFor(p => p.AreYouHappy)
#Html.EditorFor(p => p.IsInherently)
I am using view model to display a dropdownlist and i am also trying to get the value of the selected list, here is my view model
public class CreateJobViewModel
{
public int[] SelectedIndustriesIds { get; set; }
public IList<SelectListItem> IndustriesList { get; set; }
}
My controller
public ActionResult Create()
{
var industryList = repository.GetAllIndustries();
var model = new CreateJobViewModel
{
IndustriesList = industryList.Select(i => new SelectListItem
{
Value = i.IndustryId.ToString(),
Text = i.Name
}).ToList()
};
return View("~/Views/Dashboard/Job/Create.cshtml", model);
}
My post controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateJobViewModel model)
{
try
{
var job = new Job()
{
Title = "hi",
EmploymentHourId = 1,
LocationId = 1,
Salary = 50,
SalaryPeriodId = 1,
PostCode = 2131,
Role = "world",
Description = "hello",
IsPublished = false,
ShiftId = 1,
WorkDayId = 1,
NumberOfPosition = 5,
Meal = false,
SecondYearVisa = true,
Sponsorship = true,
Accommodation = true,
DurationId = 1,
IndustryExperiencePeriod = 5,
Id = User.Identity.GetUserId(),
};
foreach (int id in model.SelectedIndustriesIds)
{
var industry = repository.Industry(id);
job.Industries.Add(industry);
}
foreach (int id in model.SelectedSpecialRequirementsId)
{
var special = repository.SpecialRequirement(id);
job.SpecialRequirements.Add(special);
}
repository.AddJob(job);
return RedirectToAction("Create");
}
catch
{
return View("~/Views/Dashboard/Job/Create.cshtml");
}
}
Every time i try to submit the selected value, i get Object reference not set to an instance of an object Error on the following line in my view:
#model Taw.WebUI.Models.CreateJobViewModel
#Html.ListBoxFor(m => m.SelectedIndustriesIds, Model.IndustriesList) -- here i get the error
Any reason why?
When you submit the form your throwing an exception (confirmed in the comments) and in the catch block you are returning the view, which throws the exception you are seeing because Model.IndustriesList is null. You need to re-assign the value before you return the view.
Since you need to assign SelectLists in the GET method and in the POST method if you return the view, I tend to re-factor this to a separate method to keep the controller code a bit cleaner. Note the following code is based on your model property being public SelectList IndustriesList { get; set; } which is a bit simpler than building IList<SelectListItem>
private void ConfigureViewModel(CreateJobViewModel model)
{
var industryList = repository.GetAllIndustries();
model.IndustriesList = new SelectList(industryList, "IndustryId", "Name")
// any other common stuff
}
and then in the action methods
public ActionResult Create()
{
var model = new CreateJobViewModel();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Create(CreateJobViewModel model)
{
try
{
....
}
catch
{
ConfigureViewModel(model);
return View(model);
}
}
Note its also good practice to test if the model is valid before attempting to save it
public ActionResult Create(CreateJobViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model); // return the view so the user can correct validation errors
}
....
I previously had the following line of code from within my AdminController that was successfully returning a list of relevant subsections from within a course:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetCourseSections(int courseID)
{
var Sections = dbcontext.CourseSection.Where(cs => cs.CourseID.Equals(courseID)).Select(x => new
{
sectionID = x.CourseSectionID,
sectionTitle = x.Title
);
return Json(Sections, JsonRequestBehavior.AllowGet);
}
I was informed to take this out of the controller as it was bad practice to call dbcontext and so i moved this to the AdminViewModel. Within my AdminViewModel I have a variable public List CourseSectionList { get; set; } and I am trying to populate this variable with the JSON request details. My code is as follows:
AdminViewModel
public void GetCourseSectionDetails(int courseID)
{
var Sections = dbcontext.CourseSection.Where(cs => cs.CourseID.Equals(courseID)).Select(x => new CourseSection
{
CourseSectionID = x.CourseSectionID,
Title = x.Title
});
this.CourseSectionList = Sections.ToList();
}
AdminController
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetCourseSections(int courseID)
{
avm.GetCourseSectionDetails(courseID);
var Sections = avm.CourseSectionList.Where(cs => cs.CourseID.Equals(courseID)).Select(x => new
{
sectionID = x.CourseSectionID,
sectionTitle = x.Title
});
System.Diagnostics.EventLog.WriteEntry("Application", "JSON=" + Sections.ToList(), System.Diagnostics.EventLogEntryType.Error);
return Json(Sections, JsonRequestBehavior.AllowGet);
}
I am getting the error The entity or complex type 'MetaLearning.Data.CourseSection' cannot be constructed in a LINQ to Entities query. How can I populate this.CourseSectionList variable using the Sections?
As pointed by your error message, you can't, in linq to entities, use a
.Select(m => new <Entity>{bla bla})
where <Entity>... is one of your model's entity.
So either you use a "non model" class (DTO), which has the properties you need, or you have to enumerate before selecting (because linq to objects has not that limitation)
.ToList()
.Select(m => new <Entity>{bla bla});
You can find some nice explanations of why it's not possible here
EDIT :
you may also do something like that, if you wanna retrive only some properties of your entity, and don't wanna use a DTO :
return ctx
.CourseSection
.Where(cs => cs.CourseID.Equals(courseID))
//use an anonymous object to retrieve only the wanted properties
.Select(x => new
{
c= x.CourseSectionID,
t= x.Title,
})
//enumerate, good bye linq2entities
.ToList()
//welcome to linq2objects
.Select(m => new CourseSection {
CourseSectionID = m.c,
Title = m.t,
})
.ToList();
You don't need to repeat the same code in the controller, but directly pass the list to the view.
This being said I am informing you that placing data access code in your view model is even worse practice than keeping it in the controller. I would recommend you having a specific DAL layer:
public interface IRepository
{
public IList<CourseSection> GetSections(int courseID);
}
which would be implemented:
public class RepositoryEF : IRepository
{
public IList<CourseSection> GetSections(int courseID)
{
using (ctx = new YourDbContextHere())
{
return ctx
.CourseSection
.Where(cs => cs.CourseID.Equals(courseID))
.Select(x => new CourseSection
{
CourseSectionID = x.CourseSectionID,
Title = x.Title,
})
.ToList();
}
}
}
and finally have your controller take the repository as dependency:
public class SomeController : Controller
{
private readonly IRepository repo;
public SomeController(IRepository repo)
{
this.repo = repo;
}
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult GetCourseSections(int courseID)
{
var sections = this.repo.GetSections(courseID);
return Json(sections, JsonRequestBehavior.AllowGet);
}
}
I did this as follows using Darin's answer as a guide:
ViewModel
public void GetCourseSectionDetails(int courseID)
{
this.CourseSectionList = dbcontext.CourseSection.AsEnumerable().Where(cs => cs.CourseID.Equals(courseID)).Select(x => new CourseSection
{
CourseSectionID = x.CourseSectionID,
Title = x.Title
}).ToList();
}
Controller
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetCourseSections(int courseID)
{
var sections = avm.CourseSectionList;
return Json(sections, JsonRequestBehavior.AllowGet);
}
I'm trying to group together radiobuttons that are creating using a for loop and Razor syntax. Here is the code:
#for (var i = 0; i < Model.Sessions.Count(); i++)
{
#Html.HiddenFor(it => it.Sessions[i].Id)
#Html.RadioButtonFor(it => it.Sessions[i].Checkbox, "0", new {#class = "Sessions", #id = id, #name="Sessions"})
#Html.LabelFor(it => it.Sessions[i].Name, Model.Sessions[i].Name)
<span class="time-span"><em>#Model.Sessions[i].StartTime</em><em>#Model.Sessions[i].EndTime</em></span>
<br />
}
The third line inside the for loop is where the problem is. Basically the name doesn't change and it's always "Sessions[x].Checkbox". The checkbox is a property (bool) of a custom class. I can't seem to get the hang of debugging Razor stuff, so any help would be greatly appreciated, I'm guessing this will be extremely obvious to someone here.
EDIT
Dimitrov's post helped a lot. Below is the final code I used. I use the #class and #id attributes to be able to use Javascript to select the session originally picked (since this is an edit, not create form).
#for (var i = 0; i < Model.Sessions.Count(); i++)
{
#Html.HiddenFor(it => it.Sessions[i].Id)
var SId = #Model.Sessions[i].Id;
#Html.RadioButtonFor(it => it.selectedSession, Model.Sessions[i].Id, new { id = SId, #class = "Sessions" })
#Html.LabelFor(it => it.Sessions[i].Name, Model.Sessions[i].Name)
<span class="time-span"><em>#Model.Sessions[i].StartTime</em><em>#Model.Sessions[i].EndTime</em></span>
<br />
}
If you want to be able to select only a single radio button you need to have a single property on your view model to hold the selected session id, like this:
public class SessionViewModel
{
public int SelectedSessionId { get; set; }
public IList<Session> Sessions { get; set; }
}
public class Session
{
public int Id { get; set; }
public string Name { get; set; }
}
and then have a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new SessionViewModel
{
SelectedSessionId = 2,
Sessions = Enumerable.Range(1, 5).Select(x => new Session
{
Id = x,
Name = "session" + x,
}).ToList()
};
return View(model);
}
[HttpPost]
public ActionResult Index(SessionViewModel model)
{
return Content("Thank you for selecting session id: " + model.SelectedSessionId);
}
}
and finally a view:
#model SessionViewModel
#using (Html.BeginForm())
{
for (var i = 0; i < Model.Sessions.Count(); i++)
{
#Html.HiddenFor(x => x.Sessions[i].Id)
#Html.RadioButtonFor(x => x.SelectedSessionId, Model.Sessions[i].Id, new { id = "session_" + i })
#Html.Label("session_" + i, Model.Sessions[i].Name)
<br/>
}
<button type="submit">OK</button>
}