I have a below model
public class category
{
[Key]
public long category_id { get; set; }
public string category_desc { get; set; }
public long? client_id { get; set; }
public long? display_sno { get; set; }
}
controller passing the model to view
public ActionResult category(long? client_id)
{
var category_type = db.category.Where(m => m.client_id == null).ToList();
if(client_id == 10)
{
category_type = db.category.Where(m => m.client_id == client_id).ToList();
}
return View(category_type);
}
In view populating the radion button
#foreach (var item in Model)
{
#Html.DisplayFor(modelItem => item.display_sno)<text>.</text>
#Html.RadioButtonFor(modelItem => item.category_id, item.category_id, new { id=item.category_id})#item.category_desc
}
post Method
public ActionResult Category(category g)
{
}
In post method g is coming as null.
What I am missing here?
Your misunderstanding how radio buttons work. They are for binding one of many options to a single property, in the same way a dropdownlist works. Your code is generating radio buttons that bind to itself. And if you inspect the html you will see that it generates name="item.category_id" which would not bind to your model anyway (that would only bind to a model containing a complex object named Item which contained a property named category_id).
Assuming you have a model for a Product which contained a property for its Category, and you wanted to assign one of the values of your category table to the Category property, then your view models would look like
public class ProductVM
{
[Required(ErrorMessage = "Please select a category")]
public int? Category { get; set; }
public IEnumerable<CategoryVM> CategoryOptions { get; set; }
}
public class CategoryVM
{
public int ID { get; set; }
public string Name { get; set; }
}
Note the use of a nullable property for Category is explained in What does it mean for a property to be [Required] and nullable?
Then the controller methods (for a Create view) would look like
public ActionResult Create()
{
ProductVM model = new ProductVM();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Create(ProductVM model)
{
if (!ModelState.IsValid())
{
ConfigureViewModel(model);
return View(model);
}
.... // Initialize your Product data model, set its properties based on the view model
// Save and redirect
}
private void ConfigureViewModel(ProductVM model)
{
model.CategoryOptions = db.categories.Select(x => new CategoryVM
{
ID = x.category_id,
Name = x.category_desc
});
}
Then the view would be
#model ProductVM
....
#using (Html.BeginForm())
{
#Html.DisplayNameFor(m => m.Category)
foreach(var category in Model.CategoryOptions)
{
<label>
#Html.RadioButtonFor(m => m.Category, category.ID, new { id = "" })
<span>#category.Name</span>
</label>
}
#Html.ValidationMessageFor(m => m.Category)
<input type="submit" .... />
}
Note that the new { id = "" } is removing the id attribute which would otherwise be invalid html because of duplicates.
Related
I have been scratching my head for a whole night on an issue I can do quickly using ajax/jquery and stored procedures. I want to
1) Populate a drop down list from values obtained from a database table using Entity Framework and view model. I DO NOT WANT TO USE VIEWBAG OR VIEWDATA. Any help appreciated.
2) How can I generate a Create View using the View Model with the all the default fields ? The scaffholding works on a model but not on a view model ?
MY MODELS
public class Employee
{
public int EmployeeID { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public string City { get; set; }
public string Level { get; set; }
public int DepartmentId { get; set; }
}
public class Grade
{
public int ID { get; set; }
public string Level { get; set; }
}
View Model
public class GradeSelectListViewModel
{
public Employee Employee { get; set; }
public IEnumerable<SelectListItem> Grades { get; set; }
public GradeSelectListViewModel(Employee employee, IEnumerable grades)
{
Employee = employee;
Grades = new SelectList(grades, "Grade", "Name", employee.Level);
}
}
MY CONTEXT CLASS
public class EmployeeContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Grade> Grades { get; set; }
}
MY CONTROLLER
public ActionResult Edit (int? id)
{
using (var db = new EmployeeContext())
{
var model = new GradeSelectListViewModel(db.Employees.Find(id), db.Grades);
//model.Employee = db.Employees.Single(x => x.EmployeeID == id);
model.Grades = db.Grades.ToList().Select(x => new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Level
});
return View(model);
}
}
MY RAZOR PAGE CSHTML
#model MVCDemo.ViewModels.GradeSelectListViewModel
....
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
....
#Html.DropDownListFor(x => Model.Employee.Level,
new SelectList(Model.Grades, "ID", "Level"),
"Select Level")
....
<input type="submit" value="Create" class="btn btn-default" />
}
The main issue is that in the view you have new SelectList(Model.Grades, "ID", "Level") but Grades is IEnumerable<SelectListItem> and SelectListItem does not contain properties named ID and Level.
However there are a a few other issues with your code. First a view model should not contain a data model, and instead your view model should be
public class GradeSelectListViewModel
{
public int? ID { get; set; } // make this ID so you do not need an input for it
public string Name { get; set; }
.... // other properties of Employee that your editing
[Required(ErrorMessage = "..")]
public int? Level { get; set; } // make value types nullable to protect against under-posting attacks
public IEnumerable<SelectListItem> Grades { get; set; }
}
and add display and validation attributes as required. Note that I deleted the constructor (you don't seem to be using it, but if you did, then you also need to include a parameter-less constructor, otherwise an exception will be thrown when submitting to the POST method. I also assume that Level should be typeof int since you binding to the int ID property of Grade.
The the code in your GET method should be
Employee employee = db.Employees.Find(id);
var model = new GradeSelectListViewModel()
{
ID = employee.EmployeeID,
Name = employee.Name,
Level = employee.Level, // convert to int?
....
Grades = db.Grades.Select(x => new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Level
})
};
return View(model);
and in the view
#Html.DropDownListFor(x => Model.Level, Model.Grades, "Select Level")
Note also that in the POST method, your need to reassign the SelectList if you return the view because ModelState is invalid.
You can use the following approach that populates three DropDownListFor at the same View:
ViewModel:
public class GroupViewModel
{
public IEnumerable<SelectListItem> Schedules { get; set; }
public int ScheduleId { get; set; }
public IEnumerable<SelectListItem> Labs { get; set; }
public int LabId { get; set; }
public IEnumerable<SelectListItem> Terms { get; set; }
public int TermId { get; set; }
}
Controller:
public ActionResult Create()
{
//Populate DropDownList binding values
var model = new GroupViewModel
{
//Preselect the Lab with id 2
//LabId = 2,
Labs = repository.Labs.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
}),
Terms = repository.Terms.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
}),
Schedules = repository.Schedules.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
})
};
return View("Create", model);
}
View:
#Html.DropDownListFor(m => m.LabId, new SelectList(Model.Labs, "Value", "Text"),
"Select", new { #class = "selectpicker" })
#Html.DropDownListFor(m => m.ScheduleId, new SelectList(Model.Schedules, "Value", "Text"),
"Select", new { #class = "selectpicker" })
#Html.DropDownListFor(m => m.TermId, new SelectList(Model.Terms, "Value", "Text"),
"Select", new { #class = "selectpicker" })
I have many-to-many relationship between Student and Course. The linking entity set is Enrollment. For the sake of simplicity, they are all defined as follows.
Models
public class Course
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Enrollment
{
public int Id { get; set; }
public int StudentId { get; set; }
public int CourseId { get; set; }
public virtual Student Student { get; set; }
public virtual Course Course { get; set; }
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
ViewModels
public class StudentCourseVM
{
public Student Student { get; set; }
public IEnumerable<Course> SelectedCourses { get; set; }
public IEnumerable<Course> AvailableCourses { get; set; }
}
Controllers
public IActionResult Create()
{
var availableCourses = context.Courses;
return View(new StudentCourseVM { AvailableCourses = availableCourses });
}
[HttpPost]
public async Task<IActionResult> Create(StudentCourseVM sc)
{
if (ModelState.IsValid)
{
// What should I do here?
// ======================
await context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(sc);
}
Views
#model MasterDetails.ViewModels.StudentCourseVM
<form asp-action="Create">
<div>
<label asp-for="#Model.Student.Name"></label>
<input asp-for="#Model.Student.Name" />
</div>
<div>
<label asp-for="#Model.Student.Enrollments"></label><br />
#foreach (var course in Model.AvailableCourses)
{
<input type="checkbox" name="#course.Title" id="#course.Id" /> #course.Title <br />
}
</div>
<input type="submit" value="Create" />
</form>
Questions
How to know the selected check boxes from within the HttpPost Create action method?
You can use Editor Templates to do this.
First, create a new class for the course selection and update your view model to have a collection of that class.
public class SelectedCourse
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class StudentCourseVM
{
public int StudentId { set; get; }
public IEnumerable<SelectedCourse> SelectedCourses { get; set; }
}
You do not need to copy and paste all the properties from your entity model to your view model. View model needs only those properties which the view absolutely need. I am assuming you want to assign courses to a specific student
Now go to your ~/Views/YourControllerName and create a directory called EditorTemplates. Create a new razor file there and give the name SelectedCource.cshtml
Paste this code to the new file
#model SelectedCourse
<label>#Model.Name</label>
<input asp-for="IsSelected"/>
<input type="hidden" asp-for="Id" />
Now in your GET action, create an object of the view model, load the SelectedCourses collection and send it to the view.
public IActionResult Create()
{
// I hard coded the student id and the courses here.
// you may replace it with real data.
var vm = new StudentCourseVM { StudentId = 12 };
//Assuming we are assigning courses to the student with id 12
vm.SelectedCourses = new List<SelectedCourse>()
{
new SelectedCourse {Id = 1, Name = "CSS"},
new SelectedCourse {Id = 2, Name = "Swift"},
new SelectedCourse {Id = 3, Name = "IOS"},
new SelectedCourse {Id = 4, Name = "Java"}
};
return View(vm);
}
Now in your main view(Create.cshtml) which is strongly typed to StudentCourseVM,Use EditorFor helper method on the SelectedCourses property.
#model StudentCourseVM
<form asp-action="Create">
#Html.EditorFor(f=>f.SelectedCourses)
<input type="hidden" asp-for="StudentId"/>
<input type="submit"/>
</form>
The Editor template will execute code in the editor template file for each item in the SelectedCourses collection. So you will have the course name and a checkbox visible to the user.
In your HttpPost action method, you can use the same view model as the parameter. When the form is submitted, you may loop through the items in SelectedCourses property check the IsSelected property value. The courses user selected in the ui will have a true value.
[HttpPost]
public IActionResult Create(StudentCourseVM model)
{
var studentId = model.StudentId;
foreach (var modelSelectedCourse in model.SelectedCourses)
{
if (modelSelectedCourse.IsSelected)
{
//this one is selected. Save to db
}
}
// to do : Return something
}
Pre-selecting some checkboxes on page load
Sometimes you want to pre select some checkboxes when the page loads (Ex : For your edit screen you want to show already saved courses as checked). To do this, you simply need to set the IsSelected property of the corresponding SelectedCourse object to true in your GET action method.
public IActionResult Edit(int id)
{
// I hard coded the student id and the courses here.
// you may replace it with real data.
var vm = new StudentCourseVM { StudentId = id };
//Assuming we are assigning courses to the student with id 12
vm.SelectedCourses = new List<SelectedCourse>()
{
new SelectedCourse {Id = 1, Name = "CSS"},
new SelectedCourse {Id = 2, Name = "Swift", IsSelected = true },
new SelectedCourse {Id = 3, Name = "IOS", IsSelected = true },
new SelectedCourse {Id = 4, Name = "Java"}
};
return View(vm);
}
The above code will pre select the checkboxes for Swift and IOS.
In ASP.NET MVC 5 Please could someone help me with how I determine the value that has been selected from a Select List (implemented as a #Html.DropDownListFor) in a View so I can pass it back to the Controller Method in order to select a modified list of data please? I have spent a few days browsing and trying things to no avail:
Here is what I have. I have a parent Model:
public class ParentEndorsementViewModel
{
// Child Models:
public List<VW_CMA_PRODUCT_ENDORSEMENT> VW_CMA_PRODUCT_ENDORSEMENTS { get; set; }
// This property will hold all available Products for selection:
public IEnumerable<SelectListItem> Product_DropDownList { get; set; }
}
And the relevant “child” model is:
public class Product_DropDownList
{
private Product_DropDownList(int id, string name)
{
Id = id;
Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
}
The View has:
#model App_Endorsement.Models.ParentEndorsementViewModel
….
#using (Html.BeginForm("Manage_Endorsement", "Endorsement", new { ParentEndorsementViewModel = #Model }, FormMethod.Post, null))
{
#*#Html.AntiForgeryToken()*#
#Html.DropDownListFor(m => m.Product_DropDownList, new SelectList(Model.Product_DropDownList, "Value", "Text", Model.Product_DropDownList), "Show All Products")
<input type = "submit" value = "Send" />
}
The Controller method starts:
[HttpPost]
//[ValidateAntiForgeryToken]
public IActionResult Manage_Endorsement(ParentEndorsementViewModel In_ParentEndorsementViewModel)
{
var ParentEndorsementViewModel = In_ParentEndorsementViewModel;
As I say I want to retrieve the Selected Value that the User has chosen from within the Product_DropDownList but when the model (ParentEndorsementViewModel) arrives in the Controller it is empty.
I think you have overcomplicated this somewhat. Your views main model needs both a list to populate the control and a variable to contain the selected value.
For example your model:
public class ParentEndorsementViewModel
{
public int SelectedId { get; set; }
public List<SelectListItem> ProductList { get; set; }
public ParentEndorsementViewModel() {
// Do this just in case you forget to instantiate the list in your first call to the view.
ProductList = New List<SelectListItem>();
}
}
On your view:
#Html.DropDownListFor(x => x.SelectedId, Model.ProductList)
When the user sends this data to back to your controller via a form POST for example, the model binder should marry up to the view model. The value selected by your user is contained in SelectedId variable.
[HttpPost]
public ActionResult MyPostBack (ParentEndorsementViewModel model) {
var myselectedid = model.SelectedId;
//Do whatever
}
You need another property on your ParentEndorsementViewModel, which is what will hold the selected value from the drop down list. Currently your code is attempting to set the selected value to the property that contains the available items.
e.g. in the view model
public class ParentEndorsementViewModel
{
// Child Models:
public List<VW_CMA_PRODUCT_ENDORSEMENT> VW_CMA_PRODUCT_ENDORSEMENTS { get; set; }
// This property will hold all available Products for selection:
public IEnumerable<SelectListItem> Product_DropDownList { get; set; }
// NEW PROPERTY
public int SelectedProductId { get; set; }
}
In the view:
#model App_Endorsement.Models.ParentEndorsementViewModel
#using (Html.BeginForm("Manage_Endorsement", "Endorsement", new { ParentEndorsementViewModel = #Model }, FormMethod.Post, null))
{
#*#Html.AntiForgeryToken()*#
#* CHANGE THE TARGET PROPERTY HERE *#
#Html.DropDownListFor(m => m.SelectedProductId, new SelectList(Model.Product_DropDownList, "Value", "Text", Model.Product_DropDownList), "Show All Products")
<input type = "submit" value = "Send" />
}
In the Controller:
[HttpPost]
//[ValidateAntiForgeryToken]
public IActionResult Manage_Endorsement(ParentEndorsementViewModel In_ParentEndorsementViewModel)
{
var ParentEndorsementViewModel = In_ParentEndorsementViewModel;
var selectedProductId = ParentEndorsementViewModel.SelectedProductId;
I have declared some Non-Content data in an Orchard CMS by defining the records and schema like this:
public class CountyRecord
{
public virtual int Id { get; set; }
public virtual string CountyName { get; set; }
public virtual CountryRecord CountryRecord { get; set; }
}
public class CountryRecord
{
public CountryRecord()
{
CountyRecords = new List<CountyRecord>();
}
public virtual int Id { get; set; }
public virtual string CountryName { get; set; }
public virtual IList<CountyRecord> CountyRecords { get; set; }
}
public class Migrations: DataMigrationImpl
{
public int Create()
{
//COUNTIES
SchemaBuilder.CreateTable(typeof(CountyRecord).Name, table => table
.Column<int>("Id", col => col
.PrimaryKey()
.Identity())
.Column<string>("CountyName")
.Column<int>("CountryRecord_Id"));
//COUNTRIES
SchemaBuilder.CreateTable(typeof(CountryRecord).Name, table => table
.Column<int>("Id", col => col
.PrimaryKey()
.Identity())
.Column<string>("CountryName"));
}
}
I then have two controllers handling the admin pages for these two entities. In the country controller I have the following actions:
//DELETE
[HttpGet, Admin]
public ActionResult Delete(int countryId)
{
var country = CountryRepo.Get(countryId);
if (country == null)
{
return new HttpNotFoundResult("Couldn't find the country with ID " + countryId.ToString());
}
return View(country);
}
[HttpPost, Admin, ActionName("Delete")]
public ActionResult DeletePOST(CountryRecord country)
{
foreach (CountyRecord county in CountyRepo.Fetch(c=>c.CountryRecord.Id==country.Id))
{
CountyRepo.Delete(county);
}
CountryRepo.Delete(country);
OrchardServices.Notifier.Add(NotifyType.Information, T("Country '{0}' deleted successfully", country.CountryName));
return RedirectToAction("Index");
}
And this is the view that goes with that:
#model Addresses.Models.CountryRecord
<div class="manage">
#using (Html.BeginFormAntiForgeryPost("Delete"))
{
<h2>Are you sure you want to delete this country and ALL its counties?</h2>
#Html.HiddenFor(m => m.Id);
#Html.HiddenFor(m => m.CountryName);
#Html.ActionLink(T("Cancel").Text, "Index", "CountriesAdmin", new { AreaRegistration = "Addresses" }, new { style = "float:right; padding:4px 15px;" })
<button class="button primaryAction" style="float:right;">#T("Confirm")</button>
}
</div>
However, here's the issue, when I delete a country that still has counties assigned to it, it throws the following error:
a different object with the same identifier value was already associated with the session
Can anyone please help?
Thanks.
It's because your DeletePOST() parameter is a CountryRecord. Orchard records are all proxied by the NHibernate framework and MVC's ModelBinder can't properly create them for you.
What you need to do instead is like what you do in the non-POST method: accept just the integer ID of the CountryRecord, fetch the record fresh from the repository, then delete it.
I have MVC3 web application where we need to populate radio button list with validation. My Model is something like this:
public class EmployeesViewModel
{
public List<Employee> listEmployee { get; set; } //To persist during post
public IEnumerable<SelectListItem> selectListEmployee { get; set; }
[Required]
public Employee selectedEmployee { get; set; }
}
public class Employee
{
public int ID {get; set;}
public string Name {get; set}
public string Department {get; set}
}
I need to populate radiobutton list something like below:
Employee1ID - Employee1Name - Employee1Department // id - name - department
Employee2ID - Employee2Name - Employee2Department
Employee3ID - Employee3Name - Employee3Department
Selected Employee should be stored into "selectedEmployee" field. What is the best or clean way to populate these radio button List in MVC3?
Note: Mainly Looking for two task:
1. storing "Employee" object in each "Input" radio button tag, so that selected employee will be saved to "selectedEmployee" field
2. Best way to mark "Employee" object as required field
Much appreciate your help!
Thanks,
Here's what I would recommend you. Start with a clean view model, one that really expresses what the view contains as information:
public class EmployeesViewModel
{
public List<EmployeeViewModel> ListEmployee { get; set; }
[Required]
public int? SelectedEmployeeId { get; set; }
}
public class EmployeeViewModel
{
public int ID { get; set; }
public string Label { get; set; }
}
then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new EmployeesViewModel
{
ListEmployee = GetEmployees()
};
return View(model);
}
[HttpPost]
public ActionResult Index(EmployeesViewModel model)
{
if (!ModelState.IsValid)
{
// the model is invalid, the user didn't select an employee
// => refetch the employee list from the repository and
// redisplay the view so that he can fix the errors
model.ListEmployee = GetEmployees();
return View(model);
}
// validation passed at this stage
// TODO: model.SelectedEmployeeId will contain the id
// of the selected employee => use your repository to fetch the
// actual employee object and do something with it
// (like grant him the employee of the month prize :-))
return Content("thanks for submitting", "text/plain");
}
// TODO: This doesn't belong here obviously
// it's only for demonstration purposes. In the real
// application you have a repository, use DI, ...
private List<EmployeeViewModel> GetEmployees()
{
return new[]
{
new EmployeeViewModel { ID = 1, Label = "John (HR)" },
new EmployeeViewModel { ID = 2, Label = "Peter (IT)" },
new EmployeeViewModel { ID = 3, Label = "Nathalie (Sales)" },
}.ToList();
}
}
and finally a view:
#model EmployeesViewModel
#using (Html.BeginForm())
{
#Html.ValidationMessageFor(x => x.SelectedEmployeeId)
#foreach (var employee in Model.ListEmployee)
{
<div>
#Html.RadioButtonFor(x => x.SelectedEmployeeId, employee.ID, new { id = "emp" + employee.ID })
#Html.Label("emp" + employee.ID, employee.Label)
</div>
}
<input type="submit" value="OK" />
}