Using a one model to populate the dropdown for the view on another model - asp.net-mvc

I've been striking out on how to handle this situation. I have a model, the project model, and it has numerous items that need to be dropdowns for the user to change the selected item in question. I started out simple with the ProjectType value, which should have the selectable values populated form the ProjectTypes table. Here is the ViewModel:
public class ProjectViewModel
{
public APT_Projects Project { get; set; }
public System.Linq.IQueryable<APT_ProjectTypes> projecttypes { get; set; }
public APT_ClientTypes ClientTypes {get; set;}
}
Here is the controller:
public ActionResult Edit(int id = 0)
{
APT_Projects apt_projects = db.APT_Projects.Find(id);
if (apt_projects == null)
{
return HttpNotFound();
}
ProjectViewModel Project = new ProjectViewModel();
var apt_projecttypes = from a in db.APT_ProjectTypes
select a;
Project.Project = apt_projects;
Project.projecttypes = apt_projecttypes;
return View(Project);
}
and finally the view:
#model APTII_MVC.ViewModel.ProjectViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>APT_Projects</legend>
<div class="editor-label" style="float: left; width: 500px;" >
#Html.LabelFor(model => model.Project.Project_ID)
</div>
<div class="editor-field" style="float: left; width: 500px;" >
#Html.EditorFor(model => model.Project.Project_ID)
#Html.ValidationMessageFor(model => model.Project.Project_ID)
</div>
<div class="editor-label"">
#Html.LabelFor(model => model.Project.ProjectType_ID)
</div>
<div class="editor-field"">
#Html.DropDownListFor(model => model.Project.ProjectType_ID, Model.projecttypes)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
When I do it in this manner, .net doesn't like the Drowpdownlistfor, but I'm unclear how to use these values in a manner that the dropdownlistfor would accept. What is my mistake?
Edit: Relevent Error for the Dropdownlistfor.
'System.Web.Mvc.HtmlHelper<ViewModel.ProjectViewModel>' does not contain a definition for 'DropDownListFor' and the best extension method overload 'System.Web.Mvc.Html.SelectExtensions.DropDownListFor<TModel,TProperty>(System.Web.Mvc.HtmlHelper<TModel>, System.Linq.Expressions.Expression<System.Func<TModel,TProperty>>, System.Collections.Generic.IEnumerable<System.Web.Mvc.SelectListItem>)' has some invalid arguments c:\Visual Studio 2012\Projects\Test_MVC\Test_MVC\Views\Project\Edit.cshtml

you might need a List<SelectListItem> or SelectList().. so try converting your projecttypes object to
#Html.DropDownListFor(model => model.Project.ProjectType_ID, new SelectList(Model.projecttypes,"ID","Type")
or edit your viewmodel to
public SelectList projecttypes { get; set; }
and your controller code to
Project.projecttypes = new SelectList(db.APT_ProjectTypes,"ID","Type");
just guessing on your value/text field names

Related

ASP.Net MVC 4.0 - Validation Issues With array based properties on ViewModel

