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?
}
Related
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
}
When passing the model from my view to my controller, the data is all null. I was able to successfully pass it using an ActionLink but I don't think that is the best way; for security reasons (I do not want sensitive data in the querystring).
My models
public class DashboardModel
{
// Dasboard quick numbers
public int TotalUsers { get; set; }
public int TotalUnauthUsers { get; set; }
public int GamesPlayed { get; set; }
public int AssociatedGroups { get; set; }
public int TotalGroups { get; set; }
// Dashboard table
public IEnumerable<ManageUserData> UnauthUsers { get; set; }
}
public class ManageUserData
{
public string UserName { get; set; }
public int AlternateId { get; set; }
public string Email { get; set; }
public string Role { get; set; }
public IEnumerable<string> InvestigatorGroups { get; set; }
public string Institution { get; set; }
// User status
public bool AccountLocked { get; set; }
public bool EmailConfirmed { get; set; }
}
Snippet of my view
#model TestGame.ViewModels.DashboardModel
#foreach (var user in Model.UnauthUsers)
{
<tr>
<td>#user.UserName</td>
<td>#user.AlternateId</td>
<td>#user.Email</td>
<td>#user.Role</td>
<td>
#if (!user.EmailConfirmed)
{
<div class="text-warning">Unconfirmed Email</div>
}
#if (user.AccountLocked)
{
<div class="text-danger">Account Locked</div>
}
</td>
<td>
#if (user.AccountLocked || !user.EmailConfirmed)
{
using (Html.BeginForm("Manage", "Admin", FormMethod.Post))
{
#Html.HiddenFor(x => user.UserName)
#Html.HiddenFor(x => user.Email)
<input type="submit" value="Manage" />
You have to make sure the path starts with the object being posted back; what you have will work great if the action being posted to (HttpPost Admin/Manage action) takes an object of type User; if it takes an object of the model type, change your form to the following:
for (var i = 0; i < Model.UnAuthUsers.Count; i++)
using (Html.BeginForm("Manage", "Admin", FormMethod.Post))
{
#Html.HiddenFor(x => x.UnAuthUsers[i].UserName)
#Html.HiddenFor(x => x.UnAuthUsers[i].Email)
<input type="submit" value="Manage" />
Creating a reference from the model (being x) will do the trick.
EDIT: Based on comments, add two properties to your model:
public class DashboardModel
{
public string SelectedUserName { get; set; }
public string SelectedEmail { get; set; }
}
In your form, render a hidden for for that name; I've had trouble using HiddenFor, so I've in the past used the hidden directly:
using (Html.BeginForm("Manage", "Admin", FormMethod.Post))
{
<input type="hidden" name="#Html.NameFor(i => i.SelectedUserName)" value="#Model.UnauthUsers[i].UserName" />
<input type="hidden" name="#Html.NameFor(i => i.SelectedEmail)" .. />
And upon submitting the form, the user from that form will be posted back via the model, via these new properties.
My Model:
(i) For Transaction
public class Transaction
{
[Key]
public int TransactionId { get; set; }
public DateTime OnWardDate { get; set; }
public DateTime ReturnDate { get; set; }
public virtual UserProfile User { get; set; }
}
(ii) For User
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
private UserDetails _details = new UserDetails();
public virtual UserDetails details
{
get { return _details; }
}
}
(ii) For UserDetails
public class UserDetails
{
[Key]
public int DetailsId { get; set; }
public string DLNum { get; set; }
}
My Controller for Create Transaction:
// GET: /Transaction/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Transaction/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Transaction transaction)
{
if (ModelState.IsValid)
{
db.Transactions.Add(transaction);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(transaction);
}
My View for Create transaction:
<div class="editor-label">
#Html.LabelFor(model => model.OnWardDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.OnWardDate)
#Html.ValidationMessageFor(model => model.OnWardDate)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.details.DLNum)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.User.details.DLNum)
#Html.ValidationMessageFor(model => model.User.details.DLNum)
</div>
When I Submit the form... model.OnWardDate is getting saved but model.User.details.DLNum is not saving
I hv seen about ViewModel. Is that the only option to go with it?
Thanks In Advance.
In your case, I would think it's because your setter is private for UserDetails:
public virtual UserDetails details
{
get { return _details; }
}
If you're creating the tables based on those models and therefore can't afford to modify them, I would indeed advise to make a view model, post using that and then create / update the database entry accordingly.
The main issue is that Entity Framework requires both getter and setter for the POCO properties your class UserDetails only has a getter defined for the details property.
Your models could then be:
public class Transaction
{
[Key]
public int TransactionId { get; set; }
public DateTime OnWardDate { get; set; }
public DateTime ReturnDate { get; set; }
public virtual UserProfile User { get; set; }
}
public class UserProfile
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual UserDetails Details { get; set; }
}
public class UserDetails
{
[Key]
public int DetailsId { get; set; }
public string DLNum { get; set; }
}
2. I recommend using the pattern of instantiating the model in the Controller and passing it to the view as in:
public ActionResult Create()
{
var transaction = new Transaction
{
User = new UserProfile
{
Details = new UserDetails()
}
};
return View(transaction);
}
This ensures your model is properly constructed and allows you to initialize any properties before the view is rendered. You can often get away shortcutting this, IMHO it is better to be explicit.
I have a dashboard on my MVC 4 site that in part displays a list of data from one db table (my database was generated using the Entity Framework code-first method). The data being displayed is for a list of orders and the information that goes along with that order. Now, I'm trying to add the ability to add additional quantities for the same order (ex: I need this order quoted for 10, 25, 50 and 100 pieces). Right next to the edit, details, and delete options on each row is an add button where the user would then go to add another quantity. However, in testing, when I go to submit a quantity, the validation error comes up saying that the value entered is invalid when it should be a completely valid input. Can someone please help, because I can't figure out why this is happening. Here's some of the relevant code:
Models:
General_Info.cs
public class General_Info
{
[Key]
public int Quote_ID { get; set; }
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime Open_Quote { get; set; }
public string Customer_Name { get; set; }
public string OEM_Name { get; set; }
public int Qty { get; set; }
public string Fab_Drawing_Num { get; set; }
public string Rfq_Num { get; set; }
public string Rev_Num { get; set; }
public string Group_Number { get; set; }
public General_Info()
{
Quantitys = new HashSet<Quantity>();
Quote_Datas = new HashSet<Quote_Data>();
}
public virtual ICollection<Quantity> quantitys { get; set; }
}
Quantity.cs
public class Quantity
{
[Key]
public int Qty_ID { get; set; }
public string Group_Number { get; set; } // foreign key
public int quantity { get; set; }
public General_Info General_Info { get; set; }
}
Dashboard Controller:
[HttpGet]
public ActionResult AddQuantity(int Group_Num = 0)
{
return View();
}
[HttpPost]
public ActionResult AddQuantity(Quantity quantity)
{
if (ModelState.IsValid)
{
db.Quantitys.Add(quantity);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(quantity);
}
View
AddQuantity.cshtml
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Quantity</legend>
<div class="editor-label">
#Html.LabelFor(model => model.quantity)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.quantity)
#Html.ValidationMessageFor(model => model.quantity)
</div>
<p>
<input type="submit" value="Add Qty" />
</p>
</fieldset>
}
As you can see, in my model quantity is an integer, and if I enter an integer into my form the validation error is thrown.
Thanks in advance to anyone who can help, I appreciate it.
if you change your binding model name, that should do it
[HttpPost]
public ActionResult AddQuantity(Quantity model)
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>
}
}
}