I have the following model -
public class RoleModel
{
public int Id { get; set; }
public string RoleName { get; set; }
public virtual IEnumerable<UserModel> Users { get; set; }
public virtual IEnumerable<UserModel> SelectedUsers { get; set; }
public RoleModel()
{
}
}
The users IEnumerable is a list of users who are currently assigned to the selected role. When the View loads, this is populated correctly.
When I press Save in the view, the SelectedUsers IEnumerable and the Users IEnumerable is null, meaning I cannot unassign all users and reassign the selected users to the role.
Any ideas why the IEnumerables are null when pressing Save?
View
#Html.ListBoxFor(model => model.SelectedUsers, new SelectList(Model.Users, "Id", "SurnameFirstName"))
Controller
[Authorize]
[HttpGet]
public ActionResult Details(int id = 0)
{
RoleModel role = _roleService.GetById(id);
if (role == null)
{
return HttpNotFound();
}
return View(role);
}
[HttpPost]
public ActionResult Details(RoleModel model)
{
if (ModelState.IsValid)
{
_roleService.ReassignRights(model);
_roleService.ReassignUsers(model);
_roleService.Update(model);
return RedirectToAction("Index");
}
return View(model);
}
Reassign Users method in RoleService.cs
public void ReassignUsers(RoleModel role)
{
var roleDal = _roleRepository.FindById(role.Id);
//_roleRepository.ClearUsersForRole(role.Id);
foreach (var user in role.SelectedUsers)
{
}
}
Try replacing the type of UserModel into type of of UserModel's PK. If UserModel::Id is int then change the SelectedUsers as below.
public virtual IEnumerable<int> SelectedUsers { get; set; }
While the Users property will still be null because there is no input that is related to Users, it's just being used to render the html options in the ListBoxFor. You need to set the value again when it's submitted.
DEMO
Related
I am trying to add a create controller method for a child table of application user. I can't figure out how to populate the user id. I'm in the process of learning mvc and this seems like such a basic concept, but I can't get it to work. Here is my class.
public class Ticket
{
[Key]
public long Id { get; set; }
[StringLength(128), MinLength(3)]
[ForeignKey("AspNetUser")]
public virtual string AspNetUserId { get; set; }
public virtual ApplicationUser AspNetUser { get; set; }
public DateTime Date { get; set; }
public string Request { get; set; }
}
Here is my index - hopefully pulling only records associated to the current user. I don't have seed data setup, so I have to get create working in order to test this, but the view comes up.
public ActionResult Index()
{
//var userId = User.Identity.GetUserId();
var userId = UserManager.FindById(User.Identity.GetUserId());
var tickets = db.Tickets.Where(m => m.AspNetUser == userId); ;
return View(tickets.ToList());
}
My create get which also comes up, but doesn't seem to be linked up.
public ActionResult Create()
{
return View();
}
And here is my troublesome create post method. When I click submit nothing happens.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,AspNetUserId,Date,Request")] Ticket ticket)
{
if (ModelState.IsValid)
{
ticket.AspNetUserId = User.Identity.GetUserId();
db.Tickets.Add(ticket);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(ticket);
}
Within my view I have a #Html.HiddenFor(model => model.Id) for the Id, but since it's not posting I assume my ModelState is not valid.
So frustrating.
I have a model called club and each club has a virtual list property for the members of that club. I am lost as to how to add more members to that list and then save it to my database.
public class Club
{
[Key]
public int ClubID { get; set; }
public string ClubName { get; set; }
public string ClubDescription { get; set; }
//List of Members that are members of this Club
public virtual List<ClubMember> ClubMembers { get; set; }
}//end Club
This is the ClubMember model:
public class ClubMember
{
[Key]
public int MemberId { get; set; }
//First Name
[Display(Name = "First Name")]
public string MemberFName { get; set; }
//Last Name
[Required(ErrorMessage = "You must enter a Last Name")]
[Display(Name = "Last Name")]
public string MemberLName { get; set; }
[Display(Name = "Member Name")]
public string MemberName { get; set; }
public string MemberEmail { get; set; }
//Foreign Key for Club
public int ClubID { get; set; }
[ForeignKey("ClubID")]
public virtual Club Club { get; set; }
}
I am using a wrapper model to get a list of the selected ids for the members that the user wishes to add but I'm not sure if this is needed:
public class NewMemberList //Class used when adding new members to the members list of a club
{
public List<ClubMember> NewMembers { get; set; }
public List<int> SelectedIDs { get; set; }
}
This is what I currently have in my view for adding a member, it just displays a drop-down list with a list of members and a submit button
#model ultimateorganiser.Models.NewMemberList
#{
ViewBag.Title = "Add Members";
}
#using (Html.BeginForm(#Model.SelectedIDs))
{
#Html.AntiForgeryToken()
#Html.ListBoxFor(m => m.SelectedIDs, new MultiSelectList(Model.NewMembers, "UserId", "UserFName", Model.SelectedIDs))
<input type="submit" value="save" />
}
This is the controller method I have. It is not finished as I do not know how to handle the post part so that it gets the list of selected ids and adds all of the data for that member to the members list in the club:
[HttpGet]
public ActionResult AddMembers(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Club club = db.Clubs.Find(id);
if (club == null)
{
return HttpNotFound();
}
List<ClubMember> CurrentMembers = club.ClubMembers;
List<ClubMember> MembersList = new List<ClubMember>();
MembersList = db.ClubMembers.ToList();
ViewBag.CurrentMembersList = CurrentMembers;
return View(new NewMemberList() { NewMembers = MembersList });
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddMembers([Bind(Include = "SelectedIDs")] Club club)
{
if (ModelState.IsValid)
{
//Get selected members and add them to Members list for the club
return RedirectToAction("Index");
}
return View(club);
}
If you have questions or would like to see more of my code just ask.
Your view model should store the ClubId as well since you are adding the new members to a specific Club.
public class AddMembersToClub
{
public string Name { set;get;}
public int ClubId { set;get;}
public List<SelectListItem> Members { set;get;}
public int[] SelectedMembers { set;get;}
}
And in your GET action,
public ActionResult AddMembers(int id)
{
var club = db.Clubs.Find(id);
if (club == null)
{
return HttpNotFound();
}
var vm = new AddMembersToClub { ClubId=id , Name = club.ClubName };
//Here I am getting all the members, If you want a subset, update the LINQ query
vm.Members = db.ClubMembers
.Select(x=> new SelectListItem { Value = x.MemberId.ToString(),
Text=x.MemberFName }).ToList();
return View(vm);
}
and in your view, which is strongly typed to our AddMembersToClub view model. You need to keep the ClubId in a hidden form field as we need that in the HttpPost action.
#model AddMembersToClub
#using(Html.BeginForm())
{
<p>Adding members to #Model.Name</p>
#Html.HiddenFor(s=>s.ClubId)
#Html.ListBoxFor(s => s.SelectedMembers, Model.Members)
<input type="submit" />
}
And in your HttpPost action, Read the SelectedMembers property which is an int array storing the Id's of selected members and using the Id, get the Member entity and udpate the ClubId property.
[HttpPost]
public ActionResult AddMembers(AddMembersToClub model)
{
foreach(var item in model.SelectedMembers)
{
var member = db.ClubMembers.FirstOrDefault(s=>s.MemberId==item);
if(member!=null)
{
member.ClubId = model.ClubId;
}
db.SaveChanges();
}
return ReidrectToAction("Index");
}
Example:
I have table Orders and table OrderPositions.
public partial class Orders
{
public Orders()
{
this.OrderPositions = new HashSet<OrderPositions>();
}
public int OrderId { get; set; }
public string Title { get; set; }
public virtual ICollection<OrderPositions> OrderPositions { get; set; }
}
public partial class OrderPositions
{
public int OrderPositionId { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public virtual Orders Orders { get; set; }
}
On the view user can modify single record from OrderPositions table.
In controller:
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
So parameter orderPosition.Orders should be = null because on the form in view user can modify only order position. But can user hack it? I mean that in parameter orderPosition.Orders won't be null and I update record not only in table OrderPositions but also in table Orders? Or ASP.NET MVC prevent from that situation?
It really depends on what you do here
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
If you're saving the whole entity then yes there is nothing stopping a user passing over addition entity properties. There are a few ways to prevent this though, here are a couple...
1.Create a new entity at the point of saving
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = orderPosition.OrderPositionId,
OrderId = orderPosition.OrderId,
Name = orderPosition.Name
};
//Then save this new entity
}
}
2.Create a Model specific to the entity's action
public class EditOrderPosition
{
[Required]
public int PositionId { get; set; }
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
[HttpPost]
public ActionResult Edit(EditOrderPosition model)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = model.PositionId,
OrderId = model.Id,
Name = model.Name
};
//Then save this new entity
}
}
I generally go with the 2nd method as it stops direct user involvement with my entities. As a rule of thumb I never use entity objects as parameters in controller actions.
Hope this helps
Yes they can. This is one reason I do not expose my entities as a parameter to action methods, instead I use DTOs that only have the properties that I expect.
This is an example of the Mass Assignment Vulnerability.
Yes, there is nothing preventing a rogue app calling your endpoint with arbitrary data. Always validate everything serverside.
I'm new to MVC, so bear with me...
I've got my new form\view working (Creating and Adding a client)
But now I want to get the user so specifiy the Country where the new client is from A drop downlist. But im to sure how I to do this?
ViewModel
public class ClientNew
{
public string Company { get; set; }
public string Address { get; set; }
//New
public IEnumerable<CountryList> Country{ get; set; }
}
public class CountryList
{
public string Id { get; set; }
public string Name { get; set; }
}
Controller
(This is where is may be wrong, and is this the best way to do it?)
public ActionResult New()
{
var cl= new List<CountryList>();
cl.Add(new CountryList(){Id = "abcd",Name = "UK"});
cl.Add(new CountryList() { Id = "abce", Name = "USA" });
var model = new ViewModels.ClientNew();
model.Country= cl;
return View("New", model);
}
View (not sure how to plumb this in)
Html.DropDownList("Id" ??????)
In your view you will set up your dropdown on the property Id. This will be the current value selected in the dropdown when you POST to your form. The data that will be used for the dropdown is a SelectList called Countries that exists in your model.
#Html.DropDownListFor(m => m.Id, Model.Countries)
Your view model will have your Id, Name and Countries properties plus whatever else you need.
public class ClientNewViewModel {
public string Id { get; set; }
public string Name { get; set; }
public SelectList Countries { get; set; }
}
In your controller you need to pass the model to the view. You will need to populate the Countries SelectList. Keep in mind you will need to populate this value when you POST and fail validation as well.
public ActionResult New()
{
var model = new ClientNewViewModel();
model.Countries = new SelectList(service.GetCountries(),
"Id", "Name"); // set up what properties are used for id/name of dropdown
return View(model);
}
[HttpPost]
public ActionResult New(ClientNewViewModel model)
{
if( !ModelState.IsValid )
{
model.Countries = new SelectList(service.GetCountries(),
"Id", "Name");
return View(model);
}
// redirect on success
return RedirectToAction("Index");
}
Html.DropDownList("Id",
Country.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.Id
}));
There's a good blog post on the in's and out's of how to do this here -> http://277hz.co.uk/Blog/Show/10/drop-down-lists-in-mvc--asp-net
Every time I add a new App It creates a new AppCategory. I am seriously screwing this up somehow
code first entity framework objects
public class AppCategory
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<App> apps { get; set; }
}
public class App
{
public int ID { get; set; }
public string Name { get; set; }
public AppCategory Category { get; set; }
}
Editor Template (I would love to just make just one Foreign Key EditorTemplate)
#inherits System.Web.Mvc.WebViewPage
#Html.DropDownList("Category", LIG2010RedesignMVC3.Models.Repo.GetAppCategoriesSelect())
and of course the repository
public static IEnumerable<SelectListItem> GetAppCategoriesSelect()
{
return (from p in GetAppCategories()
select new SelectListItem
{
Text = p.Name,
Value = p.ID.ToString(),
});
}
public static ICollection<AppCategory> GetAppCategories()
{
var context = new LIGDataContext();
return context.AppCategories.ToList();
}
Every time I add a new App It creates a new AppCategory I am seriously screwing this up somehow
Adding more debug info
#inherits System.Web.Mvc.WebViewPage
#Html.DropDownList("", LIG2010RedesignMVC3.Models.Repo.GetAppCategoriesSelect())
gives me a validation message on the post
Parameters application/x-www-form-urlencoded
Category 1
Name 8
Validation error The value '1' is invalid.
This makes sense because Category should be an object not an integer.
Controller Code as asked for
pretty sure this isnt the problem as it came from MVCScaffold
[HttpPost]
public ActionResult Create(App d)
{
if (ModelState.IsValid)
{
context.Apps.Add(d);
context.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
My model was incorrectly set up ... virtual ICollection and just the foreign key id for the sub and everything worked... changes below
Model
public class AppCategory
{
public int ID { get; set; }
public string Name { get; set; }
public **virtual** ICollection<App> Apps { get; set; }
}
public class App
{
public int ID { get; set; }
********************************************
[UIHint("AppCategory")]
public int AppCategoryID { get; set; }
********************************************
public string Name { get; set; }
}
public class LIGDataContext : DbContext
{
public DbSet<AppCategory> AppCategories { get; set; }
public DbSet<App> Apps { get; set; }
}
/Views/Shared/EditorTemplates/AppCategory.cshtml
#inherits System.Web.Mvc.WebViewPage
#Html.DropDownList("", LIG2010RedesignMVC3.Models.Repo.GetAppCategoriesSelect())
AppController
[HttpPost]
public ActionResult Create(App d)
{
if (ModelState.IsValid)
{
this.repository.Add(d);
this.repository.Save();
return RedirectToAction("Index");
}
return View();
}
If you bind your dropDownList to Category.Id, you'll at least get the selected value into that filed, but nothing else in your Category Object.
The model binder cannot create the AppCategory object from the form collection in your Create action because the form only has an ID for that object (the other properties of AppCategory are not there).
The quickest solution would be setting the Category property of your App object manually, like this :
[HttpPost]
public ActionResult Create(App d) {
int categoryId = 0;
if (!int.TryParse(Request.Form["Category"] ?? String.Empty, out categoryId) {
// the posted category ID is not valid
ModelState.AddModelError("Category",
"Please select a valid app category.")
} else {
// I'm assuming there's a method to get an AppCategory by ID.
AppCategory c = context.GetAppCategory(categoryID);
if (c == null) {
// couldn't find the AppCategory with the given ID.
ModelState.AddModelError("Category",
"The selected app category does not exist.")
} else {
// set the category of the new App.
d.Category = c;
}
}
if (ModelState.IsValid)
{
context.Apps.Add(d);
context.SaveChanges();
return RedirectToAction("Index");
}
return View();
}