ASP.NET Core 6: #Html.DropDownList analogue for one item for binding multiple models in one view - asp.net-core-mvc

ASP.NET Core 6 MVC / EF model-first
Is there simple analogue of #Html.DropDownList and ViewBag to replace IDs from one model to values from another model?
I have got two models:
Model1
public int ID { get; set; } // PK
public int PersonID { get; set; } // FK
ID
PersonID
1
3
2
4
Model2
public int PersonID { get; set; } // PK
public int Name { get; set; }
PersonID
Name
3
Nick
4
James
Controller:
public async Task<IActionResult> Index()
{
return View(await _context.Model1.ToListAsync());
}
View:
#model IEnumerable<wpg.monster.Models.Model1>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ID)
</td>
<td>
#Html.DisplayFor(modelItem => item.PersonID)
</td>
</tr>
}
So I'll get
TRHead
TRHead
1
3
2
4
Is there any trick to replace 3 and 4 with Names from Model2, like ViewBag with #Html.DropDownList to see something like this in View? Or there is only one way to create ViewModel?
TRHead
TRHead
1
Nick
2
James

Here a possible method for you:
First create a model3 to collect the information from model1 and model2:
public class Model1
{
public int ID { get; set; }
public int PersonId { get; set; }
}
public class Model2
{
public int PersonId { get; set; }
public string Name { get; set; }
}
public class Model3
{
public int ID { get; set; }
public string Name { get; set; }
}
Then in your controller, you can select the data you want and insert them to the new model which will be displayed on the viewer, here you can combine two elements whose PersonId are the same to the new model3:
public List<Model3> GetModel3()
{
List<Model1> model1 = new();
List<Model2> model2 = new();
List<Model3> model3 = new();
model1.Add(new Model1 { ID = 1, PersonId = 3 });
model1.Add(new Model1 { ID = 2, PersonId = 4 });
model2.Add(new Model2 { PersonId = 3, Name = "Nick" });
model2.Add(new Model2 { PersonId = 4, Name = "James" });
foreach (Model1 m1 in model1)
{
foreach (Model2 m2 in model2)
{
if(m1.PersonId == m2.PersonId)
{ model3.Add(new Model3 { ID = m1.ID, Name = m2.Name }); }
}
}
return model3;
}
Then you should use the following method in controller to pass the new model to the viewer:
public IActionResult Test()
{
ViewBag.Message = "This is test demo";
dynamic model = new ExpandoObject();
model.Model3 = GetModel3();
return View(model);
}
Last is the viewer page:
#using Test;
#using Test.Models;
#model dynamic
#{
ViewBag.Title = "Test";
ViewData["Title"] = "Test";
}
<h2>#ViewBag.Message</h2>
<table>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
#foreach (Model3 model3 in Model.Model3)
{
<tr>
<td>#model3.ID</td>
<td>#model3.Name</td>
</tr>
}
</table>
Now you may solve your issue, here is my test to it:

Related

Set enum values as selected in Razor dropdown

I am using a razor view for showing the scored of an examination and it contains an enum value that holds 3 values "pass","Fail","absent" and i want to choose it accordingly. The model i used is
model
public class VerifyResultModel
{
[Display(Name = "StudnetId")]
public int StudentId { get; set; }
[Display(Name = "Student")]
[Required]
public string Student { get; set; }
[Display(Name = "Mark")]
[Required]
public int Mark { get; set; }
[Display(Name = "Score")]
[Required]
public string Score { get; set; }
[Display(Name = "Result")]
public App.EnumValues.ExamResultStatus Result { get; set; }
}
Controller
[HttpGet]
public ActionResult Verify(int Id)
{
List<VerifyResultModel> model_result = new List<VerifyResultModel>();
VerifyResultModel _resultItem;
foreach (exammark item in marks)
{
SchoolApp.EnumValues.ExamResultStatus result = SchoolApp.EnumValues.ExamResultStatus.Absent;
if(item.Mark >= MinMark)
{
result= SchoolApp.EnumValues.ExamResultStatus.Pass;
}
else
{
result = App.EnumValues.ExamResultStatus.Fail;
}
_resultItem = new VerifyResultModel { StudentId = (int)item.StudentId, Student = item.studentmaster.StudentName, Mark = (int)item.Mark, Score = item.Mark.ToString(), Result = result };
model_result.Add(_resultItem);
}
LoadResultsDropdown();
return View(model_result);
}
private void LoadResultsDropdown()
{
var types = (from App.EnumValues.ExamResultStatus type in Enum.GetValues(typeof(SchoolApp.EnumValues.ExamResultStatus))
select new { Id = type.ToString(), Name = type.ToString() }).ToList();
ViewBag.ResultList = new SelectList(types, "Id", "Name");
}
View
#model IList<SchoolApp.ViewModels.VerifyResultModel>
<tbody>
#for (int item = 0; item < Model.Count(); item++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[item].Student)
</td>
<td>
#Html.DisplayFor(modelItem => Model[item].Mark)
</td>*#
<td>
#Html.DisplayFor(modelItem => Model[item].Score)
</td>
<td>
#Html.DropDownListFor(model => model[item].Result, (SelectList)ViewBag.ResultList) //Here All values are showing as Pass ( first item in dropdown)
</td>
</tr>
}
</tbody>
The problem is even if i pass values Fail / Absent to enum it shows as Pass in combobox . How can i show the correct value ?
Can you please try this:
private void LoadResultsDropdown()
{
var types = (from App.EnumValues.ExamResultStatus type in Enum.GetValues(typeof(SchoolApp.EnumValues.ExamResultStatus))
select new { Id = type.ToString(), Name = type.ToString() }).ToList();
var types=Enum.GetValues(typeof(ExamResultStatus)).Cast<ExamResultStatus>();
var EnumData= types.Select(c => new { c.Key, c.Value });
ViewBag.ResultList = new SelectList(types.AsEnumerable(), "key", "value");
}
Hope this helps..