ASP.Net MVC 4.0 - Validation Issues With array based properties on ViewModel .
Scenario :
When a ViewModel has a string array as a property type,the default Scaffolding template for say, Edit, does not render the that property in the markup.
Say, I have ViewModel setup like this :
Employee.cs
public class Employee
{
[Required]
public int EmpID
{
get;
set;
}
[Required]
public string FirstName
{
get;
set;
}
[Required]
public string LastName
{
get;
set;
}
[Required]
public string[] Skills
{
get;
set;
}
}
}
The (strongly typed) Edit View generated by the scaffolding template, as shown below, typically skips the portion relevant to field Skills.
**Employee.cshtml**
#model StringArray.Models.Employee
#{
ViewBag.Title = "EditEmployee";
}
<h2>EditEmployee</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Employee</legend>
<div class="editor-label">
#Html.LabelFor(model => model.EmpID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.EmpID)
#Html.ValidationMessageFor(model => model.EmpID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
The corresponding Controller code is
..
[HttpGet]
public ActionResult EditEmployee()
{
Employee E = new Employee()
{
EmpID = 1,
FirstName = "Sandy",
LastName = "Peterson",
Skills = new string[] { "Technology", "Management", "Sports" }
};
return View(E);
}
[HttpPost]
public ActionResult EditEmployee(Employee E)
{
return View(E);
}
To get the missing section for the Skills field, I added
Snippet to the View
<div class="editor-label">
#Html.LabelFor(model => model.Skills)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Skills)
#Html.ValidationMessageFor(model => model.Skills)
</div>
Corresponding UIHint to the ViewModel
[UIHint("String[]")]
public string[] Skills ...
EditorTemplates inside relevant folder as
~\View\shared\EditorTemplates\String[].cshtml
and
~\View\shared\EditorTemplates\mystring.cshtml
string[].cshtml
#model System.String[]
#if(Model != null && Model.Any())
{
for (int i = 0; i < Model.Length; i++)
{
#Html.EditorFor(model => model[i], "mystring")
//Html.ValidationMessageFor(model => model[i])
}
}
mystring.cshtml
#model System.String
#{
//if(Model != null)
{
//To resolve issue/bug with extra dot getting rendered in the name - like
//Skills.[0], Skills.[1], etc.
//ViewData.TemplateInfo.HtmlFieldPrefix=ViewData.TemplateInfo.HtmlFieldPrefix.Replace(".[", "[");
#Html.TextBoxFor(model => model)
}
}
But despite this all, the Validations for the Skills section [with 3 fields/elements - refer the EditEmployee method in Controller above.]
are entirely skipped, on postback.
I tried below changes inside the mystring.cshtml EditorTemplate :
//to correct the rendered names in the browser from Skills.[0] to Skills for all the 3 items in the
//Skills (string array), so that model binding works correctly.
string x = ViewData.TemplateInfo.HtmlFieldPrefix;
x = x.Substring(0, x.LastIndexOf("."));
#Html.TextBoxFor(model =>model, new { Name = x })
Postback WORKS But Validations DON'T, since the "data-valmsg-for" still points to <span class="field-validation-valid" data-valmsg-for="Skills" data-valmsg-replace="true"></span>
and thus doesn't apply at granular level - string element level.
Lastly, I tried removing #Html.ValidationMessageFor(model => model.Skills) from the Employee.cshtml and correspondingly adding the
same to string[].cshtml as #Html.ValidationMessageFor(model => model[i]).
But this led to data-valmsg-for getting rendered for each granular string element like
data-valmsg-for="Skills.[0]" ,
data-valmsg-for="Skills.[1]" and data-valmsg-for="Skills.[2]", respectively.
Note: Validations work for other fields - EmpID, FirstName LastName, BUT NOT for Skills.
Question
How do I set the data-valmsg-for="Skills" for each of the above three granular elements related to Skills property.
I am stuck on this for quite some time now. It would be nice if some one can point out the issue, at the earliest.
Thanks, Sandesh L
This is where you like to change
[Required]
public string[] Skills
{
get;
set;
}
You are giving validation on the array.
you might want to have a new string class call Skill
[Required]
public string Skill
{
get;
set;
}
And you can change to you model with
[Required]
public List<Skill> Skills
{
get;
set;
}
I prefer using List instead of array. Then, you can change you skill view according to the model updated
you template view can be something like
#model IEnumerable<Skill>
<div class="editor-label">
<h3>#Html.LabelFor(model=> model.Skills)
</h3>
</div>
<div class="editor-field">
#foreach (var item in Model)
{ #Html.Label(model => item)
#Html.TextBoxFor(model => item) <br/>
}
#Html.ValidationMessageFor(model => item)

How do you post data to joined tables - Using EF 5, ASP.NET MVC 4

