Does ID property in binding model pose a threath to overposting? - asp.net-core-mvc

I'm quite new in MVC and can't get my head around a possible overposting threath. I have an "Event" model which contains an Id property. When a user, for example, wants to edit an existing "Event", I use this property for fetching the "Event" I need to update from a collection of "Events".
I tried to decorate the Id property with the [BindNever] or [Editable] attribute which results in an Id property of 0 as the property no longer binds after a post. This, of course, generates problems when I want to use this Id property for fetching an "Event" from the collection.
So I leave the property undecorated. But it feels unnatural as this property should not be editable by a user. Using a ViewModel does not solve my problem as an Id property would still be needed.
In all examples I find online, an Id property is always part of a binding model. Does this pose threats to possible overposting? I assume not as, when working with Entity Framework, for example, the Id property is not Editable as it is autoincremented. A user would still be able to change the Id in order to update another "Event" though but in the case of my application, this generates no issue as a user is able to edit any "event" he wants
Model:
public class Event
{
public int Id { get; set; }
public string Name { get; set; }
}
Controller:
[HttpGet]
public IActionResult EditEvent(int? id)
{
if (id == null)
{
return NotFound();
}
else
{
var eventToEdit = _events.GetEvent(id.Value);
return View(eventToEdit);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult EditEvent(Event postedEvent)
{
if (ModelState.IsValid)
{
if (postedEvent == null)
{
return NotFound();
}
else
{
var eventToUpdate = _events.GetEvent(postedEvent.Id);
eventToUpdate = _events.EditEvent(postedEvent, eventToUpdate);
return RedirectToAction(nameof(EventDetails), new { id = eventToUpdate.Id });
}
}
else
{
return View(postedEvent);
}
}

Does ID property in binding model pose a threat to overposting?
Yes, if you're using this id as a lookup for editing then you need to write a check that ensures that the user is allowed to perform the edit on the data.
public IActionResult EditEvent(Event postedEvent)
{
//Make sure the current user can edit the posted event
if(!CanUserEditEvent(postedEvent.Id, User.GetUserId()) return Forbid();
//User can edit this event so continue normally
if(ModelState.IsValid)
...
}
Furthermore, ensure that other "read-only" or unused properties of your model that are exposed on the view and action are not then used as a lookup or saved. A view model can help with this by allowing you to define a subset of your domain model properties.

Related

Required ModelValidation just for new objects ASP.NET MVC

I have this issue since yesterday.
In my User model I have a [NotMapped] called "ConfirmPassword". I don´t save it on the database but I use it on my Create form as a always to validate the data input for new users.
Since than, it´s ok. The problem is on my [HttpPost] Edit action. I should be able to edit some user's data without type and confirm the password. I use both Password and ConfirmPassword as a way to confirm the old password and informe the new one, if I wanna change the password. But, if I don´t, I leave them blank.
I have used already the code below to be able to pass the ModelState.IsValid() condition and it worked:
ModelState["Password"].Errors.Clear();
ModelState["ConfirmPassword"].Errors.Clear();
But, just before the db.SaveChanges(), as User user view model is considered, it has both properties empty and I got:
Property: ConfirmPassword Error: The field ConfirmPassword is invalid.
The question is: How could I skip de Required model validation when I want to update an object?
I read already about custom ModelValidations with classes extending ValidationAttribute and
DataAnnotationsModelValidator but I am not doing it right.
Any idea? How could I create a custom model validation that checks if the UserId property is null or not. It´s a nice way to check if I'm in Create or Edit action.
Thanks,
Paulo
Using the domain objects as your ViewModel will leads you to a condition of less scalability. I would opt for seperate ViewModels specific for the Views. When i have to save the data i map the ViewModel to the Domain model and save that. In your speciific case, i would create 2 ViewModels
public class CustomerViewModel
{
public string FirstName { set;get;}
public string LastName { set;get;}
}
And i will Have another ViewModel which inherits from the above class, for the Create View
public class CustomerCreateViewModel :CustomerViewModel
{
[Required]
public string Password { set;get;}
[Required]
public string ConfirmPassword { set;get;}
}
Now in my Get actions, i use this ViewModel
public ActionResult Create()
{
var vm=new CustomerCreateViewModel();
return View(vm);
}
and of course my View(create.cshtml) is now binded to this ViewModel
#model CustomerCreateViewModel
<h2>Create Csustomer</h2/>
//Other form stuff
Similarly for My Edit Action,
public ActionResult Edit(int id)
{
var vm=new CustomerViewModel();
var domainCustomer=repo.GetCustomerFromID(id);
if(domainCustomer!=null)
{
//This manual mapping can be replaced by AutoMapper.
vm.FirstName=domainCustomer.FirstName;
vm.LastName=domainCustomer.LastName;
}
return View(vm);
}
This view is bounded to CustomerViewModel
#model CustomerViewModel
<h2>Edit Info of #Model.FirstName</h2>
//Other form stuff
In your POST Actions, Map it back to the Domain object and Save
[HttpPost]
public ActionResult Create(CustomerCreateViewModel model)
{
if(ModelState.IsValid)
{
var domainCust=new Customer();
domainCust.FirstName=model.FirstName;
repo.InsertCustomer(domainCust);
//Redirect if success (to follow PRG pattern)
}
return View(model);
}
Instead of writing the Mapping yourself, you may consider using AutoMapper library to do it for you.

Can't populate ViewModel

MVC3 project.
I have several classes: Account, Address, Phone etc. which I set up in a view model
namespace ViewModels
{
public class AccountVM
{
public Account Account { get; set; }
public Address Address { get; set; }
public Phone Phone { get; set; } }
In the controller GET action I just call the view
public ActionResult Create()
{ return View(); }
In the View I pass the View Model
#model AccountVM
I then use #Html.EditorFor's to populate all the fields and successfully pass it to the POST Action and create the records in the db. So all that code is working.
#Html.EditorFor(z => z.Account.Number)
The problem arises when I try and pre-populate some of the properties. I do the following in the GET action.
public ActionResult Create()
{ var viewModel = new AccountVM();
viewModel.Account.Number = 1000000;
return View(viewModel); }
The code passes Intellisense but when I run I get the "NullReferenceException was unhandled by user code - Object reference not set to an instance of an object" error.
I get the same error if I try and populate using code in the View.
#{ Model.Account.Number = 1000000; }
I need to be able to programatically populate properties in both the controller and the View. I've read several SO posts on how to populate a view model in the controller and modeled my code on them but for some reason my code is not working. What am I'm doing wrong here? How should I go about it in both the Controller and the View? I get that the objects are null when created but can't figure out how to get around that.
Thanks
You've instantiated the VM, but not its Account property... try this:
public ActionResult Create()
{
var viewModel = new AccountVM();
viewModel.Account = new Account();
viewModel.Account.Number = 1000000;
return View(viewModel);
}
The same goes for the view:
#{
if (Model.Account == null) {
Model.Account = new Account();
}
Model.Account.Number = 1000000;
}
Though there are few times that this probably belongs in the view. It looks like something that should be set in the controller instead.

Binding DropdownList and maintain after postback

I am using MVC3. I'm binding the dropdown with the Data coming from a service. But after the page posts back and a filter applies to list, the dropdown shows the filter record value in the grid because I always bind the list coming from the service.
However, I want the dropdown to always show all the Records in the database.
I don't understand your question that clearly. But it seems that it is a dropdown that you have on your view? I also have no idea what you are trying to bind so I created my own, but have a look at my code and modify it to fit in with your scenario.
In your view:
#model YourProject.ViewModels.YourViewModel
On the view there is a list of banks in a dropdown list.
Your banks dropdown:
<td><b>Bank:</b></td>
<td>
#Html.DropDownListFor(
x => x.BankId,
new SelectList(Model.Banks, "Id", "Name", Model.BankId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.BankId)
</td>
Your view model that will be returned to the view:
public class YourViewModel
{
// Partial class
public int BankId { get; set; }
public IEnumerable<Bank> Banks { get; set; }
}
Your create action method:
public ActionResult Create()
{
YourViewModel viewModel = new YourViewModel
{
// Get all the banks from the database
Banks = bankService.FindAll().Where(x => x.IsActive)
}
// Return the view model to the view
// Always use a view model for your data
return View(viewModel);
}
[HttpPost]
public ActionResult Create(YourViewModel viewModel)
{
if (!ModelState.IsValid)
{
// If there is an error, rebind the dropdown.
// The item that was selected will still be there.
viewModel.Banks = bankService.FindAll().Where(x => x.IsActive);
return View(viewModel);
}
// If you browse the values of viewModel you will see that BankId will have the
// value (unique identifier of bank) already set. Now that you have this value
// you can do with it whatever you like.
}
Your bank class:
public class Bank
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
This is simple as it gets. I hope this helps :)
PS: Please remember with future posts, always give as much detail as possible so that we can help you better. Also don't forget to display code samples so that we can see what you have already done. The more details that we can have the better.
When you post the model back to the create[Http Post] action is it not possible to have the list of dropdown list values for the banks binded back to the model. I see that if the model is invalid, you call the code
viewModel.Banks = bankService.FindAll().Where(x => x.IsActive);
to get a list of all the banks again which I assume you need to hit the database again.
Thanks

MVC3 - how to link a newly created child object to its parent?

I am a complete novice at MVC, and can't seem to get my head around a very basic concept.
I have a parent object, that contains a collection of child objects. I want to create a new child object, and link it to the parent object, persisted in the database via EF4
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Child> Children { get; set; }
}
So far, what happens in my very basic application is this. My user goes to a page displaying the details of a parent object, which includes a list of the current children. There is also a link on that page to add a new child. That link points to a Create action on the child Controller, passing the parent Id, which in turn displays a view to create a new child. And this is where I've got stuck. I don't know how to persist the supplied parent Id so that when I click Save, I can retrieve that parent object and add my new child object to its collection.
I'm probably approaching this in completely the wrong way, but I can't seem to find any basic tutorials on how to add new child items to a parent, which is odd considering how common a scenario it is.
Any help is really appreciated!
Many thanks
Gerry
[Update 1]
I have two CreateChild actions, initially generated by MVC and then modified by myself. I can see just by looking at them that they don't do what I need, but I'm not at all sure how they need to change. Specifically, I store the parent ID within the ViewBag but between the calls to the Create actions, it gets lost, and so is not available when the second Create is called to persist the new child item to the database.
public ActionResult Create(int parentId)
{
Parent parent = db.Parents.Find(parentId);
ViewBag.ParentId = parent.Id;
return View();
}
[HttpPost]
public ActionResult Create(Child child)
{
if (ModelState.IsValid)
{
Parent parent = db.Parents.Find(ViewBag.ParentId);
parent.Children.Add(child);
db.Children.Add(child);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(child);
}
Thanks again
Gerry
When you pass the ParentId to the add child action, it looks like you are doing it with a route parameter.
Instead of storing it in the ViewBag, write it as a hidden field in your child form. Then, when someone submits the form, the ParentId will be submitted to your HttpPost action method.
You can do this by making ParentId a property on your Child viewmodel.
public class Child
{
public int ParentId { get; set; }
}
public ActionResult Create(int parentId)
{
Parent parent = db.Parents.Find(parentId);
var model = new Child { ParentId = parent.Id };
return View(model);
}
In your view, render it like this:
#Html.HiddenFor(m => m.ParentId)
Then, during your HttpPost, Child will already contain ParentId -- the default model binder will get it from the hidden field on your form.
[HttpPost]
public ActionResult Create(Child child)
{
if (ModelState.IsValid)
{
Parent parent = db.Parents.Find(child.ParentId);
parent.Children.Add(child);
db.Children.Add(child); // don't think you need this line
db.SaveChanges();
return RedirectToAction("Index");
}
return View(child);
}
Update after posting answer
Looking at your HttpPost code, it may be incorrect to add the child to the db twice. If you are using EF 4.1 or 4.2, I am pretty sure this is incorrect, but I'm not sure about previous EF versions. Adding the child to the parent.Children collection should be enough -- I don't think you should also add it to the db.Children set.
ViewBag is not ViewState (ASP.NET MVC doesn't have any built-in equivalent to ASP.NET WebForms ViewState) - it will not keep ParentId between calls. It will just allow you passing ParentId to view (in your first action) so you can for example store it in hidden field.

MonoRail BindObject() equivalent in ASP MVC3?

I need to do late binding of a complex type with DataAnnotations within an Action if condition X is true. I cant bind everything up front in the method params as a couple of them will not exist unless X == true so Model.IsValid will be false (since it tried to bind non existant params) due to validation failing on the complex type.
MonoRail solved this by allowing you to manually bind when needed, this is the exact scenario i have so im wondering if theres something similar available in MVC3?
I cant overload the Action as it blows up with an ambiguous call, i cant post to a different action as the form is all dynamic content, so i see the only alternative is rolling my own validation / binding mechanism pulling out data annotations to validate with.... boooo :(
I think what you need is the ControllerBase.TryUpdateModel method (it has a lots of overloads).
You can use it similarly like BindObject:
Some model:
public class MyModel
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
}
In the controller action:
[HttpPost]
public ActionResult UpdateModel(bool? acceptedConditions)
{
var model = new MyModel();
if (acceptedConditions ?? false)
{
if (TryUpdateModel(model))
{
//Do something when the model is valid
}
else
{
//Do something else when the model is invalid
}
}
return View();
}

Resources