Conditional view of fields in a view model in razor - c#

I have the following view model to query my table:
QuestionViewModel.cs
public enum TypeQuestion {
Long = 1,
Short = 2,
Small = 3,
}
public class QuestionViewModel
{
public string Name { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string MaxAge { get; set; }
public string Category { get; set; }
public string Account { get; set; }
public TypeQuestion CurrentTypeQuestion { get; set; }
}
if the query I'm doing is of type:
Long: displays all fields.
Short: displays Name, LastName, Address, MaxAge.
Small: displays Name, LastName.
Is there any way to put some kind of DataAnnotation to determine which fields to display in the view or some other way?, To avoid putting a "what if?" for each field.
Thank you.

This may be overkill, and i'd in fact lean towards #Mystere Man's answer, but this is another option.
Instead of regular primitive types in your ViewModel, set them up to cater for the logic. Looks like Name and LastName are always displayed, whilst Address and MaxAge are conditional.
So, setup your ViewModel like this:
public class QuestionViewModel
{
public string Name { get; set; }
public string LastName { get; set; }
public IEnumerable<ConditionalField> ConditionalFields { get; set; }
public string Category { get; set; }
public string Account { get; set; }
}
public class ConditionalField
{
public string Field { get; set; }
public bool Display { get; set; }
}
In your controller, setup the nested viewmodel and the boolean values for Address and MaxAge ccording to the value of CurrentTypeQuestion.
Then, have your View like this:
/Views/Questions.cshtml
#model QuestionViewModel
#Html.DisplayForModel()
Then create a custom display template (or editor template, if this is a form) for QuestionViewModel:
/Views/DisplayTemplates/QuestionViewModel.cshtml
#model QuestionViewModel
#Html.DisplayFor(model => model.Name)
#Html.DisplayFor(model => model.LastName )
#Html.DisplayFor(model => model.Category)
#Html.DisplayFor(model => model.Account)
#Html.DisplayFor(model => model.ConditionalFields)
Then create another custom display template for ConditionalField:
Views/DisplayTemplates/ConditionalField.cshtml
#model ConditionalField
#if (Model.Display) {
#Html.DisplayForModel()
}
As i said, may be overkill, but in the end, you only have a single if statement in the custom template, no loops, and your main view and first-level template stays clean.

To keep it simple, and avoid complex if logic in your view, just create three different views, with only the data you need in each view. Then select the view in your controller based on the question type.

Based from this link and this link
Controller:
public ActionResult Consulta()
{
return View(new QuestionViewModel());
}
ViewModel:
public enum TypeQuestion {
Long = 1,
Short = 2,
Small = 3,
}
public class QuestionViewModel
{
public string Name { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public int MaxAge { get; set; }
public string Category { get; set; }
public string Account { get; set; }
public TypeQuestion CurrentTypeQuestion { get; set; }
public bool EnabledField(ModelMetadata field)
{
//check pending implementation
return true;
}
}
View:
#model MySite.QuestionViewModel
#using System.Linq;
#using System.Collections;
#{
ViewBag.Title = "Question";
Layout = "~/Views/Shared/Layout.cshtml";
}
<h2>Question</h2>
#using (Html.BeginForm(new { id = "FormQuestion" }))
{
foreach (var prop in ViewData.ModelMetadata.Properties
.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm) && ViewData.Model.EnabledField(pm)))
{
if (prop.HideSurroundingHtml)
{
Html.Editor(prop.PropertyName);
}
else
{
<div class="editor-label">
#(prop.IsRequired ? "*" : "")
#Html.Label(prop.PropertyName)
</div>
<div class="editor-field">
#Html.Editor(prop.PropertyName, prop.Model)
#Html.ValidationMessage(prop.PropertyName, "*")
</div>
}
}
}

Related

Passing a view-model to view, saving data from text-boxes into the view-model, then transferring that data from the view-model to my models