I am new to MVC 4 and up to this point I have been retrieving data from one table and saving back to the same table. Now I have data from joined tables and need to save back to the same joined tables. I created a new model and created the get controller but when I try to save I get an error. Any constructive help would be appreciated.
Model
public partial class JoinClass
{
public string Name { get; set; }
public int EEID { get; set; }
public string Category { get; set; }
public int Points { get; set; }
public string Programs { get; set; }
public DateTime EntryDate { get; set; }
public DateTime Quarter { get; set; }
}
Controller
public ActionResult Create()
{
ViewBag.VBQuarter = new SelectList(db.Quarter2, "Id_Quarter2", "Quarter");
ViewBag.VBEEID = new SelectList(db.Users2, "Id_Users2", "EEID");
ViewBag.FK_Programs = new SelectList(db.Programs, "Id_Programs", "Programs");
ViewBag.EntryDate = DateTime.Now;
return View();
}
//
// POST:
[HttpPost]
public ActionResult Create(JoinClass joinclass)
{
if (ModelState.IsValid)
{
db.JoinClass.Add(joinclass);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(joinclass);
}
View
#model 2014_V4.Models.JoinClass
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
<fieldset>
<legend>Points</legend>
<div class="editor-label">
Quarter
</div>
<div class="editor-field">
#Html.DropDownList("VBQuarter")
</div>
<div class="editor-label">
User
</div>
<div class="editor-field">
#Html.DropDownList("VBEEID")
</div>
<div class="editor-label">
Programs
</div>
<div class="editor-field">
#Html.DropDownList("FK_Programs", String.Empty)
</div>
<div class="editor-label">
Points
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Points)
</div>
<div class="editor-label">
Entery Date
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.EntryDate, new {#Value = ViewBag.EntryDate, #readonly="readonly" })
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Your problem is in constructor of JoinClass. You should not initialize virtual navigation properties collection with new HashSet<T>. Entity framework use its own collection type - EntityCollection<T>. You should not initialize navigation property collections at all.
PS: It is good practice to use special viewmodels with views and pass data from viewmodels to entity models explicit.

How to assign one variable and pass another with post in razor

I want to take some info from my model, edit one variable and pass it to post function. Here is my model:
public class TaskInputModel
{
[Required]
[Display(Name = "Input Value")]
public decimal Value { get; set; }
public long InputId { get; set; }
public MetriceModelTaskShedule[] Tasks;
}
and this is my Index.cshtml:
#model MetriceWeb.Models.TaskInputModel
#foreach (var item in Model.Tasks)
{
using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => item.Task)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Value)
#Html.ValidationMessageFor(model =>model.Value)
</div>
#Html.Hidden("Model.InputId", Model.InputId)
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
}
I'm receiving this like that:
[HttpPost]
public ActionResult Index(TaskInputModel model)
{
...
}
When I'm submitting only InputId has some value, Value is always 0. When i delete line: #Html.Hidden("Model.InputId", Model.InputId) Value is ok, but i don't know how to receive InputId. Can you tell me how can I do this?
Problem solved. I just had to use #Html.Hidden("InputId", Model.InputId) instead of #Html.Hidden("Model.InputId", Model.InputId)

MVC3 - How to add dropdown to a form (Post) populated with different entity

Hi I am working in a MVC 3 application. I have a Create Form with following code.
#model Xrm.Student
#{
ViewBag.Title = "Create Student Record";
}
#using (Html.BeginForm("Create", "Student", FormMethod.Post))
{
<div class="editor-label">
#Html.LabelFor(model => #Model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => #Model.FirstName)
#Html.ValidationMessageFor(model => #Model.FirstName)
</div>
<div>
<input id="Submit1" type="submit" value="Submit" />
</div>
}
I want to add a new drop down under Firsname field which should be populated with pubjects. Subject is different Entity. I could be very easy, but I am newbie with MVC so I just stuck here. Can anyone please suggest me the way to achieve it.
Thanks and Regards
I would define a view model:
public class MyViewModel
{
public Student Student { get; set; }
[DisplayName("Subject")]
[Required]
public string SubjectId { get; set; }
public IEnumerable<Subject> Subjects { get; set; }
}
and then have your controller populate and pass this view model to the view:
public ActionResult Create()
{
var model = new MyViewModel();
model.Student = new Student();
model.Subjects = db.Subjects;
return View(model);
}
and finally have your view strongly typed to the view model:
#model MyViewModel
#{
ViewBag.Title = "Create Student Record";
}
#using (Html.BeginForm())
{
<div class="editor-label">
#Html.LabelFor(x => x.Student.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(x => x.Student.FirstName)
#Html.ValidationMessageFor(x => x.Student.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(x => x.SubjectId)
</div>
<div class="editor-field">
#Html.DropDownListFor(
x => x.SubjectId,
new SelectList(Model.Subjects, "Id", "Name"),
"-- Subject --"
)
#Html.ValidationMessageFor(x => x.SubjectId)
</div>
<div>
<input type="submit" value="Submit" />
</div>
}
The "Id" and "Name" values I used for the SelectList must obviously be existing properties on your Subject class that you want to be used as respectively binding the id and the text of each option of the dropdown.