Generating an MVC3 RadioButton list in a loop statement

A collegaue of mine created a model and here it is.
Model
[Serializable]
public class ModifyCollegeListModel
{
public List<SchoolModel> CollegeList { get; set; }
public List<SchoolListModel> SchoolList { get; set; }
public string Notes { get; set; }
public int QuestionnaireId { get; set; }
}
[Serializable]
public class SchoolModel
{
public Guid SchoolId { get; set; }
public string SchoolName { get; set; }
public string StateName { get; set; }
public int DisplayIndex { get; set; }
public int DetailId { get; set; }
public int CategoryId { get; set; }
public int? ApplicationStatusId { get; set; }
}
I intend to create a loop that will generate the radiobutton list for the ApplicationStatusId , something like this...
Razor Code
#foreach (SchoolModel justright in Model.CollegeList.Where(m => m.CategoryId == 3).OrderBy(m => m.SchoolName).ToList<SchoolModel>())
{
<tr class="#HtmlHelpers.WriteIf(eventCounter % 2 == 0, "even", "odd")">
<td class="school"><b>#justright.SchoolName</b></td>
<td class="location"><b>#justright.StateName</b></td>
<td><label>#Html.RadioButtonFor(x => justright.SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.DidNotApply)</label></td>
<td><label>#Html.RadioButtonFor(x => justright.SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.Accepted)</label></td>
<td><label>#Html.RadioButtonFor(x => justright.SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.NotAccepted)</label></td>
</tr>
}
but what happens is that ALL radiobhuttons created has the same name so they are grouped as one giant radiobutton collection. not via the schoolID... scratches head
Can someone help me here and point me to the right direction on how i will be able to create radio buttons that are grouped per row?
I would do two things.
First up, I would remove the filtering logic from the view. What I mean is this part:
Model.CollegeList.Where(m => m.CategoryId == 3).OrderBy(m => m.SchoolName).ToList<SchoolModel>()
That sort of logic belongs in a service. Also it will make the view much cleaner.
Secondly, I think you'll need to use a for-loop so MVC binds everything back how you want:
for (int i = 0; i < Model.CollegeList.Count; i++) {
<tr class="#HtmlHelpers.WriteIf(eventCounter % 2 == 0, "even", "odd")">
<td class="school"><b>#CollegeList[i].SchoolName</b></td>
<td class="location"><b>#CollegeList[i].StateName</b></td>
<td><label>#Html.RadioButtonFor(x => x.CollegeList[i].SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.DidNotApply)</label></td>
<td><label>#Html.RadioButtonFor(x => x.CollegeList[i].SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.Accepted)</label></td>
<td><label>#Html.RadioButtonFor(x => x.CollegeList[i].SchoolId, (int)BrightHorizons.CC.BusinessLogic.CollegeListApplicationStatusEnum.NotAccepted)</label></td>
</tr>
}
You'll notice after using the for-loop, that the radiobutton names and ID's also contain their index in the CollegeList. For example:
<input id="CollegeList_0__SchoolId" name="CollegeList[0].SchoolId" type="radio" value="2">