I don't know if I am going about this the correct way but basically I'm creating an MVC application for a club where I have a Members class, and an Events class. I have a view that displays all the events below
#model IEnumerable<Application.Models.Event>
#{
Layout = null;
}
<h1>Events For this Month!</h1>
<table>
#foreach (var events in Model)
{
<tr>
<td><h2>#events.Name</h2></td>
<td>
#Html.ActionLink("Sign-Up", "SignUpEvent", "User", new { id = events.Id }, null)
</td>
</tr>
}
</table>
when they select, signup next to the event they wish to attend, it takes them to this Action. I created a viewmodel called "MemberEventViewModel that has a member, and an event in it.
public ActionResult SignUpEvent(int id)
{
var Vmodel = new MemberEventViewModel()
{
};
return View(Vmodel);
}
the view returned is where the member enters their pin
#model Application.ViewModels.MemberEventViewModel
#{
Layout = null;
}
<h2>Sign-Up</h2>
#using (Html.BeginForm("SignUp", "User"))
{
#Html.LabelFor(c => c.Member.PIN)
#Html.TextBoxFor(c => c.Member.PIN)
#Html.HiddenFor(c => c.Events.Id)
<button type="submit">Save</button>
}
then finally goes to this controller so I can save and see the event that a member has signed up for
[HttpPost]
public ActionResult SignUp(MemberEventViewModel Vmodel)
{
var EventinDB = _context.Events.SingleOrDefault(c => c.Id == Vmodel.Events.Id);
var MemberinDB = _context.Members.SingleOrDefault(c => c.PIN == Vmodel.Member.PIN);
MemberinDB.Events.Add(EventinDB);
EventinDB.Members.Add(MemberinDB);
return View("ViewEvents", "User");
I'm confused on how to get this working. I want to view what events a member is signed up for, and vice versa. A bridge table called MemberEvents was created when I created the many to many relationship between Events and Members.
also here is my View model class
public class MemberEventViewModel
{
public Member Member { get; set; }
public Event Events { get; set; }
}
here is the events class
public class Event
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
[Display(Name = "Date")]
public DateTime? EventDate { get; set; }
[Required]
[Display(Name = "Start Time")]
public TimeSpan EventStartTime { get; set; }
[Required]
[Display(Name = "End Time")]
public TimeSpan EventEndTime { get; set; }
public int EventTotalTime{ get; set; }
public virtual ICollection<Member> Members { get; set; }
}
and members class
public class Member
{
public int Id { get; set; }
[Required]
[MaxLength(4, ErrorMessage = "PIN must be 4 numbers long"), MinLength(4, ErrorMessage = "PIN must be 4 numbers long")]
public string PIN { get; set; }
[Required]
[Display(Name ="First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Date of Birth")]
public DateTime? Birthdate { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
Looks like there's nothing wrong with how you're adding it but I think you missed context.SaveChanges() after you're adding the MemberEvents object.
[HttpPost]
public ActionResult SignUp(MemberEventViewModel Vmodel)
{
var EventinDB = _context.Events.SingleOrDefault(c => c.Id == Vmodel.Events.Id);
var MemberinDB = _context.Members.SingleOrDefault(c => c.PIN == Vmodel.Member.PIN);
// try using one only
MemberinDB.Events.Add(EventinDB);
// EventinDB.Members.Add(MemberinDB);
// you should save here
_context.SaveChanges();
return View("ViewEvents", "User");
}
Also be sure that you're assigning the properties here;
public ActionResult SignUpEvent(int id)
{
var Vmodel = new MemberEventViewModel()
{
Member = // find the member _context.Members.Where(...)
Events = _context.Events.FirstOrDefault(e=>e.Id == id)
};
return View(Vmodel);
}

Binding DropdownList property to controller method in ASP.NET Core

I'm trying to get a dropdown working on the create view for a particular model object. In this case, the model in question is Department, and within that object, there is another model object referenced called Division.
My Department model is a fairly basic POCO except for the property called Division which references another POCO (which, mind you, has simply an Id field and a Name field for the most part).
I already have a working dropdown for the create view (see code below)
<div class="form-group">
<label asp-for="Division" class="control-label"></label>
#Html.DropDownListFor(dept => dept.Division, new SelectList(ViewBag.Divisions, "Id", "Name"))
</div>
Yes, in the Create method that returns the view I'm populating the ViewBag.Divisions list and that's all working as planned.
However, in the Create method of my controller, while it's binding to the other (text) fields on the view I cannot figure out how to get it to populate the Division property for the Department object with the selection from the drop-down. My create method is below:
public async Task<IActionResult> Create([Bind("Id,Name,Description,IsActive,Division")] Department department)
{
if (ModelState.IsValid)
{
_context.Add(department);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(department);
}
Also, here are my model classes:
public class Division
{
[Key]
public int Id { get; set; }
[Required]
[Display(Name = "Division Name")]
public string Name { get; set; }
[Display(Name = "Active")]
public bool IsActive { get; set; }
}
public class Department
{
[Key]
public int Id { get; set; }
[Required]
[Display(Name = "Department Name")]
public string Name { get; set; }
public string Description { get; set; }
public Division Division { get; set; }
[Display(Name = "Active")]
public bool IsActive { get; set; }
}
The idea being that a particular department belongs to (and is associated with) a single division.
Thanks!
Write your Department class as follows:
public class Department
{
[Key]
public int Id { get; set; }
[ForeignKey("Division")]
public int DivisionId {get; set;}
[Required]
[Display(Name = "Department Name")]
public string Name { get; set; }
public string Description { get; set; }
[Display(Name = "Active")]
public bool IsActive { get; set; }
public Division Division { get; set; }
}
Then in the controller method:
public async Task<IActionResult> Create([Bind("Id,Name,Description,IsActive,DivisionId")] Department department)
{
if (ModelState.IsValid)
{
_context.Add(department);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(department);
}
Then in the View:
<div class="form-group">
<label asp-for="Division" class="control-label"></label>
#Html.DropDownListFor(dept => dept.DivisionId, new SelectList(ViewBag.Divisions, "Id", "Name"))
</div>
Here is a simple demo , you could try it
Add a ViewModel with a IList as the property for your dropdownlist items.
public class DepartmentViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public IList<int> Divisions { get; set; }
}
The code in Create.cshtml
#model BindDropDownList.Models.DepartmentViewModel
<div class="form-group">
<label asp-for="#Model.Divisions" class="control-label"></label>
#Html.DropDownListFor(depart=>depart.Divisions , (List<SelectListItem>)ViewBag.Divisions)
</div>
In Controller,retrieve ViewBag.Divisions
ViewBag.Divisions = _context.Division.Select(d => new SelectListItem { Value=d.Id.ToString(),Text=d.Name}).ToList();
Change the Create method like below
public async Task<IActionResult> Create(DepartmentViewModel department)
{
//Add department
}

BeginCollectionItem can not bind partial values

I´m doing Editing a variable length list, ASP.NET MVC 2-style tutorial, but I can´t bind values of my ViewModel to my Create View
Model:
public class WebPages
{
[Key]
public int WebPagesId { get; set; }
public String DomainName { get; set; }
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime DomainStart { get; set; }
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime DomainExp { get; set; }
public String DomainEmails { get; set; }
public String DomainUsers { get; set; }
public String DomainPasswords { get; set; }
}
}
ViewModel:
public class WebPagesViewModel
{
[Key]
public int Id { get; set; }
public String DomainName { get; set; }
public class Domain
{
public String DomainEmails { get; set; }
public String DomainUsers { get; set; }
public String DomainPasswords { get; set; }
}
public List<Domain> Domains { get; set; }
}
}
Create GetController
public ActionResult Create(WebPages model)
{
var ejemdata = new List<WebPagesViewModel.Domain>
{
new WebPagesViewModel.Domain
{
DomainEmails = "email#gmail.com",
DomainUsers = "Testuser",
DomainPasswords = "testpassword"
}
};
var vm = new WebPagesViewModel
{
Clients = new SelectList(db.ClientsList, "ClientsId", "ClientsName"),
Domains = ejemdata
};
return View(vm);
}
Create View:
#foreach (var item in Model.Domains)
{
{ Html.RenderPartial("_WebPagesPartial", item);}
}
Partial:
#using xxx.Models.ViewModels
#model WebPagesViewModel.Domain
#using HtmlHelpers.BeginCollectionItem
<h2>Partial View</h2>
<div>
#using (Html.BeginCollectionItem("Domains"))
{
Html.EditorFor(x => x.DomainPasswords);
Html.EditorFor(x => x.DomainUsers);
Html.EditorFor(x => x.DomainEmails);
}
I use breakpoint debug, and controller gets values from my ejemdata and it pass to my create view into foreach
Receiving data in View photo
So when the page loads it only loads label from partial but no display anything that is in BeginCollectionItem method and I get this on HTML
HTML:
<input type="hidden" name="Domains.index" autocomplete="off" value="28f4e8e7-cde3-44fe-a739-b4c8acb15ec2">
You use of
Html.EditorFor(x => x.DomainPasswords);
just calls the method but does not output its result. You need to change it to
#Html.EditorFor(x => x.DomainPasswords)
(and ditto for the other 2 HtmlHelpers)

How to post list of model on controller?

There is 1 form on which i will ask for Academic Details for :
Graduation
Post Graduation(Masters)
Professional Qualification.
So far any user say UserId="1" 3 entries will be created in my AcademicMaster each for bachelor,Master(Post Graduation) and Professional Qualification.
My Database AcademicMaster table fields and datamodel:
Id,Qualification(GraduationCourses),Acheievement,UserId
View Model:
public class AcademicViewModel
{
public int Id { get; set; }
public virtual Graduation Graduation{ get; set; }
public virtual PostGraduation PostGraduation{ get; set; }
public virtual ProfessionalQualification ProfessionalQualification{ get; set; }
}
public class Graduation
{
public string BachelorQualification { get; set; }
public string BachelorAchievement { get; set; }
}
public class PostGraduation
{
public string MasterQualification { get; set; }
public string MasterAchievement { get; set; }
}
public class ProfessionalQualification
{
public string ProfessionalQualifications { get; set; }
}
So my View is like this:
#model AcademicViewModel
#{
IEnumerable<SelectListItem> graduationList = ViewBag.GraduationList;
IEnumerable<SelectListItem> postGraduationList = ViewBag.PostGraduationList;
}
#using (Html.BeginForm())
{
<div class="row">
Bachelors
#Html.DropDownListFor(model => model.Graduation.Qualification, graduationList)
</div>
#Html.TextAreaFor(model => model.Graduation.Achievement)
<div class="row">
MASTERS
#Html.DropDownListFor(model => model.PostGraduation.Qualification, postGraduationList)
</div>
#Html.TextAreaFor(model => model.PostGraduation.Achievement)
<div class="row">
PROFESSIONAL QUALIFITCATION
#Html.TextAreaFor(model => model.ProfessionalQualification.ProfessionalQualifications)
</div>
<input type="submit" value="Save">
}
This is my Controller:
[HttpPost]
public ActionResult MyController(AcademicViewModel model)
{
//Actions
}
So is my View Model structure appropriate and how to create 3 entries in AcademicMaster Table??
I will start by saying that having one table may not be the best choice (what happens if later you start adding additional properties which may be applicable to Graduation that are not applicable to Professional - for example YearOfGraduation - you could end up with a huge number of fields, many of which may have null values.
However, if you want one table, then at least add another field so that you can identify if the data is related to Graduation, PostGraduation or Professional. The associated data model for the AcademicMasters table would be
public class AcademicMaster
{
public int ID { get; set; }
public string Type { get; set; } // may be an enum?
public string Qualification { get; set; }
public string Achievement { get; set; }
public int UserID { get; set; }
}
Side note: It might be better to use an enum for the Type property
public enum AcademicType
{
Graduation,
PostGraduation,
Professional
}
There does not seem to be any need to your current Graduation, PostGraduation and ProfessionalQualification models and your view model should be
public class AcademicViewModel
{
public string GraduationQualification { get; set; }
public string GraduationAchievement { get; set; }
public string PostGraduationQualification { get; set; }
public string PostGraduationAchievement { get; set; }
public string ProfessionalAchievement { get; set; }
public IEnumerable<SelectListItem> GraduationList { get; set; }
public IEnumerable<SelectListItem> PostGraduationList { get; set; }
}
Side notes: Its not clear what your current ProfessionalQualifications property is - does that get assigned to the Qualification field or the Acheievement field in the database? Since your using a view model, then it should include the SelectList's rather that using ViewBag.
Then your view will be
#model AcademicViewModel
#using (Html.BeginForm())
{
<h2>Graduation</h2>
#Html.DropDownListFor(m => m.GraduationQualification, Model.GraduationList)
#Html.TextAreaFor(m => m.GraduationAchievement)
... // repeat for PostGraduation and Professional
<input type="submit" value="Save">
}
And the POST method would be
[HttpPost]
public ActionResult MyController(AcademicViewModel model) // should be named Create?
{
var userID = ....
AcademicMaster graduation = new AcademicMaster
{
Type = AcademicType.Graduation,
Qualification = model.GraduationAchievement,
Achievement = model.GraduationAchievement,
UserId = userID;
};
db.AcademicMasters.Add(graduation);
// Repeat for PostGraduation and Professional
db.SaveChanges();
// redirect?
}

how to handle a list of checkbox?

I am developing a page which display data from service on the View and allow users to filter it.
There is a column for Countries, which allow users to filter.
I am not able to figure out a way to create a list of checkbox so that I can grab all selected values in one param like string[] countries (in the action method).
Can't use classic way :
<input type="checkbox" name="countries" value="USA" />USA<br />
<input type="checkbox" name="countries" value="Canada" />Canada<br />
This does pass the values in URL but doesn't set them back on post back (keeping checked onpostback).
I tried using checkboxlist (http://goo.gl/TUvZzu) but seemed to complicated for my Modal.
Since mine is a very straight forward Model :
public string Title { get; set; }
public string Website { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string Zip { get; set; }
public string State { get; set; }
public string Country { get; set; }
I appreciate your time and help.
You need to include a collection of countries within your view model to hold the selected values and allow them to be sent in the post.
I would also create a Country object to hold the Id, Name and Selected values.
In order for the model to be posted back you need to index each item in the view, this allows the model binder to pick it up.
Models
public class AModel
{
public AModel()
{
Countries = new List<Country>();
}
public string Title { get; set; }
public string Website { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string Zip { get; set; }
public string State { get; set; }
public string Country { get; set; }
// Country collection
public List<Country> Countries { get; set; }
}
public class Country
{
public int ID { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
View for loop
#for(var i = 0; i < Model.Countries.Count; i++)
{
<dt>
#Html.HiddenFor(m => Model.Countries[i].ID)
#Html.HiddenFor(m => Model.Countries[i].Name)
#Html.CheckBoxFor(m => Model.Countries[i].Checked)
</dt>
<dd>
#Model[i].Name
</dd>
}
Note the for loop instead of foreach to enable model binding and the hidden fields to allow the values to be posted back to the controller
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
Controller post
[HttpPost]
public ActionResult Index(AModel model)
{
//All the selected countries are available in the model
return View(model);
}
Working example

Categories

Resources