ViewModel IEnum<> property is returning null (not binding) when contained in a partial view?

I have a ViewModel that contains a Product type and an IEnumerable< Product > type. I have one main view that displays the ViewModel.Product at the top of the page but then I have a partial view that renders the ViewModel.IEnumerable< Product > data. On the post the first level product object comes back binded from the ViweModel whereas the ViewModel.IEnumerable< Product > is coming back null.
Of course if I remove the partial view and move the IEnumerable< Product > view to the main View the contents comes back binded fine. However, I need to put these Enumerable items in a partial view because I plan on updating the contents dynamically with Ajax.
Why is the IEnumerable< Prouduct> property not getting binded when it's placed in a partial view? Thx!
Models:
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ProductIndexViewModel
{
public Product NewProduct { get; set; }
public List<Product> Products { get; set; }
}
public class BoringStoreContext
{
public BoringStoreContext()
{
Products = new List<Product>();
Products.Add(new Product() { ID = 1, Name = "Sure", Price = (decimal)(1.10) });
Products.Add(new Product() { ID = 2, Name = "Sure2", Price = (decimal)(2.10) });
}
public List<Product> Products { get; set; }
}
Views:
Main index.cshtml:
#model ViewModelBinding.Models.ProductIndexViewModel
#using (#Html.BeginForm())
{
<div>
#Html.LabelFor(model => model.NewProduct.Name)
#Html.EditorFor(model => model.NewProduct.Name)
</div>
<div>
#Html.LabelFor(model => model.NewProduct.Price)
#Html.EditorFor(model => model.NewProduct.Price)
</div>
#Html.Partial("_Product", Model.Products)
<div>
<input type="submit" value="Add Product" />
</div>
}
Parial View _Product.cshtml:
#model List<ViewModelBinding.Models.Product>
#for (int count = 0; count < Model.Count; count++)
{
<div>
#Html.LabelFor(model => model[count].ID)
#Html.EditorFor(model => model[count].ID)
</div>
<div>
#Html.LabelFor(model => model[count].Name)
#Html.EditorFor(model => model[count].Name)
</div>
<div>
#Html.LabelFor(model => model[count].Price)
#Html.EditorFor(model => model[count].Price)
</div>
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
BoringStoreContext db = new BoringStoreContext();
ProductIndexViewModel viewModel = new ProductIndexViewModel
{
NewProduct = new Product(),
Products = db.Products
};
return View(viewModel);
}
[HttpPost]
public ActionResult Index(ProductIndexViewModel viewModel)
{
// work with view model
return View();
}
}
When you use #Html.Partial("_Product", Model.Products) your input fields do not have correct names. For example instead of:
<input type="text" name="Products[0].ID" />
you get:
<input type="text" name="[0].ID" />
Just look at your generated markup and you will see the problem. This comes from the fact that when you use Html.Partial the navigational context is not preserved. The input fields names are not prefixed with the name of the collection - Products and as a consequence the model binder is not able to bind it correctly. Take a look at the following blog post to better understand the expected wire format.
I would recommend you using editor templates which preserve the context. So instead of:
#Html.Partial("_Product", Model.Products)
use:
#Html.EditorFor(x => x.Products)
and now move your _Product.cshtml template to ~/Views/Shared/EditorTemplates/Product.cshtml. Also since the editor template automatically recognizes that the Products property is an IEnumerable<T> it will render the template for each item of this collection. So your template should be strongly typed to a single Product and you can get rid of the loop:
#model Product
<div>
#Html.LabelFor(model => model.ID)
#Html.EditorFor(model => model.ID)
</div>
<div>
#Html.LabelFor(model => model.Name)
#Html.EditorFor(model => model.Name)
</div>
<div>
#Html.LabelFor(model => model.Price)
#Html.EditorFor(model => model.Price)
</div>
Now everything works by convention and it will properly bind.

Resources