Getting distinct fields from an indirectly related table

I am new to MVC3, so apologies if this is basic, but I couldn't work it out.
I have a view model that includes 3 tables, an 'Albums' table with 2 foreign keys (Artist ID and Label ID).
I have a 'Labels' controller and a Details method where I display the Label table fields. I am trying to get the distinct 'Artists' from the collection of Albums related to the Label.
At the moment, I can get the name of the artists, but one is generated for each album - I have included the code for this scenario below.
I have tried a bunch of different things, like including Artists in the collection and using the Distinct and group by functions, but to no avail. Not sure if it is doable this way, or whether, due to the indirect relationship between the tables, I need to use a different approach.
Any helps is much appreciated.
Controller:
public ActionResult Details(int id)
{
var viewModel = new LabelsDetailsVM();
viewModel.Lables = db.Labels
.Include(a => a.Albums)
.SingleOrDefault(x => x.LabelID == id);
return View(viewModel);
View:
#foreach (var artist in Model.Lables.Albums)
{
<tr>
<td>
#Html.DisplayFor(model => artist.Artist.ArtistName)
</td>
</tr>
}
View Model
public class LabelsDetailsVM
{
public Label Lables { get; set; }
public IEnumerable<Album> Albums { get; set; }
public IEnumerable<Artist> Artists { get; set; }
}
}
Here's an example that might get you on the right track:
view model:
public class LabelViewModel
{
public Label Label { get; set; }
public IEnumerable<Artist> Artists { get; set; }
}
Controller:
public class LabelController : Controller
{
public ActionResult Details(int id)
{
var label = db.Labels
.Include(l => l.Albums)
.SingleOrDefault(l => l.LabelID == id);
if (label == null)
{
return HttpNotFound();
}
var distinctArtists = label
.Albums
.Select(a => a.Artist)
.Distinct(ArtistComparer.Default);
var model = new LabelViewModel
{
Label = label,
Artists = distinctArtists
};
return View(model);
}
}
and the equality comparer used to distinguish between 2 artists used in the controller that could of course be adapted to match your requirements. In this example it considers that 2 artists represent the same entity if they have the same ID. But you could work with some other properties such as the name and so on, all depends on what you need:
public class ArtistComparer : IEqualityComparer<Artist>
{
protected ArtistComparer()
{
}
private static readonly IEqualityComparer<Artist> _default = new ArtistComparer();
public static IEqualityComparer<Artist> Default
{
get
{
return _default;
}
}
public bool Equals(Artist x, Artist y)
{
if (x != null && y != null)
{
return x.ArtistID.Equals(y.ArtistID);
}
return false;
}
public int GetHashCode(Artist obj)
{
return obj.ArtistID.GetHashCode();
}
}
View:
#model LabelViewModel
<h3>#Html.DisplayFor(x => x.Label.LabelName)</h3>
<div>Artists</div>
<table>
<thead>
<tr>
<th>artist name</th>
</tr>
</thead>
<tbody>
#foreach (var artist in Model.Artists)
{
<tr>
<td>#artist.ArtistName</td>
</tr>
}
</tbody>
</table>

How to access IQueryable in Razor?

I’m building MVC3 “code first” application and to display data on my page I’m using the following:
Controller Code:
public ViewResult Index()
{
dynamic assignedT = from t in db.AssignedTasks
join a in db.Approvers on t.ApproverID equals a.ID
join r in db.Requestors on t.RequestorID equals r.ID
select new {Approver =a.FirstName + " " +a.LastName,
Requestor=r.FirstName + " " + r.LastName,
Title = t.Title,
t.RequestedDate,
t.CompletedDate, t.Description,
Status =(int)t.InternalStatut
};
return View(assignedT);
}
and on the page:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<table>
#foreach (var item in Model) {
<tr>
<td>#item.GetType().GetProperty("Title").GetValue(item, null)
</td>
</tr>
}
</table>
I know that using dynamic is not the best way.
How to achieve same functionality using strongly-typed view?
Thank you
You can define the following class:
public class MyViewModel
{
string Approver { get; set; }
string Requestor { get; set; }
string Title { get; set; }
DateTime RequestedDate { get; set; }
DateTime CompletedDate { get; set; }
string Description { get; set; }
int Status { get; set; }
}
If you then change select new in your Linq query to select new MyViewModel then you have a strongly typed viewmodel (and dynamic to var).

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

Resources